高校就业招聘系统实战:SpringBoot + Vue 全栈开发与部署避坑指南
简介本资源为基于JavaSpringBootVueMySQL的高校就业招聘系统完整毕业设计包面向计算机相关专业学生与课程设计开发者提供可直接运行的高分项目方案。包内共980个文件涵盖169个Java后端源码、87个Vue前端组件、161个JavaScript脚本、74个HTML页面及52个CSS样式文件另含SQL数据库脚本、论文文档与部署批处理脚本压缩包约27.58MB前后端代码与数据库脚本齐备。系统实现招聘信息发布查询、学生简历在线提交与更新、单位信息审核、面试安排及就业数据分析等模块界面简洁、操作直观后台管理便捷。项目经严格调试搭配IDEA、Maven与Navicat即可部署运行已有58人学习关注。读者可获得完整源码、数据库脚本、论文资料与项目结构参考适合作为毕设、课程设计或期末大作业直接使用也便于理解SpringBoot与Vue前后端分离的开发逻辑与工程组织方式。1. 高校就业招聘系统从三方对接到简历投递链路的完整拆解每年三四月份高校就业办的老师最头疼的一件事就是企业HR在微信群里发招聘简章学生把简历发到邮箱就业办再手动汇总成Excel。三方数据对不上岗位过期没人下架学生投了简历石沉大海。这套基于 Java SpringBoot Vue MySQL 的高校就业招聘系统解决的就是这条链路上的信息断层问题。它把企业入驻、岗位发布、学生投递、就业办审核四个角色拉到同一个平台里数据一次录入、多方可见。适合正在做毕设的计算机专业学生也适合想用一套完整项目练手 SpringBoot Vue 前后端分离的 Java 工程师。下面从表结构设计一路讲到部署上线把每个环节的参数和坑都摊开说。2. 技术选型与数据库设计为什么是 SpringBoot Vue MySQL 这套组合2.1 后端选 SpringBoot 而不是 SSM 的四个理由很多高校毕设还在用 SSMSpring SpringMVC MyBatis做招聘系统但实际开发中你会发现配置量太大。SpringBoot 的核心优势在于自动配置和起步依赖对于招聘系统这种中等复杂度的业务能省掉大量 XML 配置时间。具体来说招聘系统需要的能力包括RESTful 接口、MyBatis 或 MyBatis-Plus 做 ORM、拦截器做登录校验、定时任务处理岗位过期下架。SpringBoot 用 starter 把这些整合好了spring-boot-starter-web管 Web 层mybatis-plus-boot-starter管数据层spring-boot-starter-validation管参数校验。你只需要在application.yml里写数据库连接和端口剩下的交给自动配置。版本选择上有个血泪经验SpringBoot 3.x 要求 Java 17 起步而很多学校机房环境还是 JDK 8 或 11。如果毕设答辩用的机器不受你控制建议锁 SpringBoot 2.7.x JDK 8/11 这套组合稳定且资料多。用 SpringBoot 3.x 的话javax.servlet全部换成了jakarta.servlet网上搜到的很多代码直接跑不起来。提示如果导师没有硬性要求版本SpringBoot 2.7.18 是 2.x 的最后一个稳定版社区资料最全踩坑成本最低。2.2 前端选 Vue 3 还是 Vue 2Vue 3 的 Composition API 在招聘系统这种表单密集、列表密集的场景下逻辑复用比 Vue 2 的 Options API 清晰很多。比如「岗位列表」和「简历列表」都需要分页、搜索、筛选用setup语法可以把这些逻辑抽成usePagination和useSearch两个组合函数两个页面直接复用。但如果你对前端构建工具不熟Vue 2 Element UI 的上手速度更快因为网上现成的招聘系统模板大多是这套。Vue 3 对应的是 Element Plus组件属性有变化比如el-table的slot-scope写法在 Vue 3 里改成了v-slot。安装和环境配置这块Node.js 建议用 16.x 或 18.x LTS 版本。用nvm管理 Node 版本避免全局装一个版本导致老项目跑不起来。创建项目用 Vite# 用 Vite 创建 Vue 3 项目选择 Vue JavaScript 模板 npm create vitelatest employment-frontend -- --template vue # 进入目录安装依赖 cd employment-frontend npm install # 安装 Element Plus、Axios、Vue Router、Pinia npm install element-plus axios vue-router4 pinia # 启动开发服务器 npm run devnpm create vite是 Vite 官方脚手架比vue-cli快很多。--template vue指定用 JavaScript 而不是 TypeScript毕设场景下 JS 够用且省去类型定义的麻烦。Element Plus 是 Vue 3 的 UI 库Axios 负责和后端通信Pinia 替代 Vuex 做状态管理Vue Router 4 是 Vue 3 配套的路由版本。2.3 招聘系统核心表结构设计数据库设计是招聘系统的地基表建错了后面改起来伤筋动骨。核心表包括用户表、企业表、岗位表、简历表、投递记录表、就业办审核表。下面给出关键表的建表 SQL-- 用户表存储学生、企业HR、就业办管理员三类角色 CREATE TABLE sys_user ( user_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 登录账号, password VARCHAR(100) NOT NULL COMMENT 密码BCrypt加密, real_name VARCHAR(50) DEFAULT NULL COMMENT 真实姓名, role TINYINT NOT NULL COMMENT 角色1学生 2企业 3就业办, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, status TINYINT DEFAULT 1 COMMENT 状态0禁用 1正常, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (user_id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; -- 岗位表企业发布的招聘岗位 CREATE TABLE job_position ( job_id BIGINT NOT NULL AUTO_INCREMENT, company_id BIGINT NOT NULL COMMENT 发布企业ID, job_name VARCHAR(100) NOT NULL COMMENT 岗位名称, job_desc TEXT COMMENT 岗位描述, salary_range VARCHAR(50) DEFAULT NULL COMMENT 薪资范围, city VARCHAR(50) DEFAULT NULL COMMENT 工作城市, degree_require VARCHAR(20) DEFAULT NULL COMMENT 学历要求, headcount INT DEFAULT 1 COMMENT 招聘人数, deadline DATE DEFAULT NULL COMMENT 截止日期, audit_status TINYINT DEFAULT 0 COMMENT 审核状态0待审 1通过 2驳回, publish_status TINYINT DEFAULT 0 COMMENT 发布状态0下架 1上架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (job_id), KEY idx_company (company_id), KEY idx_audit (audit_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT招聘岗位表; -- 投递记录表学生投递简历到岗位的关联 CREATE TABLE delivery_record ( record_id BIGINT NOT NULL AUTO_INCREMENT, student_id BIGINT NOT NULL COMMENT 学生用户ID, job_id BIGINT NOT NULL COMMENT 岗位ID, resume_id BIGINT NOT NULL COMMENT 使用的简历ID, delivery_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 0 COMMENT 状态0已投递 1已查看 2面试邀请 3录用 4拒绝, PRIMARY KEY (record_id), UNIQUE KEY uk_student_job (student_id, job_id), KEY idx_job (job_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT简历投递记录表;sys_user表用role字段区分三类角色避免建三张用户表导致登录逻辑分叉。job_position表把审核状态和发布状态拆成两个字段因为企业提交岗位后先经过就业办审核审核通过后企业自己控制上架下架这是两个独立动作。delivery_record表加了uk_student_job唯一索引防止同一个学生对同一个岗位重复投递这个约束在业务层也要做校验但数据库层兜底更可靠。字符集统一用utf8mb4因为学生姓名和岗位描述里可能出现生僻字或特殊符号utf8三字节存不下。引擎用 InnoDB 支持事务投递记录和岗位余量扣减需要事务保证一致性。3. 后端接口实现从登录鉴权到简历投递的完整链路3.1 SpringBoot 项目结构与依赖配置一个清晰的包结构能让后续维护省很多力气。我一般按功能模块划分而不是按技术分层src/main/java/com/example/employment/ ├── EmploymentApplication.java // 启动类 ├── config/ // 配置类 │ ├── WebMvcConfig.java // 拦截器、跨域配置 │ └── MybatisPlusConfig.java // 分页插件配置 ├── controller/ // 接口层 │ ├── AuthController.java // 登录注册 │ ├── JobController.java // 岗位管理 │ └── DeliveryController.java // 投递管理 ├── service/ // 业务层 │ ├── impl/ │ └── JobService.java ├── mapper/ // 数据访问层 ├── entity/ // 数据库实体 ├── dto/ // 请求参数对象 ├── vo/ // 响应视图对象 └── common/ // 通用工具 ├── Result.java // 统一返回格式 └── JwtUtil.java // JWT 工具类pom.xml里核心依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies !-- Web 层 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus比原生 MyBatis 少写大量 XML -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- JWT 做无状态登录 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency !-- 参数校验 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependenciesMyBatis-Plus 的BaseMapper提供了单表 CRUD 的现成方法招聘系统里 80% 的查询是单表条件分页用LambdaQueryWrapper写起来比 XML 快得多。JWT 选 0.9.1 是因为 API 简单Jwts.builder()链式调用就能生成 token不需要额外配置。application.yml关键配置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/employment_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true # 数据库下划线转 Java 驼峰 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发期打印 SQL global-config: db-config: logic-delete-field: deleted # 逻辑删除字段 logic-delete-value: 1 logic-not-delete-value: 0map-underscore-to-camel-case让job_name自动映射到jobName省去手写Results。log-impl在开发期打开能看到实际执行的 SQL调分页和联表查询时非常有用上线前记得关掉。3.2 JWT 登录鉴权与拦截器配置招聘系统里学生、企业、就业办三种角色看到的菜单和能调的接口完全不同鉴权必须做细。方案是登录成功后签发 JWTtoken 里放userId和role前端每次请求带在Authorization头里后端拦截器解析后存入ThreadLocal。// JwtUtil.java 核心方法 public class JwtUtil { private static final String SECRET employment-system-secret-key-2024; private static final long EXPIRE 7 * 24 * 60 * 60 * 1000L; // 7天 // 生成 token把用户ID和角色写进 claims public static String generateToken(Long userId, Integer role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } // 解析 token失败会抛异常 public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }SECRET不要硬编码在代码里生产环境应该放到环境变量或配置中心。EXPIRE设 7 天是因为毕设演示场景下频繁登录很烦实际生产建议 2 小时配合 refresh token。拦截器里做三件事放行登录注册接口、校验 token 有效性、把用户信息塞进ThreadLocal。角色权限校验放在具体接口上用自定义注解RequireRole(role 2)标记拦截器统一处理。// AuthInterceptor.java 核心逻辑 Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 放行 OPTIONS 预检请求否则跨域会失败 if (HttpMethod.OPTIONS.matches(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BizException(401, 未登录); } try { Claims claims JwtUtil.parseToken(token.substring(7)); UserContext.set(claims.get(userId, Long.class), claims.get(role, Integer.class)); return true; } catch (ExpiredJwtException e) { throw new BizException(401, 登录已过期请重新登录); } catch (JwtException e) { throw new BizException(401, token 无效); } }UserContext用ThreadLocal存当前请求的用户信息Service 层直接UserContext.getUserId()就能拿到不用每个方法都传参。请求结束后在afterCompletion里调UserContext.remove()防止线程池复用导致信息串号这个坑不踩一次很难记住。3.3 岗位分页查询与多条件筛选岗位列表是招聘系统里最复杂的查询需要支持关键词搜索、城市筛选、学历筛选、薪资范围筛选、分页排序。用 MyBatis-Plus 的LambdaQueryWrapper动态拼接条件// JobServiceImpl.java 分页查询岗位 public PageJobVO queryJobPage(JobQueryDTO query) { LambdaQueryWrapperJobPosition wrapper new LambdaQueryWrapper(); // 只查审核通过且上架的岗位 wrapper.eq(JobPosition::getAuditStatus, 1) .eq(JobPosition::getPublishStatus, 1); // 关键词模糊匹配岗位名或企业名 if (StringUtils.hasText(query.getKeyword())) { wrapper.and(w - w.like(JobPosition::getJobName, query.getKeyword()) .or() .like(JobPosition::getCompanyName, query.getKeyword())); } // 城市精确匹配 if (StringUtils.hasText(query.getCity())) { wrapper.eq(JobPosition::getCity, query.getCity()); } // 学历要求 if (StringUtils.hasText(query.getDegree())) { wrapper.eq(JobPosition::getDegreeRequire, query.getDegree()); } // 薪资范围数据库存的是字符串如 8k-12k这里做区间重叠判断 if (query.getMinSalary() ! null) { wrapper.ge(JobPosition::getSalaryMax, query.getMinSalary()); } // 按发布时间倒序 wrapper.orderByDesc(JobPosition::getCreateTime); PageJobPosition page new Page(query.getPageNum(), query.getPageSize()); PageJobPosition result jobMapper.selectPage(page, wrapper); // 转 VO补充企业logo、投递人数等字段 return convertToVoPage(result); }wrapper.and(w - ...)这个写法很关键。如果直接写wrapper.like(...).or().like(...)后面的.eq(city)会和or条件混在一起变成(job_name like ? or company_name like ?) and city ?还是job_name like ? or (company_name like ? and city ?)取决于拼接顺序容易出玄学 bug。用and()包一层括号逻辑才明确。分页插件需要在配置类里注册Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }不注册这个插件selectPage返回的是全量数据分页参数不生效。这是 MyBatis-Plus 新手最常见的翻车点之一。3.4 简历投递的事务处理与重复投递拦截投递动作涉及两张表的写操作插入delivery_record记录同时更新岗位的投递计数。必须加事务否则计数和记录不一致。Service public class DeliveryServiceImpl implements DeliveryService { Transactional(rollbackFor Exception.class) Override public void deliver(Long studentId, Long jobId, Long resumeId) { // 1. 校验岗位是否存在且上架 JobPosition job jobMapper.selectById(jobId); if (job null || job.getPublishStatus() ! 1) { throw new BizException(岗位不存在或已下架); } // 2. 校验是否已投递数据库唯一索引兜底这里提前拦截给友好提示 Long count deliveryMapper.selectCount( new LambdaQueryWrapperDeliveryRecord() .eq(DeliveryRecord::getStudentId, studentId) .eq(DeliveryRecord::getJobId, jobId) ); if (count 0) { throw new BizException(您已投递过该岗位请勿重复投递); } // 3. 校验简历归属 Resume resume resumeMapper.selectById(resumeId); if (resume null || !resume.getStudentId().equals(studentId)) { throw new BizException(简历不存在或不属于当前用户); } // 4. 插入投递记录 DeliveryRecord record new DeliveryRecord(); record.setStudentId(studentId); record.setJobId(jobId); record.setResumeId(resumeId); record.setStatus(0); deliveryMapper.insert(record); // 5. 更新岗位投递计数用乐观锁或直接 update count count 1 jobMapper.update(null, new LambdaUpdateWrapperJobPosition() .eq(JobPosition::getJobId, jobId) .setSql(delivery_count delivery_count 1) ); } }Transactional(rollbackFor Exception.class)必须指定rollbackFor因为 Spring 默认只回滚RuntimeException如果抛的是受检异常不会回滚。setSql(delivery_count delivery_count 1)用数据库层面的原子自增避免先查后改的并发问题。4. 前端页面实现Vue 3 组件拆分与接口联调4.1 路由设计与角色菜单控制招聘系统前端有三套菜单学生端岗位浏览、我的投递、简历管理、企业端岗位管理、收到简历、企业信息、就业办端岗位审核、用户管理、数据统计。路由用meta.role标记可访问角色全局前置守卫里校验。// router/index.js const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /student, component: () import(/layout/StudentLayout.vue), meta: { role: 1 }, children: [ { path: jobs, component: () import(/views/student/JobList.vue) }, { path: deliveries, component: () import(/views/student/MyDelivery.vue) }, { path: resume, component: () import(/views/student/ResumeEdit.vue) } ] }, { path: /company, component: () import(/layout/CompanyLayout.vue), meta: { role: 2 }, children: [ { path: jobs, component: () import(/views/company/JobManage.vue) }, { path: received, component: () import(/views/company/ReceivedResume.vue) } ] } ] // 全局前置守卫 router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role Number(localStorage.getItem(role)) if (to.path /login) return next() if (!token) return next(/login) // 校验角色是否有权访问目标路由 if (to.meta.role to.meta.role ! role) { return next(/403) } next() })路由懒加载() import(...)让每个页面单独打包首屏只加载登录页学生端和企业端的代码按需加载。meta.role做粗粒度控制按钮级别的权限用v-ifrole 2控制。4.2 Axios 封装与统一错误处理前后端分离项目里请求拦截和响应拦截是必须封装的。请求拦截器统一加 token响应拦截器统一处理 401 跳登录、业务错误弹提示。// utils/request.js import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, // 开发环境走 Vite 代理 timeout: 10000 }) // 请求拦截加 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截统一处理业务码 request.interceptors.response.use( response { const res response.data // 后端统一返回 { code, msg, data } if (res.code ! 200) { ElMessage.error(res.msg || 请求失败) // 401 跳登录 if (res.code 401) { localStorage.clear() router.push(/login) } return Promise.reject(new Error(res.msg)) } return res.data }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default requestbaseURL: /api配合 Vite 的server.proxy配置开发时把/api代理到http://localhost:8080避免跨域。打包上线后由 Nginx 做同样的转发。// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } })changeOrigin: true修改请求头里的 Host某些后端框架会校验 Host 做安全防护不开这个可能被拒。rewrite把/api前缀去掉因为后端接口路径本身不带/api。4.3 岗位列表页的分页与筛选联动岗位列表页是学生端使用频率最高的页面分页、搜索、筛选三个状态要联动。用 Vue 3 的reactive管理查询参数watch监听变化重新请求。template div classjob-list !-- 筛选栏 -- el-form :inlinetrue :modelquery el-form-item el-input v-modelquery.keyword placeholder岗位/企业名称 clearable / /el-form-item el-form-item el-select v-modelquery.city placeholder城市 clearable el-option v-forc in cities :keyc :labelc :valuec / /el-select /el-form-item el-form-item el-button typeprimary clickhandleSearch搜索/el-button /el-form-item /el-form !-- 岗位卡片列表 -- div v-loadingloading el-card v-forjob in jobList :keyjob.jobId classjob-card h3{{ job.jobName }}/h3 p{{ job.companyName }} | {{ job.city }} | {{ job.salaryRange }}/p el-button typeprimary clickhandleDeliver(job)投递简历/el-button /el-card /div !-- 分页 -- el-pagination v-model:current-pagequery.pageNum v-model:page-sizequery.pageSize :totaltotal layouttotal, prev, pager, next current-changefetchJobs / /div /template script setup import { reactive, ref, onMounted } from vue import request from /utils/request import { ElMessage } from element-plus const query reactive({ keyword: , city: , pageNum: 1, pageSize: 10 }) const jobList ref([]) const total ref(0) const loading ref(false) const fetchJobs async () { loading.value true try { const data await request.get(/job/page, { params: query }) jobList.value data.records total.value data.total } finally { loading.value false } } const handleSearch () { query.pageNum 1 // 搜索时重置到第一页 fetchJobs() } const handleDeliver async (job) { try { await request.post(/delivery/deliver, { jobId: job.jobId, resumeId: 1 }) ElMessage.success(投递成功) } catch (e) { // 错误已在拦截器统一提示 } } onMounted(fetchJobs) /scripthandleSearch里把pageNum重置为 1 是个细节。如果用户翻到第 5 页再搜索不重置的话请求的是新条件下的第 5 页很可能为空用户以为没数据。v-model:current-page是 Vue 3 的写法Vue 2 里是:current-page.sync。5. 避坑与排查部署上线时最容易翻车的五个地方5.1 跨域问题开发环境正常打包后 404现象本地开发时前端调后端接口正常npm run build后部署到 Nginx所有接口返回 404 或 CORS 错误。原因开发环境的跨域是 Vite 的server.proxy解决的打包后静态文件由 Nginx 托管代理配置不存在了。如果后端也没配 CORS浏览器直接拦截。解决Nginx 配置里加location /api反向代理到后端端口同时后端加 CORS 配置兜底。server { listen 80; root /usr/share/nginx/html; index index.html; # 前端路由 history 模式刷新 404 的解决 location / { try_files $uri $uri/ /index.html; } # 接口代理到后端 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files $uri $uri/ /index.html解决 Vue Router history 模式刷新 404 的问题。proxy_pass末尾的/很关键http://127.0.0.1:8080/会把/api/job/page转成/job/page不加/则转成/api/job/page后端接口路径对不上就 404。5.2 MySQL 8 时区与驱动类名问题现象启动报The server time zone value йʱ is unrecognized或者连接被拒绝。原因MySQL 8 的驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver且默认时区不是东八区。解决JDBC URL 里加serverTimezoneAsia/Shanghai驱动类写com.mysql.cj.jdbc.Driver。如果 MySQL 装在 Windows 10 上安装时字符集选utf8mb4否则中文乱码。MySQL 5.7 和 8.0 的安装配置有差异5.7 默认latin18.0 默认utf8mb4用 5.7 的话建库时手动指定CHARACTER SET utf8mb4。5.3 逻辑删除与唯一索引冲突现象学生删除一条投递记录后重新投递同一岗位报唯一索引冲突。原因delivery_record表加了uk_student_job唯一索引逻辑删除只是把deleted置为 1记录还在表里唯一索引仍然生效。解决两个方案。一是唯一索引加上deleted字段变成uk_student_job_deleted(student_id, job_id, deleted)但这样同一条记录只能删一次。二是投递记录不做逻辑删除用状态字段标记「已撤回」撤回后允许重新投递时更新原记录状态而不是插入新记录。我一般用第二种业务上更合理。5.4 前端打包后白屏publicPath 配置错误现象npm run build后打开index.html白屏控制台报资源 404。原因Vite 默认base: /如果部署在子路径下如http://xxx/employment/资源路径全错。解决vite.config.js里设base: ./用相对路径或者设成实际子路径base: /employment/。用相对路径./最省心但要注意路由 history 模式下相对路径可能导致路由跳转异常这种情况还是用绝对子路径更稳。5.5 简历文件上传大小限制现象学生上传 PDF 简历超过 1MB 就报错。原因SpringBoot 默认的spring.servlet.multipart.max-file-size是 1MBmax-request-size是 10MB。解决application.yml里调大限制同时 Nginx 的client_max_body_size也要调否则请求到不了后端就被 Nginx 拒了。spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MBclient_max_body_size 20m;两个地方都要改只改一个另一个还会拦。文件存储路径不要放在项目目录里因为重新部署会覆盖建议配一个外部目录如/data/upload/resume/数据库里存相对路径。6. 让这套系统在答辩时多拿十分的两个进阶技巧第一个技巧是给岗位列表加 Redis 缓存。招聘系统里岗位列表是读多写少的典型场景每次请求都查数据库没必要。用 SpringBoot 整合 Redis把第一页热门岗位缓存 5 分钟企业发布新岗位时主动删缓存。核心代码就三行redisTemplate.opsForValue().get(key)查缓存查不到走数据库再set回去发布岗位时delete掉对应 key。答辩时老师问「高并发怎么优化」这就是现成的答案。注意缓存 key 要带上查询条件比如job:page:1:10:北京:本科否则不同筛选条件返回同一份缓存就出错了。第二个技巧是加一个简单的数据统计看板。就业办端最需要看到的数据是各学院投递人数、各企业收到简历数、岗位供需比。用一条 SQL 就能查出来-- 各学院投递人数统计 SELECT s.college, COUNT(DISTINCT d.student_id) AS student_count, COUNT(d.record_id) AS delivery_count FROM delivery_record d JOIN sys_user s ON d.student_id s.user_id GROUP BY s.college ORDER BY delivery_count DESC;前端用 ECharts 渲染成柱状图答辩演示时视觉效果拉满。ECharts 在 Vue 3 里用echarts包配合onMounted初始化注意在组件卸载时调dispose()销毁实例否则切换页面会内存泄漏。这套系统我从建表到部署完整走过一遍最大的教训是数据库设计阶段多花一天后面能省一周。尤其是角色和状态字段一开始想清楚有几种角色、岗位有几种状态流转比写到一半发现字段不够再改表强得多。另外SpringBoot 版本和 JDK 版本一定要在动手前确认好我见过太多人写到一半发现 SpringBoot 3 要 JDK 17 而自己环境是 JDK 8换版本导致依赖全红。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Vue组件中this丢失之谜:从JavaScript绑定机制到回调函数修复方案

