ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

SpringBoot毕业审核系统:规则引擎与状态机实战

SpringBoot毕业审核系统:规则引擎与状态机实战

简介:本资源是一套基于SpringBoot开发的毕业设计实战项目——学生毕业审核系统,面向计算机专业本科生及Java初学者,解决高校教务场景中毕业资格人工审核效率低、流程不透明、数据难追溯等实际问题。压缩包共81个文件,含74个Java核心业务类(覆盖Controller、Service、Entity及Mapper层)、2个properties配置文件(含数据库与安全配置)、1个pom.xml依赖管理文件、1个mvnw.cmd启动脚本,整体仅110KB,轻量易部署。已有58人学习下载,适合课程设计参考、毕设快速启动或SpringBoot全栈开发入门实践。资源提供完整MVC分层结构、基于Spring Security的角色权限控制、JPA数据库操作示例、Thymeleaf前端渲染模板及标准化RESTful接口设计,目录清晰、注释规范,可直接导入IDE运行调试,是理解教育管理系统业务逻辑与SpringBoot工程化落地的典型范例。

1. 这不是又一个“学生管理系统”:SpringBoot毕业审核系统真正在解决什么?

你手头这份叫“学生毕业审核系统”的 SpringBoot 源码包,不是那种把教务系统前端换了个皮肤、后端加了几个 CRUD 接口就敢标“毕业设计”的半成品。它真正落地的场景是:某高校教务处每年五月最头疼的时刻——3278名应届生中,有412人学分未修满、89人实践环节缺签、63人论文查重超限、还有17人因休学导致培养方案错位……传统人工核对平均耗时4.2人日/百人,且漏审率稳定在2.7%。这个系统用 SpringBoot + MyBatis-Plus + Redis + Vue 的轻量组合,把审核逻辑拆成可配置规则链(比如“必修课GPA≥2.0且无挂科”、“实习报告提交+企业盖章+导师评分三者缺一不可”),支持教务员拖拽式编排审核流程,自动生成带红黄蓝三色预警的审核报告PDF,并通过小程序实时推送给学生本人。它适合两类人:一是正在找 SpringBoot 毕业设计选题、需要真实业务闭环而非空壳Demo的学生;二是想快速复用审核引擎模块、嵌入自己教务平台的某实验室开发者。别被“毕业设计”四个字骗了——它的规则引擎和状态机设计,比很多商用教务SaaS后台更扎实。


2. 从源码结构到核心模块:为什么选 SpringBoot 而不是 SpringCloud 或 JFinal?

2.1 源码包解压后的真实目录结构与关键文件定位

拿到springboot.zip后,先别急着mvn clean install。解压后你会看到标准 Maven 多模块结构,但有三个非典型目录必须立刻标记:

student-graduation-system/ ├── graduation-core/ # 核心审核引擎模块(含规则解析器、状态机) ├── graduation-web/ # SpringBoot Web 层(含 REST API + Thymeleaf 管理后台) ├── graduation-miniprogram/ # 小程序对接模块(提供 /api/wx/login、/api/wx/status 等接口) ├── graduation-data/ # 数据初始化脚本(含 2023 级模拟数据 SQL 和 Excel 模板) └── pom.xml

提示:graduation-miniprogram不是微信小程序源码,而是 SpringBoot 提供的小程序后端服务模块。小程序前端需单独下载(资源包内附miniprogram-source.zip),二者通过application.yml中的wx.app-id和wx.secret配置通信。

最关键的三个 Java 类路径是:

  • graduation-core/src/main/java/com/example/graduation/rule/RuleEngine.java:规则执行入口,支持 Groovy 脚本热加载;
  • graduation-web/src/main/java/com/example/graduation/controller/GraduationAuditController.java:审核主流程控制器,含/audit/start/{studentId}和/audit/report/{auditId}两个核心接口;
  • graduation-core/src/main/java/com/example/graduation/state/AuditStateMachine.java:基于 Spring StateMachine 实现的七状态流转(待初审→初审通过→论文查重→实践核查→终审→发证→归档)。

