Spring Boot四六级英语自主学习平台:从设计到答辩全解析
做计算机毕业设计选题永远比写代码难。Spring Boot大学四六级英语考试自主学习平台这个题目在我接触过的毕业生项目里出现频率相当高。它本质上是一个在线学习与考试系统面向高校学生的四六级备考场景把词汇打卡、专项练习、模拟考试、错题归集、成绩分析这些功能整合到一个基于Spring Boot的后端加Vue前端的Web应用里。适合两类人参考一类是计算机相关专业、正在为毕设选题发愁的应届生另一类是学过Spring Boot基础但想通过完整项目练手的开发者。这篇文章我从需求设计讲到数据库表结构从核心代码写到答辩避坑全程按我实际带项目的经验来尽量把每一步的道理讲明白。1. 项目概述这是一个什么样的毕业设计1.1 选题背景与项目定位先说为什么四六级英语学习平台适合做毕业设计。第一需求真实。四六级是大量高校学生的刚需每年两次考试备考期间学生需要大量的词汇练习、专项刷题和模拟考试一个能自动判分、能统计错题、能画出成绩趋势图的平台上线之后是真实有人愿意用的。答辩的时候老师问“这个系统有什么实际意义”你不需要编直接拿数据说话就行。第二技术覆盖面恰到好处。它不是一个简单的增删改查管理系统而是涉及题库管理、随机组卷、自动评分、缓存加速、实时计时推送、数据可视化等多个技术点写进论文里每个章节都有干货。但这些技术点又都没有难到做不完的程度一个认真投入的学生用两个月左右完全能拿下。第三展示效果直观。成绩分析页用ECharts画几张雷达图、折线图答辩PPT一放功能完整性和技术含量一眼就能看出来比单纯的管理系统有视觉冲击力。这里要特别提醒一句这种题目的定位很重要。不要把它做成纯题库管理系统那样太普通和市面上的在线考试系统重复度高也不要一上来就加AI作文批改、智能推荐算法、大数据分析毕设周期根本撑不住答辩时还容易把自己绕进去。合理的定位是“能稳定运行、核心功能完整、有1到2处技术亮点”的系统把基础链路做扎实再挑一两个点深入。1.2 核心功能与目标用户这个平台的目标用户分两类设计的时候功能边界要划清楚学生用户是平台的核心使用者。注册登录以后可以每天做词汇打卡按听力、阅读、写作、翻译四个题型进行专项练习参加模拟考试考试后查看客观题成绩和答案解析主观题等待教师批改。系统自动把做错的题目收进错题本学生可以反复重做直到彻底掌握。学习一段时间后数据看板会展示各题型的正确率、成绩变化趋势和薄弱环节。教师或管理员使用后台管理功能。主要包括题库的增删改查和批量导入、试卷规则的配置、学生信息的查看和禁用、主观题的批改打分。如果做精细一点还可以看到某个学生的完整学习记录和成绩报告。这里要强调一下题目的两个关键词“自主学习”和“个性化”。市面上很多在线考试系统只是把卷子搬到网上做完就结束没有后续。这个平台和它们的本质区别在于学习路径不是千篇一律的而是根据每个学生的做题记录动态调整——错得多的题型在专项练习里出现的频率更高到期该复习的单词会主动推送到学习计划里这种设计让“个性化”三个字有了实际落脚点也正好是论文里可以重点展开的创新点。2. 技术选型为什么用Spring Boot这一套2.1 后端技术栈的选择逻辑Spring Boot为什么是毕业设计的主流选择不用我多说但道理还是要讲清楚因为论文里要写。它是基于Spring框架的快速开发脚手架内核是“约定优于配置”把以往Spring MVC项目里繁琐的XML配置全部简化成自动化配置内置Tomcat打包成jar就能跑。很多同学在论文技术选型部分写不清楚Spring、Spring MVC、Spring Boot三者的关系这里顺便理一下Spring是核心容器框架提供依赖注入和面向切面编程能力Spring MVC是Spring旗下的Web模块负责处理HTTP请求的路由和分发Spring Boot则是Spring全家桶的一键启动器让开发者不用花大量时间在环境配置上专注于业务代码。持久层我建议选 MyBatis-Plus。在国内实际开发中MyBatis 系的占有率远高于 JPA毕设项目用 MP 有两个直接好处一是它封装了通用Mapper单表的增删改查几乎不用写SQL配合 LambdaQueryWrapper 连表名字符串都不用拼二是内置分页插件一行代码实现物理分页考试记录、错题列表这些高频翻页场景直接用。再加上代码生成器可以从数据库表直接生成实体类、Mapper、Service、Controller 四层代码开发效率比手写高出一大截。缓存层是第二个要重点考虑的选型。为什么毕设也要做缓存因为听力音频资源、高频词汇、热门试卷这些数据在考试集中时段会被大量学生同时访问数据库直连在压力下会很吃力。做一层 Redis 缓存不仅系统性能明显提升论文里也多了一个有分量的技术点。如果追求性能极致还可以做 Redis Caffeine 两级缓存——Caffeine 是本地缓存命中速度是微秒级适合读多写少的热点数据Redis 是分布式缓存可以做多实例共享。两级缓存的读取顺序是本地缓存优先未命中再查 Redis最后查数据库逐级回填。对毕设来说做到 Redis 单级缓存已经够用两级缓存可以作为扩展方向写进“系统展望”里。登录鉴权方案选择 JWT 而不是传统的 Session。原因很简单前后端分离架构下Session 存在跨域和集群共享的问题而 JWT 是无状态 token服务端不用维护会话用户登录成功后就拿到一个加密签名的 token后续请求在拦截器里校验 token 的合法性和过期时间即可。这种方案也是当前互联网公司的主流做法写进简历不丢人。还有一个容易忽略的选型日志。搜索热词里也提到了 Spring Boot 日志很多同学开发时只看控制台输出上线后出了问题没法排查。项目中要引入 logback 的 XML 配置按天滚动生成日志文件同时把 Spring Boot 自带日志的级别调整到 INFO业务系统的必要日志用 Slf4j 注解输出到指定文件这样排查问题有据可依。2.2 前端方案与交互设计前端技术栈选 Vue 3 Element Plus ECharts Axios。前后端分离已经是当前开发的主流模式论文里可以明确写“系统采用前后端分离架构后端提供 RESTful API前端通过 Axios 异步交互”。Element Plus 是 Vue 3 生态里最成熟的组件库后台管理最常用的表格、弹窗、表单、选项卡全都有现成组件样式统一且简洁不需要自己写复杂 CSS。ECharts 用于数据可视化学生成绩报告里的题型正确率雷达图、历次成绩趋势折线图、词汇掌握度饼图都靠它实现。这里要劝一句前端功底弱的同学如果对 Vue 确实没把握可以退而求其次用 Thymeleaf 服务端渲染开发速度快但毕业设计的评分上限会受影响。既然题目已经写了“个性化学习平台”前端交互和数据可视化是重要加分项我还是建议花点时间把 Vue 的基本功补上——组件通信、路由守卫、Axios 拦截器这三个点掌握了开发这个项目的前端绰绰有余。另外一个经验是前端代码不要照搬网上那种大型后台管理模板。那些模板动辄几百个文件样式、组件、工具函数堆积如山你拿过来改完连自己都不清楚整体结构答辩一问就露馅。最佳做法是自己从空项目搭起需要哪个组件就从 Element Plus 里引哪个页面结构自己设计代码量控制在两三千行讲起来清清楚楚。2.3 开发环境与版本匹配环境方面给一组经过验证的组合照着配基本不会踩坑JDKSpring Boot 2.7.x 配 JDK 8成熟稳定资料多如果追求新特性用 Spring Boot 3.x必须换 JDK 17但 Spring Security 等组件的配置方式和 2.x 差别明显新手我不推荐。IDEA 2023 及以上版本装好 Lombok 插件。MySQL 8.0数据库管理工具用 Navicat 或者 DataGrip 都行。Redis 6.xWindows 下直接下载压缩包启动 redis-server.exe或者用 Docker 跑一个。Maven 3.8配置好阿里云镜像依赖下载速度会快很多。权限方案上毕业设计项目我建议一个务实的做法用户表里直接存一个 role 字段学生/教师/管理员后端用 Spring Security 的注解比如 PreAuthorize或者自定义拦截器做角色校验都比搭建一整套完整的 RBAC 权限模型简单可靠。如果论文里想体现 RBAC 设计思路可以加一张角色表和关联表但不要继续细化到权限点级别的控制不然开发的复杂度会明显超出毕设应有的工作量。3. 系统模块拆解与设计思路3.1 用户角色与权限设计系统的角色分三角形结构学生、教师、管理员。学生权限只涉及学习路径相关接口教师可以进入后台管理题库和批改主观题管理员拥有全部权限包括用户管理。虽然角色简单前端和后端仍然要做双重校验——前端在 Vue Router 的 beforeEach 路由守卫里根据角色判断当前用户能否进入某个页面不合格的直接跳转到首页后端的拦截器或注解再做一层兜底防止有人绕过前端直接调用接口。权限设计里有一个细节值得注意前端判断角色依赖 JWT 里携带的 role 字段后端每次请求也重新从 token 解析角色所以用户信息变更比如管理员把某个学生账号禁用不会立即影响已签发的 token。为了解决这个问题后端在需要严格控制的操作上比如交卷、进入考试会额外查一次数据库确认用户状态消耗不大但安全性提升明显。3.2 题库管理与试题生成逻辑题库是这类平台的核心数据资产表结构设计好坏直接影响后续开发的复杂度。题目按四级和六级两种考试类型区分每个类型下再分听力、阅读、写作、翻译四个专项。题型的设计上听力客观题和阅读客观题用单选题阅读里的选词填空用不定项或填空翻译和写作是主观题后台只保存题目内容不做自动判分。每道题需要准备的字段包括所属考试类型、题型分类、题干文本、四个选项、正确答案、答案解析、难度系数、关联的听力音频URL。这里有一个可以简化的小技巧单选题的答案直接存在题目表里比如 answer_key 字段存 A不用单独建选项表。因为四六级客观题就是标准的四选一选项内容写在题目表里的 option_a、option_b 四个字段就行了很多学校数据库课程设计里那种“题目表加选项表”的两表结构在这个场景下反而把事情搞复杂了。模拟考试的抽题算法是这个模块的核心难点可以直接作为论文的“关键技术”章节。正常流程是管理员预先配置试卷规则比如四级模拟卷听力25题、阅读25题、翻译1题、写作1题学生点击开始考试时后端按规则从题库中随机抽题组卷。最简单的抽题写法是 ORDER BY RAND() 配合 LIMIT但如果题库量达到几千道全表随机排序的性能会明显下降。更合理的方案是先按题型和难度分层简单题占30%、中等题占50%、困难题占20%的比例范围每层单独随机抽取抽不够再从其他难度补足最后把抽出的题目列表随机打乱顺序。这样既能保证试卷的难度稳定性也让学生每次模考遇到的题目不会重复。3.3 模拟考试与评分引擎模拟考试模块的完整流程要设计清楚这个流程图是论文“系统实现”部分的标配。学生点击“开始考试”后前端发请求给后端组卷接口后端返回试卷信息和题目列表但绝不返回答案。考试界面左侧是题目列表可以点击跳转中间是题目区和选项区顶部有倒计时。这里用 WebSocket 做服务端计时推送——后端维护每个学生的考试会话每30秒推送一次剩余时间还剩下10分钟、5分钟、1分钟的关键节点再单独推送提醒。为什么不用前端倒计时因为前端倒计时可以被篡改刷新页面还会重置WebSocket 推的时间以服务端为准这是评分公平性的技术保障。集成 WebSocket 的 yml 配置不复杂关键是把端点路径和允许的跨域来源写对后面常见问题部分会专门讲。交卷时的评分引擎分两段处理。客观题听力、阅读的选择题由后端逐题比对答案学生答案先做 trim 和转大写处理再判断按每题分值累加得到客观题分数。主观题翻译、写作不自动判分保存学生答题内容后将记录状态变为“待批改”教师在后台看到后手动打分。最终成绩 客观题得分 主观题得分论文里要交代清楚四六级原始分的计分逻辑。真实的四六级成绩是常模转换后的报道分不是简单卷面分毕设里可以用标准分公式模拟比如“试卷总分按百分制折算再映射到710分制”并在论文里注明是简化模拟即可不需要完全复刻考试院的统计模型。考试提交后学生可以立即查看客观题得分、正确答案和解析主观题分数等教师批改后再更新。同时每道错题会自动写入错题本学生后续可以一键重做。3.4 学习行为追踪与个性化推荐逻辑这个模块直接回应题目的“个性化学习平台”定位也是让这个系统区别于普通考试系统的关键。个性化推荐的前提是完整的行为数据。每次学生做一道题后端就往 study_log 表插一条记录包含学生ID、题目ID、是否答对、答题耗时、来源专项练习还是模拟考试、时间戳。这张日志表不断累积就成了分析学生薄弱点的数据源。基于这些数据可以做三件很实际的事情。第一正确率统计。按题型分类聚合得出听力正确率68%、阅读正确率52%、翻译正确率45%、写作正确率60%雷达图画出每个学生的能力轮廓。第二错题优先。错题本里的题目凡是重做后仍然答错的在专项练习的抽题池里权重翻倍保证学生反复接触自己没掌握的知识点。第三薄弱题型加权。对于正确率低于60%的题型专项练习出题时在基础数量的基础上额外增加题量比如听力平时一次出10题正确率低的学生一次出15题用这种规则式的策略模拟“个性化”。其实这个模块的技术含量不高核心就是一个聚合查询加几条 if 逻辑但它在论文里的意义非常大。很多学生的毕设功能做了一大堆论文里却写不出“系统相比同类产品的改进点”这套行为追踪加推荐逻辑恰好提供了可写的差异化内容答辩时回答“这个系统有什么特别之处”这个问题就有了抓手。4. 数据库设计表怎么建、代码怎么写4.1 核心表结构设计数据库设计是论文里篇幅最长的章节之一表的划分要合理。这个项目的核心表大致如下用户表 userid、username、passwordBCrypt加密、nickname、avatar、role、status、create_time。题目表 questionid、exam_type四级/六级、category听力/阅读/写作/翻译、qtype单选/填空/主观、content、option_a、option_b、option_c、option_d、answer_key、analysis、difficulty、audio_url、create_time。试卷表 exam_paperid、title、exam_type、duration、total_score、status、create_time。试卷和题目的多对多关系用 exam_paper_question 关联表记录字段包括 paper_id、question_id、question_order、score。考试记录表 exam_recordid、student_id、paper_id、objective_score、subjective_score、total_score、status进行中/已交卷/待批改/已完成、start_time、end_time。考试记录明细表 exam_record_detail 记录每道题的作答情况id、record_id、question_id、student_answer、is_correct、score、duration。错题本表 wrong_questionid、student_id、question_id、wrong_count、last_wrong_time、status待重做/已掌握。学习日志表 study_logid、student_id、question_id、is_correct、duration_seconds、source_type、create_time。词汇表 wordid、word、phonetic、meaning、example_sentence、difficulty。词汇学习记录表 word_learning_recordid、student_id、word_id、review_status、next_review_time、create_time。学习计划表 learning_planid、student_id、plan_date、plan_content、is_finished、create_time。这套表结构基本遵循三范式但又不是死板的规范化——比如试卷和题目的关联、考试记录和明细的拆分都是典型的业务导向设计。有一个地方要特别注意尽量少用外键约束。毕设项目在论文里可以画 ER 图展示表关系但实际的建表 SQL 建议都不写 FOREIGN KEY逻辑关系在 Service 层维护。这是我在实际项目里养成的习惯外键在并发写入时会带来额外的锁开销而且删除数据时容易被约束卡住对毕设这种中低并发场景弊大于利。4.2 Spring Boot项目初始化与核心配置搭建项目建议直接用 IDEA 的 Spring Initializr 创建Group 填 com.exampleArtifact 填 exam-platform语言选 Java打包方式选 jar。依赖这里不用一次选全先选 Spring Web剩下的 MyBatis-Plus、JWT 工具包、Lombok、Hutool 等后面手动加进 pom.xml 更灵活。核心配置文件 application.yml 长这样注释里说清楚每一项的作用server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/exam_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 redis: host: localhost port: 6379 database: 0 lettuce: pool: max-active: 16 max-idle: 8 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: your-32-byte-random-secret-key expire: 86400000 logging: level: com.example.exam: debug启动类上记得加 MapperScan(com.example.exam.mapper)不加的话 MyBatis-Plus 的 Mapper 不会被扫描注册。WebSocket 的配置在这个项目里需要一个配置类关键点是要实现握手拦截器用于从 token 里解析出用户身份和自定义端点Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(examWebSocketHandler(), /ws/exam) .addInterceptors(new ExamAuthHandshakeInterceptor()) .setAllowedOrigins(*); } Bean public WebSocketHandler examWebSocketHandler() { return new ExamWebSocketHandler(); } }这里要提醒一个常见问题Spring Boot 2.4 之后 setAllowedOrigins 不再支持写 * 通配符如果前后端端口不同要么改成具体的前端地址要么用 setAllowedOriginPatterns(*)否则浏览器会报跨域错误。4.3 核心业务代码实现登录鉴权模块的代码要先讲。用户注册时密码用 BCrypt 加密存储登录成功后签发 JWT。JWT 工具类提取 token 中的用户ID和角色后续接口通过拦截器或注解校验。Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire}) private Long expire; public String generateToken(Long userId, Integer role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expire)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }登录接口的写法很常规但有两个细节要处理一是用户不存在和密码错误要用相同的提示语防止用户名被遍历二是用户状态为禁用时直接拒绝登录。PostMapping(/login) public Result login(RequestBody LoginDTO dto) { User user userService.lambdaQuery() .eq(User::getUsername, dto.getUsername()) .one(); if (user null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return Result.error(用户名或密码错误); } if (user.getStatus() ! 1) { return Result.error(账号已被禁用请联系管理员); } String token jwtUtil.generateToken(user.getId(), user.getRole()); return Result.ok(new LoginVO(token, user)); }抽题算法的实现是本项目最有“含金量”的一段代码。用注解SQL的方式直接在 Mapper 里写随机查询按难度分层抽取Select(SELECT * FROM question WHERE exam_type #{examType} AND category #{category} AND difficulty #{difficulty} ORDER BY RAND() LIMIT #{count}) ListQuestion selectRandomQuestions(Param(examType) Integer examType, Param(category) Integer category, Param(difficulty) Integer difficulty, Param(count) Integer count);Service 层根据试卷规则计算每类题型各难度的题目数量依次调用查询并合并最后洗牌。如果某个难度的题库数量不够就从相邻难度中补足保证整卷题目数量不缺public ListQuestion buildPaperQuestions(PaperRule rule) { ListQuestion result new ArrayList(); // 每个题型按 简单30% 中等50% 困难20% 分配 for (CategoryRule cr : rule.getCategories()) { int total cr.getQuestionCount(); int easy (int) Math.round(total * 0.3); int medium (int) Math.round(total * 0.5); int hard total - easy - medium; result.addAll(questionMapper.selectRandomQuestions(rule.getExamType(), cr.getCategory(), 1, easy)); result.addAll(questionMapper.selectRandomQuestions(rule.getExamType(), cr.getCategory(), 2, medium)); result.addAll(questionMapper.selectRandomQuestions(rule.getExamType(), cr.getCategory(), 3, hard)); } // 不足的从全局题库补 if (result.size() rule.getTotalCount()) { result.addAll(questionMapper.selectRandomQuestions(rule.getExamType(), null, 0, rule.getTotalCount() - result.size())); } Collections.shuffle(result); return result; }交卷判分的核心是遍历答题明细、逐个比对客观题答案并累加分数。用 MyBatis-Plus 批量保存明细判分逻辑里顺便写错题本这样一个业务闭环就完整实现了Transactional public SubmitResult submitExam(Long recordId, ListAnswerDTO answers) { ExamRecord record examRecordMapper.selectById(recordId); ListExamRecordDetail details new ArrayList(); int objectiveScore 0; for (AnswerDTO answer : answers) { Question q questionMapper.selectById(answer.getQuestionId()); boolean correct q.getAnswerKey() ! null q.getAnswerKey().equalsIgnoreCase(answer.getStudentAnswer().trim()); ExamRecordDetail detail new ExamRecordDetail(); detail.setRecordId(recordId); detail.setQuestionId(q.getId()); detail.setStudentAnswer(answer.getStudentAnswer()); detail.setIsCorrect(correct); detail.setDuration(answer.getDuration()); details.add(detail); if (correct q.getCategory() ! CategoryEnum.WRITING.getCode() q.getCategory() ! CategoryEnum.TRANSLATION.getCode()) { objectiveScore q.getScore(); } // 错题进入错题本 if (!correct) { wrongQuestionService.recordWrong(record.getStudentId(), q.getId()); } } examRecordDetailMapper.insertBatch(details); record.setObjectiveScore(objectiveScore); record.setStatus(ExamStatus.SUBMITTED.getCode()); examRecordMapper.updateById(record); return new SubmitResult(recordId, objectiveScore); }这段代码里有三个细节值得展开说第一主观题不参与客观分累加但要保存答题内容第二answer_key 为 null 的题直接判错避免空指针第三错题本的写入放在判分循环里一次遍历完成两个业务动作比分开写效率更高。成绩统计部分用 Java 8 的 Stream 分组代码简洁而且可读性好MapInteger, Long totalByCategory details.stream() .collect(Collectors.groupingBy(d - d.getQuestion().getCategory(), Collectors.counting())); MapInteger, Long correctByCategory details.stream() .filter(ExamRecordDetail::getIsCorrect) .collect(Collectors.groupingBy(d - d.getQuestion().getCategory(), Collectors.counting()));5. 从零搭建到答辩实操过程与问题排查5.1 完整开发步骤拆分一个完整可复现的推进顺序照做就能把项目顺利跑起来。第一步安装环境。JDK、MySQL、Redis、IDEA、Maven一个都不能少。建议用 Docker 装 MySQL 和 Redis省去本机配置的麻烦缺点是网络不好时镜像拉不动所以也可以直接本机装。第二步创建项目。用 Spring Initializr 生成 Spring Boot 项目选择 Web 依赖。pom.xml 里补上 MyBatis-Plus、MySQL 驱动、Lombok、JWT 工具包、Hutool用于统一返回结果和常用工具类。第三步配置数据库。执行建库建表SQL用 Navicat 可视化操作导入。先把测试数据准备好用 SQL 脚本批量插入一两百道题目和几十个单词后面的功能调试全靠这批数据。第四步代码生成。MyBatis-Plus 的代码生成器可以根据数据表反向生成实体类和 Mapper省去大量模板代码的编写时间。如果不想用代码生成器手写核心表的实体类和 Mapper 也完全可以反正表数量不多。第五步登录鉴权。先写 JwtUtil 工具类再写用户登录接口最后配置拦截器拦截 /api/** 并放行 /api/auth/**。第六步核心业务开发。按题库管理、专项练习、模拟考试、错题本、成绩统计、后台管理这个顺序推进每完成一个模块就用 Postman 或 Apifox 测试一遍接口。第七步前端开发。Vue 项目初始化后先搭路由和 Axios 拦截器再逐页开发。可以先做管理后台的题库管理页再做学生的学习页面最后做数据看板。第八步联调部署。前后端联调解决跨域和接口字段问题然后用 mvn clean package -DskipTests 打包成 jar放到服务器上用 java -jar 启动MySQL 和 Redis 保证可用即可。5.2 常见问题与排查技巧速查表这个项目开发周期长踩坑是必然的。把我知道的高频问题整理成一张速查表参照处理能省不少时间。问题现象可能原因解决方案启动报 DataSource 错误MySQL 没启动、密码错误、驱动版本不匹配确认 MySQL 服务已启动url 中 serverTimezone 配置正确MySQL 8 必须用 com.mysql.cj.jdbc.Driver前端请求返回 401/跨域前后端端口不一致拦截器拦截了登录接口CORS 没配好后端放行 /api/auth/**写 CorsConfig 配置允许的前端来源跨域用 setAllowedOriginPatterns 代替通配符JWT 解析报签名错误secret 密钥长度不够或前后端解析用的密钥不一致secret 至少32位随机字符确认 yml 中配置的 secret 和拦截器读取到的一致MyBatis-Plus 分页不生效没注册分页插件查询返回全量数据新建 MybatisPlusInterceptor Bean添加 PaginationInnerInterceptor数据库类型记得设成 MySQL批量插入报语法错误没开启批量插入的 SQL 会话MySQL 连接串加 rewriteBatchedStatementstrue或用 MP 自带的批量 saveBatch 方法WebSocket 连不上端点路径不一致握手拦截器把 token 校验失败的连接直接断了前端连接路径填 /ws/exam握手拦截器里从 query 参数取 token失败时返回 401 而不是直接关闭连接交卷后分数为0主观题也算进了客观题判断、或答案没有归一化判分前对答案做 trim toUpperCase主观题型分类单独判断不参与自动判分听力音频播放不了静态资源映射没配置配置 WebMvcConfigurer 的 addResourceHandlers把 /audio/** 映射到本地存储目录还有一个实际项目里最常见的坑我多提两句MyBatis-Plus 的逻辑删除。如果 student 表加了 TableLogic 字段做逻辑删除那所有涉及该表的查询都会被自动拼接 deleted0 条件。有一次我改造一个项目业务更新死活不生效最后发现是逻辑删除字段影响了底层SQL。毕设项目如果不需要历史数据恢复功能建议干脆别用逻辑删除直接把逻辑写在代码里少一点魔法少一点麻烦。5.3 开发排期与答辩准备建议毕设的合理周期是12周左右建议这样分配需求分析和系统设计2周技术选型和环境搭建1周数据库设计和基础代码生成1周核心功能开发4周前端页面开发3周测试、修bug和打包部署1周。如果时间紧张压缩的是前端页面的打磨时间而不是后端核心功能因为答辩时老师最关注的还是业务逻辑是否跑通、技术方案是否合理。论文的结构按照“绪论、需求分析、系统设计、系统实现、系统测试、总结”来写就符合常规要求。系统设计章节重点画数据库 ER 图、系统架构图、功能模块图如果你不会画架构图用 Office 的 SmartArt 或者 draw.io 都可以不要直接抄模板截图被看出雷同很尴尬。答辩时老师最常问的问题我有过总结为什么选 Spring Boot 而不是其他框架答快速开发、生态成熟、内置容器部署简单、社区资料丰富适合中小型业务系统。为什么用 MyBatis-Plus 不用 JPA答国内企业使用习惯、灵活的SQL控制、分页插件完善。抽题算法怎么保证试卷难度稳定答按难度分层随机抽取加数量补足。系统有什么不足这里不要心虚地说“没有”正确的回答是目前主观题依赖人工批改后续可以接入智能评分接口现在的个性化推荐基于规则实现后续可以引入协同过滤算法做更精准的推荐。这样的回答既展示了你的思考深度又给论文查重和后期扩展留下了合理空间。结尾写到这里这个项目从选题到上线的完整链路基本都覆盖了。我个人在带这类项目时最大的一个体会是做毕设千万别贪功能很多同学一开始列的需求清单能写两三页A4纸最后真正能稳定运行的不到一半。更好的做法是把用户登录、题库管理、答题判分、成绩统计这条主干链路跑通每个环节都做到能演示、能讲清楚然后再考虑加 WebSocket 推送、错题推荐这些锦上添花的东西。主干稳了答辩就有了底气亮点有了分数就有了上限。如果你正准备做这个选题照着这篇文章把环境搭起来把代码一行一行敲进去遇到问题回来看第5部分的速查表大概率能少走很多弯路。