Vue组件中this丢失之谜:从JavaScript绑定机制到回调函数修复方案

并不是标题党。几乎每个写过Vue的开发者都撞上过这么一幕:在methods里定义了一个方法,模板里click调用一切正常,this妥妥指向组件实例。然而有一天你把它丢进setTimeout、addEventListener或者某个第三方库的回调里,this突然就变成…

2026/10/7 12:15:59 阅读更多 →
Allegro 17.4 Gerber导出实战指南:极性、钻孔与阻焊层配置避坑

Allegro 17.4 Gerber导出实战指南:极性、钻孔与阻焊层配置避坑

1. 为什么说Allegro 17.4的Gerber导出不是“点几下就完事”的活儿?在PCB设计圈里,我见过太多人把Gerber导出当成一个收尾动作——原理图画完、布局布线搞定、DRC跑通,最后打开Export菜单,勾几个框,点Export&#xff0c…

2026/10/7 12:15:58 阅读更多 →
Polar Si9000阻抗计算实战:从微带线到差分对的PCB叠层设计指南

Polar Si9000阻抗计算实战:从微带线到差分对的PCB叠层设计指南

1. 为什么硬件工程师都绕不开Polar Si9000 做硬件这行十几年,画过的板子从两层板到十几层背板都有,但真正让我意识到阻抗控制重要性的,是早年一块DDR3的板子。板子打样回来,功能跑不起来,折腾了整整两周,最…

