毕业生网络招聘系统毕设全攻略:从数据库设计到答辩演示
简介面向Java方向毕业设计这套基于WEB的毕业生网络招聘信息系统资料包针对传统招聘耗时、目的性弱、效率低等痛点采用B/S三层结构借助JSP、JavaBean、JDBC完成求职者、用人单位、管理员三类角色的业务闭环适合高校计算机相关专业学生作为课程设计或毕设蓝本参考。压缩包共312个文件体积仅5.61MB涵盖105个Java源码及对应class文件、44个JSP页面、17个CSS样式、16个Jar依赖库、3个Word文档和1个SQL脚本代码与配置齐全。已有1269人学习下载。除可运行源码外还包含开题报告、中期检查与答辩相关Word文档以及数据库初始化脚本和项目工程文件方便直接导入IDE运行。同时为毕业设计说明书撰写、项目结构梳理与系统二次开发提供清晰参照。1. 毕业生网络招聘信息系统这个题目是 WEB 工程里最容易答崩的那一类毕业生网络招聘信息系统这个题目是高校毕设清单里看着简单、答辩时最容易翻车的 WEB 工程之一。它从来不是简单的“职位表 简历表”增删改查而是学生、企业、管理员三套角色视图、一条完整投递状态链的多角色业务系统。这篇文章面向正在选毕设题目、或手里已有 web 项目但讲不清源码的人按我带学生做这类题目的顺序从数据库设计、Spring Boot 接口、服务器部署一直讲到开题报告、中期检查和答辩怎么准备。标题里带的源码、答辩、开题报告和中期检查四个词我会逐个拆给你看。2. 先画边界再建表三套角色、四种状态、八张表系统设计这步我一般要求先出角色功能清单再往表结构走。很多毕设源码翻车都是表结构设计阶段就埋了雷企业信息挂在 user 表里、投递记录只存一个状态字符串、简历文件直接 base64 塞进数据库。这些设计不是不能跑而是到了“企业端要看是谁投的简历”“管理员要统计各专业投递量”这类典型功能时查询根本写不出来或者要写一堆不规范的子查询。2.1 先用一页纸把三套角色边界定下来我的做法是先画一张权限二维表行是功能模块列是三类角色交叉格里打勾。学生端能注册、维护个人信息、投递简历、收藏职位、查看投递状态企业端能发职位、浏览收到的简历、变更投递状态、安排面试时间管理员端管用户启禁用、审核职位上下架、发公告。这张表画清楚后边的 Controller 路径前缀和拦截器规则就有了依据你就不会再出现“学生能删企业职位”这种答辩时被当场点破的失误。一个常见的选型问题是同样做这个 web 项目选 java 课程设计里最常见的 Spring Boot还是用 php 源码直接改或者用 python 的 django web 应用开发框架快速搭我建议毕设优先 Spring Boot MyBatis。它把你需要讲的架构概念——IOC、AOP、拦截器、ORM——全部明明白白暴露在源码里答辩时能说出来的技术点比另外两个方案多得多。django 和 php 都适合快速出活但讲到“底层运行原理”时容易变成背概念。2.2 用户表统一、业务表拆分建表前的两个决策第一个决策是登录用户怎么存。常见做法是一张 user 表统一放登录账号role 字段用 1 管理员、2 学生、3 企业区分学生和企业的基础资料各自再建表通过 user_id 关联。不要在 student 表里重复存 username 和 password否则以后做“修改密码”要同时改两张表而且管理员禁用账号时不知道该禁哪张表。把密码单独收在 user 表还有一个好处加密逻辑只做一遍。密码不要明文存哪怕毕设也要用 BCrypt 加密。你自己做实验数据无所谓答辩老师一旦看到明文密码印象分会掉一大截。第二个决策是物理外键用不用。我的建议是不建物理外键用逻辑外键加索引。物理外键在插入和删除时要维护约束毕设里的假数据经常要先删职位再删企业、删了学生又删不掉投递记录被外键卡住时老师就在旁边看你排查场面很难看。用逻辑外键配合索引查询性能有保证代码里也能自己控制删除顺序灵活性高很多。2.3 八张表建表 SQL一次能跑通的核心版本下面是我一般会用的核心建表 SQL。student 和 company 的字段按各自需要增减但身份证这类敏感字段毕设里能不加就不加少一个字段少一堆校验代码。-- 登录用户表所有角色共用一张表role 区分身份 CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 登录名, password varchar(255) NOT NULL COMMENT BCrypt加密后的密码, role tinyint NOT NULL DEFAULT 2 COMMENT 角色1管理员 2学生 3企业, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1启用 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT登录用户表; -- 学生信息表user_id 关联 user.id业务字段单独存放 CREATE TABLE student ( id int NOT NULL AUTO_INCREMENT, user_id int NOT NULL COMMENT 关联用户表, name varchar(50) DEFAULT NULL, school varchar(100) DEFAULT NULL, major varchar(100) DEFAULT NULL COMMENT 专业, education varchar(20) DEFAULT NULL COMMENT 学历, phone varchar(20) DEFAULT NULL, email varchar(100) DEFAULT NULL, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生信息表; -- 企业信息表 CREATE TABLE company ( id int NOT NULL AUTO_INCREMENT, user_id int NOT NULL COMMENT 关联用户表, company_name varchar(100) NOT NULL, industry varchar(50) DEFAULT NULL COMMENT 所属行业, city varchar(50) DEFAULT NULL, introduction text COMMENT 公司简介, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT企业信息表; -- 职位表company_id 关联企业status 控制上下架 CREATE TABLE position ( id int NOT NULL AUTO_INCREMENT, company_id int NOT NULL COMMENT 发布企业id, title varchar(100) NOT NULL COMMENT 职位名称, city varchar(50) DEFAULT NULL, salary_min int DEFAULT NULL COMMENT 薪资下限单位K, salary_max int DEFAULT NULL, education varchar(20) DEFAULT NULL COMMENT 学历要求, description text COMMENT 职位描述, status tinyint NOT NULL DEFAULT 1 COMMENT 1发布中 0已下线, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_company_id (company_id), KEY idx_city (city) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT职位表;-- 简历文件表只存文件路径和元信息不存文件内容 CREATE TABLE resume ( id int NOT NULL AUTO_INCREMENT, student_id int NOT NULL COMMENT 学生信息表id, file_name varchar(255) DEFAULT NULL COMMENT 原始文件名, file_path varchar(255) NOT NULL COMMENT 服务器保存路径, file_size bigint DEFAULT NULL COMMENT 单位字节, upload_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_student_id (student_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT简历文件表; -- 投递记录表一次投递一条记录status 是业务核心列 CREATE TABLE delivery ( id int NOT NULL AUTO_INCREMENT, student_id int NOT NULL, position_id int NOT NULL, status tinyint NOT NULL DEFAULT 0 COMMENT 0已投递 1被查看 2通知面试 3已录用 4已淘汰, interview_time datetime DEFAULT NULL COMMENT 面试安排时间, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_student_id (student_id), KEY idx_position_id (position_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT投递记录表; -- 收藏职位表唯一约束保证一个学生对一个职位只能收藏一次 CREATE TABLE favorite ( id int NOT NULL AUTO_INCREMENT, student_id int NOT NULL, position_id int NOT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_student_position (student_id, position_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT职位收藏表;说明用户表用 role tinyint 而不是 varchar是因为角色枚举以后做权限判断只需要比较数字写进拦截器代码里也短。position 表给 city 建索引是因为毕业生招聘系统里“城市筛选”几乎是必做功能不带索引的数据量虽然小但这种建模习惯能让答辩评委会觉得你懂索引的适用场景。resume 表只存路径不存文件内容这非常关键后面部署章节会回来说它。favorite 表加了唯一约束收藏按钮就能用 insert ignore 实现“重复点击不报错”。到这里八张表已经出现七张第八张 announcement 公告表结构更简单字段就 id、title、content、create_time。带着这套表结构你可以对照手里的源码检查如果源码把企业名称直接写到 position 表里而不是通过 company_id 关联那说明当时是图省事答辩时老师追问第三张表就很容易露馅。2.4 投递状态机从 0 到 4 的合法流转投递状态是这个系统的业务核心不能用随意字符串我习惯用 tinyint 加数字状态。0 已投递、1 被查看、2 通知面试、3 已录用、4 已淘汰。合法流转规则是投递后是 0企业查看简历后从 0 变 1企业安排面试后从 0 或 1 变 2面试完成后从 2 变 3 或 4。注意 4 已淘汰不允许从 0 直接跳过来因为简历没被查看就淘汰对企业端来说不合业务流程也会让学生端界面出现逻辑错乱。状态变更在代码里用条件更新实现-- 企业安排面试只有当前状态是0或1时才允许更新到2 UPDATE delivery SET status 2, interview_time 2025-06-10 10:00:00 WHERE id #{deliveryId} AND status IN (0, 1);这条 SQL 的精髓在 WHERE 条件里的status IN (0, 1)。它保证两个人同时操作同一条投递记录时后执行的那条更新会因为条件不匹配而影响 0 行天然避免了状态被覆盖。如果只在代码里做 if 判断再用无条件 UPDATE并发情况下会出现很隐蔽的丢状态问题——这是很多教学项目源码不会写的细节但面试官和答辩评委都爱问。3. 用 Spring Boot 把核心接口写出来拦截器、动态 SQL 与文件上传设计表结构只是第一步真正让系统能跑起来的还是接口层。这一章我直接给三段最核心的代码登录拦截与角色路由、职位多条件搜索、简历上传。这三段代码覆盖了招聘系统 70% 的日常操作把它们弄明白剩下的页面就是数据展示。3.1 项目骨架与依赖清单在 IDEA 2024 里创建 web 项目直接用 Spring Initializr选 Java 8 或 17 都行关键是要把下面这几个依赖加进 pom.xml。dependencies !-- Web启动器内嵌Tomcat页面和REST接口都靠它 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Thymeleaf模板引擎服务端渲染页面毕设演示最简单 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- MyBatis与Spring Boot整合 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency !-- MySQL驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- 数据库连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.23/version /dependency !-- Lombok减少getter/setter样板代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies版本号以你创建工程时 IDEA 拉到的稳定版为准。mybatis-spring-boot-starter 2.3.2 对应 Spring Boot 2.7 系列如果你建出来的是 Spring Boot 3.x要换 3.x 的 mybatis starter不然启动时会报组件扫描失败。Lombok 不是必须但建议保留能让实体类短一半。3.2 application.yml 里最容易影响联调的三个参数server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/recruit?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 20MB max-request-size: 25MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.recruit.entity这里的 serverTimezoneAsia/Shanghai 是必填项MySQL 8.x 驱动如果拿不到时区会报 CST 相关异常。max-file-size 默认只有 1MB不调大后面传简历必然翻车我见过太多源码在这卡住。Druid 连接池在本地环境记得给初始连接数设小一点默认配置在低配电脑上启动会慢好几秒。3.3 登录拦截与角色路由不用注解的最小实现登录状态用 Session 存拦截器负责两件事没登录的重定向到登录页登录了但角色不对的重定向到 403 页。我一般不用自定注解直接按 URL 前缀判角色简单直接。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从Session里拿登录用户拿不到说明未登录 User loginUser (User) request.getSession().getAttribute(loginUser); if (loginUser null) { response.sendRedirect(request.getContextPath() /login); return false; } // 路径前缀匹配角色/admin 开头必须是管理员/company 开头必须是企业 String uri request.getRequestURI(); if (uri.startsWith(/admin) loginUser.getRole() ! 1) { response.sendRedirect(request.getContextPath() /403); return false; } if (uri.startsWith(/company) loginUser.getRole() ! 3) { response.sendRedirect(request.getContextPath() /403); return false; } return true; } }注意 role 是 tinyint这里直接用! 1比较所以建表时用数字枚举而不是字符串在 Java 代码里会舒服很多。注册拦截器时有一个最常见的坑下面这段配置Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /register, /css/**, /js/**, /img/**); } }excludePathPatterns 里必须放行静态资源目录否则浏览器加载样式表也被拦截页面会变成纯 HTML 无排版。还有一点Thymeleaf 渲染的页面里如果有静态资源引用路径拼错浏览器会请求到/static/xxx而不是/css/xxx同样会被拦截器拦下来跳登录页表现就是“页面偶尔正常偶尔乱”排查方向往往错。3.4 职位搜索接口MyBatis 动态 SQL 的三种写法对比毕业生找工作时最常用的功能是按关键词、城市、薪资范围筛职位。这个查询条件不固定不能用写死的 SELECT 语句我一般用 MyBatis 的动态 SQL。select idsearchPositions resultTypecom.example.recruit.entity.Position select * from position where if testkeyword ! null and keyword ! and (title like concat(%, #{keyword}, %) or company_name like concat(%, #{keyword}, %)) /if if testcity ! null and city ! and city #{city} /if if testsalaryMin ! null and salary_min gt; #{salaryMin} /if /where order by create_time desc /select参数说明keyword 做模糊匹配用 concat 拼接百分号而不是在 Java 代码里拼好再传是为了防止用户输入百分号或下划线时把 LIKE 语义搅乱city 用等值匹配因为城市一般做下拉框而不是自由输入salaryMin 是整数注意这里用了gt;而不是XML 文件里裸写大于号会解析报错这是 MyBatis XML 最常见的语法坑。另外两种写法供参考一是用注解 Select 直接拼字符串只适合条件固定的接口二是 MyBatis-Plus 的 LambdaQueryWrapper代码里用 Java 方法链拼条件能少写一半 XML但如果你想让答辩评委看到你对 SQL 的控制力动态 XML 是更好的选择。3.5 简历上传文件存储与访问这条路要打通简历文件必须存到服务器磁盘数据库只存路径。上传接口的核心代码PostMapping(/resume/upload) public String upload(RequestParam(file) MultipartFile file, Value(${file.upload-dir}) String uploadDir, HttpSession session) { if (file.isEmpty()) { return 上传失败文件为空; } // 原始文件名只保留后缀主名用UUID重生成避免中文名和特殊字符问题 String originalFilename file.getOriginalFilename(); String suffix ; if (originalFilename ! null originalFilename.contains(.)) { suffix originalFilename.substring(originalFilename.lastIndexOf(.)); } String filename UUID.randomUUID().toString().replace(-, ) suffix; // 目录不存在就创建这是部署到Linux后最容易忽略的一步 File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File(dir, filename)); } catch (IOException e) { e.printStackTrace(); return 上传失败; } // 把 /files/xxx.pdf 这个访问路径存进resume表页面直接拼URL预览 return /files/ filename; }uploadDir 从配置文件读取而不是写死在代码里是为了 Windows 本地和 Linux 服务器用两套配置。文件重命名用 UUID 是为了避免两个学生上传同名简历互相覆盖。transferTo 是 MultipartFile 的标准落盘方法注意 transferTo 的目标父目录必须存在否则会抛 IOException。文件访问还需要一个映射配置把/files/**指向磁盘目录Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: uploadDir /); } }这段配置让浏览器可以直接访问服务器磁盘上的简历 PDF实现“上传后点开预览”。addResourceLocations 必须以file:开头且末尾要带/两个细节缺一个都 404。很多源码在这用相对路径本地跑正常一部署就挂原因就是相对路径的基准目录在 jar 启动时和 IDE 运行时不一样。4. 从 IDEA 到一台能访问的服务器本地 jar 启动、配置项与验收清单代码在 IDE 里能跑离“系统完成”还很远。我的验收标准很简单不用 IDEA用命令行能启动换一台干净的机器能启动部署到 Linux 服务器后浏览器从外网能访问。这一章就是沿着这三条线走。4.1 本地先过三关jar 启动、端口占用、配置切换mvn clean package -DskipTests java -jar target/recruit-0.0.1-SNAPSHOT.jar --server.port8080-DskipTests 跳过测试不是因为测试没用而是很多毕设工程的测试类里还引用着测试数据库跑不起来会卡住打包。jar 启动成功后本地浏览器访问 http://localhost:8080/login。如果端口被占用换一个端口重新启动java -jar target/recruit-0.0.1-SNAPSHOT.jar --server.port8081。不要用 IDE 里 Run 一次就以为跑通了jar 包启动才等同于服务器上的运行方式。4.2 部署到服务器前必改的五个配置项这五个配置不改部署必出错。列成清单逐条对。配置项本地值服务器值不改的后果server.port808080 或保持 8080 Nginx 转发外网访问要带端口号spring.datasource.urllocalhost:3306127.0.0.1:3306连不上数据库file.upload-dirD:/temp/upload/opt/recruit/uploadLinux 上找不到路径druid 连接池默认调小初始连接数低配服务器启动慢日志级别root: infocom.example.recruit: debug出问题时日志看不清其中数据库地址最容易写错服务器上的 localhost 指的是服务器自己不是你的电脑。很多人本地指向 localhost 能跑部署后忘了改页面一直报连接数据库失败。上传路径的改动我习惯放到 application.yml 里用${file.upload-dir}占位启动时用命令行覆盖java -jar /opt/recruit/recruit.jar \ --file.upload-dir/opt/recruit/upload \ --spring.datasource.urljdbc:mysql://127.0.0.1:3306/recruit?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai这样不用重新打包就能在不同环境切换配置也是答辩时可以讲一句的“部署技巧”。日志级别调到 debug 后MyBatis 会打印每条 SQL 和参数线上排查状态不对、查不到数据时省一大半时间。4.3 Nginx 转发与服务器安全默认项如果只想让系统能访问jar 直接监听 80 端口就够了。但一台服务器以后可能还要跑其他 web 工程我一般会在前面加一层 Nginx 做域名转发和静态资源缓存。server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这段配置把 80 端口收到的请求转发到本机 8080 的 jar。服务器安全方面有几个默认动作安全组只放行 80 和 443MySQL 的 3306 端口绝不对公网开放Linux 防火墙同样只放行必要端口数据库密码换成强密码不要用 root/123456 的组合放到公网服务器上。这些都是毕设答辩加分项因为评委最常问的就是“你这系统放到公网安全吗”。4.4 验证系统“真的能用”的四个验收动作部署完不能只看首页能开就结束。我按用户真实操作路径列四个验收动作任何一个失败都说明系统还差最后一公里。验收动作预期结果失败排查方向未登录访问 /student/home跳转登录页拦截器 exclude 配置是否放行过多学生登录后投递一个职位个人中心显示“已投递”delivery 表是否插入成功企业登录后变更投递状态学生端能看到“通知面试”动态 SQL 的 status 判断条件上传简历后点击预览浏览器打开 PDF文件映射配置和磁盘权限第四个动作最容易在 Linux 上失败。如果页面能上传但 URL 访问 404先检查 uploadDir 目录是否存在且有写权限再检查映射配置。这个排查过程如果你写成文档放在中期检查报告里评委一眼就能看出你是真部署过还是只在 IDEA 里点过运行。5. 避坑与排查五个必踩的坑每个都讲现象、原因、解决这一章是我带学生做这个题目时实际遇到最多的五类问题不是从报错文档里抄来的。每个按“现象 → 原因 → 解决”写你照着排查比自己翻日志快得多。5.1 上传 1MB 以上的简历报 500页面没有明确错误提示现象PDF 小于 1MB 能传上去大一点就报服务器内部错误控制台出现 MaxUploadSizeExceededException。原因Spring Boot 内嵌 Tomcat 的 multipart 默认限制单文件 1MB超过直接抛异常。解决在 application.yml 里调大限制同时设置 max-request-size 比单文件上限稍大spring: servlet: multipart: max-file-size: 20MB max-request-size: 25MB5.2 本地正常部署到 Linux 后上传文件报 java.io.FileNotFoundException现象本地 Windows 上传文件没问题部署到 Linux 云服务器后一上传就报 FileNotFoundException 或 No such file or directory。原因源码里把上传路径写死了比如D:/temp/upload或者用了相对路径。Linux 上没有 D 盘相对路径的基准目录在 jar 启动时又经常不在预期位置。解决上传目录做成配置项代码里用Value(${file.upload-dir})注入启动时通过命令行参数指定绝对路径/opt/recruit/upload并确保目录存在且有写权限。5.3 登录后无限跳回登录页像进入了死循环现象输对账号密码请求页面后马上又跳回登录页或者页面反复刷新。浏览器地址栏能看到 login 和主页来回跳。原因拦截器把登录页本身也拦截了。常见的两个触发点excludePathPatterns 里漏了/login或者登录请求是 POST/login但页面跳转用的 GET/login没放行。解决检查拦截器注册配置确保/login、/register、静态资源目录/css/**、/js/**、/img/**都在放行列表里。放行规则写成.excludePathPatterns(/login, /register, /css/**, /js/**, /img/**, /files/**)这里把/files/**也放行因为简历 PDF 预览的请求不该被登录拦截器拦否则企业点开简历附件会被弹回登录页。5.4 MyBatis 动态 SQL 里写 大于等于 直接报错现象XML 里写了and salary_min #{salaryMin}启动时提示 Error creating document instance 或解析 XML 失败。原因XML 文件里裸写和是非法字符MyBatis 解析 mapper XML 时会当成标签处理。解决大于号和小于号都要转义。转成gt;转成lt;或者直接把整个比较条件包进 CDATAif testsalaryMin ! null and salary_min ![CDATA[ ]] #{salaryMin} /if注意if testsalaryMin ! null里的!是合法的因为它在属性值里而不是元素的文本内容里。分不清就统一用gt;这种转义写法最不容易出错。5.5 部署后启动报 CommunicationsException 或时区异常现象jar 在本地能启动部署到服务器后连数据库报com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure或者The server time zone value йʱ is unrecognized。原因两个问题叠加最常见。一是数据库地址写错服务器上 localhost 指向自己二是 JDBC URL 缺 serverTimezone 参数MySQL 8 驱动拿不到 JVM 默认时区就会抛异常。解决数据库地址改成 127.0.0.1URL 加上serverTimezoneAsia/Shanghai驱动类用com.mysql.cj.jdbc.Driver。如果用的是 MySQL 5.7驱动选com.mysql.jdbc.Driver同样需要指定字符集jdbc:mysql://127.0.0.1:3306/recruit?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai6. 开题报告、中期检查与答辩准备源码之外决定分数的那三张纸标题里源码、答辩、开题报告、中期检查四件套源码部分前面讲完了。最后一章专门说文档和答辩因为代码能跑只是及格这三张纸才是拉分项。6.1 开题报告把“为什么做”讲出工作量开题报告最常见的错误是写成“我国就业形势严峻所以做一个招聘系统”。这不是开题是作文。我一般围绕三块写选题背景里重点写“现有校招平台对学生和中小企业不友好”这两个切入点国内外现状不空谈去找两个同类系统说它们缺什么缺的正好是你做的技术路线画一条从 Web 前端到 Spring Boot 后端再到 MySQL 的数据流。这三块能写满两页方案可行性分析才站得住。6.2 中期检查进度表比描述性文字有用中期检查主持人最关心“你做了没有”。一张表三列完胜大段描述功能模块、完成状态、演示截图。我用过的表格大概这样模块状态验证方式数据库设计与账号体系已完成建表脚本 三类角色登录演示职位发布与搜索已完成企业端发布学生端搜索投递与状态流转完成 80%投递已通面试通知待联调剩余空间写一句“遇到的问题与解决”比如 5.1 的上传限制问题这就是真实的进度痕迹。6.3 答辩演示十分钟里让评委看到状态机我建议的节奏是 5 分钟演示 4 分钟讲设计 1 分钟讲难点。演示要从学生视角走登录、搜职位、投递、看状态再切换企业账号对同一份投递做“查看 → 面试 → 录用”。这三次状态变更把投递状态机完整演出来了比讲十个 CRUD 页面都有说服力。代码部分不要逐行念挑拦截器里 URI 前缀判角色那段以及 2.4 的条件 UPDATE这是整个系统设计浓度最高的两处。6.4 现场演示的三个保护动作演示现场最容易翻车的三件事提前处理掉用本地环境而不是线上服务器现场网络不可控本地最稳准备一套演示账号和学生甲、企业乙的固定数据角色齐全且状态各异关闭浏览器通知和无关窗口。另外不用依赖外部链接的字体库把 CSS 和 JS 都放在项目里断网也能完整渲染。第一次答辩我就吃过亏现场网络一抖动页面白屏了大半后来凡是演示我都默认按断网环境准备。至于功能扩展这题目还能往深处做的方向不少简历解析、职位推荐、web 端实时视频面试都可以写进展望。但基础版本先把投递状态机和文件上传走通这套骨架树立起来后面加任何模块都有地方挂。这是我带这题目几年下来最大的体会也希望帮到你。本文还有配套的精品资源点击获取