☰
基于SpringBoot的监狱管理系统毕设:权限、审批流与状态机设计
2026/10/7 20:58:25 网站建设 项目流程

简介:这份资源是面向高校计算机相关专业学生与Java初学者的一套监狱管理系统毕业设计完整资料,包含论文与可运行源码,适合作为SpringBoot课程设计、毕业设计选题或企业级管理系统入门练手项目。压缩包为zip格式,整体约14.62MB,内含论文文档与项目源码等文件,论文部分覆盖绪论、开发环境、系统分析、系统设计、系统实现与系统测试等章节,源码基于SpringBoot框架与MySQL数据库,采用B/S模式,划分服刑人员、管理员、民警三大功能模块,并配有E-R图与数据表设计说明。资源标签涉及SpringBoot、毕业设计、论文与源码、远程调试等方向,已有107人学习浏览。读者可据此获得一套结构完整的赛题级实现方案,既能参考论文的可行性分析、流程分析与数据库设计思路,也能对照源码理解各角色模块的落地方式,便于快速搭建环境、梳理开发脉络并完成二次开发或答辩准备。

1. 监狱管理系统为什么还在用 SpringBoot 做毕设:一份能跑通的选型判断

如果你正在搜「基于SpringBoot的监狱管理系统」,大概率是三种人之一:要交毕设的学生、被派去做司法/监管行业信息化改造的工程师、或者想拿一个真实业务系统练手的后端。这个标题背后其实是一个很典型的权限密集 + 流程密集 + 数据敏感的管理系统:在押人员档案、入所出所登记、家属会见预约、民警排班、减刑假释审批、监控设备台账,每一块都牵扯多角色、多状态、多审批节点。它跟电商、博客那种 CRUD 玩具完全不是一个量级,难点不在增删改查,而在权限边界、状态机流转、操作留痕这三件事上。

SpringBoot 之所以在这个场景里被反复选中,不是因为它新,而是因为它把 Spring 那一堆 XML 配置压成了 starter 依赖,让一个学生或小团队能在两三周内把「能登录、能分角色、能走审批」的骨架搭起来。监狱管理系统本身业务不复杂到需要微服务,单体 SpringBoot + MyBatis-Plus + Vue 前后端分离,就是当前最常见的落地形态。这篇笔记就按这个形态,把从建表到权限到审批流的关键路径讲清楚,顺带把论文里最容易写空的那部分——系统设计与实现——填上能复现的细节。

2. 先把业务模型拆对:在押人员、民警、审批流三张核心表怎么设计

2.1 为什么监狱管理系统的表设计不能照抄通用后台模板

通用后台模板通常给一张 user 表加一张 role 表就完事,但监狱管理系统的实体关系要复杂一层。核心实体至少有:在押人员(prisoner)、民警/狱警(officer)、监区(prison_area)、会见记录(visit_record)、奖惩记录(reward_punish)、审批单(approval)。其中在押人员和民警是两类完全不同的「人」,不能塞进同一张 user 表用 type 字段区分——因为他们的字段差异极大:在押人员有罪名、刑期、入所日期、剩余刑期、危险等级;民警有警号、职务、所属监区、值班班次。硬合并会导致大量空字段,查询时还要不停过滤 type,后期加字段就是灾难。

我一般会拆成三张表:sys_user只存登录凭证和账号状态,prisoner和officer各自通过user_id外键关联到sys_user。这样登录逻辑统一走sys_user,业务信息各查各的,权限校验时再根据角色决定能看哪张表的数据。

2.2 在押人员表的关键字段与状态机

在押人员不是一条静态记录,它的状态会流转:入所 → 在押 → 减刑/假释审批中 → 出所/释放/转监。状态字段设计错了,后面审批流就没法接。

CREATE TABLE prisoner ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT COMMENT '关联sys_user,用于家属远程查询', prisoner_no VARCHAR(32) NOT NULL UNIQUE COMMENT '囚号,业务主键', name VARCHAR(32) NOT NULL, id_card VARCHAR(18) COMMENT '身份证号,脱敏存储', crime VARCHAR(128) COMMENT '罪名', sentence INT COMMENT '刑期(月)', enter_date DATE COMMENT '入所日期', release_date DATE COMMENT '预计释放日期', area_id BIGINT COMMENT '所属监区', risk_level TINYINT DEFAULT 1 COMMENT '1低2中3高', status TINYINT DEFAULT 1 COMMENT '1在押2审批中3已出所4转监', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT DEFAULT 0 COMMENT '逻辑删除' ) COMMENT '在押人员表';

这里有几个参数值得说清楚。prisoner_no单独做业务唯一键,是因为真实场景里囚号是对外沟通用的,而自增 id 不该暴露。status用 TINYINT 而不是字符串,是为了索引效率和状态机判断方便,但一定要在代码里定义枚举,别在 SQL 里裸写数字。deleted逻辑删除几乎是这类系统的标配——在押人员记录不允许物理删除,出所也是改状态而不是删行,这是审计要求。

2.3 审批流表:减刑假释为什么不能只用一个 status 字段

减刑假释审批是监狱系统里最像「工作流」的部分。一个减刑申请要经过:民警提交 → 监区长审核 → 狱政科复核 → 监狱长批准。如果只在 prisoner 表加一个 status,你根本不知道当前卡在谁那里、谁批过、谁驳回、驳回理由是什么。

常见做法是单独建一张approval表,用biz_type区分业务类型,用current_node记录当前节点,用approval_record子表记录每一步操作。

CREATE TABLE approval ( id BIGINT PRIMARY KEY AUTO_INCREMENT, biz_type VARCHAR(32) COMMENT 'REDUCE_SENTENCE/PAROLE/VISIT', biz_id BIGINT COMMENT '关联业务id,如prisoner.id', current_node VARCHAR(32) COMMENT '当前审批节点', status TINYINT DEFAULT 0 COMMENT '0待审1通过2驳回', submit_by BIGINT COMMENT '提交人user_id', submit_time DATETIME, remark VARCHAR(255) ) COMMENT '审批主表'; CREATE TABLE approval_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, approval_id BIGINT, node VARCHAR(32) COMMENT '节点名', operator BIGINT COMMENT '操作人', action TINYINT COMMENT '1同意2驳回', opinion VARCHAR(255), op_time DATETIME ) COMMENT '审批流转记录';

这样设计的好处是:审批历史可追溯,任何一个节点都能查到谁在什么时候做了什么决定。论文里写「系统实现了减刑假释审批功能」时,把这两张表的结构和流转逻辑贴出来,比空谈「采用工作流引擎」有说服力得多。至于要不要上 Activiti/Flowable,我的经验是毕设级别没必要——引入工作流引擎会带来额外的表和学习成本,用状态机 + 审批记录表手写反而更可控,也更容易讲清楚。

3. 用 SpringBoot 搭骨架:依赖、分层、权限拦截的最小可跑配置

3.1 依赖选型:哪些 starter 是必须的,哪些是坑

一个能跑的监狱管理系统后端,pom.xml 里真正需要的依赖并不多。核心是spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、spring-boot-starter-validation,权限用spring-boot-starter-security或更轻的 JWT 方案。这里有个高频翻车点:SpringBoot 版本和 MyBatis-Plus 版本不匹配。网上搜「springboot版本太高」的人,多半是用了 SpringBoot 3.x 但 MyBatis-Plus 还是 3.4.x,导致启动报NoClassDefFoundError。稳妥组合是 SpringBoot 2.7.x + MyBatis-Plus 3.5.x,或者 SpringBoot 3.2.x + MyBatis-Plus 3.5.5 以上。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>

参数说明:spring-boot-starter-parent锁定了整套依赖的版本,避免手动指定每个库的版本号。MyBatis-Plus 的版本要显式写,因为它不在 SpringBoot 的 BOM 管理范围内。MySQL 驱动用runtimescope,编译期不需要,运行期才加载。

3.2 分层结构:controller / service / mapper 之外还要加什么

标准三层结构在监狱管理系统里不够用,因为权限校验和操作日志是横切关注点。我一般会加两层:security包放 JWT 工具和拦截器,aspect包放操作日志切面。

src/main/java/com/prison/ ├── controller/ // 接口层 ├── service/ // 业务逻辑 │ └── impl/ ├── mapper/ // 数据访问 ├── entity/ // 数据库实体 ├── dto/ // 请求/响应对象 ├── security/ // JWT、权限拦截 ├── aspect/ // 操作日志切面 └── common/ // 统一返回、异常处理

这个结构的好处是:权限和日志不侵入业务代码。比如民警修改在押人员信息时,业务 service 只管改数据,日志切面通过注解自动记录「谁在什么时候改了哪条记录」,这在审计场景里是刚需。

3.3 JWT 权限拦截:怎么区分民警、监区长、狱政科

监狱管理系统的权限不是简单的「登录/未登录」,而是「这个角色能不能操作这个监区的数据」。常见做法是 JWT 里存 userId 和 role,拦截器解析后放进 ThreadLocal,service 层再根据 role 决定数据范围。

@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { response.setStatus(401); return false; } Claims claims = JwtUtil.parse(token.substring(7)); // 把用户信息放进上下文,供后续业务使用 UserContext.set(claims.get("userId", Long.class), claims.get("role", String.class), claims.get("areaId", Long.class)); return true; } @Override public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) { UserContext.clear(); // 防止线程复用导致数据串号 } }

逻辑说明:preHandle在请求进入 controller 前校验 token,解析出的 role 和 areaId 决定了后续能查哪些数据。afterCompletion里必须清理 ThreadLocal,否则 Tomcat 线程池复用时会串数据——这是很多人踩过的坑,表现为「A 用户看到了 B 用户的数据」,排查起来很玄学。

参数说明:areaId是监区 id,用于数据隔离。监区长只能看本监区,狱政科能看全部,这个判断放在 service 层而不是拦截器里,因为不同接口的隔离规则不一样。

4. 会见预约与审批流:把状态机写进代码而不是写在文档里

4.1 会见预约的完整状态流转

家属会见是监狱管理系统里使用频率最高的功能之一。一次会见预约要经过:家属提交 → 民警初审 → 监区长审批 → 生成会见通知。状态流转必须严格,不能出现「已驳回又变成已通过」这种脏数据。

public enum VisitStatus { SUBMITTED(0, "待初审"), FIRST_PASS(1, "初审通过"), APPROVED(2, "审批通过"), REJECTED(3, "已驳回"), FINISHED(4, "已完成"); private final int code; private final String desc; // 构造、getter 省略 // 定义合法流转,非法流转直接抛异常 public static boolean canTransfer(int from, int to) { if (from == SUBMITTED.code && to == FIRST_PASS.code) return true; if (from == FIRST_PASS.code && to == APPROVED.code) return true; if (from == SUBMITTED.code && to == REJECTED.code) return true; if (from == FIRST_PASS.code && to == REJECTED.code) return true; if (from == APPROVED.code && to == FINISHED.code) return true; return false; } }

逻辑说明:把合法流转写成白名单,任何不在白名单里的状态变更都拒绝。这样即使前端传了错误的状态值,后端也不会产生脏数据。参数说明:code 用数字存库,desc 用于前端展示,两者通过枚举绑定,避免在代码里散落魔法数字。

4.2 审批接口的幂等与并发处理

审批接口最怕两件事:重复提交和并发审批。同一个减刑申请,两个领导同时点「同意」,如果不做控制,会产生两条审批记录。常见做法是在 approval 表加乐观锁版本号,或者用update ... where status = 0的条件更新。

@Transactional public Result approve(Long approvalId, Integer action, String opinion) { // 条件更新:只有当前还是待审状态才更新成功 int rows = approvalMapper.updateStatus(approvalId, action, opinion); if (rows == 0) { return Result.fail("该审批已被处理,请刷新后重试"); } // 更新成功后写流转记录 approvalRecordMapper.insert(buildRecord(approvalId, action, opinion)); // 根据业务类型回调对应业务表状态 callbackBiz(approvalId, action); return Result.ok(); }

逻辑说明:updateStatus的 SQL 里带where status = 0,靠数据库的行锁保证只有一个请求能更新成功,返回 0 行说明已被别人处理。这是最轻量的并发控制,不需要引入分布式锁。参数说明:action是同意/驳回,opinion是审批意见,callbackBiz根据 biz_type 决定更新 prisoner 还是 visit_record 的状态。

4.3 操作日志切面:审计要求下的必备件

监狱系统的任何数据变更都要留痕,这不是可选项。用 AOP 切面 + 自定义注解,可以在不改业务代码的前提下记录操作日志。

@Aspect @Component public class OpLogAspect { @Around("@annotation(opLog)") public Object around(ProceedingJoinPoint pjp, OpLog opLog) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); // 记录操作人、方法、参数、耗时 OpLogEntity log = new OpLogEntity(); log.setUserId(UserContext.getUserId()); log.setModule(opLog.module()); log.setAction(opLog.action()); log.setCost(System.currentTimeMillis() - start); opLogMapper.insert(log); return result; } }

逻辑说明:@Around在目标方法执行前后各插一段逻辑,pjp.proceed()执行真正的业务方法。参数说明:opLog.module()和opLog.action()来自注解,比如@OpLog(module="在押人员", action="修改"),这样日志可读性强,也方便按模块检索。注意日志插入不要和业务方法放在同一个事务里,否则业务回滚会连日志一起回滚,审计就断了。

5. 避坑与排查:监狱管理系统落地时最容易翻车的 5 个点

5.1 现象:登录后接口返回 403,但 token 明明是对的

原因:Spring Security 的过滤器链和自定义 JWT 拦截器同时生效,请求被 Security 先拦了。很多人只配了 JWT 拦截器,忘了放行 Security 的默认拦截,或者WebSecurityConfigurerAdapter里没把登录接口加入白名单。

解决:要么完全用 Spring Security 的过滤器链管理 JWT,要么在配置里http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().permitAll()把 Security 的默认拦截关掉,只用自己的拦截器。两者不要混用。

5.2 现象:分页查询在押人员时,第二页数据和第一页重复

原因:MyBatis-Plus 的分页插件没配置,或者排序字段不唯一。如果按create_time排序,同一秒插入的多条记录顺序不稳定,翻页时就会重复或漏数据。

解决:配置MybatisPlusInterceptor并加入PaginationInnerInterceptor,排序时加上唯一键兜底,比如order by create_time desc, id desc。

5.3 现象:修改在押人员信息后,列表页还是旧数据

原因:MyBatis 一级缓存或 Spring 缓存没失效。如果 service 上加了@Cacheable,更新时忘了@CacheEvict,就会读到脏数据。

解决:更新方法上加@CacheEvict(value="prisoner", key="#id"),或者干脆在毕设阶段不开缓存,等业务稳定后再加。缓存带来的收益在这个数据量级下不明显,但排查成本很高。

5.4 现象:审批通过后,在押人员状态没变

原因:审批主表更新成功,但回调业务表的逻辑抛异常被吞了,或者事务传播行为不对。如果callbackBiz用了REQUIRES_NEW,主事务回滚时它不会回滚,导致数据不一致。

解决:审批和业务回调放在同一个事务里,用默认的REQUIRED传播行为。回调失败就整体回滚,保证审批状态和业务状态一致。

5.5 现象:前端传的日期格式后端解析报错

原因:SpringBoot 默认只支持yyyy/MM/dd格式的日期绑定,前端传yyyy-MM-dd就报Failed to convert。

解决:在 DTO 的日期字段上加@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8"),或者在全局配置里注册ObjectMapper的日期格式。时区一定要写,否则会出现日期差一天的问题。

6. 论文里怎么把「设计与实现」写出工程味:三个能直接用的技巧

写这类毕设论文,最容易空的就是「系统实现」章节,通篇「本模块实现了 XX 功能」没有信息量。我的经验是抓住三个能体现工程判断的点来写,评审一看就知道你真做过。

第一个技巧是用状态流转图代替功能描述。不要写「系统支持减刑审批」,而是把SUBMITTED → FIRST_PASS → APPROVED的状态表和非法流转的拒绝逻辑写出来,配上canTransfer那段代码。这证明你考虑过边界,而不是只跑了正常流程。

第二个技巧是把参数选择写成对比。比如为什么用 TINYINT 存状态而不是 VARCHAR,为什么用逻辑删除而不是物理删除,为什么审批流不上工作流引擎。用表格列出来:

方案优点缺点本项目选择
物理删除实现简单无法审计,违反监管要求否
逻辑删除可追溯,符合审计查询需带条件是
工作流引擎功能完整学习成本高,表结构复杂否
状态机手写可控,易调试复杂流程需自己维护是

第三个技巧是留一个可验证的接口示例。论文附录里放一段 curl 请求和返回,比截图更有说服力:

curl -X POST http://localhost:8080/api/visit/approve \ -H "Authorization: Bearer eyJhbGci..." \ -H "Content-Type: application/json" \ -d '{"approvalId": 1001, "action": 1, "opinion": "同意会见"}'

返回{"code":200,"msg":"操作成功"}说明审批链路通了。如果返回「该审批已被处理」,说明并发控制生效了。这种可复现的验证点,比任何文字描述都硬。

最后说个我自己的习惯:每次写完一个模块,我都会用 Postman 把异常路径也跑一遍——重复提交、越权访问、状态非法流转。能扛住这三类的系统,才敢往论文里写「实现了权限控制和审批管理」。希望帮到你。

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

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

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

立即咨询