
每年毕业季我身边都有不少计算机专业的同学在选题和开发这一步反复纠结。今天要聊的这套“一网寻职”校园学生网就是一个非常典型的Java毕业设计方向——基于SpringBootVue的校园招聘求职一体化平台。它把大学生就业信息和企业人才对接整合在一个系统里覆盖前端展示、后端业务、数据库设计、接口联调全链路技术点很贴合主流Java岗位的日常开发作为毕业设计选题既有实际应用场景又能向面试官展示全栈能力。这套系统适合谁参考一是正在做类似选题、需要完整思路和技术方案的同学二是想把SpringBoot和Vue真正串起来做一个完整项目的初学者三是准备Java面试、需要项目经验来支撑简历的人。下面我就把整个项目的设计思路、核心实现、关键难点和踩坑记录完整拆一遍你照着这条路线走少走很多弯路。1. 项目定位与需求拆解——搞清楚“一网寻职”到底在做什么1.1 盘点校园招聘的三个痛点动手写代码之前先想清楚一个问题校园招聘场景里最大的痛点是什么我总结下来有三点。第一信息分散。企业的招聘信息散落在各种群聊、海报、就业网站里学生要反复切换渠道才知道哪里有岗位很多机会因为信息不及时就错过了。第二流程割裂。投递简历、查看进度、接收面试通知每一步都是割裂的学生不知道自己的简历到底有没有被查看。第三数据不闭环。学校就业办想统计就业率企业想筛选优质毕业生双方没有统一的数据入口汇总全靠人工。所以这类校园招聘平台的核心价值就是用一个系统把学生、企业、管理员三方的需求统一起来。在这个基础上我再把功能拆成角色权限、简历管理、职位发布与检索、投递管理、消息通知、数据统计几大块每一块都有明确的业务目标而不是简单做一个“增删改查”的架子糊弄答辩。1.2 三类用户角色与核心功能拆分“一网寻职”这类平台最合理的角色拆分就是三方学生、企业、管理员。三方权限边界清楚功能各自独立又相互关联数据模型也能串起来。学生端功能相对核心。注册登录是基础登录后需要能维护在线简历简历内容不是简单的文本域而是分块的基本信息、教育经历、项目经历、实习经历、技能标签、自我评价每一块都单独录入和修改。职位检索要有支持按关键词、城市、岗位类型、薪资范围多条件过滤。投递和收藏是学生的日常操作投递后要能看到投递记录和状态流转——企业未查看、已查看、已发送面试邀请、已录用、已拒绝每一步学生都要有感知。企业端的核心是职位和简历筛选。企业注册后要经过管理员审核才能发布职位发布职位时要填写岗位名称、招聘人数、学历要求、薪资区间、工作地点、职位描述这些字段后面会直接影响检索和推荐。企业还需要能浏览主动投递过来的学生简历并且能对简历进行标记比如“已查看”“已下载”。这两个操作看似简单实际上对权限设计和数据表设计都有要求。管理员端负责全局把控。用户管理涵盖学生和企业账号的启用与禁用职位审核负责过滤掉重复、虚假或不合规的招聘信息数据统计需要能按学院、按专业、按时间维度展示就业数据这个模块在论文里特别好写因为能体现系统分析的深度。基于这三个角色我还要在功能清单里加一个容易忽略的东西——消息中心。企业发送面试邀请学生收到站内信学生投递简历企业收到投递提醒。这个功能虽然不起眼但能显著提升系统完整度答辩时也容易讲出彩。1.3 技术选型为什么是SpringBoot Vue而不是别的组合选题阶段很多人会纠结技术栈。我一贯的建议是毕业设计不求技术最前沿求的是体系完整、你能讲透原理、跑得通完整链路。SpringBoot Vue这个组合恰好满足这些条件。后端选SpringBoot不选SSH或者SpringMVC老项目理由很直接。SpringBoot解决了Spring配置地狱的问题内嵌Tomcat一个main方法就能启动前后台分离开发的模式也更贴近企业实际。而且SpringBoot生态极其丰富MyBatis-Plus、Spring Security、JWT认证、POI导出表格全都是现成的starter和文档对毕业设计这种周期固定的项目来说能省下大量和环境做斗争的时间。前端选Vue核心原因是组件化开发和成熟的配套工具链。Vue的响应式数据绑定写起来直观Element UI、Element Plus这些组件库能直接拿出表格、表单、下拉选择、日期选择器、分页组件开发效率非常高。再加上Vue Router做路由管理、Axios做HTTP请求、Pinia或Vuex做状态管理前后端联调体验非常顺滑。数据库方面我推荐MySQL配MyBatis-Plus。MySQL是毕业设计标配兼容性最好MyBatis-Plus在MyBatis基础上升级了CRUD能力内置分页插件和LambdaQueryWrapper写复杂条件查询时不用手拼大量的XML代码可读性高很多。这个组合对刚接触项目开发的同学非常友好还能在论文里理直气壮地分析它的优势。2. 系统设计与数据库建模——先把地基夯实2.1 前后端分离架构与RESTful接口约定架构上不搞微服务卒业设计也没必要。我用的就是经典的前后端分离单体架构前端Vue项目独立部署后端SpringBoot提供RESTful接口通过HTTP协议交互。数据格式统一使用JSON前端通过Axios携带Token请求接口后端通过拦截器校验Token再根据角色做权限控制。接口设计要遵守一个简单的约定路径按资源命名动词交给HTTP方法。比如查询职位列表就GET /api/positions发布职位就POST /api/positions修改简历就PUT /api/resumes/{id}删除投递记录就DELETE /api/deliveries/{id}。遵循这套规范前后端联调时不用反复争论路径格式接口文档也自然清爽。这里有一个我特别想强调的经验接口返回的数据结构必须统一。我项目里定义了一个全局响应类包含code、message、data三个字段。后端所有接口都返回这个结构前端Axios拦截器统一判断code为200就放行为401就跳转登录页并弹出提示。这样全局的错误处理就有了统一入口而不是每个页面单独写try-catch代码量直接砍掉三分之一。2.2 核心数据表设计与关联关系数据库设计这一步千万别马虎表设计不合理后面写业务代码会处处别扭。我按模块把核心表拆成六张用户表、简历表、企业信息表、职位表、投递记录表、收藏表再辅以消息表、系统通知表、职位类别字典表。用户表是最基础的一张表字段我建议这样设计id主键、username登录名、password加密后的密码、real_name真实姓名、role角色学生/企业/管理员、status账号状态、avatar头像路径、mobile手机号、email邮箱、create_time创建时间。角色字段用字符串存储最简单配合后端拦截器判断即可。简历表要存学生的详细信息字段包括id、user_id关联用户表、education最高学历、school_name学校名称、major专业、graduation_year毕业年份、phone联系电话、email联系邮箱、skill_tags技能标签、self_evaluation自我评价、file_path简历附件路径、update_time更新时间。这个表就成为一个学生的完整画像企业查看简历时直接读取这里的字段不需要额外拼表。企业信息表单独建一张字段包括id、user_id关联用户表、company_name企业名称、industry所属行业、scale公司规模、address办公地址、description企业简介、license_path营业执照图片路径。职位表关联企业信息表字段包括id、company_id、title职位名称、category岗位类别、salary_min最低薪资、salary_max最高薪资、education_requirement学历要求、work_city工作城市、description职位描述、status状态待审核/已上架/已下架、create_time发布时间。投递记录表和收藏表实现关联功能。投递记录表字段有id、student_id、position_id、company_id、status待查看/已查看/已邀约/已录用/已拒绝、create_time。收藏表字段有id、student_id、position_id、create_time。两张表都用联合字段定位一条记录查询时用唯一索引保证同一个学生对同一个职位只能投递一次或收藏一次。表与表之间的关系用一个简单的话说用户表是“人”的底座企业信息表是企业用户的扩展简历表是学生用户的扩展职位表挂在企业信息表下面投递记录表是学生和职位的多对多关联表收藏表则是学生与职位的轻量级关联。理解了这层关系数据库物理建模基本就清晰了。2.3 统一响应结构与全局异常处理后端项目里我最不看好的情况就是每个接口里各种try-catch、返回不同类型的Map或Object这会导致前端拿到的数据结构参差不齐联调起来非常痛苦。我的做法是在项目启动阶段就定义好统一响应类Result静态方法提供成功和失败两种构造方式。成功时返回Result.success(data)失败时返回Result.error(code, message)同时配合全局异常处理器RestControllerAdvice把业务异常、参数校验异常、未知异常全部拦下来转换成统一错误结构返回给前端。这里有一个很容易被忽视的细节全局异常处理器的优先级和捕获范围。为了让参数校验异常返回更友好的提示我会在校验异常处理器里读取BindingResult中的字段错误信息拼成一句完整的话返回给前端。比如“学历要求不能为空”而不是一串堆栈信息。细节上再打磨一下答辩时老师会觉得你的系统工程化水平不错。3. 核心功能模块实现——从登录到投递的完整链路3.1 基于JWT的登录认证与权限控制登录认证是这个项目的第一个硬骨头也往往是面试必问的点。我选的是JWT方案用户登录成功后后端生成一个包含用户ID、角色、过期时间的Token返回给前端前端把Token存到本地后续每次请求都在请求头带上Authorization: Bearer token。后端用一个自定义拦截器拦截需要认证的接口解析Token并校验有效期如果Token非法或过期就返回401。JWT方案比起传统的Session方案最大优势在于无状态。服务器不需要在内存或Redis里保存用户的会话信息横向扩展时每个服务实例都能独立校验Token。毕业设计虽然用不到集群部署但把这个优势写进论文里能体现你对认证机制的理解深度。权限控制方面我用拦截器加角色判断的方式。自定义拦截器拦截所有/api/**请求解析Token后从认证上下文拿到当前用户的角色然后判断该角色是否有权限访问当前接口。比如发布职位的接口只有企业角色能访问职位审核接口只有管理员角色能访问。前端再配合路由守卫控制页面跳转Vue Router里用beforeEach判断当前用户的角色字段没有权限就重定向到首页或403页面。这就是双层权限控制前端限制“看不到”后端限制“进不来”两者缺一不可。3.2 职位检索关键词匹配 分页 筛选职位检索是学生端的核心入口也是后端查询逻辑的集中体现。需求看起来简单就是输入关键词、选择条件、点击搜索但实现起来牵扯到多条件动态查询和分页。我用的MyBatis-Plus分页插件解决分页问题。集成方式是配置一个MybatisPlusInterceptor添加PaginationInnerInterceptor然后业务层调用Page对象配合LambdaQueryWrapper查询即可。分页插件会自动生成带LIMIT的SQL语句返回的IPage对象里直接带着total总记录数、records当前页数据、current当前页码等字段前端组件直接使用。多条件动态查询用LambdaQueryWrapper来实现最合适。比如用户传了关键词、城市、学历要求三个条件代码大概长这样LambdaQueryWrapperPosition wrapper new LambdaQueryWrapper(); if (StringUtils.isNotBlank(keyword)) { wrapper.and(q - q.like(Position::getTitle, keyword) .or().like(Position::getDescription, keyword)); } if (StringUtils.isNotBlank(city)) { wrapper.eq(Position::getWorkCity, city); } if (StringUtils.isNotBlank(education)) { wrapper.eq(Position::getEducationRequirement, education); } wrapper.eq(Position::getStatus, 1); wrapper.orderByDesc(Position::getCreateTime); PagePosition page positionMapper.selectPage(new Page(pageNum, pageSize), wrapper);这段代码的逻辑核心有两个。第一if判空后动态拼接查询条件传入的参数为空就不拼该条件避免了写N个固定SQL的尴尬第二用and包裹关键词的模糊匹配确保关键词范围限定在标题和描述两个字段里而不是把整个表的所有字段都like一遍。关键词搜索性能在数据量小的时候没有问题后期要优化可以在title和description字段上加全文索引或改用Elasticsearch——这是扩展方向论文里提一句就行。3.3 简历在线编辑与文件上传简历这个模块的难点在于它分块很多而且可编辑性要求高。我的方案是前端把简历拆成一个多Tab表单组件每个Tab对应一块内容比如基本信息、教育经历、项目经历、技能标签等用户填完当前Tab后自动保存到后端。这里要注意自动保存的防抖处理用watch监听表单变更后延迟两秒发送请求避免用户每敲一个字就触发一次接口调用。技能标签用标签选择器组件处理用户从预设标签里选取也能手动输入自定义标签。后端存储用字符串拼接方式比如“Java,SpringBoot,MySQL”读取时用逗号分割。虽然不够规范但上字的快、实现简单对毕业设计完全够用。文件上传这里要处理两类文件学生上传简历附件和头像企业上传营业执照。将文件保存到服务器的本地目录而不是直接存数据库的BLOB字段是实践中的常规做法。数据库只保存文件的访问路径后端通过一个全局配置映射把/upload/**路径映射到磁盘目录这样浏览器直接用路径就能访问图片和文件实现简单高效。上传接口有一个坑值得注意前端以multipart/form-data格式传文件后端接收时用MultipartFile类型接参。本地存储时用UUID生成新文件名并且带上原始扩展名避免文件名重复覆盖和中文文件名乱码。具体逻辑是String originalFilename file.getOriginalFilename(); String extension originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName UUID.randomUUID() extension; file.transferTo(new File(uploadDir newFileName));上传完成后返回/upload/xxx.pdf这样的访问路径前端拼接完整地址展示预览。这样处理的最大好处是前端HTML里直接img :srcavatarUrl就能显示图片完全不需要写后端读文件流的接口。3.4 投递、收藏与状态流转投递和收藏逻辑不复杂但容易踩到重复提交的坑。学生在同一个职位页面上快速点两次“投递”后端第一次投递完成后第二次可能又插一条记录。我处理的办法是在投递记录表上建联合唯一索引(student_id, position_id)数据库中直接约束同一个学生对同一职位只能有一条有效投递记录同时后端在插入前先查一次是否已存在。两层保险下重复投递问题彻底解决。投递状态流转是这里面的一个小亮点。我给每个状态定义一个整型值0表示待查看1表示已查看2表示已邀约3表示已录用4表示已拒绝。学生投递成功时状态为0企业打开简历详情时后端自动把状态改为1企业点击邀请面试按钮后状态变为2后续录用或拒绝根据企业操作对应修改。每次状态变更同步向消息表插入一条站内信记录学生登录后就能在消息中心看到“企业已查看你的简历”“企业向你发送了面试邀请”等提示。消息中心这个功能很多同学会忽略但它恰恰能体现系统完整性。投递后状态更新、企业处理后反馈、管理员审核结果确认全部通过消息中心推送学生不用反复刷新页面去猜进展。这个模块用一张messages表就够了id、user_id接收方、content消息内容、is_read是否已读、type消息类型、create_time创建时间。用户登录后后端提供一个“未读消息数量”接口前端在导航栏顶部的徽标组件上显示数字实时提醒交互感和真实产品非常接近。4. 关键难点与优化方案——让答辩有亮点4.1 文件上传与访问本地存储方案详解文件上传我已经写了基本方案这里补充一个部署时的细节。开发阶段和后端部署阶段本地上传路径我建议统一用绝对路径并且把路径配在application.yml里比如file: upload-dir: D:/upload/ # Windows环境示例Linux环境一行改掉业务代码里通过Value(${file.upload-dir})注入上传时创建目录再保存文件。之所以强调配置文件管理是因为开发时你用本机路径部署到服务器后要改成/home/xxx/upload如果不走配置文件每次部署都要重新编译或者改代码麻烦程度拉满。文件访问映射也要配好。SpringBoot里实现WebMvcConfigurer接口重写addResourceHandlers方法把/upload/**映射到磁盘路径Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceResolver(new PathResourceResolver()) .addResourceLocations(file: uploadDir); }配置完成后浏览器直接访问http://localhost:8080/upload/abc.pdf就能打开文件。这块配置是很多同学忽略的以为保存文件就结束了结果前端图片404排查半天才发现是资源映射没配。4.2 安全防护密码加密与XSS过滤安全是毕业设计答辩时很容易被追问的点。我至少会做好两件事密码加密和XSS过滤。密码加密我用的BCrypt算法。Spring Security里自带BCryptPasswordEncoder也可以单独引入spring-security-crypto包调用encoder.encode(password)保存校验时调用encoder.matches(rawPassword, hashedPassword)。BCrypt的优点是每次哈希值都带随机盐同样的密码两次加密结果完全不同即使数据库泄露攻击者也无法通过彩虹表反推明文。这是业界标准做法答辩时提出来非常加分。XSS过滤方面前端应对与后端防御要联动起来。尤其是允许用户输入富文本内容的页面比如企业发布职位描述、学生填写自我评价都不能把输入直接当HTML渲染。前端设置优先使用纯文本展示内容特殊字符如、、自动转义为HTML实体。后端在全局过滤器层面做一层拦截把请求参数中的危险字符替换成安全字符。我实现的思路是注册一个自定义Filter继承OncePerRequestFilter对request.getParameterMap()中的所有参数值执行消毒逻辑把script、javascript:等危险片段替换为空或转义。这里有一个细节直接修改request.getParameterMap()是做不到的因为它是不可变的。实际做法是自定义一个HttpServletRequestWrapper重写getParameter和相关方法在返回前先执行消毒。这个wrapper在过滤器中包装原请求再传递给后续链路。代码框架大致是这样public class XssFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { filterChain.doFilter(new XssRequestWrapper(request), response); } }其中XssRequestWrapper负责在getParameter、getParameterValues、getInputStream、getReader等方法里统一进行HTML转义。注意富文本字段和纯文本字段的过滤强度可能不同所以要给富文本字段留一个白名单机制避免把正常的业务描述给过滤掉了。这块需要结合你的实际字段来调整建议先从纯文本开始跑通后再考虑富文本放行。4.3 性能优化分页查询、缓存与索引毕设项目数据量不大性能问题不是主要矛盾但论文里写一段优化分析会显得你考虑全面。第一层优化是SQL层面的索引。职位表的status字段和work_city字段经常出现在查询条件里简历表关联用户查询频繁用到user_id投递记录表联合查询常用(student_id, position_id)这些字段建上普通索引就够了。注意一个原则索引不是越多越好每次写入都要同步更新索引现在建索引要权衡查询频率和写入频率。第二层优化是热点数据的本地缓存。比如职位类别字典数据几乎没有变化每次查询都打数据库完全是浪费。我用一个MapString, ListString在系统初始化时加载到内存里查询时直接从Map取。如果数据量再大可以用Caffeine或Redis做缓存给缓存设置过期时间这个方案写进论文也是一个很好的扩展点。第三层优化是前端层面的分页和懒加载。职位列表用分页组件页大小设置为10切页时重新请求接口。简历附件列表用懒加载图片组件滚动到视口时才加载资源减少首屏请求数量。这些前端优化方案虽然不起眼但能展示你不仅有后端思维也有用户视角的性能意识。4.4 常用工具扩展导出表格与统一数据统计校园招聘平台还有一个高频需求数据导出。管理员需要把某个时间段内的投递数据导出成Excel表格企业需要把收到简历的列表导出备份。这里我用的Apache POI解析。POI生成Excel表格的基本流程是创建工作簿XSSFWorkbook创建工作表Sheet创建行Row创建单元格Cell并填充数据最后把工作簿写入HttpServletResponse的输出流。写一个通用导出工具类传入表头和数据集就能生成表格。这里有一个细节导出接口的请求类型是POST因为查询条件可能比较复杂参数放在请求体里传给后端前端用一个Blob方式接收文件流并触发浏览器下载。需要设置响应头Content-Disposition: attachment; filenamexxx.xlsx否则浏览器可能直接打开文件流而不是下载。数据统计模块我也用了简单套路。管理端首页展示几张统计卡片学生总数、企业总数、职位总数、投递总数四个统计用四条SQL一查就完。再向下一层是近六个月的投递趋势折线图后端按月份分组统计投递记录数返回前端一组[月份, 数量]格式的数据前端用ECharts画图效果一目了然。这个模块在答辩演示时视觉效果很好能直观展示系统的统计分析能力。5. 常见问题与排查技巧实录——毕业设计踩坑合集5.1 环境搭建阶段的经典坑位我见过太多同学把时间浪费在装环境和启动报错上这里把最高频的几个问题整理出来你对照排查。第一个是Node版本问题。Vue3项目一般要求Node.js 16以上但很多电脑上装的是旧版Node导致npm install报错或者npm run serve启动失败。解决方法是去Node官网下最新LTS版本重装或者用nvm管理多版本Node。装完后在终端执行node -v和npm -v确认版本号。第二个是SpringBoot版本和Java版本不匹配。很多同学用最新版SpringBoot 3.x但本地JDK还是8启动直接报UnsupportedClassVersionError。SpringBoot 3.x必须搭配JDK 17以上如果你熟悉的是JDK 8稳妥做法是用SpringBoot 2.7.x版本这个版本支持JDK 8到JDK 17兼容性最广。选版本时先确认自己电脑的JDK版本用java -version查看后再决定。第三个是Maven依赖下载过慢或失败。国内环境使用阿里云Maven镜像可解决mirror idaliyun/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror配置完后再执行mvn clean install依赖下载速度会有明显提升。这里特别提醒不要在一个项目里混用多个Maven仓库源否则容易出现依赖冲突。第四个是npm安装依赖时卡在某个包上。我的经验是优先设置淘宝镜像源npm config set registry https://registry.npmmirror.com装完之后再把镜像切回来也来得及切回官方源执行npm config set registry https://registry.npmjs.org即可。如果某个特定包一直安装失败可以单独尝试npm install 包名 --force来绕过缓存冲突但先不要一上来就强制安装优先清缓存。5.2 联调阶段的跨域与接口问题前端工程跑在localhost:8080后端跑在localhost:9090跨域问题不可避免。前后端分离项目最常见的就是这个错Access to XMLHttpRequest at http://localhost:9090/api/xxx from origin http://localhost:8080 has been blocked by CORS policy解决办法有两种。开发阶段在后端配置CORS跨域策略我用的WebMvcConfigurer加CorsRegistryOverride public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:8080) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true); }注意allowedOrigins要指定前端真实地址不要直接写*因为allowCredentials(true)模式下不能同时使用*。生产环境部署后如果前后端同域或通过Nginx代理CORS配置就不需要了这块讲道理容易理解。联调阶段的另一个经典问题是前端拿到接口数据后页面不渲染。大部分人第一反应是后端接口有问题但我排查过的案例里百分之七十是前端写法问题。用Vue3的ref或reactive声明响应式数据时赋值直接用response.data整体覆盖是没问题的但如果你用Object.assign给一个深层对象加属性或者直接给数组按索引赋值Vue的响应式系统可能监听不到变化。遇到这种情况先把JSON.stringify(response.data)打印出来确认接口数据确实拿到了再检查赋值方式是否响应式一般问题立刻浮现。另一个联调坑是接口返回JSON里包含时间格式的字符串比如2024-01-15T08:30:00.00008:00前端直接显示会很难看。稳妥方案是后端在统一响应结构里使用JsonFormat(pattern yyyy-MM-dd HH:mm:ss)控制时间格式前端再配合dayjs或moment格式化显示两层配合确保界面干净。5.3 部署上线前后端分离打包与运行如果你的毕设要求在服务器上部署演示你需要掌握最基本的打包和部署流程。后端打包用Maven执行mvn clean package -DskipTests在target目录下得到xxx.jar通过java -jar xxx.jar运行。重点提醒一下打包前先把application.yml里的数据库连接、文件上传路径、端口号改成服务器上的真实配置否则直接用本地配置启动会连不上数据库。数据库连接串用服务器IP或云数据库内网地址账号密码也要独立配置不要在代码里写死。前端打包执行npm run build生成dist目录。部署方式有几种最简单的是把dist目录丢进Nginx的html目录配置Nginx转发接口请求到后端服务。Nginx配置示例server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有两个细节容易出问题。第一try_files $uri $uri/ /index.html;这段配置必须写上否则前端项目用的是Vue Router history模式刷新页面时会出现404——因为前端路由是虚拟路径实际文件系统里不存在。第二/api/前缀的请求要转发给后端否则所有接口请求都会落到Nginx找不到对应文件。如果不想折腾Nginx也可以选择更省事的方案前端打包后由Nginx托管后端直接java -jar启动二者网络打通就完事。但答辩时讲到部署方案能把Nginx反代、前后端分离下载这些点讲清楚明显比只说“双击jar包运行”更有说服力。在部署过程中我还遇到过服务器上端口被占用的问题。排查方法是执行netstat -tlnp | grep 9090查看谁占用了端口找到占用进程后kill掉或者改端口重配Nginx。杀进程时注意确认一下是不是自己的误操作别误杀了系统的关键进程。5.4 面试追问里的高频问题准备毕设做完接着就是找工作答辩时老师或面试官针对这个项目的追问我整理几个高频点提前准备不慌。第一个追问很经典“你的JWT存在什么安全风险”回答要点是JWT只有签名没有加密Payload部分用Base64编码任何人拿到Token都能解码看到里面的信息所以不要在Payload里存敏感数据。同时设置合理过期时间比如2小时过期后前端用刷新Token去重新换新Token。能把这个逻辑讲清楚说明你真的用过不是背概念。第二个追问是“数据库为什么这么设计”回答的思路是抓住表与表之间的引用关系。比如简历表为什么要单独建因为一个用户可能有多份简历后续也要支持按简历模板生成新简历所以用户和简历是一对多关系投递记录表为什么要加联合唯一索引因为业务上同一个学生对同一职位只能投递一次。能讲清楚设计动机比单纯背字段列表有用得多。第三个追问是“如果用户量变大你的系统瓶颈在哪”这个问题你只要回答“当前是单体架构后续可以拆分模块、加Redis缓存、引入消息队列解耦投递通知流程”就足够。重点是展现你有扩展思维而不是真的要求你现在就搞微服务。把上面提到的缓存、索引、分页优化方案再系统地说一遍面试官基本满意了。写在最后的一点实操建议“一网寻职”这套系统我把从项目定位、数据库设计、前后端核心功能实现到安全防护、性能优化、部署上线、面试追问全链路拆了一遍。套用我最近做项目时的一个习惯先定好表和接口再动手写前端页面先把登录和权限的闭环跑通再做简历和职位模块先把投递和收藏跑通再去细化消息通知。按这个顺序推进你的毕设进度不会卡壳。最后再分享一个小经验项目演示时提前准备两套账号一个学生账号一个企业账号把两条核心链路先走一遍比如“学生发布简历 - 企业搜索到学生 - 企业发面试邀请 - 学生收到消息”。这条链路能走通整个系统的主体功能就全部验证过了。别等到了答辩现场才发现消息中心没通台下老师等着看你急着手忙脚乱去改代码那体验实在太差。好好把上面的细节过一遍你的毕业设计绝对不会差。