ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于SpringBoot+Vue的勤工助学系统:从表设计到部署的完整实战

基于SpringBoot+Vue的勤工助学系统:从表设计到部署的完整实战 简介这是一套基于SpringBoot与Vue的勤工助学系统完整项目源码面向计算机专业学生及Java全栈初学者可用于毕业设计、课程设计、大作业或工程实训也适合作为前后端分离项目的入门参考。资源包共448个文件约11.45MB其中122个Java文件承载后端业务逻辑100个Vue文件构建前端页面另有48个JS、19个CSS及若干PNG、JPG图片资源并附带SQL建库脚本与项目说明文档结构完整、层次清晰。项目采用SpringBoot后端搭配Vue前端运行于JDK8与Tomcat7环境数据库使用MySQL 5.7配合Maven构建源码经过调试可直接运行。目前已有103人学习关注。读者可借此掌握前后端分离架构的接口设计、权限控制与数据交互思路并在此基础上进行二次开发或功能扩展快速搭建属于自己的勤工助学管理平台。1. 从一份“0_vue.zip”说起勤工助学系统到底该怎么落地很多同学拿到毕设题目“基于SpringBoot的勤工助学系统的设计与实现”第一反应是去搜一份现成的0_vue.zip解压、跑起来、改个名字就交差。但真正做过这类系统的人都知道勤工助学不是简单的增删改查——它牵扯到岗位发布、学生申请、用工部门审核、工时上报、薪酬结算、月度统计这一整条业务链任何一环的数据结构设计错了后面全是补丁摞补丁。这个标题背后其实是一个典型的SpringBoot Vue 前后端分离的校园业务系统适合计算机专业毕设、课程设计也适合想练手一套完整 CRUD 权限 流程审批的初中级开发者。它解决的核心问题是把线下纸质申请、微信群通知、Excel 统计这套低效流程变成一个有角色权限、有状态流转、有数据留痕的在线平台。如果你只是想跑通一个 demo那很简单但如果你想让它经得起答辩追问、经得起真实使用就得从表结构和状态机开始认真设计。这一篇就按我实际做过的路径把选型、建表、接口、前端路由、部署和踩坑一次讲清楚。2. 技术选型与工程骨架为什么是 SpringBoot Vue 而不是别的2.1 后端选 SpringBoot 的四个现实理由勤工助学系统的后端需求其实很明确要有用户角色学生、用工部门、管理员、要有审批流、要有文件上传简历、岗位附件、要有分页查询和统计导出。SpringBoot 在这个场景下的优势不是“新”而是“稳”和“省事”。第一自动装配让你不用再写一堆 XMLspring-boot-starter-web引进来就能写 Controller第二MyBatis-Plus 或 Spring Data JPA 对分页和条件查询支持成熟勤工助学里最常见的“按部门查岗位、按学生查申请记录”用Page对象几行就能搞定第三Spring Security 或 Sa-Token 能快速把角色权限卡住避免学生看到管理员菜单第四打包成可执行 jar 后java -jar就能跑部署到学校服务器或 Docker 都方便。我一般会选 SpringBoot 2.7.x 这个版本线因为它在 JDK 8 和 JDK 11 上都稳依赖生态也最全不会出现“springboot版本太高”导致某些老教程里的配置失效的问题。2.2 前端选 Vue 而不是 React 或 Thymeleaf 的原因这个系统前端交互不算复杂但页面数量不少登录、注册、岗位列表、岗位详情、申请表单、我的申请、部门审核、工时填报、薪酬统计。用 Thymeleaf 做服务端渲染页面跳转多了以后体验很割裂而且前后端耦合太紧改一个字段要动两边。用 Vue 的话vue-router管路由、axios管请求、Element Plus管表格和表单开发效率高而且“vue动态路由”可以根据后端返回的角色菜单动态生成侧边栏这对多角色系统非常实用。Vue 3 的 Composition API 在写申请表单这种带多个联动字段的页面时逻辑聚合比 Options API 清晰得多。如果你之前只写过 Vue 2也不用慌核心的v-model、v-for、v-if概念完全一样只是setup写法需要适应一下。2.3 从零搭出可运行的前后端骨架先建后端。用 IDEA 新建 SpringBoot 项目勾选 Spring Web、MyBatis Framework、MySQL Driver、Lombok。建好后在application.yml里配数据源和端口server: port: 8081 spring: datasource: url: jdbc:mysql://localhost:3306/work_study?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:/mapper/*.xml configuration: map-underscore-to-camel-case: true这里map-underscore-to-camel-case一定要开否则数据库的student_id映射不到 Java 的studentId查出来全是 null这个坑我见过太多次。端口用 8081 是为了避开前端 dev server 的 8080减少跨域配置的干扰。再建前端。用 Vite 创建 Vue 3 项目npm create vitelatest work-study-web -- --template vue cd work-study-web npm install npm install vue-router4 axios element-plus npm run devnpm install如果卡住先换国内镜像源这是“vue安装依赖”最常见的翻车点。装完后在vite.config.js里配代理把/api转发到 8081import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://localhost:8081, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } })这样前端请求/api/student/list实际打到后端/student/list开发阶段不用在后端写 CORS 配置省事且不容易出错。骨架跑通的标准是后端启动无报错前端页面能打开浏览器 Network 里能看到代理请求返回 200。3. 数据库表设计与核心状态流转勤工助学系统的地基3.1 六张核心表怎么拆勤工助学系统的表不用多但关系要清楚。我一般会建这几张user统一账号含角色字段、student学生扩展信息、department用工部门、job岗位、application申请记录、work_record工时记录。user表里用role字段区分STUDENT、DEPT、ADMIN登录时后端根据角色返回不同菜单前端用“vue动态路由”渲染。job表要有一个status字段控制岗位是“招募中”还是“已截止”application表要有audit_status控制“待审核 / 通过 / 驳回”。这两条状态线是整个系统的骨架设计错了后面统计全乱。CREATE TABLE job ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dept_id BIGINT NOT NULL, title VARCHAR(100) NOT NULL, content TEXT, salary DECIMAL(10,2) DEFAULT 0, headcount INT DEFAULT 1, status TINYINT DEFAULT 1 COMMENT 1招募中 0已截止, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE application ( id BIGINT PRIMARY KEY AUTO_INCREMENT, job_id BIGINT NOT NULL, student_id BIGINT NOT NULL, reason VARCHAR(500), audit_status TINYINT DEFAULT 0 COMMENT 0待审核 1通过 2驳回, audit_remark VARCHAR(255), apply_time DATETIME DEFAULT CURRENT_TIMESTAMP );application表上要加唯一索引(job_id, student_id)防止同一个学生重复申请同一个岗位。这个约束在代码里也要判但数据库层兜底更可靠。3.2 申请审批的状态机与接口实现申请状态只有三个待审核、通过、驳回。学生提交后是 0部门审核后改成 1 或 2。这里的关键是只有audit_status 0的记录才能被审核已经通过或驳回的不能再改。后端接口要写成这样PutMapping(/audit/{id}) public Result audit(PathVariable Long id, RequestBody AuditDTO dto) { Application app applicationMapper.selectById(id); if (app null || app.getAuditStatus() ! 0) { return Result.fail(该申请不存在或已审核); } app.setAuditStatus(dto.getStatus()); app.setAuditRemark(dto.getRemark()); applicationMapper.updateById(app); return Result.ok(); }AuditDTO里只暴露status和remark两个字段不要直接把整个Application对象从前端传回来否则学生可以伪造studentId或jobId。这是权限校验之外的第二道防线。审核通过后如果岗位的headcount已满要把job.status置为 0后续学生就看不到这个岗位了。3.3 工时上报与薪酬统计的字段设计工时记录表work_record要有student_id、job_id、work_date、hours、status字段。status用来标记这条工时是否已被部门确认未确认的不计入薪酬。薪酬统计不是单独存一张表而是用 SQL 聚合出来SELECT w.student_id, s.name, SUM(w.hours) AS total_hours, SUM(w.hours * j.salary) AS total_salary FROM work_record w JOIN job j ON w.job_id j.id JOIN student s ON w.student_id s.id WHERE w.status 1 GROUP BY w.student_id, s.name;这样统计永远和明细一致不会出现“统计表更新了但明细没同步”的玄学问题。如果数据量大再加一个按月汇总的定时任务但毕设阶段直接查就够用。4. 前后端联调与权限控制从登录到动态菜单4.1 登录鉴权用 JWT 还是 Session前后端分离项目里我一般用 JWT。后端登录成功后签发 token前端存localStorage每次请求在axios拦截器里塞到Authorization头。后端写一个全局过滤器或拦截器校验 token解析出userId和role放进ThreadLocal后续业务代码直接取当前用户。这样不用在服务器存 session部署多个实例也不怕。注意 JWT 的过期时间别设太长毕设演示设 2 小时足够生产环境要配刷新机制。public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Authorization); if (token null || !JwtUtil.verify(token)) { response.setStatus(401); return false; } UserContext.set(JwtUtil.parse(token)); return true; } }UserContext用ThreadLocal存当前用户信息请求结束记得在afterCompletion里remove()否则线程池复用时会串数据这个坑很隐蔽。4.2 前端动态路由与菜单渲染登录后前端拿到角色从后端请求菜单列表然后用router.addRoute()动态挂载。比如学生角色只挂载“岗位浏览、我的申请、我的工时”部门角色挂载“岗位管理、申请审核、工时确认”。这样即使有人手动改 URL路由没挂载也进不去。配合后端接口的权限注解双保险。const routes [ { path: /jobs, component: () import(/views/JobList.vue), meta: { roles: [STUDENT, DEPT] } }, { path: /audit, component: () import(/views/AuditList.vue), meta: { roles: [DEPT] } } ] // 登录后根据 role 过滤并 addRoute routes.filter(r r.meta.roles.includes(userStore.role)).forEach(r router.addRoute(r))meta.roles只是前端展示层控制真正的权限校验必须在后端每个接口上做。前端隐藏菜单只是体验优化不是安全措施。4.3 文件上传与 XSS 过滤的注意点勤工助学系统里学生可能要上传简历部门要上传岗位附件。文件上传接口要限制后缀和大小别直接信任前端传来的文件名。另外如果岗位描述允许富文本后端要做 XSS 过滤把script之类的标签清掉。常见做法是用Jsoup.clean()或者自己写白名单过滤。全局过滤器里对multipart/form-data请求要放行否则文件流会被当成参数解析导致上传失败这个点很多人踩过。5. 避坑与排查那些让我加班到凌晨的细节5.1 跨域配置了但还是报 CORS 错误现象后端加了CrossOrigin前端请求依然被浏览器拦截。原因通常是拦截器或过滤器的执行顺序在 CORS 处理之前导致预检请求OPTIONS被拦下。解决把 CORS 配置放在最高优先级或者用WebMvcConfigurer的addCorsMappings统一配不要混用CrossOrigin和拦截器。如果用了 Spring Security还要在configure里显式放行OPTIONS。5.2 MyBatis 查询返回字段全是 null现象数据库有数据接口返回的对象字段都是 null。原因九成是map-underscore-to-camel-case没开或者实体类字段名和列名对不上。解决检查application.yml里 MyBatis 配置确认实体类用TableField或遵循驼峰命名。如果是多表关联查询检查resultMap有没有写错列名。5.3 Vue 路由刷新后 404现象开发时正常打包部署到 Nginx 后刷新非首页路由报 404。原因前端是单页应用Nginx 找不到对应的物理文件。解决在 Nginx 配置里加try_files $uri $uri/ /index.html;把所有未匹配的请求回退到index.html。这个坑在“vue项目实战”里几乎人人都会遇到一次。5.4 打包后接口请求地址不对现象本地开发正常npm run build后部署接口全部 404。原因开发时用了 Vite 代理打包后代理失效请求打到了前端服务器。解决生产环境用 Nginx 反向代理/api到后端或者在前端配baseURL指向后端地址。别把开发代理当成生产方案。5.5 工时统计金额出现小数精度问题现象SUM(hours * salary)算出来的金额有0.30000000000000004这种尾巴。原因浮点数运算精度丢失。解决数据库里salary用DECIMAL(10,2)Java 里用BigDecimalSQL 聚合后用ROUND(..., 2)保留两位。别用double存钱这是血泪经验。6. 部署上线与一个能扛住答辩的验证技巧部署这套系统最省事的方式是 Docker Compose 一把梭。后端打 jar前端npm run build出静态文件MySQL 单独一个容器。Nginx 容器同时托管前端静态资源和反向代理/api。这样换台机器也能十分钟内跑起来。version: 3 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: work_study ports: - 3306:3306 backend: build: ./backend ports: - 8081:8081 depends_on: - mysql nginx: image: nginx:alpine volumes: - ./dist:/usr/share/nginx/html - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - 80:80 depends_on: - backendnginx.conf里核心就两行location / { try_files $uri $uri/ /index.html; }和location /api/ { proxy_pass http://backend:8081/; }。注意proxy_pass末尾的斜杠带斜杠和不带斜杠的路径拼接结果不一样写错了就是 404。答辩时老师最爱问“你怎么保证数据一致性”和“权限怎么控制的”。我的习惯是提前准备一个验证脚本用学生账号登录尝试直接调部门审核接口看后端是否返回 403再用部门账号审核一条已通过的申请看是否被状态机拦住。这两个用例跑通基本就能说明权限和状态流转是可靠的。另外把关键 SQL 的EXPLAIN结果截个图证明你考虑过索引答辩加分。最后说一个我自己的习惯每次改完表结构一定同步更新schema.sql和实体类绝不手动在数据库里改字段然后指望代码能跑。这个系统不大但“后悔药”就是版本控制加自动化脚本。希望帮到你。本文还有配套的精品资源点击获取
返回列表