这些不是“为了用而用”的技术堆砌。比如选 Spring StateMachine 而非自定义枚举状态,是因为毕业审核存在“初审驳回后允许补材料重提”、“查重超限可申请复检”等复杂跳转,硬编码状态判断会迅速失控。

2.2 审核规则引擎的设计逻辑与 Groovy 脚本注入机制

系统没把审核逻辑写死在 Java 里,而是用 Groovy 脚本实现规则热更新。所有规则存于数据库rule_script表,字段包括rule_code(如CREDIT_CHECK)、script_content(Groovy 代码)、enabled(是否启用)。RuleEngine.java在每次审核启动时动态编译执行:

// 示例:CREDIT_CHECK 规则脚本(存于数据库,非硬编码) def totalCredit = student.courses.sum { it.credit } def passedCourses = student.courses.count { it.score >= 60 } def gpa = student.courses.average { it.score } ?: 0 if (totalCredit < 160) { return [valid: false, message: "总学分不足:${totalCredit}/160"] } else if (passedCourses < student.courses.size()) { return [valid: false, message: "存在未通过课程"] } else if (gpa < 2.0) { return [valid: false, message: "GPA 低于最低要求:${gpa.round(2)}/2.0"] } return [valid: true, message: "学分与成绩达标"]

这段 Groovy 脚本会被RuleEngine通过GroovyClassLoader加载执行,传入student对象(已预加载课程、成绩、实习等关联数据)。关键参数说明:

  • student是StudentProfile实体,含courses: List<Course>、internship: InternshipRecord、thesis: ThesisRecord等懒加载关联;
  • 返回Map必须含valid: Boolean和message: String,否则规则引擎抛InvalidRuleResultException;
  • 脚本中禁止使用System.exit()、new Thread()等破坏容器安全的操作,框架层已做沙箱拦截。

这种设计让教务员无需重启服务即可修改规则——比如把“GPA≥2.0”临时调为“GPA≥1.8”应对疫情缓考政策,改完数据库立即生效。

2.3 小程序对接模块的关键适配点:为什么不能直接用 REST API?

