领取优惠


毕业设计管理系统Java实战:从零搭建新手友好型后端架构

摘要:许多计算机专业学生在开发毕业设计管理系统时,常因缺乏工程经验陷入代码混乱、模块耦合、权限失控等问题。本文面向Java初学者,基于Spring Boot + MyBatis + JWT技术栈,手把手构建一个结构清晰、可扩展的毕业设计管理系统后端。你将掌握分层架构设计、师生角色权限控制、选题流程状态管理等核心逻辑,并获得一套符合Clean Code规范的可复用代码模板,显著降低开发门槛与后期维护成本。


一、背景痛点:为什么“能跑”≠“能毕业”

每年 3-4 月,校园里的咖啡味都会浓几分——那是毕设系统 DDL 的味道。很多同学把“能跑起来”当成终点,结果答辩现场被导师灵魂三问:

  1. 业务代码全写在 Controller,Service 层只有一行 return dao.xxx(),事务回滚靠运气?
  2. 学生、教师、教务三种角色共用一套接口,权限判断靠 if(userType==1) 硬编码?
  3. 选题并发时,最后一份课题被两人同时选中,数据库却毫无异常?

把代码打包发给导师后,还要远程连调,一不小心把本地 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

  1. 后端用 Apache Tika 检测 MIME,白名单仅允许 application/pdfapplication/msword 等。
  2. 文件名使用 UUID 重命名,路径与业务字段隔离,避免目录穿越。
  3. 存储到非 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/耗时,方便教务回溯“谁把课题状态改错了”。

六、生产环境避坑指南

  1. 数据库连接池
    默认 HikariCP 足够,但云服务器 1C2G 要把 maximum-pool-size 调到 10 以内,否则内存炸。

  2. Swagger 暴露
    生产务必加 spring.profiles.active=prod 时屏蔽 Swagger,或者加 IP 白名单拦截器,别让外扫描器抓到 /v3/api-docs

  3. 本地 vs 部署差异
    Windows 不区分大小写,Linux 区分。上传路径写死 D:\\upload 会导致线上 404,用 spring.resources.static-locations=file:${upload.path} 可配置化。

  4. 打包与启动
    mvn clean package 后,把 application-prod.yml 放在 jar 同级目录,Spring Boot 会优先加载外部配置,数据库账号密码无需写进 jar,方便运维。


七、可扩展思考:多轮选题 & 前端对接

  • 多轮选题:在状态机里再增加 STAGE6(退选)STAGE7(二轮开放),把 ThesisStage 枚举与数据库 stage 字段解耦到字典表,后台可配置轮次。
  • 前端对接:Vue 或 React 只需专注调用 REST,列表分页、文件上传进度、JWT 过期自动跳登录——这些通用逻辑封装进拦截器即可,后端保持无状态,真正前后并行开发。

调试现场

图:凌晨两点,行级锁压测通过,终于敢合上电脑。


写在最后

整套代码不到 2k 行,却覆盖了毕设系统 90% 的共性需求:角色权限、状态流转、并发安全、日志审计。
把它当成“骨架”,你可以继续加:

  • WebSocket 实时通知“导师审核通过”
  • 甘特图排期
  • 相似度检测避免翻译抄袭

先让系统跑通,再让功能增值——毕业设计不是终点,而是把“工程思维”练成肌肉记忆的开始。
下一步,你会如何扩展支持“多轮选题”?或者动手写个 Vue3 前端,把这套接口真正用起来?
欢迎把遇到的坑继续写在注释里,下一个接手的同学,会感谢你今天留下的那行 // 别删,这里并发会炸

领取优惠


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