2026/10/7 12:14:57 阅读更多 →

最新新闻

OpenShell教程:在Windows 10/11上恢复经典开始菜单

OpenShell教程:在Windows 10/11上恢复经典开始菜单

1. 为什么我在 2025 年还在装"复古"开始菜单如果你在最近两三年里拿到过一台预装 Windows 11 的电脑,大概率会有过那么一瞬间的恍惚:屏幕左下角那一排居中的图标,真的能叫"开始菜单"吗?微软把开始按钮放在了任…

2026/10/7 13:20:20 阅读更多 →
智能语音技能实战:ponytail插件教你轻松扎好马尾辫

智能语音技能实战:ponytail插件教你轻松扎好马尾辫

第一次看到"ponytail"这个项目名时,我也以为是个讲扎马尾辫的美妆教程。后来才发现,在当下智能语音生态里,它其实是一个打包好的技能插件——挂在智能音箱或者手机语音助手上,用一句"帮我扎个马尾"就能唤出整…

2026/10/7 13:20:20 阅读更多 →
AI Agent实时搜索能力接入指南:基于MCP协议集成SERP搜索

AI Agent实时搜索能力接入指南:基于MCP协议集成SERP搜索

1. 为什么你的 AI Agent 需要一个“实时搜索”外挂 做 AI Agent 开发的朋友大概率都遇到过这个尴尬场景:你精心搭建的 Agent 在本地知识库里对答如流,一旦用户问起“今天有什么新闻”“某家公司最新股价”“某个技术的最新版本号”,它要么一本…