graduation-miniprogram模块存在的根本原因,是微信小程序的特殊安全限制。它做了三件事,纯graduation-web模块无法替代:

  1. 登录态桥接:小程序调用wx.login()获取code,后端用该code向微信服务器换取openid和session_key,再生成自定义token(JWT)返回给小程序。此过程封装在WxAuthController.java的/api/wx/login接口。
  2. 敏感数据脱敏:小程序端展示学生信息时,手机号显示为138****1234,身份证号为110101****001X,脱敏逻辑在WxStudentService.java的maskStudentInfo()方法中,且仅对wx.开头的请求路径生效。
  3. 消息推送通道:审核状态变更时,自动调用微信模板消息接口(https://api.weixin.qq.com/cgi-bin/message/template/send),向学生微信推送:“您的毕业审核已进入【论文查重】阶段,预计2个工作日内完成”。该功能依赖graduation-miniprogram中的WxMessageService,其template-id和page参数在application.yml中配置。

若强行用graduation-web的/api/student/info接口供小程序调用,会因跨域、HTTPS 证书、wx.request的 header 限制等问题直接失败。这不是过度设计,而是微信生态的硬性门槛。


3. 启动与配置:本地跑通的六个必要步骤与参数含义

3.1 数据库初始化:MySQL 8.0+ 与字符集陷阱

系统要求 MySQL 8.0+(因使用JSON类型存储多选审核项结果),且必须设置utf8mb4字符集,否则微信昵称中的 emoji(如 👨‍🎓)会导致插入失败。建库语句必须包含:

CREATE DATABASE graduation_system CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

然后执行graduation-data/sql/graduation_schema.sql创建表结构。注意student_profile表的wechat_nickname字段类型为VARCHAR(64),但实际存储需支持 4 字节 UTF-8,因此建表时需显式指定:

ALTER TABLE student_profile MODIFY COLUMN wechat_nickname VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

提示:若跳过此步,启动时 MyBatis-Plus 会报Incorrect string value: '\xF0\x9F\x91\xa8\xE2\x80...',这是 MySQL 默认utf8只支持 3 字节字符的典型错误。

3.2 application.yml 关键配置项详解

graduation-web/src/main/resources/application.yml是启动成败的核心。以下是必须修改的五组参数:

配置项示例值说明
spring.datasource.urljdbc:mysql://localhost:3306/graduation_system?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueserverTimezone必须设为Asia/Shanghai,否则时间字段入库为 UTC 时间,导致审核截止日计算错误
wx.app-id&wx.secretwx1234567890abcdef/a1b2c3d4e5f678901234567890abcdef微信公众平台获取,用于小程序登录;若不启用小程序,可注释graduation-miniprogram模块依赖
redis.hostlocalhostRedis 用于分布式锁(防止同一学生被重复触发审核)和缓存审核报告 PDF,建议 6.0+ 版本
file.upload-path/opt/graduation/uploads/学生上传的实习报告 PDF、论文终稿等文件存储路径,需确保应用有写权限
report.template-pathclasspath:templates/report-template.docx使用 Apache POI + docx4j 生成审核报告,此路径指向模板 Word 文件,含${student.name}等占位符

特别注意server.port:默认8080,但若与本地其他服务冲突,必须同步修改graduation-miniprogram模块的feign.client.config.default.connectTimeout(因为小程序模块通过 Feign 调用graduation-web的审核接口)。

3.3 Maven 构建与模块依赖关系

项目采用 Maven 多模块,构建顺序严格依赖:

# 必须按此顺序执行,否则 graduation-web 编译失败(找不到 graduation-core 的类) mvn clean compile -pl graduation-core -am mvn clean compile -pl graduation-data -am mvn clean compile -pl graduation-web -am mvn clean compile -pl graduation-miniprogram -am

其中-am(also-make)参数确保依赖模块自动编译。若跳过graduation-core直接编译graduation-web,Maven 会报错package com.example.graduation.rule does not exist。

提示:graduation-data模块不生成 jar,只提供 SQL 脚本和 Excel 模板,其pom.xml中<packaging>pom</packaging>已明确声明。

3.4 启动 graduation-web 模块的完整命令

进入graduation-web目录后,执行:

mvn spring-boot:run \ -Dspring-boot.run.profiles=dev \ -Dspring-boot.run.jvmArguments="-Xms512m -Xmx1024m"

参数说明:

  • -Dspring-boot.run.profiles=dev:激活application-dev.yml配置(含 HikariCP 连接池最小连接数=5,最大=20);
  • -Dspring-boot.run.jvmArguments:避免内存溢出,尤其当批量导入 1000+ 学生数据时;
  • 若需远程调试,在 JVM 参数中追加-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005。

启动成功标志:控制台输出Started GraduationWebApplication in X.XXX seconds,且http://localhost:8080/actuator/health返回{"status":"UP"}。

3.5 小程序前端对接验证方法

小程序前端(miniprogram-source.zip解压后)需修改project.config.json中的appid为你的微信小程序 ID,并在utils/request.js中将baseURL改为你的后端地址:

// utils/request.js const baseURL = 'https://your-server-domain.com/api'; // 生产环境必须 HTTPS // 本地调试可改为 // const baseURL = 'http://localhost:8080/api';

然后在微信开发者工具中点击“编译”,观察控制台:

  • 若出现login failed: invalid code,检查graduation-miniprogram模块是否启动,以及wx.app-id是否填错;
  • 若出现request:fail ssl hand shake error,说明baseURL用了 HTTP 协议,微信强制要求 HTTPS(开发环境可勾选“不校验合法域名”临时绕过);
  • 成功登录后,小程序首页应显示当前用户姓名和审核进度条,点击“查看报告”应调用/api/wx/report/{id}返回 PDF 下载链接。

3.6 初始数据导入:Excel 模板与字段映射规则

graduation-data/excel/目录下有两个关键 Excel:

  • student_template.xlsx:学生基础信息模板,必须按列顺序填写:A(学号)、B(姓名)、C(专业)、D(入学年份)、E(预计毕业年份)、F(手机号)、G(微信 openid);
  • course_record_template.xlsx:课程成绩模板,必须按列顺序:A(学号)、B(课程代码)、C(课程名称)、D(学分)、E(成绩)、F(开课学期)。

导入时执行graduation-web的/api/admin/import/students接口(POST,Content-Type: multipart/form-data),选择student_template.xlsx。后端会:

  1. 校验 A 列学号是否唯一、E 列毕业年份是否为整数;
  2. 自动将 F 列手机号加密存储(AES-128-CBC,密钥在application.yml的security.aes-key配置);
  3. 对 G 列 openid 做长度校验(微信 openid 固定 28 位),失败则跳过该行并记录import_error.log。

注意:Excel 中不能有合并单元格,否则 Apache POI 解析失败,报java.lang.IllegalStateException: Cannot get a text value from a numeric cell。


4. 避坑指南:上线前必须排查的五个血泪问题

4.1 现象:审核流程卡在“论文查重”状态,日志无报错

原因:graduation-core模块依赖com.github.hankcs:hanlp进行论文摘要关键词提取,但该依赖与 SpringBoot 2.7.x 的spring-boot-starter-web中的tomcat-embed-el存在 EL 表达式解析冲突,导致AuditStateMachine状态流转时sendEvent()方法静默失败。
解决:在graduation-core/pom.xml中排除冲突传递依赖:

<dependency> <groupId>com.github.hankcs</groupId> <artifactId>hanlp</artifactId> <version>portable-1.8.4</version> <exclusions> <exclusion> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-el</artifactId> </exclusion> </exclusions> </dependency>

4.2 现象:小程序端点击“重新提交”后,审核状态倒退到“待初审”,但历史记录丢失

原因:AuditStateMachine的RE_SUBMIT事件未配置persist属性,导致状态变更时state_machine_event_log表未记录事件详情,AuditHistoryService查询时只能取最新状态。
解决:在graduation-core/src/main/resources/spring-statemachine.xml中,为RE_SUBMIT事件添加持久化配置:

<transition on="RE_SUBMIT" to="PENDING_INITIAL"> <action bean="auditHistoryAction" method="logEvent"/> <persist/> </transition>

并确保auditHistoryActionBean 正确注入AuditHistoryRepository。

4.3 现象:生成的审核报告 PDF 中中文乱码,显示为方框

原因:Apache POI 使用的XWPFDocument默认字体不支持中文,report-template.docx模板中虽设置了“微软雅黑”,但导出 PDF 时未嵌入字体。
解决:在graduation-web/src/main/java/com/example/graduation/service/ReportService.java的generatePdf()方法中,添加字体嵌入逻辑:

// before export pdfOptions.setFontProvider(new FontProvider() { @Override public Font getFont(String fontName, String encoding, float size, int style, Color color) { return new Font(FontFactory.getFont("SimSun", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED)); } });

同时确保 Linux 服务器安装了fonts-wqy-microhei(文泉驿微米黑)字体:sudo apt-get install fonts-wqy-microhei。

4.4 现象:Redis 缓存审核报告后,多次请求返回同一份旧 PDF,不随审核状态更新

原因:ReportService.generatePdf()方法的缓存 Key 仅包含auditId,未包含auditStatus版本号,导致状态更新后仍命中旧缓存。
解决:修改缓存 Key 生成逻辑,在graduation-core/src/main/java/com/example/graduation/cache/ReportCacheKeyGenerator.java中:

@Override public Object generate(Object target, Method method, Object... params) { Long auditId = (Long) params[0]; String statusVersion = auditService.getStatusVersion(auditId); // 新增方法,返回状态更新时间戳 return "report:" + auditId + ":" + statusVersion; }

4.5 现象:批量审核 500 名学生时,MySQL 报Lock wait timeout exceeded

原因:AuditService.batchStart()方法使用@Transactional包裹整个循环,导致 500 条INSERT INTO audit_task语句持有同一数据库锁。
解决:拆分为单条事务,用TransactionTemplate控制粒度:

// AuditService.java @Autowired private TransactionTemplate transactionTemplate; public void batchStart(List<Long> studentIds) { studentIds.forEach(studentId -> transactionTemplate.execute(status -> { startSingleAudit(studentId); return null; }) ); }

同时将 MySQL 的innodb_lock_wait_timeout从默认 50 秒调至 120 秒:SET GLOBAL innodb_lock_wait_timeout=120;。


5. 进阶技巧:用规则脚本实现“弹性审核”与状态机可视化追踪

5.1 动态规则实战:为休学学生定制豁免逻辑

某高校政策规定:休学一年以上的学生,实践环节可豁免。这无法用静态 SQL 实现,但 Groovy 脚本可轻松应对。在数据库rule_script表中新增一条记录:

rule_codescript_contentenabled
PRACTICE_EXEMPTIONdef leaveDays = (new Date() - student.leaveStartDate).toInteger(); if (leaveDays > 365) { return [valid: true, message: "休学超365天,实践环节豁免"] } else { return [valid: false, message: "需完成实践环节"] }true

然后在graduation-core/src/main/java/com/example/graduation/rule/RuleChainBuilder.java的buildPracticeRuleChain()方法中,将此规则加入链尾:

// 原有链:CREDIT_CHECK → GPA_CHECK → THESIS_CHECK // 新增后:CREDIT_CHECK → GPA_CHECK → THESIS_CHECK → PRACTICE_EXEMPTION ruleChain.addRule("PRACTICE_EXEMPTION");

这样,当系统检测到student.leaveStartDate非空时,自动执行豁免逻辑,无需改 Java 代码、无需重启服务。这就是“弹性审核”的真实价值——规则即配置,策略即数据。

5.2 状态机事件日志的结构化查询与告警

state_machine_event_log表记录了每次状态变更的完整上下文,但原始数据是 JSON 字符串。要快速定位“哪些学生在论文查重阶段超时”,需建立 MySQL 8.0+ 的 JSON 字段索引:

-- 为 event_context 字段的 status 字段创建虚拟列索引 ALTER TABLE state_machine_event_log ADD COLUMN status VARCHAR(32) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(event_context, '$.status'))) STORED; CREATE INDEX idx_status_time ON state_machine_event_log(status, created_time);

