
毕业后找工作那会儿我盯着招聘网站上的岗位列表突然想到一个问题学校里的就业信息发布大多还是靠辅导员转发群消息、学院官网贴公告学生和企业之间隔着好几层。后来做毕业设计我决定直接做一个大学生就业招聘系统平台。这个项目用的是 SpringBoot Vue 前后端分离架构附带完整的 SQL 脚本和接口文档涉及学生、企业、管理员三种角色覆盖职位发布、简历投递、面试邀请、数据统计等完整招聘闭环。对 Java Web 方向的毕设来说它覆盖面广、业务逻辑清晰、演示效果直观而且技术栈足够主流拿得出手。这篇文章我会从整体设计、数据库建模、后端核心模块、前端页面实现、接口文档规范到常见坑位把整个项目拆开讲清楚适合正在做同类毕设、或者想快速上手前后端分离项目的同学参考。1. 项目背景与整体设计思路1.1 这个系统到底解决什么问题传统的校园招聘流程信息散落在各个渠道——企业 HR 把职位发给学校就业办就业办整理成 Excel 发到各学院辅导员再转发到班级群学生看到消息后还要自己把简历发到指定邮箱。这个过程至少有三层损耗信息传递慢、格式不统一、反馈不闭环。这个系统的核心目标就是把这些散落的环节集中到一个平台上。企业可以自己注册、发布职位、查看收到的简历学生可以完善在线简历、浏览岗位、一键投递、查看投递状态管理员负责审核企业资质、维护基础数据、查看整体招聘数据。三方在同一个平台里完成完整的业务闭环这就是这个项目最有价值的地方。1.2 为什么选这个方向作为毕设毕设选题最怕两件事一是题目太大做不完二是题目太小没内容写。就业招聘系统刚好处于一个合适的中间地带——业务路径清晰角色划分明确每个角色的功能模块都可以横向扩展。比如学生端可以扩展简历模板、面试记录、收藏职位企业端可以扩展在线测评、批量筛选简历管理员端可以扩展数据报表、操作日志。这意味着你在开题报告里写的每一项研究内容都能在系统里找到对应的落地实现答辩的时候有东西可讲而不是停留在概念层面。另外一个实际考量是招聘系统天然适合展示前后端分离的开发模式。企业的职位列表、学生的投递记录、管理员的审核列表这些都是典型的 CRUD 场景配合状态流转投递中、已查看、已邀请、已录用可以很自然地引入 RESTful API 设计、统一响应格式、请求拦截器等知识点这些恰好也是面试官爱问的东西。2. 技术选型与架构设计2.1 后端技术栈SpringBoot 2.x 的选择后端我用了 SpringBoot 2.x 而不是最新的 3.x这个选择是经过考虑的。SpringBoot 2.7 是目前市面上教程资源最丰富、兼容性最稳妥的版本很多公司的存量项目也还在这个版本线上跑着。SpringBoot 3.x 要求 JDK 17 起步虽然新项目用没什么问题但如果你的部署环境还是 JDK 8那会遇到不少兼容性麻烦。具体用到的组件包括SpringBoot 2.7.x基础框架快速搭建 RESTful APIMyBatis-Plus 3.5.x数据访问层内置分页插件代码量比原生 MyBatis 省不少MySQL 8.0数据库注意 8.0 的驱动类名和 5.x 不一样JWT 拦截器登录认证无状态会话管理Hutool工具类库处理日期、随机数、文件上传都很方便MyBatis-Plus 这个选择对毕设来说很划算。它内置了常用的增删改查方法单表操作几乎不用手写 SQL你只需要把精力集中在多表关联和业务逻辑上。给导师演示的时候分页查询是加分项MyBatis-Plus 的分页插件一行配置就能搞定比手写 PageHelper 要省事。2.2 前端技术栈Vue 2.x 还是 Vue 3.x前端框架我纠结过 Vue 2 还是 Vue 3。最终选了 Vue 2.7 Element UI 的组合原因很现实Element UI 对 Vue 2 的生态已经非常成熟你随便搜一个问题Stack Overflow 和掘金上基本都有现成答案。如果你的基础是跟着网课学的 Vue 2那继续用 Vue 2 做毕设是阻力最小的路径。Vue 3 Element Plus 当然也可以而且更适合长远发展。但要注意 Element Plus 的组件 API 和 Element UI 有一些差异比如el-dialog的visible.sync变成了v-model如果中途切换调试成本会明显增加。我的建议是选你熟悉的那套然后一条路走到黑不要在开发中期换框架。前端其他配套选型Vue Router 3.x路由管理实现页面跳转和菜单高亮AxiosHTTP 请求库统一封装请求拦截和响应处理ECharts 5管理员端数据可视化展示职位分布、投递趋势SCSS样式预处理嵌套写法更清晰2.3 前后端分离架构的权衡前后端分离是这个项目的核心架构。后端只提供 JSON 数据接口前端负责页面渲染和交互两者通过 HTTP 协议通信。这样做的好处是开发和部署可以独立进行前端用 Node 的 dev server 跑在 8080 端口后端用 SpringBoot 内嵌 Tomcat 跑在 8081 端口本地开发互不干扰。跨域问题需要在后端处理。我用了一个CorsConfig配置类放行了所有来源和方法同时允许携带 Authorization 请求头。生产环境下这种全放行的配置肯定不安全但毕设环境下够用部署到云服务器后也可以用 Nginx 反向代理来解决跨域前端请求统一走/api前缀由 Nginx 转发到后端服务。3. 数据库设计与 SQL 脚本3.1 核心数据表设计思路数据库设计是整篇论文的重要支撑。我建了 8 张核心表按角色划分清晰user用户表存储学生、企业、管理员的账号信息用role字段区分类型student_profile学生简历表一对一关联 user 表company企业信息表包括企业名称、规模、行业、简介、营业执照图片job职位表关联企业记录岗位名称、薪资、要求、状态resume_delivery投递记录表关联学生和职位记录投递时间、状态interview面试邀请表企业对学生发起面试邀请collection职位收藏表学生收藏感兴趣的职位admin_log管理员操作日志表关键设计点是投递记录表。很多初做这个系统的人会把投递状态直接写死在职位表或者简历表里这是不对的。投递是一个独立的行为一个学生可以投递多个职位一个职位会收到多个学生的简历这是典型的多对多关系必须用中间表来维护。resume_delivery表里冗余了delivery_status字段用 0/1/2/3 表示待查看、已查看、已邀请、已拒绝这样企业端列表页可以直接按状态筛选不需要额外关联查询。3.2 SQL 脚本的导入与初始化附带的 SQL 脚本是整个项目能跑起来的第一步。脚本一共包含三个部分建库语句、建表语句、初始数据。数据库名我用的是job_portal建议你保持统一不然改配置文件的连接字符串会很麻烦。用 Navicat 或者命令行导入都可以。命令行导入的方式是mysql -u root -p job_portal.sql导入前注意两点。一是 MySQL 版本如果你本地装的是 5.7脚本里的utf8mb4字符集没问题但如果有ENGINEInnoDB DEFAULT CHARSETutf8mb4之外的额外参数比如ROW_FORMAT旧版本可能不认。二是初始数据里我放了几个测试账号一个管理员、两家企业、三个学生方便拿到项目后立刻登录演示不用自己一条条造数据。初始数据的作用不要小看。答辩的时候导师要看效果你现场临时注册一个企业账号、填一堆资料体验很尴尬。预置好的数据直接展示列表页、详情页、统计图表专业感立刻就出来了。3.3 关键表关系与字段设计细节用 Navicat 看设计图的话能更直观地理解表关系。学生表通过user_id和student_profile一对一关联企业表同理。job表通过company_id关联企业resume_delivery同时持有student_id和job_id两个外键。collection表也类似。有两个字段设计上的细节值得提一下。时间字段我统一用datetime类型而不是timestamp因为timestamp有 2038 年问题而且范围受到时区影响。job表里的salary_min和salary_max我没有合并成一个字符串而是拆成两个整型字段这样后面前端做薪资区间筛选时可以直接用 SQL 比较大小如果用字符串存储筛选条件会写得非常痛苦。企业资质审核状态我也是用整型0 表示待审核1 表示已通过2 表示已驳回。用数字而不是字符串的好处是枚举的扩展性更强而且数据库占用更小。前端拿到数字后用自定义过滤器映射成对应的文字展示即可。4. 后端核心功能实现4.1 登录认证与 JWT 拦截器登录是整个系统的入口我用的方案是 JWT Spring 拦截器没有引入 Spring Security原因很简单Spring Security 的学习成本高配置复杂对毕设来说属于过度设计。用户登录成功后后端生成一个 token把userId和role都放进 token 的 claims 里。前端把它存在 localStorage 中每次请求在 axios 拦截器里放到Authorization请求头。后端的拦截器会统一放行登录接口和注册接口其他接口全部校验 token解析失败直接返回 401。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; } String token request.getHeader(Authorization); if (token null || token.isEmpty()) { throw new BusinessException(401, 未登录或 token 已过期); } // 解析 token把用户信息放入 request 属性 Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } }有了 token 里自带的 userId后端接口就不需要前端再传用户 ID 了。比如学生投递简历的接口直接从拦截器设置好的 request 属性里取 userId再去查对应的学生资料这样避免了用户伪造 ID 操作他人账号的风险也省去了不少参数传递。4.2 学生端简历管理与投递流程学生端的核心功能是简历和投递。简历表设计成和教育背景、项目经历分开的形式但这版为了控制复杂度我用的是单表存储字段包括姓名、性别、出生日期、学历、院校、专业、手机号、邮箱、期望城市、期望职位、个人介绍等。这样前端在一个表单里就能完成全部编辑存储和查询都简单。投递流程是系统里业务逻辑最完整的一段。学生点击职位详情页的立即投递按钮后端要做三步校验当前登录用户是否是学生简历是否已完善没有完善提示先去完善是否已经投递过这个职位防止重复投递。校验通过后插入一条resume_delivery记录状态为 0待查看。PostMapping(/delivery) public Result delivery(RequestBody DeliveryDTO dto, HttpServletRequest request) { Integer userId (Integer) request.getAttribute(userId); StudentProfile student studentProfileService.getByUserId(userId); if (student null) { return Result.error(请先完善个人简历); } int count deliveryService.count( new LambdaQueryWrapperResumeDelivery() .eq(ResumeDelivery::getStudentId, student.getId()) .eq(ResumeDelivery::getJobId, dto.getJobId()) ); if (count 0) { return Result.error(您已投递过该职位); } // 插入投递记录 ... }投递状态的变化会产生通知。这里我没有引入消息队列或者 WebSocket而是采用了一个简单方案企业端进入收到的简历列表时按create_time倒序排列新的投递自然排在最上面。对于毕设项目这种实现方式已经能很好地展示业务流转了。4.3 企业端职位发布与简历筛选企业端的核心是职位管理和简历处理。企业登录后只能看到自己发布的职位这是通过 MyBatis-Plus 的LambdaQueryWrapper加上companyId条件实现的。发布职位时后端会校验当前用户角色是否为企业以及企业资质是否审核通过。简历处理是企业端最有演示价值的功能。企业打开职位详情能看到所有投递这个职位的学生列表点击每条记录可以查看学生简历详情然后操作通过或拒绝对应delivery_status从 1已查看变为 2已邀请或 3已拒绝。如果通过了后端会生成一条面试邀请记录包含面试时间、地点、备注。学生端的投递记录页面立刻就能看到状态变化并展示面试邀请详情。整个流程跑一遍就是一个完整的业务闭环这也是答辩时最容易出彩的演示路径。4.4 管理员后台审核与统计管理员端承担了平台的监督职责。核心功能有四个企业资质审核、职位审核、数据统计、管理员日志。企业注册后状态默认是待审核企业发布职位时也会标记待审核管理员可以在后台选择通过或驳回驳回时填写原因企业端登录后可以看到。数据统计用了 ECharts 展示。后端提供两个统计接口一个按行业维度统计职位数量柱状图一个按时间维度统计投递趋势折线图。SQL 上就是简单的GROUP BY加COUNT配合 MyBatis-Plus 的selectMaps方法来返回列表。前端在管理员首页加载后调接口拿到数据用 ECharts 渲染出图表。这两张图放在页面上整个管理后台的分量就上来了。4.5 接口统一响应与全局异常处理这个项目里我把所有接口的返回结果都封装成了一个统一格式包括状态码、消息、数据三个字段。这样做的好处是前端的 axios 拦截器可以统一判断状态码。业务上还有自己定义的异常和全局异常处理器。全局异常处理非常值得一做。如果没有它代码里出现空指针异常时返回的是 SpringBoot 默认的错误页前端拿到的是 HTML 内容调试起来特别痛苦。加上RestControllerAdvice之后所有异常都被统一转换成 JSON 响应同时可以在异常处理器里打日志方便定位问题。这是整个项目里性价比最高的一个工具一定不要省。5. 前端页面与接口联调5.1 Vue 项目初始化与目录结构前端我用 Vue CLI 4 初始化项目目录结构按功能做了划分src/ ├── api/ # 接口调用模块按业务域拆分 │ ├── auth.js │ ├── student.js │ ├── company.js │ └── admin.js ├── router/ # 路由配置 ├── store/ # Vuex 状态管理 ├── views/ # 页面组件 │ ├── login/ │ ├── student/ │ ├── company/ │ └── admin/ ├── components/ # 公共组件 └── utils/ # axios 封装、工具函数api目录的拆分是我觉得前端最值得保持的习惯。每个页面的组件不要直接从views里发 axios 请求而是通过api模块导出函数页面只负责调用。比如登录页面调用的是api/auth.js里的login(userInfo)函数请求 URL 集中管理后续修改后端路径时只需要改一个地方。5.2 登录拦截与路由守卫前端路由守卫实现了页面级别的访问控制。beforeEach里检查localStorage是否有 token有就放行没有就跳转到登录页。登录成功后根据返回的角色字段跳转到对应的首页学生跳/student/home企业跳/company/home管理员跳/admin/dashboard。这里有个容易踩的坑路由守卫里不要只判断有没有 token还要判断 token 是否过期。如果后端返回 401axios 的响应拦截器应该统一处理清空本地存储并强制跳回登录页。否则会出现 token 过期但是前端仍然停留在页面里用户点了任何按钮都会报错体验很糟。// axios 响应拦截器 service.interceptors.response.use( response { const res response.data if (res.code 401) { localStorage.removeItem(token) router.push(/login) return Promise.reject(new Error(登录已过期)) } return res }, error { return Promise.reject(error) } )5.3 列表页与分页组件的实践列表页是前后端分离项目里最常见的页面类型企业端的职位列表、学生端的岗位列表、管理员的审核列表都是。前端用 Element UI 的el-pagination组件把currentPage和pageSize传递给后端接口后端返回total总数和当前页的记录列表前端更新表格数据和总条数。岗位列表页的搜索条件包括关键词、城市和薪资区间。前端把筛选表单绑定的对象作为请求参数传给后端后端用 MyBatis-Plus 的LambdaQueryWrapper动态拼接查询条件。注意salary_min和salary_max要分开传后端分别用ge和le方法比较。动态条件查询是高频考点写的时候要多注意避免空条件导致的查询范围过大问题。5.4 前后端联调的协作方式联调阶段最怕的就是接口路径对不上。我提供的接口文档在联调中的核心作用是定义每个接口的路径、请求方式、请求参数、响应格式。前端同学照着文档写 axios 调用后端口按文档实现两边用同一个标准对齐开个玩笑说这叫按文档办事。实际开发中接口文档不是一次写死的往往需要沟通调整。比如我原本设计的投递接口是 POST 请求参数是{studentId, jobId}但后来发现学生 ID 可以从 token 里取于是改成了{jobId}前端自动就从 9 变成了 8 个参数。这种调整记录下来最终版接口文档就是答辩时最好的技术产出之一。6. 接口文档与系统测试6.1 接口文档的规范与示例这套接口文档我用的是 Markdown 格式按角色模块分成几部分公共接口、学生端接口、企业端接口、管理员接口。每个接口包含字段说明、请求示例、响应示例和错误码说明。接口文档不是随便写写就行的它有实际的用处。一是你自己在开发调试过程中对着文档检查返回给前端的数据结构能更快发现问题。二是毕设论文里需要附上系统设计说明接口文档可以直接作为核心内容。三是答辩时有老师问你觉得这个项目的接口设计有什么特点你可以直接引用文档里的统一响应格式和分页参数设计来说明。一个完整的接口说明大致长这样POST /api/auth/login请求参数username账号必填password密码必填role角色类型1 学生、2 企业、3 管理员必填响应示例{ code: 200, message: success, data: { token: eyJhbGciOiJIUzI1NiJ9, role: 1 } }6.2 接口权限控制设计接口权限是整个系统安全性的核心。我采用的是基于角色的拦截器逻辑根据 request 属性里解析出来的角色判断能否访问接口。拦截器里做了一层分类/api/auth/**放行/api/student/**需要角色为 1/api/company/**需要角色为 2/api/admin/**需要角色为 3。这里有个微妙的问题投递简历这个动作是学生端发起的但是企业端也需要查看投递记录。我的方案是路由设计上做了拆分投递动作放在/api/student/delivery下查看投递列表放在/api/company/delivery下。按操作者而不是按数据归属来划分接口路径这样权限控制逻辑就很清晰每个接口的角色归属没有歧义。6.3 功能测试与性能注意点整个系统的联调测试我大约花了两天时间。第一遍主要测功能注册、登录、发布职位、投递简历、审核通过、数据统计走通主流程。第二遍测边界重复投递、未登录访问、错误密码、越权访问。这些问题在第一轮测试中大多数都能发现发现后第一时间修复并补进接口文档。性能方面毕设项目并发量不会太大但我做了两个优化点分页查询是必需的后端所有列表接口都做了分页图片资源走的是本地存储路径没有用 Base64 上传到数据库避免数据库体积过大。如果你部署到云服务器建议把上传路径单独目录化配置一个虚拟路径映射不然项目重新打包后上传的文件会丢失。7. 常见问题与避坑指南7.1 环境配置类问题问题一SpringBoot 版本太高导致 JDK 版本不兼容。SpringBoot 3.x 需要 JDK 17如果你电脑上是 JDK 8启动时直接报UnsupportedClassVersionError。解决办法是换成 SpringBoot 2.7.x它兼容 JDK 8。如果你非要尝鲜用 SpringBoot 3.x那就要先装 JDK 17并且 IDEA 里的 Project Structure 也要改。问题二MyBatis-Plus 分页失效。分页插件没有生效通常是因为没在配置类里添加拦截器。MyBatis-Plus 3.5 以后分页需要显式配置MybatisPlusInterceptor否则Page对象返回的 total 是 0查询结果还是全量数据。加了配置类以后重启项目就能正常分页。问题三接口访问全部 404 或者报 405。404 大概率是路径写错了检查 controller 的RequestMapping路径和前端 api 模块里的路径是否一致。405 是请求方式不匹配比如后端定义的是 POST前端用 GET 请求。这类问题靠接口文档就能解决每个接口都标注清楚方法和路径对照检查即可。7.2 业务逻辑类问题问题四重复投递导致数据脏。这个问题的本质是缺少唯一约束。在resume_delivery表设计时我给student_id job_id建了唯一索引uk_student_job虽然代码层也做了校验但数据库索引是最后一道防线。如果代码逻辑有漏洞有并发情况出现唯一索引也能保证不会产生重复数据。问题五企业看不到学生投递的简历。排查思路是检查投递记录里的company_id是否在插入数据时正确保存。如果你在resume_delivery表里没有冗余company_id字段查询时要通过job表关联企业的 idSQL 要写对关联条件。建议在业务设计上把company_id冗余到投递记录表查询时少一层 JOIN性能更好逻辑也更直接。7.3 答辩演示的注意事项答辩演示是最容易出状况的环节。我的经验是演示前先把测试数据准备好登录几个账号都提前把 session 调通页面打开不要现场挂后台看日志因为日志刷屏会让人感觉系统不稳定尽量走通一条学生投递、企业处理、面试邀请的完整流程让导师直观感受到系统的业务闭环。如果在演示现场遇到接口报错不要慌也别去翻日志。直接说这个问题我本地已经处理过可能是现场网络/环境问题然后切换到下一个演示功能。强推的优化点是把演示脚本提前写下来包含登录、发布职位、投递简历、查看状态、管理员审核、数据统计的完整链路按脚本演基本不会卡壳。8. 从毕设到真实项目一些延伸建议这个系统做完之后我发现它的代码结构可以很方便扩展出很多真实场景下的功能。比如数据库表加一张message表配合 WebSocket 就能做成站内信通知职位表加一个view_count字段就能实现职位的浏览量统计把学生端和企业端的列表改成 ElasticSearch 搜索就能解决关键词查询的性能问题。如果你打算把这个项目当作能力展示放进简历里我建议你重点打磨三块一是把权限模型搞清楚最好画一张角色权限矩阵图面试官问起来能讲清楚二是把接口文档整理成一份完整的说明文档这本身就是规范化开发的体现三是了解一些基本的部署知识比如怎么把前后端分别部署到一台云服务器上用 Nginx 做反向代理。这些在简历上都能成为实打实的加分项。我在实际做这个项目的过程中最大的体会是毕设项目的价值不在用了多新多炫的技术而在于你能不能在限定的时间内把一个业务完整地闭环实现掉。遇到问题知道怎么查、怎么试、怎么改这个能力比什么都值钱。如果你打算基于这个项目来改造建议第一步先跑起来按照 SQL 脚本导入数据、改配置、启动后端、启动前端、登录测试账号走一遍流程把工程跑通了再谈定制比一上来就改代码要稳妥得多。