2026/10/7 13:20:20 阅读更多 →
操作系统实验避坑指南:从环境搭建到内核接口落地

操作系统实验避坑指南:从环境搭建到内核接口落地

简介:操作系统课程配套实验源码包,面向高校计算机专业学生、Linux系统学习者及备考者,聚焦进程管理、存储器管理、设备管理与文件系统四大核心模块。资源共32个文件,以C/C源代码为主体,含20个头文件、11个C源文件与1个…

2026/10/7 13:20:20 阅读更多 →
建筑工地安全目标检测数据集实战:从构建到模型部署

建筑工地安全目标检测数据集实战:从构建到模型部署

简介:建筑工地安全目标检测数据集(YOLO格式)包含600张真实工地监控图片,其中训练集426张、验证集117张、测试集57张,覆盖安全帽、防护靴、安全背心、人员、机械以及未戴安全帽、未戴口罩、未穿背心等8个关键类别&#…

2026/10/7 13:20:20 阅读更多 →
YOLOv5+SAHI+超分辨率:小目标检测与遥感影像分析实战

YOLOv5+SAHI+超分辨率:小目标检测与遥感影像分析实战

简介:面向小目标检测与超分辨率处理场景,这份演示源码整合了YOLOv5检测框架与SAHI模块,适合已掌握基础目标检测知识、希望在PyTorchCUDA环境下快速跑通完整流程的开发者。压缩包内共4个文件,约23.05MB,包括Python主程序…

2026/10/7 13:19:19 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 7:15:40 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 5:29:09 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:29:10 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 8:21:32 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 11:43:46 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 1:18:13 阅读更多 →