☰
基于SpringBoot+Vue的勤工助学系统:从表设计到部署的完整实战
2026/10/2 4:54:59 网站建设 项目流程

简介:这是一套基于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 在这个场景下的优势不是“新”,而是“稳”和“省事”。第一,自动装配让你不用再写一堆 XML,spring-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?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/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 vite@latest work-study-web -- --template vue cd work-study-web npm install npm install vue-router@4 axios element-plus npm run dev

npm install如果卡住,先换国内镜像源,这是“vue安装依赖”最常见的翻车点。装完后在vite.config.js里配代理,把/api转发到 8081:

import { 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 里用BigDecimal,SQL 聚合后用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: - backend

nginx.conf里核心就两行:location / { try_files $uri $uri/ /index.html; }和location /api/ { proxy_pass http://backend:8081/; }。注意proxy_pass末尾的斜杠,带斜杠和不带斜杠的路径拼接结果不一样,写错了就是 404。

答辩时老师最爱问“你怎么保证数据一致性”和“权限怎么控制的”。我的习惯是提前准备一个验证脚本:用学生账号登录,尝试直接调部门审核接口,看后端是否返回 403;再用部门账号审核一条已通过的申请,看是否被状态机拦住。这两个用例跑通,基本就能说明权限和状态流转是可靠的。另外,把关键 SQL 的EXPLAIN结果截个图,证明你考虑过索引,答辩加分。

最后说一个我自己的习惯:每次改完表结构,一定同步更新schema.sql和实体类,绝不手动在数据库里改字段然后指望代码能跑。这个系统不大,但“后悔药”就是版本控制加自动化脚本。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询