1. 这个题目为什么经典:班级事务系统的业务边界与角色需求
每年到毕业设计选题的时候,“SpringBoot + 高校 + 班级事务管理”这类题目都会出现。我第一次看到“河北水利电力学院班级事务管理系统”这个课题时,第一反应是:这不就是一个带增删改查的学生管理系统吗?但真正把需求拆开之后才发现,班级事务管理和传统意义上的“学生管理系统”完全是两码事。
学生管理系统管的是人员档案、学籍状态,是学校视角。而班级事务管理系统管的是班级内部每天都会发生的琐碎事情:今天谁没上课、下周班级活动怎么报名、班费还剩多少、请假条批到哪一步、辅导员的通知有没有人看。它的服务对象是学生、班长、辅导员和院系管理员,核心价值是“把班级里那些用 Excel、微信群、纸质登记表处理的事务,变成一条条可查询、可统计、可追溯的记录”。
这类项目非常适合做毕设有几个原因:业务不复杂,但也不是简单到只有一个登录;技术点覆盖面广,SpringBoot、MyBatis-Plus、MySQL、Vue、文件上传、权限拦截、报表统计都能用到;演示效果也很直观,因为每个功能都能对应到真实场景。
如果要把角色需求列清楚,基本是下面这张表:
| 角色 | 关心的事情 | 系统里对应的功能 |
|---|---|---|
| 学生 | 是否有新通知、请假是否通过、活动怎么报名、班费有没有记录 | 通知查看、请假申请、活动报名、班费明细查询 |
| 班长 | 班级信息维护、发布通知、记录考勤、收班费、组织活动 | 班级成员管理、考勤管理、班费管理、活动管理 |
| 辅导员 | 审批请假、查看班级考勤异常、发布院系通知、了解班费情况 | 请假审批、考勤统计、通知管理、数据看板 |
| 院系管理员 | 管理班级、查看多个班级的对比数据、维护基础字典 | 班级管理、用户管理、全院数据统计 |
从这张表能看出来,系统不是把“学生”当成一个实体去管理,而是把“班级日常发生的事”当成业务主线。明白这一点,后面的数据库设计、接口设计都不会跑偏。
2. 技术栈选择的真实理由:为什么SpringBoot + Vue + MySQL最稳
毕设选型有一个很现实的原则:不是选最流行的,也不是选最新的,而是选你自己能在三个月内完全讲清楚的。对于这类管理系统,“SpringBoot + Vue + MySQL”是我认为最稳妥的组合。
2.1 SpringBoot解决的核心问题
SpringBoot 在这个项目里解决的不是业务复杂度,而是“配置地狱”。传统 SSM 项目光配置 applicationContext.xml、spring-mvc.xml、mybatis-config.xml 就能劝退一大半人,SpringBoot 通过自动装配把这些配置改成约定。比如你想连数据库,只需要在 application.yml 里写数据源地址;你想写接口,一个 @RestController 就搞定。这种“低配置 + 快速启动”的特性,正好适合课程设计、毕业设计这种开发时间有限的场景。
选择 SpringBoot 版本时要注意 JDK 环境。如果你本机是 JDK 8,建议用 SpringBoot 2.7.x;如果是 JDK 17,可以用 SpringBoot 3.x。很多同学在网上找教程,结果 3.x 的项目在 JDK 8 环境下一启动就报错,其实就是版本和 JDK 不匹配。
2.2 数据访问层:MyBatis-Plus 比 MyBatis 更合适
传统 MyBatis 每写一个查询都要配 XML、写 resultMap,开发效率低。MyBatis-Plus 在保留 MyBatis 能力的同时,提供了 BaseMapper,像 selectById、selectPage、lambdaQuery 这些操作根本不需要写 SQL。班级事务系统里有大量“按班级查成员”“按学生查请假记录”“按时间查考勤”的简单查询,用内置方法就能完成。
但要注意,MyBatis-Plus 不是万能的。报表统计里的“每个班本周旷课人数”“班费按月支出对比”这类带聚合函数和连表查询的需求,还是需要手写 SQL。所以项目里我的建议是:简单 CRUD 用 MyBatis-Plus,复杂统计用 @Select 注解方法,很清晰,答辩也讲得明白。
2.3 前端为什么选 Vue + Element UI
后台管理系统的界面要求不高,重点是表格、表单、弹窗、侧边栏这些组件。Vue 的响应式数据和组件化开发非常适合,Element UI 又是现成的后台组件库,一个 el-table 绑定数据就能出列表,一个 el-form 加 rules 就能做校验。如果你不想自己画界面,甚至可以找一个开源的 Vue 后台模板,比如 vue-element-admin 的简化版,改改路由和页面就行。
前端项目建议做成前后端分离,单独跑在 8080 端口,后端跑在 8081 端口。这样部署和答辩演示时逻辑清楚:一种是因为前端利用 Nginx 托管 dist 目录,后端打成 jar 包,整体就是“静态页面 + API 服务”的结构,非常符合现在企业开发的习惯。
3. 数据库设计:把“班级事务”翻译成数据表
数据库设计是这类系统最见功底的地方。很多人的表设计是“用户表 + 班级表 + 随便几个业务表”,结果写着写着发现通知发给了谁查不到、考勤补签没有记录、班费支出对不上账。我这里直接给一套我在类似项目里实际验证过的表结构思路。
3.1 基础信息表:用户、院系、专业、班级
用户表不要只存字段,还要考虑角色查询。我的建议是一张 sys_user 表,包含 user_id、username、password、real_name、role、student_no、phone、email、class_id、college_id、major_id、status 等字段。password 必须存加密后的密文,推荐使用 BCrypt。role 可以用字符串或者数字,比如 0 管理员、1 辅导员、2 班长、3 学生,不要动辄设计五六张权限表。
班级表 class_info 的核心字段是 class_id、class_name、grade、college_id、major_id、head_teacher、counselor_id、monitor_id。这里 monitor_id 指向 sys_user 表,用来标识班长。表面上这只是一个普通字段,但它能让你在写“班长操作自己班级事务”的接口时,不需要额外查角色表。
院系、专业这两张表可以合并到基础字典,但作为独立表更直观,也方便做下拉筛选。
3.2 业务表:通知、考勤、请假、班费、活动
业务表是系统的重头戏,逐一说一下。
通知表 notice:notice_id、title、content、type、publisher_id、class_id、attachment_url、create_time。如果是面向全院的公告,class_id 为空;如果是班级通知,class_id 指定某个班。还可以加一个 notice_read 关联表,记录“谁看了这条通知”,这样辅导员能知道通知到底有没有传到人。
考勤表 attendance 和 attendance_item:这是最容易设计错的地方。如果直接把每个学生的出勤状态作为一行存,一次 40 人的考勤就要插 40 行,而且每次查“某天某节课谁缺勤”都费劲。正确做法是分两层:attendance 表记录一次考勤事件,比如“2025年6月10日第3节课 计科2101班考勤”,attendance_item 表记录每个学生的出勤结果,状态分为正常、迟到、早退、请假、缺勤。
请假表 leave_info:student_id、leave_type、start_time、end_time、reason、attachment_url、status、approver_id、approve_time、comment。这里特别注意 start_time 和 end_time 要精确到节次,否则学生请假“下午”这种模糊描述会导致考勤统计时无法判断到底算不算请假。
班费表 class_fee:fee_id、class_id、fee_type(收入/支出)、amount、category、occur_date、operator_id、description。amount 用 DECIMAL(10,2),category 可以写成“班费收缴”“集体活动”“打印材料”这些字典值。班费这块最容易出问题的是没有余额字段,其实余额不一定要存,查询时用 SUM(CASE WHEN fee_type='收入' THEN amount ELSE -amount END) 就能实时算出。
活动表 activity 和 activity_signup:activity 表存活动标题、时间、地点、内容、负责人,activity_signup 表存报名记录。如果还要记录活动是否实际参与,可以在 signup 表里加 status 字段。
3.3 索引和字段规范
所有外键关联字段都建议加普通索引,比如 class_id、student_id。时间字段统一用 datetime 类型,Java 端用 LocalDateTime 接收。逻辑删除字段 deleted 建议每张表都预留,MyBatis-Plus 的 @TableLogic 可以自动实现。这样万一误删数据,还能从数据库里恢复。
4. 核心功能编码:登录、考勤、班费、请假的一条龙实现
数据库设计好之后,业务编码其实就是把这些表串联起来。下面我挑几个核心功能,把实现思路和关键代码讲清楚。
4.1 登录认证与权限拦截
班级事务管理系统不要求高并发,也不涉及复杂的第三方认证,所以不一定要引入 Spring Security。用“JWT + 拦截器”足够,而且代码更容易讲清楚。
登录接口的大致逻辑是:前端传 username 和 password,后端从 sys_user 表查出用户,用 BCrypt 校验密码,通过后生成 token,返回给前端。token 里可以只放 user_id、role、class_id,过期时间设置为 24 小时。我用的是 jjwt 0.11.5,生成 token 的代码大致如下:
String token = Jwts.builder() .setSubject(user.getUserId().toString()) .claim("role", user.getRole()) .claim("classId", user.getClassId()) .setExpiration(new Date(System.currentTimeMillis() + 86400000)) .signWith(Keys.hmacShaKeyFor(secret.getBytes()), SignatureAlgorithm.HS256) .compact();然后写一个拦截器继承 HandlerInterceptorAdapter,在 preHandle 里从请求头 Authorization 里取 token,解析成功就把 userId 放到请求域里,解析失败直接返回 401。注册拦截器时注意排除登录接口和静态资源:
registry.addInterceptor(new AuthInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/auth/login");权限控制不用做太复杂。在 Controller 方法上通过注解或者手动判断角色即可,比如班长才能创建考勤:
if (!"2".equals(currentUser.getRole())) { throw new BusinessException("只有班长可以执行此操作"); }4.2 考勤:一次事件 + 多条明细的写入
考勤是体现事务控制的好例子。班长选择班级、日期、节次,然后勾选正常/迟到/缺勤状态,点击提交。后端要做两件事:往 attendance 表插一条事件记录,往 attendance_item 表批量插入每个学生的明细。这两步必须在一个事务里,否则会出现“考勤事件创建了但学生明细缺失”的脏数据。
用 MyBatis-Plus 的 saveBatch 可以一次性插入 List,代码如下:
@Transactional(rollbackFor = Exception.class) public void submitAttendance(AttendanceAddDTO dto) { Attendance attendance = new Attendance(); attendance.setClassId(dto.getClassId()); attendance.setAttendanceDate(dto.getAttendanceDate()); attendance.setSection(dto.getSection()); attendance.setOperatorId(dto.getOperatorId()); attendanceMapper.insert(attendance); List<AttendanceItem> items = dto.getItems().stream().map(item -> { AttendanceItem ai = new AttendanceItem(); ai.setAttendanceId(attendance.getAttendanceId()); ai.setStudentId(item.getStudentId()); ai.setStatus(item.getStatus()); return ai; }).collect(Collectors.toList()); attendanceItemService.saveBatch(items); }查询的时候,如果要做“某学生一个月缺勤多少次”的话,直接连表统计:
SELECT student_id, COUNT(*) AS absent_count FROM attendance_item WHERE status = '缺勤' AND attendance_id IN ( SELECT attendance_id FROM attendance WHERE attendance_date BETWEEN #{start} AND #{end} ) GROUP BY student_id;4.3 班费:每一笔进出都要能说清
班费功能的核心不是增删改查,而是“结余计算”。我的建议是不允许修改和删除,只能红冲。所谓红冲,就是如果记错了一笔支出,不要删掉,而是再记一笔负数支出或者做作废标记。这样流水账永远完整,答辩时如果评委问“有人不小心录错了怎么办”,你也能答得有理有据。
班费列表页需要展示余额,可以直接在 SQL 里算:
SELECT SUM(CASE WHEN fee_type = '收入' THEN amount ELSE -amount END) AS balance FROM class_fee WHERE class_id = #{classId} AND deleted = 0;如果需要按月统计收支,可以用 DATE_FORMAT(occur_date, '%Y-%m') 分组,后端返回给前端用 ECharts 画柱状图,这个功能展示效果很好,建议保留。
4.4 请假审批:流程清晰、状态可追溯
学生提交请假申请,辅导员审批。这个功能看起来简单,但边界条件很多。比如请假开始时间不能晚于结束时间,请假时间不能和已有请假时间冲突,请假状态只能从“待审批”变为“通过/驳回”,审批通过后需要附上审批人和审批时间。
审批状态我建议用整数枚举:0 待审批、1 通过、2 驳回。前端按钮根据状态控制显示,如果已经是通过状态,就不能再显示“通过”按钮。后端接口也要做校验,不要只靠前端隐藏按钮,因为接口是可以直接调用的。
另外请假和考勤是关联的。考勤统计时,如果某个学生当天在请假时间段内,状态应该自动识别为“请假”而不是“缺勤”。这可以在考勤查询 SQL 里做子查询判断,但如果做起来复杂,也可以在生成考勤时手动勾选“请假”并关联请假单号。毕设阶段推荐后者,逻辑简单,答辩容易说明。
4.5 通知公告:已读未读是亮点
通知功能如果想做出亮点,就加已读未读。建一张 notice_read 表,每次学生查看通知详情时插入一条记录。列表页给每条通知显示“已读人数/班级总人数”的进度条。这个需求实现成本很低,效果却很好,建议作为默认功能写进项目里。
5. 我自己实测最容易翻车的5个地方
这类系统代码本身不难,但我在带项目和帮人排查问题的过程中,发现翻车点非常集中。提前避开这些坑,能帮你省下大量调试时间。
5.1 跨域问题:前端调不通接口的第一大原因
前后端分离后,前端请求 http://localhost:8081/api/... 会触发跨域。解决方式是在后端写一个 CorsFilter,而不是前端代理。配置时注意,如果允许携带 token 等认证信息,allowedOrigins 不能写成 “*”,必须指定具体地址,比如 http://localhost:8080。
5.2 时间字段格式化混乱
很多同学用 java.util.Date,接口返回给前端的时间格式是一串数字,前端还要自己格式化。正确做法是实体类里用 LocalDateTime,SpringBoot 里配置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8这样接口直接返回格式化好的字符串,省去前端大量处理。
5.3 文件上传大小限制
如果在通知功能里支持上传图片、附件,SpringBoot 默认只允许 1MB 文件。200KB 的限制改一下:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB上传的目录建议放在项目外部,比如 D:/upload 或者 /data/upload,不要放在 resources 里,否则重新打包会丢失。访问图片时,可以通过配置静态资源映射:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:D:/upload/"); } }5.4 事务注解没生效
像考勤这种需要同时操作两张表的接口,一定要把 @Transactional 加在 public 方法上,并且是类通过 Spring 注入后调用才能生效。自己 new 出来的对象方法上加 @Transactional 是无效的,这是排查“为什么只插了一张表”时最容易踩的坑。
5.5 数据库时间与服务器时间不一致
部署到服务器后,数据库连接字符串建议加上 serverTimezone=Asia/Shanghai,否则 MySQL 和 Java 程序之间会出现 8 小时时差,导致考勤日期和实际日期对不上。
6. 交付与答辩:部署包、论文结构和演示脚本
最后一个环节,也是很多同学最不重视但最影响最终成绩的环节:交付和答辩。代码写完了只是第一步,要让它顺利跑起来、能讲清楚,还需要做几件事。
6.1 把项目构建成可以直接运行的 jar 包
SpringBoot 项目最终交付一定是一个可执行的 jar 包,而不是在 IDEA 里点启动按钮。用 Maven 打包时,如果用了外部依赖,先执行 mvn clean,再执行 mvn package。打包后 target 目录下会生成 xxx.jar,运行命令:
java -jar class-affair-system.jar --server.port=8081如果你是使用 Gradle 构建的项目,也可以用 gradle bootJar 得到等价产物。答辩时只需要提前把 MySQL 数据导入、jar 包启动,浏览器打开前端页面就能演示,不容易出幺蛾子。
6.2 论文结构建议
论文不要按模块流水账写,而应该按“问题—方案—实现—验证”的逻辑写。我建议的结构是:绪论(背景、意义、国内外现状)、相关技术介绍、系统需求分析、系统设计(总体架构、功能设计、数据库设计)、系统实现(核心代码和截图)、系统测试(功能测试用例和结果)、总结。其中“系统设计”和“系统实现”至少占全文一半,数据库设计部分要把表结构、E-R 图展示出来,尽量画清楚实体之间的关系。
6.3 演示脚本和答辩问题准备
演示的时候不要一上来就登录管理员账号看一堆列表。按角色演示,效果最好:先用学生账号登录,看到班级公告、报名活动、提交请假;再切换到辅导员账号,审批请假、查看考勤统计、看到通知已读情况;最后用管理员账号查看全院班级数据对比。这就是一条完整的故事线,评委看得懂,你也有话讲。
常见答辩问题也要提前准备:为什么用 SpringBoot 而不是 SSM?为什么不用 Spring Security?班费余额怎么计算?考勤数据如何避免重复提交?通知已读未读是怎么实现的?如果你的项目真的自己动手写过,这几个问题都能答得上来。
6.4 如果参考代码是一份 jar 包
很多同学会从学长那里拿到一份打包好的 jar 包,想通过反编译还原出项目。我不建议直接照搬反编译代码去改,但可以用反编译工具看它的表结构和接口思路,用来帮助你设计自己的数据库和接口。至于你自己的代码,尽量一行一行写出来,或者至少把核心逻辑完全看懂,否则答辩一问就露馅。
最后再分享一点我自己的体会:做这类“班级事务管理”系统,最容易失误的地方不是技术,而是把需求做浅了。如果你只是把学生信息、班级信息做了增删改查,那它就和一个普通管理系统没什么区别。真正能拿高分的项目,一定是在请假审批、考勤统计、班费流水、通知触达这些“事”上面做出了细节。先花一天时间把班级里真实会发生的事务列清楚,再去动手建表写代码,你会发现后面开发顺畅得多。