ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Boot+Vue校园勤工俭学平台:从业务闭环到答辩要点

Spring Boot+Vue校园勤工俭学平台:从业务闭环到答辩要点 每到毕业设计季最让人头疼的事莫过于选题。如果你搜索过计算机毕业设计源码大概率会被几千个XX商城系统、XX管理系统刷屏。这些题目不是不行只是答辩老师已经看腻了一问业务场景、问权限设计、问流程闭环很多同学说不出所以然。相比之下我这次做的基于SpringVue的校园勤工俭学平台属于典型的业务有真实需求、角色有权限区分、流程有完整闭环的题目做起来不虚答辩时也有东西可讲。这篇文章就把整个项目的核心设计思路、后端Spring的模块划分、前端Vue的页面组织、数据库表结构以及LW文档毕业论文的写作骨架完整拆一遍给正在选题或者已经选了类似题目的同学做个参考。先说结论这个平台本质上是把校园里找兼职这件事从线下公告栏、QQ群接龙搬到线上核心参与方有三个——学生、用工单位校内部门、食堂、图书馆、周边商家、系统管理员。技术栈是Spring Boot Vue前后端分离数据库MySQL鉴权用JWTORM用MyBatis Plus。整个项目我前后做了三周左右源码加文档一起整理下面逐块讲。1. 为什么我选了校园勤工俭学这个题目而不是烂大街的商城系统1.1 选题背后的真实业务痛点毕业设计最忌讳的就是为了做而做。校园勤工俭学这个场景天然存在而且痛点非常明确信息分散校内勤工俭学岗位通常由学生处、教务处、图书馆、食堂等部门分别发布有的贴公告栏有的发在年级群里学生很难集中获取。申请流程不透明以前的方式基本是看到公告→打印申请表→找老师签字→交到对应部门跑好几个地方还不知道自己到底有没有申请上。用工单位缺乏管理工具部门负责人想发布一个岗位只能靠微信群接龙收集报名信息再手动筛简历效率低还容易漏人。管理员无法宏观把控学校层面希望知道现在有多少岗位在招、多少人申请了、哪些岗位长期无人问津但线下方式根本做不了统计。有了这些真实痛点平台两个字就不是空壳了。它需要做到三件事集中展示岗位信息、在线完成申请与审核、提供角色化的管理视图。这也决定了系统必须具备多角色、多状态、多权限的设计而不是简单的一张表CRUD。1.2 技术栈选型为什么是Spring Boot Vue而不是SSH或JSP说实话现在如果还有人用SSHStrutsSpringHibernate或者纯JSP做毕设不是不行但两个问题很现实第一网上可参考的资料越来越少第二答辩时老师大概率会问为什么不用更主流的技术。Spring Boot Vue这个组合已经是当前Java Web领域招聘和毕设的主流配置选它的理由可以讲得很充分Spring Boot相比传统Spring省去了大量XML配置内嵌Tomcat一个main方法就能启动项目。这意味你可以把精力放在业务逻辑上而不是折腾配置文件。而且Spring Boot的自动配置、Starter机制在答辩时可以延伸出自动配置原理starter是如何加载的这类加分考点。Vue作为前端框架组件化开发方式很适合这种多角色后台系统。页面复用率高——比如岗位列表学生端、管理员端、用工单位端都在用只是操作按钮和字段不同抽成组件后维护成本低。前后端分离本身就是一个可以展开讲的架构亮点。前端打包成静态资源部署在Nginx或直接由后端静态目录托管后端只提供JSON接口职责清晰。答辩时老师问为什么前后端分离你可以从开发效率、团队协作、部署方式三个角度回答。提示如果你的指导老师对技术栈有硬性要求比如必须用Spring MVC传统项目那就按老师要求来。但如果自主选型Spring Boot 2.7 Vue 2/3是容错率最高的组合资料多、坑少。2. 系统功能拆解三种角色、四类模块、一条完整业务闭环2.1 角色权限划分与功能边界我把系统分成三个端学生端前台、用工单位端中台、管理员端后台。每个端的功能边界必须清晰这是答辩时最容易考察的点。角色核心功能权限边界学生注册登录、维护简历、浏览岗位、申请岗位、查看申请状态、确认录用只能操作自己的简历和申请记录不能创建岗位用工单位注册需管理员审核、发布岗位、查看收到的申请、审核申请、录用/拒绝学生只能管理自己发布的岗位与对应申请管理员审核用工单位注册、审核岗位发布、管理公告、统计平台数据、冻结违规账户全平台最高权限这里的重点在于权限边界。很多同学做多角色系统前端隐藏个按钮就当权限控制完成了这是不对的。正确的做法是后端接口也必须校验角色比如发布岗位的接口POST /api/employer/job在Controller或拦截器中必须校验当前登录用户的角色是EMPLOYER否则直接返回403。前端隐藏按钮只是体验优化后端校验才是安全底线。2.2 核心业务流程一条从发布到录用的完整链路整个平台最重要的业务闭环是这样的用工单位注册并提交营业执照或校内部门证明文件状态默认待审核。管理员审核通过后该单位才能登录并使用岗位发布功能。用工单位发布岗位填写岗位名称、招聘人数、薪资、工作地点、时间要求、岗位描述岗位初始状态为待审核。管理员审核岗位通过后岗位才在学生端可见。学生浏览岗位查看详情后可点击申请申请时可以选择绑定自己的简历没有简历则提示先完善。用工单位收到申请查看学生简历后选择录用或拒绝。学生看到结果如果被录用可以确认/放弃该岗位如果被拒绝申请状态变为已拒绝。这个链路里最容易被忽略但答辩必问的是岗位审核为什么要两轮。答案是第一轮审核用工单位资质解决谁可以发岗位的问题第二轮审核具体岗位解决什么岗位可以发的问题。这个设计是符合现实逻辑的你说得清楚老师就会觉得你确实思考过业务而不是只会写CRUD。3. Spring后端核心设计分层、鉴权与防重复申请3.1 工程目录结构与分层职责后端我用的是标准的Spring Boot工程结构分包清晰答辩时可以直接拿出来讲com.campus.job ├── controller # 接口层只做参数接收和结果封装 ├── service # 业务逻辑层处理核心业务规则 │ └── impl # service实现类 ├── mapper # MyBatis Plus的DAO层 ├── entity # 实体类与数据库表对应 ├── dto # 前端交互的数据传输对象 ├── vo # 视图对象按需返回字段 ├── config # 配置类跨域、拦截器、MyBatis Plus配置 ├── common # 通用类统一返回结果、状态码、常量 └── util # 工具类JWT工具、日期工具等分层的意义不是形式主义而是为了应付改需求。我实际开发中遇到过一个典型的例子原本岗位列表的返回字段直接把job_id暴露给了前端后来用户反馈这样不安全我只需要在vo层新增一个JobVO把敏感字段过滤掉controller和service基本不用动。如果当初图省事直接把entity返回给前端这一改动就要动很多地方。这就是分层的价值。3.2 统一返回结果与全局异常处理前后端分离项目里接口返回格式必须统一。我定义了ResultT类所有接口统一返回public class ResultT { private Integer code; // 200成功4xx业务错误500系统错误 private String message; // 提示信息 private T data; // 返回数据 }配合RestControllerAdvice做全局异常处理业务中抛出BusinessException时统一被捕获并转为标准错误格式前端拿到后直接弹message提示。这里有个细节不要把所有异常都往500上堆。比如重复申请应该返回业务错误码40001而不是500。前端可以根据不同的code做不同处理——401跳登录页40001弹具体提示。这个设计在答辩时可以展开讲属于健壮性设计的体现。3.3 JWT鉴权与拦截器设计鉴权方案我选的是JWT 拦截器而不是Spring Security。原因很实际毕业设计周期有限Spring Security的学习曲线陡配置复杂而JWT的原理清晰、代码量少、且能讲清楚无状态鉴权这个核心概念。JWT的流程用户登录成功后后端生成一个token包含userId和role信息有效期设24小时。前端把token存在localStorage也可以用sessionStorage看你的需求每次请求在Authorization头带上Bearer token。后端写一个AuthInterceptor拦截除登录、注册、岗位浏览外的所有请求解析token并放入ThreadLocal供后续使用。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String authHeader request.getHeader(Authorization); if (authHeader null || !authHeader.startsWith(Bearer )) { throw new BusinessException(401, 未登录); } String token authHeader.substring(7); // 解析token失败则抛token无效或已过期 // 成功则将userId、role写入request attribute return true; } }这里有一个我踩过的坑拦截器校验完token只是第一步角色校验不能只靠前端。比如学生端调用删除岗位接口即使token有效后端也必须判断role ! EMPLOYER就拒绝。我的做法是在service层或自定义注解里额外做角色判断而不是只依赖拦截器放行。否则随便一个学生拿着自己的token就能调管理接口这问题在答辩演示时一旦被发现基本就凉了。3.4 防重复申请事务与唯一性约束的双保险学生重复申请同一个岗位是这类平台必须处理的业务问题。我的处理方式分两层数据库层在application表申请记录表中对(job_id, student_id)建立唯一联合索引。这是最硬的防线即使代码逻辑有bug数据库也会拦下来。代码层申请前先查一次该学生是否已有该岗位的申请记录如果有且状态不是已拒绝则直接抛出你已申请过该岗位。ALTER TABLE application ADD UNIQUE KEY uk_job_student (job_id, student_id);为什么两层都要因为纯靠代码校验存在并发问题——学生手速极快两个请求同时到达都发现没有申请记录都插入成功这时候唯一索引就能防止脏数据。但如果你只靠数据库唯一索引抛出的异常不够友好用户体验差。所以最佳实践是代码校验提示在前数据库约束兜底在后。这个点在答辩时是非常好的加分项属于典型的并发场景思考。4. Vue前端页面组织路由守卫、组件复用与联调细节4.1 项目初始化与目录组织前端我用Vue 3 Vite Element Plus Pinia。Vue 3现在是主流Vite的启动速度比Vue CLI的webpack快很多对于毕设这种中小型项目体验极佳。核心目录结构src ├── api # 接口请求封装按模块拆分auth.js, job.js, application.js ├── assets # 静态资源 ├── components # 公共组件岗位卡片、分页组件、状态标签 ├── router # 路由配置与守卫 ├── store # Pinia状态管理用户信息、token ├── views # 页面组件 │ ├── student # 学生端页面 │ ├── employer # 用工单位端页面 │ └── admin # 管理端页面 ├── utils # 工具axios封装、格式化 ├── App.vue └── main.js可能有人觉得这个目录有点过度设计但对于毕设来说清晰的目录结构在答辩写文档时能省大量时间——文档里的系统实现章节就是你照着目录讲一遍而已。4.2 路由守卫与登录态管理前端最核心的逻辑就是路由守卫。未登录的用户只能访问首页、岗位列表和登录注册页登录后根据角色不同可访问的页面也不同。我是这样写的router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { // 未登录跳登录页 next(/login) return } const role localStorage.getItem(role) if (to.meta.roles !to.meta.roles.includes(role)) { // 角色不匹配跳403页面 next(/403) return } next() })这里要注意一个细节路由守卫只做页面级权限控制真正的接口鉴权还得靠后端的拦截器。前端不管怎么改用户都可以通过开发者工具恶意调用接口。所以前端路由守卫的意义是引导和体验后端接口校验才是安全。4.3 Axios封装与接口联调Axios封装是前端项目质量的分水岭。很多同学的代码里到处是axios.get(...)然后各自处理错误项目又乱又难维护。我的封装思路创建axios实例设置baseURL和超时时间。请求拦截器自动从localStorage读取token并加到Authorization头。响应拦截器判断HTTP状态码若是200但业务code为401则清除本地登录信息并跳转登录页若业务code不为200则用Element Plus的ElMessage统一弹出错误提示。service.interceptors.response.use( (response) { const res response.data if (res.code 401) { localStorage.clear() router.push(/login) ElMessage.error(登录已过期请重新登录) return Promise.reject(new Error(unauthorized)) } if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, (error) { ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )联调阶段最容易出问题的是跨域。我开发时前端跑在http://localhost:5173后端跑在http://localhost:8080必然跨域。解决方式有两种后端加CorsFilter配置或者前端在Vite的server.proxy里配置代理。我两种都试过推荐前期联调用前端代理因为不用改后端但部署时就要用后端的CORS配置或Nginx反向代理这个区别在答辩时可以讲清楚。4.4 岗位列表页一个组件三种用法的复用实践岗位列表是学生端最核心的页面也是用工单位端和管理员端都要用到的功能。我的做法是抽一个JobTable组件通过props传入不同的配置来决定展示哪些列、哪些操作按钮JobTable props: - columns: 显示哪些列岗位名、薪资、地点、状态 - showActions: 是否显示操作列 - actionType: 操作类型apply/viewApplications/audit比如学生端操作列是申请按钮用工单位端操作列是查看申请和下架按钮管理员端操作列是审核通过/驳回按钮。同一个组件三种角色三种用法代码量减少一半而且风格统一。这就是Vue组件化思想最好的答辩案例——如何复用代码也是老师经常问的问题之一。5. 数据库表设计五张核心表与状态流转规则5.1 核心表结构与设计思路数据库设计直接决定了项目的上限。我把核心表做成了五张主体表加两张关联表这里列一下关键表的结构和设计思路。用户表user字段类型说明idbigint主键自增usernamevarchar(50)登录账号唯一passwordvarchar(100)加密后存储BCryptnicknamevarchar(50)显示名称rolevarchar(20)STUDENT / EMPLOYER / ADMINphonevarchar(20)联系方式statustinyint1正常0冻结create_timedatetime注册时间密码必须用BCrypt加密存储绝对不能明文。答辩时如果老师看到数据库里密码是明文印象分会掉一大截。Spring Security里自带BCryptPasswordEncoder也可以单独引入jBCrypt库。岗位表job字段类型说明idbigint主键employer_idbigint发布单位IDtitlevarchar(100)岗位名称categoryvarchar(50)岗位分类助教/图书整理/食堂帮工等descriptiontext岗位描述salarydecimal(10,2)薪资按小时或按月locationvarchar(100)工作地点headcountint招聘人数statustinyint岗位状态0草稿1待审核2已发布3已下架4审核驳回create_timedatetime发布时间申请记录表application字段类型说明idbigint主键job_idbigint岗位IDstudent_idbigint学生IDresume_idbigint关联的简历IDstatustinyint0待审核1已录用2已拒绝3学生放弃create_timedatetime申请时间update_timedatetime审核时间简历表resume字段类型说明idbigint主键student_idbigint学生ID唯一real_namevarchar(50)真实姓名majorvarchar(50)专业gradevarchar(20)年级phonevarchar(20)联系电话skillstext技能特长experiencetext实践经历self_evaluationtext自我评价5.2 状态机设计为什么要用状态字段而不是靠删除记录这个系统里的每个核心实体都有状态字段这是事务型系统和管理型系统的根本区别。如果用删除来表示岗位下架那么历史申请记录就失去了能追溯的岗位信息——学生明明申请过图书馆管理员但岗位被删了他的申请记录里就只剩一个悬空的外键ID查不出岗位名。所以下架不是删除而是把status从2已发布改为3已下架。这个设计思路在写论文时也可以作为一个小节数据生命周期管理。通过状态字段控制数据的可见性与可变性避免物理删除带来的数据断层同时为后续统计和审计保留了完整链路。答辩时老师问岗位下架后申请记录怎么处理你就能答得有理有据。5.3 MyBatis Plus的使用技巧ORM我选了MyBatis Plus而不是原生MyBatis。理由很简单单表CRUD不用写SQL继承BaseMapper后直接有selectById、insert、updateById等方法能省不少时间。但复杂查询还是要写SQL的我举个岗位分页条件查询的例子public PageJobVO getJobPage(int pageNum, int pageSize, JobQueryDTO query) { PageJob page new Page(pageNum, pageSize); LambdaQueryWrapperJob wrapper new LambdaQueryWrapper(); wrapper.eq(Job::getStatus, 2); // 只查已发布的岗位 wrapper.like(StringUtils.hasText(query.getKeyword()), Job::getTitle, query.getKeyword()); wrapper.eq(query.getCategory() ! null, Job::getCategory, query.getCategory()); wrapper.orderByDesc(Job::getCreateTime); return jobMapper.selectPage(page, wrapper); }这里LambdaQueryWrapper的条件构造器很关键——like方法的第一个参数是布尔值只有条件成立时才拼接这条SQL。这样就不用写一堆if...else去拼接SQL了。分页插件PaginationInnerInterceptor也必须在MyBatis Plus的配置类里注册否则selectPage不生效这是新手最容易卡住的地方之一。6. LW文档毕业设计论文写作骨架与答辩追问应对6.1 文档大纲与写作节奏LW在很多毕业设计资源包里指的就是论文/设计文档。我把LW文档的结构和写作节奏整理成下表照着这个节奏写一周内出初稿是没问题的章节核心内容写作建议摘要系统概述技术栈解决的问题最后写200字左右中英文各一份第一章 绪论研究背景、国内外现状、研究意义背景写校园勤工俭学的现实痛点现状查2-3篇文献第二章 需求分析可行性分析、功能需求、非功能需求用用例图文字描述非功能需求写安全性、易用性、性能第三章 系统设计架构设计、功能模块设计、数据库设计附系统架构图、ER图、表结构说明第四章 系统实现环境搭建、关键模块实现选3-4个核心模块截图代码片段讲解比如登录鉴权、岗位申请第五章 系统测试测试环境、测试用例设计、测试结论列功能测试用例表格覆盖正常流程和异常流程结论项目成果总结不足与展望实事求是写不足比如并发性能有待提升写文档最容易犯的错是把截图贴得密密麻麻但没有文字解释。论文不是展示产品说明书每一张图/代码都必须配合你为什么要这样做的文字说明。比如贴数据表截图要说清楚每个表解决什么问题、表之间的关系是什么。这就是前面反复强调的知其所以然。6.2 答辩必问高频题与参考回答我把本人在模拟答辩和实际答辩中被问过的问题整理了一下大致是这几类Q1为什么选用Spring Boot参考答题思路两个层面回答技术选型和开发效率。Spring Boot的自动配置减少样板配置内嵌服务器让部署简单生态成熟适合快速开发同时基于Spring框架本身继承了IOC和AOP特性后续学习Spring Cloud等技术可以无缝衔接。Q2系统有哪些角色每个角色能做什么参考答题思路按学生、用工单位、管理员三端分别讲功能同时强调数据隔离——单位只能看到自己发布的岗位和收到的申请不能看到其他单位的数据。这一问主要考察你是否理解权限边界。Q3如何防止同一个学生对同一个岗位重复申请参考答题思路分两层说。应用层在申请前校验已有待审核或被录用的申请记录则拦截数据库层在(job_id, student_id)上建唯一索引兜底。这两层设计既保证用户体验又保证数据一致性。Q4岗位下架后申请记录怎么办参考答题思路用状态字段而非物理删除。岗位下架是状态流转2→3申请记录仍然关联该岗位ID查询时通过表关联拿到岗位名称。这样历史数据被完整保留后续如果让学生确认最终录用情况也能追溯是哪个岗位。Q5前端路由权限和后端接口权限有什么区别参考答题思路前端路由守卫解决页面可见性和跳转引导但用户可以绕过后端需要真正的接口级权限校验拦截器角色判断保证系统的安全性。两者配合实现前端体验优化后端安全兜底。6.3 一个调研中的意外收获做这个项目时我还顺带研究了一下市场上类似产品的做法发现有些高校用的还是表格收集表人工审核模式。这说明校园勤工俭学的数字化程度总体偏低一个功能完整、流程闭环的平台在真实场景中是有落地价值的。如果你论文里需要写国内外研究现状就可以从这个角度切入——国内很多高校的勤工俭学管理还停留在半线下状态这类平台有明确的改进空间。这个观点写进去比空泛的互联网教育更有说服力。拿我自己的实际体会来说这个项目做完之后对我的帮助不只是拿到一个毕设高分。三周里我真正理解了什么叫状态设计、为什么要做数据隔离、接口权限和页面权限各司其职这些在后来实习写真实需求时都用得上。如果你也选了类似的题目我给你三条最实在的建议第一先把完整闭环跑通再做细枝末节。很多同学喜欢先抠登录页好不好看、按钮动画顺不顺滑结果核心的申请-审核-录用流程还没走通。顺序一定是一号主线流程注册→登录→发布→审核→申请→录用先打通再往上面加功能。第二数据库状态字段设计时预留好扩展位。比如岗位状态我用0到4共5个状态预留了草稿状态。后来用工单位反馈说想先存草稿改好了再提交审核这个需求因为预留了0状态改起来非常快。如果你一开始只有已发布和已下架两个状态后面加需求就得动数据库加字段改代码麻烦得多。第三文档和源码同步整理别等代码写完再补文档。每写完一个模块顺手把核心代码截图、设计思路文字填进文档对应章节最后整体统一排版。等你答辩前一周再熬夜写几万字论文质量和精神状态都不会太好。最后再分享一个答辩小技巧演示系统时故意设置一个错误场景比如用一个被冻结的账户登录或者申请一个已经截止的岗位然后解释系统是如何拦截的。这一手比全程顺风顺水地演示更能打动评委因为它直接展示了你在异常处理上做的设计。祝准备做毕设的同学一切顺利。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进