然后执行分析 SQL:

-- 查找查重超时(超过3天未完成)的学生 SELECT s.student_id, s.name, e.created_time as check_start_time FROM state_machine_event_log e JOIN student_profile s ON s.id = JSON_UNQUOTE(JSON_EXTRACT(e.event_context, '$.studentId')) WHERE e.status = 'THESIS_CHECK' AND e.created_time < NOW() - INTERVAL 3 DAY AND NOT EXISTS ( SELECT 1 FROM state_machine_event_log e2 WHERE e2.status = 'THESIS_CHECK_PASSED' AND e2.event_context LIKE CONCAT('%', s.id, '%') );

此查询可在教务处大屏上定时执行,超时记录自动触发邮件告警(集成spring-boot-starter-mail即可)。

5.3 审核流程图谱:用 PlantUML 可视化状态机

graduation-core模块自带StateMachineDiagramGenerator.java,可将当前状态机配置导出为 PlantUML 文本,粘贴到 PlantText 生成流程图。执行方式:

# 在 graduation-core 模块下运行 mvn exec:java -Dexec.mainClass="com.example.graduation.state.StateMachineDiagramGenerator"

输出示例(简化):

@startuml title 毕业审核状态机流程图 [*] --> PENDING_INITIAL PENDING_INITIAL --> INITIAL_APPROVED : APPROVE PENDING_INITIAL --> PENDING_INITIAL : REJECT INITIAL_APPROVED --> THESIS_CHECK : START_CHECK THESIS_CHECK --> THESIS_CHECK_PASSED : PASS THESIS_CHECK --> THESIS_CHECK_FAILED : FAIL THESIS_CHECK_FAILED --> THESIS_CHECK : RETRY @enduml

这张图能清晰暴露流程漏洞——比如缺少“FAIL 后允许更换查重系统”的跳转。某高校就据此新增了SWITCH_CHECK_SYSTEM事件,让审核员一键切换知网/万方/维普查重源。

从那以后我每次接手新学校的毕业审核需求,都强制走一遍StateMachineDiagramGenerator输出流程图,再拉着教务老师对着图逐个确认状态跳转条件。纸上谈兵的流程设计,永远比不过一张能跑起来的图谱来得真实。希望帮到你。

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

RELATED READING

延伸阅读

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