
每年到毕业季总有人私信问我同一个问题毕设到底选什么题才不容易翻车我的答案一直很明确——选一个你好理解、业务上能讲出完整闭环、技术栈又足够主流的项目。今天拆的这个基于Spring Boot的电竞赛事管理系统就是计算机毕业设计里出现频率相当高、做完也最容易出效果的一类。它要解决的核心问题很具体把一场电竞赛事从创建、报名、审核、排赛到比分录入、战绩统计、榜单更新的整个流程搬到Web上让管理员、战队队长、普通观众各取所需。这篇文章适合正在准备计算机毕业设计、或者想用Spring Boot练一个完整项目的同学我会把从选题、技术选型、数据库设计到论文答辩的思路全部讲透并重点还原我在实际开发过程中踩过的坑。1. 选题这件事为什么电竞赛事管理系统适合做毕设1.1 我为什么推荐这个方向毕设项目最怕的不是技术难而是需求空洞。很多同学选XX管理系统做到最后发现只是对一张表做增删改查答辩时老师一问你这个系统解决的真实问题是什么就接不上话。电竞赛事管理恰好反着来它的使用场景非常贴近真实生活学校里办一场王者荣耀或者英雄联盟的比赛需要一个地方展示赛事信息、收集战队报名、安排赛程、发布比分。这些事情每一件都真实发生过你只是把它们系统化了。从技术覆盖度上看这个选题也相当划算。它天然带有多种角色管理员、队长、队员、观众这就逼着你去做权限控制它有赛程和状态流转可以用状态机去设计它有比分录入和榜单统计涉及事务和聚合数据更新它还有文件上传头像、战队Logo、赛事封面能顺便把上传和静态资源访问讲清楚。一个项目把这些功能点全打通论文每一章都有东西可写答辩也能拿出几个亮点而不是空吹。另外从工作量控制的角度它又不会无限膨胀。我把功能边界一划去掉在线直播、支付购票这些复杂度不可控的部分就能在三个月内稳稳落地。毕设的评分标准看的是你能否独立完成一个有完整业务闭环的系统而不是你的系统功能数量多么惊人。1.2 先给系统画出一条清晰的功能边界动手写代码之前我先花了两天时间梳理角色和功能。系统最终划分了四种身份不同身份能做的事情完全不同角色核心权限游客浏览赛事列表、查看赛事详情、查看公告与赛果普通用户注册登录、关注战队和选手、查看完整赛程与比分战队队长创建战队、添加队员、报名参赛、维护队员信息系统管理员用户管理、赛事管理、报名审核、赛事编排、比分录入、公告发布为什么这么划分因为赛事业务的真实流程就是这样主办方管理员发起赛事战队长带队报名审核通过后进入赛程最后比分公开展示。我把这个主链路作为系统的脊柱所有功能都围绕它转不做无关的边角料功能。比如留言评论我放在后期有余力才加而不是一开始就铺开。1.3 把业务主流程画成可执行的任务清单确定完角色我梳理出系统必须支持的核心流程管理员创建赛事设置赛事名称、游戏类型、开始时间、报名截止时间、最大队伍数、奖金池与简介状态设为筹备中。赛事到报名时间后状态变为报名中队长在赛事详情页点击报名系统校验战队人数是否达标。管理员审核报名记录通过后战队正式进入赛事如果队伍数量达到上限或报名时间截止赛事状态自动推进。赛事开始时管理员根据报名队伍生成对阵表系统按轮次创建比赛记录。每场比完后由管理员录入双方比分系统自动判断胜者、更新战队胜场和选手数据。最后一轮打完赛事状态变为已结束前台展示冠军战队和完整成绩单。这份清单写到后面成了我的开发顺序表一个功能一个功能往下推不混乱。我觉得做毕设的第一步不是建项目而是把这种业务故事线写清楚它比任何架构图都管用。2. 技术栈选型不是越新越高级是要能答上答辩2.1 Spring Boot版本怎么选别让最新版毁掉你的投入技术选型最容易踩的第一个坑就是版本。Spring Boot 3.x 目前已经很常见了但它要求 JDK 17 及以上并且把原先的javax.*包全面迁移到了jakarta.*。如果你电脑上装的是 JDK 8硬上 Spring Boot 3.x 会直接在启动阶段报各种类找不到、包名对不上的错误。而绝大部分学校的教学环境、论文里写的运行环境还是 JDK 8 居多。我自己做这个项目时用的是 Spring Boot 2.7.18。这是 2.x 系列的最后一个版本修复了大量已知问题同时对 JDK 8 非常友好。如果你本机已经是 JDK 17 或更高版本用 3.x 也完全没问题但要注意在写代码时把import javax.servlet.*换成import jakarta.servlet.*这两个包长得像混着用一定会出问题。我给你的建议是先用java -version看本地 JDK再决定 Spring Boot 版本不要一上来就创建 3.x 项目。2.2 ORM选型MyBatis-Plus为什么更适合毕设场景数据访问层我最终选了 MyBatis-Plus 而不是原生 MyBatis也不推荐 JPA。理由是毕设项目需要你快速把 CRUD 写完把精力留给业务逻辑而 MyBatis-Plus 恰好能帮你省掉大量基础代码。内置的BaseMapper提供了selectById、insert、updateById这些方法还自带分页插件PaginationInnerInterceptor写榜单分页就是几行配置的事。更关键的是它有一个QueryWrapper比如我要查询所有状态为报名中的赛事、并且按创建时间倒序排列代码可以这样写LambdaQueryWrapperEvent wrapper new LambdaQueryWrapper(); wrapper.eq(Event::getStatus, EventStatus.REGISTERING) .orderByDesc(Event::getCreateTime); ListEvent eventList eventMapper.selectList(wrapper);这种写法可读性极高论文里贴出来答辩老师一眼就能看懂。而 JPA 虽然也很成熟但它的关联关系映射、懒加载、N1 查询这些问题对初学者来说反而容易把自己绕晕。技术选型的原则很简单你是要做毕业设计不是要当某个框架的布道师选择你能讲清楚原理的工具才是最好的。2.3 前端方案Vue 前后端分离再把打包产物放进Spring Boot这个项目我选择的是 Vue 3 Vite Element Plus 做前端Spring Boot 只提供 RESTful API。开发阶段我用npm run dev启动前端开发服务器通过 Vite 的代理把/api转发到后端的 8080 端口让跨域问题在开发期就不存在。到了部署或者给老师演示时我执行npm run build把生成的dist目录里的文件复制到 Spring Boot 项目的src/main/resources/static下然后直接启动一个 8080 端口就能访问整个系统。这样做的直接好处是论文里可以写采用前后端分离架构答辩演示又不需要同时开两个服务非常实用。不过这中间有个大坑就是 Vue Router 如果用了history模式刷新页面时会出现 404。原因在于前端路由的地址比如/events在服务器上根本不存在Spring Boot 默认会返回 404而不会帮你把请求转发回index.html。解决方法我后面在踩坑章节里详细讲。2.4 Maven项目结构一上来就分好包后面省心初始化项目时我就把包结构定好了避免后期到处挪代码。标准结构如下com.example.esports ├── common // 统一返回结果、全局异常处理 │ ├── Result.java │ ├── ResultCode.java │ └── GlobalExceptionHandler.java ├── config // 配置类WebMvc、拦截器、跨域 │ ├── WebConfig.java │ └── MybatisPlusConfig.java ├── controller // 接口层 │ ├── EventController.java │ ├── AuthController.java │ ├── TeamController.java │ └── MatchController.java ├── entity // 数据库实体类 ├── mapper // MyBatis-Plus Mapper接口 ├── service // 业务层 │ ├── EventService.java │ └── impl/EventServiceImpl.java └── util // JWT工具类等这里特别提一下common包。我定义了一个统一的ResultT返回体包含code、message、data三个字段。所有接口都返回这个结构前端拿到后统一判断。这个设计虽然简单但它是你在答辩时能说的统一响应规范的落地体现而且它让全局异常处理变得非常顺滑——业务异常只需抛一个自定义异常由GlobalExceptionHandler捕获并转成规范格式controller 里就不用到处写 try-catch。3. 数据库设计把赛事领域拆成一张张表3.1 核心表的划分与字段设计我建表的原则是业务上能独立变化的实体就单独成表。最终设计了 8 张表用户表、战队表、选手表、赛事表、报名表、比赛表、公告表以及一个用于存储战队与选手关联关系的关联表。以赛事表为例创建语句我精简成下面这样CREATE TABLE event ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 赛事ID, event_name varchar(100) NOT NULL COMMENT 赛事名称, game_type varchar(30) NOT NULL COMMENT 游戏类型: LOL/DOTA2/CS2/王者荣耀, cover_image varchar(255) DEFAULT NULL COMMENT 赛事封面图片URL, start_time datetime DEFAULT NULL COMMENT 比赛开始时间, end_time datetime DEFAULT NULL COMMENT 比赛结束时间, registration_deadline datetime DEFAULT NULL COMMENT 报名截止时间, max_teams int(11) DEFAULT 16 COMMENT 最大参赛队伍数, current_teams int(11) DEFAULT 0 COMMENT 当前已报名队伍数, prize_pool decimal(10,2) DEFAULT 0.00 COMMENT 奖金池, status tinyint(4) DEFAULT 0 COMMENT 赛事状态: 0筹备中 1报名中 2进行中 3已结束 4已取消, description text COMMENT 赛事介绍, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT电竞赛事表;这里有两个细节值得注意。第一current_teams是一个冗余字段它保存当前报名成功的队伍数每次报名审核通过时加 1避免每次统计都要count()全表。冗余字段在毕设里完全没有问题你只要在代码里保证更新逻辑正确即可。第二我在status上加了索引因为查正在进行中的赛事列表是最常被点击的查询加了索引后演示时数据量几百条虽然看不出差别但这个设计思路要在论文里写出来。3.2 赛事状态机用一个字段控制全生命周期赛事和比赛的状态是整个系统最容易出 bug 的地方。我采用的办法是给两个核心实体各定义一个状态字段并提前画好状态流转规则。赛事的状态机定义如下0 筹备中管理员刚创建任何人都不能报名。1 报名中到了报名开始时间队长可以提交报名。2 进行中审核结束、对阵生成比赛开始。3 已结束所有比赛打完榜单锁定。4 已取消管理员手动取消或者因为某些异常终止。我在 Service 层写了一个统一的状态校验方法任何更新状态的操作都必须经过它public void changeEventStatus(Event event, Integer targetStatus) { Integer current event.getStatus(); // 只允许相邻状态流转避免出现报名中直接跳到已结束这种非法操作 if (current EventStatus.DRAFT targetStatus EventStatus.REGISTERING) { event.setStatus(targetStatus); } else if (current EventStatus.REGISTERING targetStatus EventStatus.ONGOING) { event.setStatus(targetStatus); } else if (current EventStatus.ONGOING targetStatus EventStatus.FINISHED) { event.setStatus(targetStatus); } else { throw new BusinessException(非法的状态流转); } eventMapper.updateById(event); }这个设计的价值在于状态变化路径是显式的。答辩时老师问你怎么保证赛事状态不乱你可以直接回答我把状态流转规则集中在一个方法里做校验非法路径直接抛业务异常。这就是一个很具体的系统设计亮点。比赛表的match_status同样用这个思路处理无非是状态变成了未开始、进行中、已结束。3.3 物理外键到底建不建?我的结论是不建这是一个我做了很久的纠结。传统的数据库课程都会教你用FOREIGN KEY维护引用完整性但在真实项目中物理外键往往会带来三个麻烦删除数据时要先删子表再删父表顺序错了就报错批量插入时外键检查拖慢速度分库分表、做数据迁移时还需要额外处理外键关系。所以我最终所有的表关联关系都靠应用层维护也就是逻辑外键比如match表里的home_team_id、away_team_id只是存了战队的 ID并没有在 MySQL 层面建立约束。这样做的好处是开发灵活删除赛事或战队时只需要在 Service 层写清楚我先删掉关联的报名表和比赛表数据再删主表避免外键约束暴跳。当然代价是如果你代码里漏了清理操作会留下孤儿数据。我的习惯是每次删除操作都去检查所有关联表并把这段清理代码放在一个事务方法里确保要么全部删除成功要么全部回滚。4. 核心功能实现让一场比赛从报名走到榜单4.1 登录注册与三种角色的权限控制系统使用 JWT 做登录态管理。用户登录成功后后端生成一个 token并把用户 ID 和角色封装进 token 里返回。前端每次请求在请求头里带上Authorization: Bearer token后端通过拦截器从 token 中解析出用户信息再根据接口要求判断角色。拦截器的核心代码如下Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; // 放行跨域预检请求 } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { Claims claims JwtUtil.parseToken(token.replace(Bearer , )); if (claims ! null) { request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } } response.setStatus(401); return false; }然后在需要管理员权限的接口上我再判断一次角色String role (String) request.getAttribute(role); if (!ADMIN.equals(role)) { throw new ForbiddenException(); }。这个方案的优点是代码量少、逻辑直白你完全可以对着论文把拦截器原理讲清楚。如果你觉得 Spring Security 太难也不要强行堆上去答辩时被问到底层过滤器链反而容易垮。4.2 赛事创建、报名审核与状态推进管理员创建赛事后赛事状态是筹备中。等到了报名开始时间我提供一个按钮开放报名管理员点击后 Service 执行状态流转并同步把start_time作为报名开始时间。之后队长在赛事详情页看到立即报名按钮点击后创建一条报名记录Transactional public Result? applyEvent(Long eventId, Long teamId) { Event event eventMapper.selectById(eventId); if (event null || event.getStatus() ! EventStatus.REGISTERING) { return Result.error(当前赛事不在报名期内); } if (event.getCurrentTeams() event.getMaxTeams()) { return Result.error(参赛队伍数量已满); } // 防重复报名 Long count registrationMapper.selectCount( new LambdaQueryWrapperRegistration() .eq(Registration::getEventId, eventId) .eq(Registration::getTeamId, teamId) .eq(Registration::getStatus, APPROVED)); if (count 0) { return Result.error(该队伍已报名成功); } Registration reg new Registration(); reg.setEventId(eventId); reg.setTeamId(teamId); reg.setApplyTime(LocalDateTime.now()); reg.setStatus(PENDING); registrationMapper.insert(reg); return Result.ok(); }这里我特别建议把报名的方法加上Transactional防止出现记录插进去了队伍数却没加上这种数据不一致。事务不是只用在订单支付这类场景报名同样需要。管理员审核通过报名后current_teams加 1当current_teams等于max_teams时比赛状态可以一键推进到进行中。这个自动推进的逻辑你可以做成手动按钮也可以写一个定时任务去扫描。我两种都试过毕设建议用手动按钮因为流程可控、不容易出 bug论文里再提一句可以使用定时任务实现自动推进作为改进方向反而显得你有思考深度。4.3 对阵生成单败淘汰制的实现思路这是项目里算法含量最高的一部分也是答辩时可以展开的亮点。常见的赛事赛制有单败淘汰、双败淘汰、循环积分毕设项目单败淘汰就足够展示能力了。我实现了一个根据报名队伍列表生成对阵表的方法public ListMatch generateBracket(Long eventId, ListLong teamIds) { ListLong sorted new ArrayList(teamIds); // 1. 如果队伍数不是2的幂给部分队伍首轮轮空 int size sorted.size(); int nextPow 1; while (nextPow size) { nextPow * 2; } int byeTeams nextPow - size; // 轮空队伍数量 Collections.shuffle(sorted); // 抽签打乱顺序随机分配 ListMatch matches new ArrayList(); int index 0; // 2. 非轮空队伍两两配对 while (index 1 size - byeTeams) { Match m new Match(); m.setEventId(eventId); m.setRound(1); m.setHomeTeamId(sorted.get(index)); m.setAwayTeamId(sorted.get(index 1)); m.setStatus(MatchStatus.NOT_STARTED); matches.add(m); index 2; } // 3. 轮空队伍直接进入第二轮可以用一个虚拟字段标记 return matches; }这里要说明的是轮空队伍设计上可以有两种处理要么生成一条只有一个参赛方的比赛记录要么把轮空队伍记入自动晋级列表。我采用的是后者在第二轮生成时把轮空队伍优先放入对阵位置。虽然代码比理想状态多写了几十行但论文里描述赛制时能非常清楚。4.4 比分录入、战绩统计与排行榜更新管理员录入比分的表单是选择一场比赛填写主队得分、客队得分。提交后 Service 做三件事更新比赛记录的比分和胜者 ID把赛事状态推进到下一轮或结束更新参赛队伍的总胜场、总败场。最后这一步最能体现一个系统的完整性因为排行榜的数据全靠这些字段累加。Transactional public Result? reportScore(Long matchId, Integer homeScore, Integer awayScore) { Match match matchMapper.selectById(matchId); if (match null || match.getStatus() MatchStatus.FINISHED) { return Result.error(比赛不存在或已结束); } match.setHomeScore(homeScore); match.setAwayScore(awayScore); Long winnerId homeScore awayScore ? match.getHomeTeamId() : match.getAwayTeamId(); match.setWinnerId(winnerId); match.setStatus(MatchStatus.FINISHED); matchMapper.updateById(match); // 更新两队战绩 updateTeamRecord(match.getHomeTeamId(), homeScore awayScore, homeScore awayScore); updateTeamRecord(match.getAwayTeamId(), awayScore homeScore, homeScore awayScore); // 如果当前轮次全部结束自动生成下一轮对阵 advanceToNextRound(match.getEventId(), match.getRound()); return Result.ok(); }排行榜就简单了一个查询语句按胜场倒序、净胜分倒序排配合 MyBatis-Plus 分页插件输出。把战绩字段冗余到战队表里就是为了让排行榜查询直接基于一行数据避免每次做 join 加聚合运算。毕设数据量不大这种方式完全够用而且响应速度极快。5. 实测踩坑记录这些问题我调了一整个通宵5.1 Spring Boot版本过高引发的第一道坎我第一次创建项目时直接选了当时 Spring Initializr 默认推荐的 3.x 版本结果项目里引入了spring-boot-starter-web之后启动就报错Error creating bean with name dispatcherServlet defined in class path resource Caused by: java.lang.NoClassDefFoundError: javax/servlet/ServletContext排查了大半天才发现是 JDK 8 环境 Spring Boot 3.x 不兼容。后来我把项目换成了 Spring Boot 2.7.18也需要做两件事第一在 Maven 的settings.xml里配置阿里云镜像仓库不然下载依赖会慢到怀疑人生第二把所有用到 Java 时间新特性的地方统一用LocalDateTime。这个坑给我的教训是创建 Spring Boot 项目之前第一步先用java -version确认环境再决定版本。后面带学弟学妹做毕设我再也没让任何人直接从默认最新版起步。5.2 Vue打包放入Spring Boot后刷新404与空白页这是我调试时间最长的一个问题。开发模式一切正常但npm run build之后把dist文件拷进static目录访问首页没问题一旦点击跳转到/events再刷新就变成 404。原因我在前面提过就是 Vue Router 的history模式在刷新时让后端服务器找不到对应的资源路径。解决方案很成熟写一个配置类把所有非静态资源的请求转发到index.htmlConfiguration public class WebConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{spring:\\w}) .setViewName(forward:/index.html); registry.addViewController(/**/{spring:\\w}) .setViewName(forward:/index.html); registry.addViewController(/{spring:\\w}/**{spring:?!(\\.js|\\.css|)$}) .setViewName(forward:/index.html); } }如果同时配置了拦截器还要在拦截器的excludePathPatterns里排除/index.html、/assets/**、/favicon.ico这些静态资源否则拦截器会在资源到达之前就拦截掉。这两个点叠加起来我熬了一整晚才确认后来我把解决方案直接写在了自己笔记里供整个课题组复用。5.3 LocalDateTime 前后端差8小时这个问题是典型的本地对着部署到服务器就歪了。前端的create_time显示比实际时间慢了 8 个小时原因有两层第一层是 JSON 序列化时本地时间没有带上时区第二层是数据库连接串没有指定serverTimezone。两个地方的处理如下。application.yml里的 Jackson 配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8数据库连接串 URLjdbc:mysql://localhost:3306/esports ?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/Shanghai useSSLfalse这两个写完之后LocalDateTime的序列化和数据库存取都保持同一个时区问题消失。顺带提醒一句如果还用着Date类型的字段强烈建议统一改成LocalDateTime它在日期加减、格式转换上的体验好太多了。5.4 MyBatis查询多表返回部分字段为null做赛事列表时我需要返回赛事名称、报名队伍数、当前状态同时还要带出创建者的用户名。联表查询写得没错但实体的userName字段始终是null。查了一圈发现 MyBatis 的默认配置里camelCase映射没有打开导致数据库的user_name无法自动映射到实体的userName。解决办法是在application.yml加一行mybatis-plus: configuration: map-underscore-to-camel-case: true如果你用原生 MyBatis需要在mybatis-config.xml里设置setting namemapUnderscoreToCamelCase valuetrue/。这个错误太隐蔽了因为它不会报任何异常只是字段静默为空。我建议大家在写完第一个联表查询接口后立刻检查返回 JSON 里是否所有字段都有值别等到页面展示才发现。5.5 数据库连接池耗尽一次忘记释放连接引发的假死这个问题是在压力测试接口时发现的。页面打开后第一波请求正常过几分钟后所有请求全部卡住控制台不断输出HikariPool-1 - Connection is not available, request timed out after 30000ms。排查发现是有一个 Service 方法里手动获取了Connection用完后没有close()。后来我把所有手动连接改为通过 MyBatis 的SqlSession或直接使用BaseMapper并在方法上使用try-with-resources保证连接释放。毕设里如果你也用了 JdbcTemplate 或者原生 JDBC一定要检查连接是否关闭。HikariCP是 Spring Boot 默认连接池它的超时和最大连接数都可以在配置里调整spring: datasource: hikari: maximum-pool-size: 10 connection-timeout: 30000不过最根本的解决办法还是不手动拿连接让框架去管理从源头上避免连接泄漏。6. 从代码到论文再到答辩把项目讲成高分毕设6.1 论文骨架怎么搭每一章对应一段工作代码写完后论文是最容易让平时写代码很猛但写作苦手的同学翻车的地方。我搭的论文骨架是这样几乎每一章都能直接对应到我的开发产出论文章节核心内容绪论选题背景电竞产业规模、校园赛事需求、国内外研究现状、研究内容与意义需求分析角色分析、功能需求用例、非功能需求性能、安全、可用性系统设计总体架构图、功能模块划分、数据库设计、状态机设计系统实现按功能模块贴核心代码和截图对应第4章的实现过程系统测试功能测试用例表、测试结果、性能测试总结与展望完成的工作、不足、改进思路定时任务自动排赛、数据分析等一定要把状态机统一返回结果JWT权限拦截事务控制这些点写进论文的设计章节它们让整篇论文有设计感。很多同学论文写得像代码注释平铺就是因为缺了这一层抽象设计描述。6.2 答辩前必须准备好的六个问题我把自己被老师问过的高频问题整理成了一个准备清单每个问题我都写了对应的回答思路这里分享给你为什么选这个课题从真实需求出发说清楚校园电竞比赛需要一个报名与赛程管理工具而不是为了做毕设硬凑题目。系统架构是什么样画清前后端分离架构、Spring Boot 提供 API、Vue 负责展示、MySQL 存储数据再补一句通过 JWT 管理登录态通过状态机控制业务流转。权限控制怎么实现的解释拦截器如何解析 token、如何校验角色、哪些接口需要管理员权限。数据库为什么这样设计讲清楚每张表的用途重点讲冗余字段current_teams和状态字段的设计理由。比分录入时怎么保证数据一致性回答用Transactional保证比分、胜者、战绩统计在同一事务里更新任何一步失败都会回滚。系统有什么不足和扩展空间主动提三点当前是单败淘汰制后续可以扩展循环积分报名审核是手动可以做自动审核和通知榜单可以引入数据分析比如选手 MVP 计算。这比老师提问后哑口无言要好得多。做完这个项目我最大的体会是毕设真正难的不是写代码而是把一个业务从模糊想法变成结构清晰的系统再把这个过程讲给别人听。Spring Boot 给了你完善的生态MyBatis-Plus 帮你省下大量重复工作JWT 和状态机让项目有了深度路由回退、时区、驼峰映射这些坑趟一次就会了。你按这个路径走下来代码、论文、答辩三个环节都不会虚。最后提醒一句任何毕设项目都别等到一个月冲刺把主流程每天推进一步你会感谢这样稳扎稳打的自己。