SpringBoot+Vue校园招聘管理系统:表设计、权限控制与答辩验证
简介基于Spring Boot与Vue的校园招聘管理系统是一份答辩通过的高分毕业设计源码项目适合Java方向的毕业生或在校学生用于毕业设计、课程设计、期末大作业等场景。系统覆盖企业、职位、简历投递与后台管理等校园招聘核心功能能够帮助学习者直观理解前后端分离项目的目录结构与业务流程。压缩包共698个文件约15.41MB核心包含145个Java后端源码、78个Vue前端组件、81个HTML页面以及JavaScript、CSS、SQL数据库脚本、Word说明文档、截图与GIF效果图等辅助资源资源按模块分层组织查阅与二次开发都很方便。项目代码完整、开箱即用无需修改即可本地部署展示内置数据库初始化脚本与配置文件可快速完成运行演示也方便在此基础上扩展功能或进行二次开发。目前已有729人学习适合需要快速获取成熟毕业设计模板或学习Spring BootVue开发流程的使用者。1. 校园招聘管理系统SpringBoot 与 Vue 在这个题目里真正卡住人的地方每年毕业设计季基于 SpringBoot Vue 的校园招聘管理系统都是 java 方向出现频率最高的选题之一。源码包拿到手之后真正卡住人的不是启动不了而是三件事表结构经不起追问、投递状态流转讲不清、答辩演示时临时翻数据库。这套系统的业务本质是学生、企业、管理员三个角色围绕职位和简历做状态流转SpringBoot 提供接口与权限控制Vue 负责页面交互MySQL 存全量业务数据。选题不新但把角色边界、状态机、权限拦截和部署验证做扎实就能和只会跑通 CRUD 的作业拉开差距。下面按表设计、后端、前端、答辩验证四个环节展开思路可以直接套到自己的项目里。2. 选型与表设计SpringBoot 单体架构下的招聘业务建模这个题目的数据量级就是几千学生、几百家企业、几万条投递记录完全落在单体 SpringBoot 的能力范围内。先把业务边界划清楚再动手建表后端接口写起来会顺很多。2.1 为什么用 SpringBoot 单体加 Vue 页面而不是微服务校园招聘系统的并发量和服务端复杂度都不需要微服务。常见的可靠做法是 SpringBoot 单体拆成 controller/service/mapper 三层登录态用 JWT 管理数据库只开一个 MySQL 实例。Vue 侧用 vue-router 管理页面axios 调接口前端打包后的 dist 目录由 SpringBoot 静态资源托管整个系统一个端口跑完部署成本最低。微服务、消息队列、Redis 缓存这些技术可以在论文的选型对比章节里分析但不要写进实现否则答辩被问到分布式事务、缓存一致性时很难收场。选型原则是一台机器能跑通的系统不要引入第二台机器的复杂度。同理数据库连接池用 HikariCP 默认配置即可不需要额外引入中间件。2.2 核心表设计sys_user、job_position、job_application 的字段取舍招聘业务的核心不是用户表而是投递记录表。一张设计合理的投递表要同时回答三个问题谁投了哪个职位、当前状态是什么、什么时候投的。围绕这个目标四张表足够覆盖全部业务。表名职责关键字段设计要点sys_user学生、企业、管理员统一账号username, password, rolepassword 存 BCrypt 密文不存明文company_info企业资料user_id, company_name, industry与 sys_user 一对一注册时联动创建job_position职位信息title, company_id, salary_min, salary_max, statusstatus 控制审核流列表查询只过滤已发布resume学生简历student_id, file_url, profile文件存磁盘或 OSS表里只存路径job_application投递记录job_id, student_id, status加唯一索引防重复投递status 是状态机role 字段决定权限1 管理员、2 学生、3 企业。job_position 的 status 是审核流的核心0 待审核、1 已发布、2 已下架学生端列表只查 status1。job_application 的 status 是业务流程核心0 已投递、1 已查看、2 面试中、3 已录用、4 已拒绝。投递表上加UNIQUE KEY uk_job_student (job_id, student_id)是最容易被忽略但最有价值的约束。没有它前端重复点击投递按钮就会产生多条脏数据代码里再判断都不如数据库约束兜底。2.3 数据库初始化MySQL 建库脚本与初始数据-- 校园招聘管理系统核心建表脚本MySQL 8.0字符集 utf8mb4 CREATE DATABASE IF NOT EXISTS campus_recruit DEFAULT CHARACTER SET utf8mb4; USE campus_recruit; -- 用户表学生、企业、管理员共用role 区分身份 CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt 加密后的密码, role TINYINT NOT NULL DEFAULT 2 COMMENT 1管理员 2学生 3企业, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 职位表企业发布status 控制审核与上下架 CREATE TABLE job_position ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT 职位名称, company_id BIGINT NOT NULL COMMENT 发布企业关联 sys_user.id, salary_min DECIMAL(10,2) DEFAULT 0, salary_max DECIMAL(10,2) DEFAULT 0, location VARCHAR(100) DEFAULT NULL, requirement TEXT COMMENT 任职要求, status TINYINT DEFAULT 0 COMMENT 0待审核 1已发布 2已下架, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; -- 投递记录表唯一索引保证一个学生对一个职位只能投一次 CREATE TABLE job_application ( id BIGINT PRIMARY KEY AUTO_INCREMENT, job_id BIGINT NOT NULL, student_id BIGINT NOT NULL, cover_note VARCHAR(255) DEFAULT NULL COMMENT 求职留言, status TINYINT DEFAULT 0 COMMENT 0已投递 1已查看 2面试中 3已录用 4已拒绝, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_job_student (job_id, student_id) ) ENGINEInnoDB;建表脚本里的 COMMENT 不是装饰MyBatis-Plus 生成实体注释、答辩画 E-R 图都要靠它。初始管理员账号不要在 SQL 里手写加密串正确做法是在 SpringBoot 启动类里写一个 CommandLineRunner检测到 sys_user 表为空时用 BCrypt 加密默认密码后插入避免手抄哈希出错。3. 后端实现SpringBoot 招聘接口、JWT 权限与参数适配后端的关键在于把三个角色的权限边界收住。学生不能发布职位企业不能替学生投递管理员不参与投递流程这些规则要落在接口层而不是前端按钮的显隐上。3.1 JWT 登录与拦截器一个 token 区分学生、企业、管理员采用 JWT 而不是 session是因为前端是 Vue 独立部署接口需要无状态认证。登录成功后把 userId 和 role 放进 token 的 claims 里拦截器解析后塞进 request attributecontroller 直接取用不需要每次查库。// JwtInterceptor.java - 登录态校验解析 token 并放入 request 上下文 Component public class JwtInterceptor implements HandlerInterceptor { Value(${jwt.secret}) private String secretKey; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 预检请求直接放行否则前端跨域请求会被拦截 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } response.setStatus(401); return false; } }代码逻辑分三步先放行 OPTIONS 预检再取 Authorization 头并去掉 Bearer 前缀最后解析 JWT 并写入 userId 和 role。解析失败或 token 缺失统一返回 401由前端 axios 拦截统一跳登录页。注意 jjwt 的setSigningKey在 0.11.x 版本里已废弃直接Jwts.parser().verifyWith(secretKey).build()是新写法如果下载的源码报错先看这个位置。3.2 职位发布与投递接口状态流的最小可运行实现发布和投递是招聘系统的两个核心动作一个属于企业角色一个属于学生角色权限校验都放在 controller 层。// JobController.java - 核心两个接口角色判断优先于业务逻辑 RestController RequestMapping(/api/job) public class JobController { PostMapping(/publish) public Result publish(RequestBody JobPosition job, RequestAttribute(userId) Long userId, RequestAttribute(role) Integer role) { // 角色校验只有企业角色能发布职位 if (role null || role ! 3) { return Result.error(无发布权限); } job.setCompanyId(userId); job.setStatus(0); // 新职位默认待审核管理员通过后才可见 jobService.save(job); return Result.ok(提交成功等待审核); } PostMapping(/apply) public Result apply(RequestParam Long jobId, RequestAttribute(userId) Long studentId) { JobApplication app new JobApplication(); app.setJobId(jobId); app.setStudentId(studentId); app.setStatus(0); boolean ok jobService.apply(app); // 内部捕获唯一索引冲突 return ok ? Result.ok(投递成功) : Result.error(已投递过该职位请勿重复投递); } }发布接口里把companyId强制覆盖为当前登录用户而不是信任前端传值这是权限校验的常见漏洞。投递接口依赖数据库唯一索引兜底service 层捕获DuplicateKeyException返回友好提示。职位审核、下架、状态更新都是对 job_position.status 的枚举值变更流程上每变更一次记录一次操作时间答辩时这几条 SQL 查询记录就是完整证据链。3.3 application.yml 关键参数与 SpringBoot 版本适配下载的源码跑不起来十有八九是配置参数问题。把这几项对一遍能省半天时间。配置项推荐值说明spring.datasource.urljdbc:mysql://localhost:3306/campus_recruit?serverTimezoneAsia/Shanghai缺时区参数会出现 8 小时时间差spring.datasource.password本地环境密码提交源码前改为空或写进文档不要提交真实密码server.servlet.context-path/api前后端统一前缀避免跨域路径混乱spring.servlet.multipart.max-file-size10MB简历附件大小上限mybatis-plus.global-config.db-config.logic-delete-value1用逻辑删除替代物理删除保留投递记录提示SpringBoot 2.x 用的是 javax.servlet 命名空间SpringBoot 3.x 全面切换到 jakarta.servlet两者不兼容。网上下载的源码大多基于 2.7不要为了追新强行升级到 3.x否则 import 全会报错。如果答辩被问到 SpringBoot 版本能说清 2.x 与 3.x 在 javax/jakarta 上的分界点比背任何 springboot 面试题都更有说服力。4. Vue 前端联调路由、Axios 封装与简历上传前端部分的核心不是页面样式而是路由权限控制和接口调用的统一封装。把这两个骨架搭好业务页面只是往里面填充。4.1 vue-router 按角色拆分路由路由设计采用布局组件加子路由的方式学生、企业、管理员的页面各自挂在自己的布局下通过路由元信息 meta.role 标记访问角色。// router/index.js - 按角色拆路由meta.role 供守卫判断 import { createRouter, createWebHistory } from vue-router const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /student, component: () import(/layout/StudentLayout.vue), meta: { role: 2 }, children: [ { path: jobs, component: () import(/views/student/JobList.vue) }, { path: applications, component: () import(/views/student/MyApplications.vue) } ] }, { path: /company, component: () import(/layout/CompanyLayout.vue), meta: { role: 3 }, children: [ { path: publish, component: () import(/views/company/PublishJob.vue) }, { path: applicants, component: () import(/views/company/Applicants.vue) } ] } ]meta.role配合全局前置守卫router.beforeEach读取 localStorage 里的角色信息不匹配就重定向到登录页。页面间传参建议用 query 方式传 jobId刷新页面后参数不会丢比 this.$route.params 更适合列表跳详情这种场景。使用 vue-router 的createWebHistory时后端要对未匹配路径做转发否则部署后直接刷新子路由页面会 404。4.2 Axios 实例封装与 Vite 转发配置所有接口调用统一走一个 axios 实例请求拦截器自动加 Authorization 头响应拦截器统一处理 401 和业务错误码。// utils/request.js - 统一请求实例 import axios from axios import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器从 localStorage 取 token 并写入请求头 request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器401 时清 token 并跳登录页 request.interceptors.response.use( res res.data, err { if (err.response err.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(err) } )开发环境的跨域问题在 vite.config.js 里用 server.proxy 转发解决前端所有请求走相对路径 /apiVite 开发服务器把请求转发到后端的 8080 端口。生产环境前端打包后的 dist 由 SpringBoot 托管浏览器直接访问同源服务不存在跨域。一套 baseURL 适配两种环境关键是后端也要配server.servlet.context-path/api两边保持一致。// vite.config.js - 开发环境转发配置 server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }4.3 职位列表与简历上传组件的实现要点职位列表页面就是典型的加载数据、渲染列表、触发操作三段式。核心逻辑放在 script setup 里用 onMounted 拉取第一页数据投递成功后重新加载避免手动拼接数据造成状态不一致。简历上传有两种常见做法选哪种取决于你是否需要额外参数。方式适用场景注意点el-upload 的 action 直传页面结构简单必须动态设置 headers 里的 Authorizationaxios 手动 FormData需要附加字段可自定义进度回调和错误处理!-- 职位列表页面核心逻辑 -- script setup import { ref, onMounted } from vue import request from /utils/request const jobs ref([]) const loading ref(false) async function loadJobs() { loading.value true const data await request.get(/job/list?status1current1size10) jobs.value data.records loading.value false } async function apply(jobId) { await request.post(/job/apply, null, { params: { jobId } }) await loadJobs() // 投递成功后刷新列表让已投递按钮置灰 } onMounted(loadJobs) /script简历上传建议用 axios 手动构造 FormData这样外层可以拿到上传进度还能在同一个请求里带上 coverNote 和 resumeId。上传成功接口返回 file_url存到 resume 表之后再投递时把 resumeId 一起传给后端形成简历和投递记录的关联。5. 答辩前必做的三件验证数据一致性、权限边界、演示脚本第一件验证状态机完整性。用 SQL 按 status 分组统计 job_application 的记录数走一遍投递→查看→面试→录用的完整链路确认每个状态变更都有接口触发、页面能同步看到。常见的坑是数据库枚举值和前端下拉选项对不上职位 status 在库里是 1已发布前端判断条件写成了 2整个列表空转。第二件验证权限边界。开三个浏览器分别登录学生、企业、管理员直接粘贴对方页面的 URL确认会被路由守卫和后端拦截器双重挡下。重点测试学生 token 调企业发布接口、企业 token 调投递接口这两个越权场景接口层返回 401 或业务错误码而不是只靠前端隐藏按钮。第三件准备演示脚本和两个高频追问。演示顺序固定为注册学生和企业账号→企业发布职位→管理员审核→学生检索并投递→企业查看更新状态。被问到设计取舍时能说清投递表唯一索引、密码 BCrypt 加密、JWT 无状态认证这三个点的理由比背八股文有效得多。检查项操作步骤预期结果重复投递拦截同一学生重复点击投递同一职位第二次返回已投递过提示角色越权学生 token 调用发布接口返回无权限401 或业务错误时间字段查看投递记录的 created_at与本地时间一致无 8 小时时差提示演示时把重复投递被唯一索引拦截这个细节当众演出来比翻十张列表页更能让评委相信系统是你亲手调通的。这三点验证全部走完再导出最新的数据库 SQL 文件放进源码包源代码和数据库就能一致地交付出去。最后记得在 README 里写明 JDK、MySQL、Node 版本号这一步能挡住大多数拿到源码跑不起来的追问。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →