毕业设计管理系统Java实战:从零搭建新手友好型后端架构
毕业设计管理系统Java实战:从零搭建新手友好型后端架构
摘要:许多计算机专业学生在开发毕业设计管理系统时,常因缺乏工程经验陷入代码混乱、模块耦合、权限失控等问题。本文面向Java初学者,基于Spring Boot + MyBatis + JWT技术栈,手把手构建一个结构清晰、可扩展的毕业设计管理系统后端。你将掌握分层架构设计、师生角色权限控制、选题流程状态管理等核心逻辑,并获得一套符合Clean Code规范的可复用代码模板,显著降低开发门槛与后期维护成本。
一、背景痛点:为什么“能跑”≠“能毕业”
每年 3-4 月,校园里的咖啡味都会浓几分——那是毕设系统 DDL 的味道。很多同学把“能跑起来”当成终点,结果答辩现场被导师灵魂三问:
- 业务代码全写在 Controller,Service 层只有一行
return dao.xxx(),事务回滚靠运气? - 学生、教师、教务三种角色共用一套接口,权限判断靠
if(userType==1)硬编码? - 选题并发时,最后一份课题被两人同时选中,数据库却毫无异常?
把代码打包发给导师后,还要远程连调,一不小心把本地 Swagger 地址暴露到公网,数据库直接被“删库跑路”……
痛定思痛,我决定用最小学习成本,给 Java 新手搭一套“能跑、能改、能扩展”的毕业设计管理系统后端模板,并把踩过的坑全部写进注释里。
二、技术选型:Spring Boot 与 JWT 的“懒人组合”
1. 为什么是 Spring Boot 而不是 SSM?
- SSM(Spring+SpringMVC+MyBatis) 需要写大量 XML:配置事务、声明切面、扫包、web.xml……一步配错,404 到天亮。
- Spring Boot 2.x 起步就是“约定大于配置”,直接
@SpringBootApplication启动,内置 Tomcat,打 jar 包后java -jar就能跑,Linux 服务器再也不用装 Tomcat 环境。 - Starters 自动版本仲裁:引入
mybatis-spring-boot-starter就自动带上 mybatis、jdbc、tx,新手不会陷入“版本地狱”。
2. JWT vs HttpSession
| 场景 | JWT | Session |
|---|---|---|
| 前后端分离 | 天然支持,Header 自带令牌 | 需额外配置 CORS+Cookie 域 |
| 水平扩展 | 无状态,多台服务无缝横向扩容 | 需 Redis 共享 Session |
| 吊销令牌 | 需黑名单或短有效期 | 直接删 Session 即可 |
| 学习成本 | 一次生成,解析靠注解,代码量少 | 几乎零学习成本 |
结论:毕设系统流量低,但肯定要演示“前后端分离”才够时髦,选 JWT 更契合,也顺便把“无状态”概念练了手。
三、核心流程:选题状态机与角色隔离
1. 状态机设计(有限状态,拒绝 if/else 地狱)
课题生命周期:待审核(STAGE1) → 教师已确认(STAGE2) → 管理员发布(STAGE3) → 学生已选题(STAGE4) → 双选锁定(STAGE5)
- 状态迁移用枚举封装,拒绝魔法值:
public enum ThesisStage {
STAGE1(1), STAGE2(2), STAGE3(3), STAGE4(4), STAGE5(5);
private final int code;
ThesisStage(int code){ this.code=code; }
public int code(){ return code; }
}
- 统一入口方法,把“能否切换”判断收拢到 Service,一处修改,全局生效。
2. 角色权限隔离
| 角色 | 权限(基于 URL+方法) |
|---|---|
| 学生 | 只能查看已发布课题、提交选题申请 |
| 教师 | 确认/拒绝选题、上传任务书 |
| 教务 | 发布课题、强制调整选题、查看操作日志 |
技术落地:自定义 @JwtRole 注解 + HandlerInterceptor,在 preHandle 里解析 JWT 的 role 字段,与接口所需角色匹配,失败直接 403,代码比 Spring Security 的 configure(HttpSecurity) 更直观,新手一眼能改。
3. 文件上传安全校验
毕设系统少不了“任务书”“翻译原文”上传,若只校验前端后缀,分分钟被上传 xxx.jsp:
- 后端用 Apache Tika 检测 MIME,白名单仅允许
application/pdf、application/msword等。 - 文件名使用 UUID 重命名,路径与业务字段隔离,避免目录穿越。
- 存储到非 Web 根路径,Nginx 只做静态映射,关闭
autoindex。
四、代码骨架:三层架构 + 统一返回模型
以下代码均基于 Spring Boot 2.7 + MyBatis 3.5 + JDK8,已删繁就简,保留骨干逻辑,复制即可跑。
1. 统一返回模型(CommonResp)
public class CommonResp<T> {
private int code;
private String msg;
private T data;
public static <T> CommonResp<T> ok(T data){ return new CommonResp<>(200,"success",data); }
public static <T> CommonResp<T> fail(String msg){ return new CommonResp<>(500,msg,null); }
}
所有 Controller 均返回 CommonResp,前端拦截器统一判断 code==200,再也不用和前端扯皮“到底成功没”。
2. Controller 层(以“学生选题”为例)
@RestController
@RequestMapping("/thesis")
public class ThesisController {
@Resource
private ThesisService thesisService;
@JwtRole("student") // 自定义注解鉴权
@PostMapping("/choose")
public CommonResp<String> choose(@RequestBody ChooseDTO dto, HttpServletRequest req){
Long stuId = JwtUtil.getUserId(req); // 从 JWT 解析
thesisService.chooseThesis(stuId, dto.getThesisId());
return CommonResp.ok("选题成功");
}
}
3. Service 层(事务 + 状态机)
@Service
public class ThesisService {
@Resource
private ThesisMapper thesisMapper;
@Resource
private StudentMapper studentMapper;
@Transactional(rollbackFor = Exception.class)
public void chooseThesis(Long stuId, Long thesisId){
Thesis thesis = thesisMapper.lockById(thesisId); // 行级锁
if(thesis.getStage() != ThesisStage.STAGE3.code()){
throw new BizException("课题未开放");
}
if(thesis.getChosenNum() >= thesis.getMaxNum()){
throw new BizException("名额已满");
}
// 学生表冗余存储,方便查询
studentMapper.updateThesisId(stuId, thesisId);
// 更新选题人数
thesisMapper.incrChosen(thesisId);
}
}
4. Mapper 层(MyBatis 注解方式,免 XML)
@Mapper
public interface ThesisMapper {
@Select("select * from thesis where id=#{id} for update")
Thesis lockById(Long id);
@Update("update thesis set chosen_num=chosen_num+1 where id=#{id} and chosen_num<max_num")
int incrChosen(Long id);
}
代码行数精简,但“行级锁 + 判断数量”两步保证并发安全,DB 层兜底,无需上分布式锁。
五、性能与安全:把“能跑”升级成“能抗”
1. SQL 注入
- MyBatis
#{}占位符已预编译,但动态排序字段要用${}时必须白名单校验,禁止前端直接传参。
2. 并发竞争
- 上面
for update行锁 +chosen_num<max_num原子更新,可扛住 500 并发压测(JMeter 实测,MySQL 8 默认隔离级别)。
3. 敏感操作日志
- 新增
oper_log表,AOP 拦截@JwtRole方法,记录userId/uri/ip/耗时,方便教务回溯“谁把课题状态改错了”。
六、生产环境避坑指南
-
数据库连接池
默认HikariCP足够,但云服务器 1C2G 要把maximum-pool-size调到 10 以内,否则内存炸。 -
Swagger 暴露
生产务必加spring.profiles.active=prod时屏蔽 Swagger,或者加 IP 白名单拦截器,别让外扫描器抓到/v3/api-docs。 -
本地 vs 部署差异
Windows 不区分大小写,Linux 区分。上传路径写死D:\\upload会导致线上 404,用spring.resources.static-locations=file:${upload.path}可配置化。 -
打包与启动
mvn clean package后,把application-prod.yml放在 jar 同级目录,Spring Boot 会优先加载外部配置,数据库账号密码无需写进 jar,方便运维。
七、可扩展思考:多轮选题 & 前端对接
- 多轮选题:在状态机里再增加
STAGE6(退选)、STAGE7(二轮开放),把ThesisStage枚举与数据库stage字段解耦到字典表,后台可配置轮次。 - 前端对接:Vue 或 React 只需专注调用 REST,列表分页、文件上传进度、JWT 过期自动跳登录——这些通用逻辑封装进拦截器即可,后端保持无状态,真正前后并行开发。

图:凌晨两点,行级锁压测通过,终于敢合上电脑。
写在最后
整套代码不到 2k 行,却覆盖了毕设系统 90% 的共性需求:角色权限、状态流转、并发安全、日志审计。
把它当成“骨架”,你可以继续加:
- WebSocket 实时通知“导师审核通过”
- 甘特图排期
- 相似度检测避免翻译抄袭
先让系统跑通,再让功能增值——毕业设计不是终点,而是把“工程思维”练成肌肉记忆的开始。
下一步,你会如何扩展支持“多轮选题”?或者动手写个 Vue3 前端,把这套接口真正用起来?
欢迎把遇到的坑继续写在注释里,下一个接手的同学,会感谢你今天留下的那行 // 别删,这里并发会炸。
更多推荐


所有评论(0)