ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSM+Flask混合架构:在线考试系统从开发到部署全解析

SSM+Flask混合架构:在线考试系统从开发到部署全解析 我先交代一下背景。这个学生在线考试系统是近几年毕业设计里被选烂了的题目但大部分人拿到的所谓源码要么缺模块要么文档对不上代码真正能一台新电脑从零跑起来、还能讲清楚原理的没几个。这篇文章我按自己的实操经验把这个题目的架构逻辑、核心实现、常见坑一次性讲透你不管是想复现做课设还是想弄明白这套技术栈怎么配合都能找到对应的东西。1. 项目拆解SSM 和 Flask 到底怎么分工的1.1 题目背后的真实需求学生在线考试系统从名字上就能拆出三层需求学生要能在线答题、自动判分、看成绩老师要能维护题库、组卷、批改主观题、查看统计管理员要管用户、管考试、管系统参数。这三层需求叠在一起就是一个典型的后台管理 在线业务系统也是面试和答辩时最容易被追问的地方。技术选型里出现了 Java SSM Flask很多人第一次看会觉得奇怪SSM 是 Spring SpringMVC MyBatis纯 Java 后端框架Flask 是 Python 的轻量 Web 框架这俩怎么凑到一块的实际项目里这种Java 主服务 Python 辅助服务的架构其实很常见核心原因就一句话让合适的工具干合适的活。SSM 负责的主体业务比如用户登录、权限拦截、题库管理、考试流程控制、成绩持久化这类业务 Java 生态非常成熟事务管理有 Spring数据库操作有 MyBatis接口开发有 SpringMVC稳定性和团队熟悉度都占优。而 Flask 在这个项目里通常承担两类事一是主观题自动评分的辅助服务比如简答题的文本相似度分析、关键词匹配打分Python 做文本处理比 Java 少写太多代码二是数据统计分析的轻量接口比如考试结果的图表数据聚合。这种混合架构后面我会专门讲通信方式。1.2 作为毕设/课设这个题目好在哪里这个题目是典型的看起来不复杂但足够撑起一篇完整论文的选题。它覆盖了软件工程的全流程需求分析、数据库设计、前后端交互、接口设计、测试部署而且功能边界清晰不容易做到一半失控。更关键的是它的可扩展性很强。基础版做完了可以加 Redis 缓存热点数据可以加 RabbitMQ 做考试消息异步处理可以加 Shiro 或 Spring Security 做更细粒度的权限控制可以加 WebSocket 做实时考试监控。这些扩展点随便挑两个写进论文的未来展望或者现场演示答辩老师都会觉得你有思考深度。从技术学习的角度这套系统把 SSM 框架最常见的使用方式——Mapper 接口、Service 层事务、Controller 接收参数、拦截器校验——全部用了一遍学完基本能应对大多数 Java Web 岗位的 CRUD 业务开发。Flask 这边则用到了路由、请求处理、JSON 交互、跨域配置Python 后端的基础也顺带练了。1.3 你拿到这套东西之后第一步该干什么我不建议你直接双击打开 IDEA 就点运行。先花 30 分钟把整个工程结构看懂比什么都重要。下载源码后你会看到大概是这样的目录exam-system/ ├── exam-admin # SSM 管理员/教师端 ├── exam-api # SSM 学生端接口服务 ├── exam-flask # Flask 辅助服务主观题评分等 ├── sql/ # 数据库初始化脚本 ├── 论文/ # 毕业设计/课程设计文档 └── README.md # 部署说明先看 sql 目录用 Navicat 之类工具把数据库脚本导进去再按 README 把 SSM 工程跑起来最后启动 Flask用接口测试工具验证一下辅助服务通不通。这个顺序能帮你把数据库—后端—辅助服务的依赖关系捋清楚后面定位问题也快。很多同学的误区是上来就看代码细节结果被 Spring 的配置文件绕晕其实配置文件只需要知道改哪里不需要每行都懂。2. 核心设计思路功能模块与数据库怎么规划才合理2.1 系统功能全景这套系统按角色可以拆成三个端每个端的功能边界必须清晰否则代码写着写着就变成一个大杂烩。下面是我觉得最合理的基础功能划分角色核心功能关键备注学生在线考试、我的考试记录、查看成绩、错题回顾、个人信息维护考试要有计时断网或刷新要能续答教师题库管理增删改查/批量导入、组卷策略、人工批改主观题、成绩导出、统计图表组卷支持手工选题和随机抽题两种管理员学生/教师账号管理、班级/课程管理、考试公告管理、系统参数配置账号初始化和重置密码是高频功能这里面最容易做浅的是错题回顾。很多人的实现就是简单查一下答错的题但其实它应该记录用户当时的答案、正确答案、题目解析、所属知识点这样学生回看的时候才有复习价值。做的时候别怕多建一张表后面我会讲到具体设计。2.2 数据库设计一张好表胜过十层 Service在线考试系统的数据库是整个项目的地基表设计不合理后面写 SQL 会写到怀疑人生。核心表我梳理成这几张用户相关用户表sys_user要冗余一个role字段1 学生、2 教师、3 管理员别单独拆三张表因为登录验证逻辑是统一的拆了反而每次都要 join。如果有班级概念再加class_id外键没有就不加别为了扩展硬建关联表。题库与试卷相关试题表question的字段值得认真设计question_type区分单选1、多选2、判断3、简答4options用 JSON 字符串存选项解析在 Java 里做answer存标准答案判断题存 T/F选择题存 A/B/C/D多选存 A,C,Danalysis存题目解析difficulty存难度系数方便后面随机组卷按难度比例抽题。试卷表paper和试卷题目关联表paper_question是分开的。paper存考试名称、总分、时长、考试开始/结束时间、状态paper_question存试卷 ID、题目 ID、每题分值。这样同样一份试卷可以被多个考试引用组卷逻辑也清晰——先建 paper再往 paper_question 里按策略插记录。考试记录与答题明细这是最核心的两张表。考试记录表exam_record要记录学生 ID、试卷 ID、开始时间、交卷时间、总得分、考试状态1 进行中、2 已交卷、3 超时自动交卷、4 已批改。答题明细表answer_detail记录每次作答的题目 ID、用户答案、是否正确、得分、批改状态。这两张表属于高频读写索引要建好建议联合索引(record_id, question_id)。建表的时候有一个很容易踩的坑不要把用户答案和正确答案直接拼在一个字段里看起来省事但统计正确率、做错题分析的时候SQL 会写得极其痛苦。我见过一个二手项目就是这种设计最后想统计某道题的错误率不得不写一段极度扭曲的字符串处理逻辑纯属自找麻烦。2.3 考试流程一个状态机撑起整个核心业务在线考试最核心的业务流程我习惯用状态来理解考试状态 0 未开始 - 1 进行中 - 2 已交卷 - 3 已批改 \- 4 超时自动交卷 - 3 已批改学生点击开始考试时后端做两件事创建一条exam_record状态置为进行中记录start_time然后把考题列表查出来返回给前端。前端拿到题目后在本地维护答题缓存并开启倒计时。这里有一个很重要的设计决策答案要定时自动保存不能依赖用户手动点击保存下一题。我的做法是前端每 30 秒把当前已答内容通过一个自动保存接口推到后端这样就算学生中途浏览器崩了重新进入还能续答不会丢数据。交卷分主动和被动两种。主动交卷由用户点击触发后端校验未答题目数如果考试有禁止漏题交卷规则计算客观题得分如果有主观题状态先置为已交卷总分要等教师批改完才最终确定。被动交卷是时间到了前端倒计时归零触发交卷同时后端也要做一层时间校验——别信任前端传的时间要以服务器时间为准不然学生改本地时间就能无限考试。这个状态机是我强烈建议你用if/else或者switch老老实实写在 Service 层的东西别到处散落判断逻辑。代码里每个状态流转都做一个统一入口比如submitExam()方法里先查考试记录状态只有进行中才允许往已交卷流转非法状态直接抛业务异常。这个设计在答辩时讲出来非常加分因为体现了异常数据的兜底思维。3. 实操核心SSM 主服务的关键实现与避坑3.1 环境准备JDK、Maven、Tomcat 和 MySQL 的版本匹配这个项目对新手最不友好的就是环境版本不匹配会导致各种莫名其妙的问题。先说结论我调试过多台电脑这套组合最稳JDK 1.8即 JDK 8虽然 Oracle 早已停止免费商用更新但毕业设计和课设环境用它最兼容Maven 3.6.3 或 3.8.x别用最新的 4.x部分插件会不兼容Tomcat 8.5和 JDK 8 搭配最稳妥MySQL 5.7 或 8.0如果本地是 8.0注意驱动要用com.mysql.cj.jdbc.Driver且连接 URL 需要加时区参数IDEA 2020 以上版本都行社区版也能跑JDK 环境变量配置是每次必踩的坑。JAVA_HOME 一定要配到 JDK 安装目录的上一级比如C:\Program Files\Java\jdk1.8.0_202而不是C:\Program Files\Java\jdk1.8.0_202\bin。Path 里要追加%JAVA_HOME%\bin。配置完在命令行敲java -version验证注意如果出现的是 1.8 但你的项目想要 11那就是系统里装了多个 JDKPath 顺序不对把高版本的路径从 Path 里临时挪掉再试。Maven 的本地仓库和阿里云镜像也建议提前配好。默认中央仓库在国内下载依赖非常慢改settings.xmlmirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror这段配置能帮你省掉大量等下载的时间。还有一个细节IDEA 里 File - Settings - Build Tools - Maven要确保 User settings file 指向你改过的settings.xml否则改了也是白改。3.2 用户登录与权限控制的落地用户登录这块最基础但必须正确实现的是密码加密别用明文存数据库答辩时被问到会很尴尬。我的做法是用 MD5 加盐盐值可以用用户名或者一个固定密钥拼接虽然 MD5 不算强加密但用于毕设演示足够也能体现安全意识。如果你想让代码更值钱一点可以直接引入 Spring Security 的 BCryptPasswordEncoder这也是一个可以写进论文的亮点。登录流程是这样的前端提交 username password - Controller 接收参数 - Service 查用户表 - 校验状态是否被禁用- MD5 比对密码 - 生成 token 存入 Redis/内存 - 返回用户信息和 token会话管理这里有两种方案。方案一是用传统的 HttpSession简单但前后端分离时跨域会有 Session 失效问题。方案二是用 token登录后生成一个唯一 token返回给前端前端后续请求都带着 token后端写一个拦截器统一校验。对于在线考试这种需要长时间保持登录、且可能前后端分离部署的系统我推荐用 token 方案。拦截器配置在 SpringMVC 里是这么写的public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从请求头获取 token String token request.getHeader(Authorization); if (token null || .equals(token)) { response.setStatus(401); return false; } // 验证 token这里调用 Redis 或内存中校验 Object userId TokenStore.getUserId(token); if (userId null) { response.setStatus(401); return false; } // 把用户信息塞到 request 属性里方便 Controller 取用 request.setAttribute(userId, userId); return true; } }然后在 springmvc 配置里注册拦截器并设置好哪些路径放行比如登录接口、注册接口哪些路径必须拦截比如考试接口、管理接口。这里最容易出错的是拦截路径写错导致静态资源也被拦要单独放行/static/**、/js/**、/css/**等资源路径。我见过一个同学的系统页面样式全部失效排查半天发现就是拦截器把所有 CSS/JS 都拦了直接返回 401。3.3 在线答题、自动交卷和超时处理在线答题的接口设计成两个即可获取试题列表接口、提交单题答案接口。第一个接口在进入考试时调用返回试卷的所有题目前端根据题型渲染不同组件第二个接口由前端定时调用把当前题目 ID 和用户答案传给后端后端更新answer_detail表。这里有一个值得注意的点试卷题目在考试期间不应该允许被修改。也就是说题目快照要么在组卷时就固化到paper_question表要么在考试接口里直接从 paper_question 查而不是从 question 表实时查。否则考试进行到一半老师改了题目描述学生端的题目也会跟着变这种情况在演示现场出现一次就是灾难。组卷时固化题目信息题干、选项、答案、分值都冗余一份到 paper_question虽然会多占一点存储但保证了考试的一致性和历史可回溯性。这是标准的空间换时间/可靠性思路。交卷处理是后端业务里最需要考虑并发的地方。同一时间可能有几百个学生点交卷如果每个请求都去数据库里把所有答案查出来再逐题判分数据库压力会很大。我的优化做法是只查当前 record 对应的 answer_detail 列表在内存里完成判分逻辑然后批量更新成绩。客观题判分很简单遍历答案和标准答案比对对的多选题按全对才得分处理判断题也一样。主观题标记为待批改状态不算入自动得分最终总分等老师批改后重新计算并更新。超时自动交卷这里我提醒一个前端小技巧倒计时不要只依赖 setInterval还要记录一个服务器时间基准。页面加载时从后端拿一个当前服务器时间戳然后在前端做差值倒计时这样就算学生修改本地系统时间倒计时的剩余时间也不会被影响。时间到了前端自动调用交卷接口同时后端在交卷接口里也做一次服务器时间判断超过start_time duration就直接强制交卷。前端的倒计时更多是用户体验后端的时间判断才是最终保障。3.4 成绩管理与数据统计怎么实现成绩发布之后还要做三个看似简单但经常被忽略的功能成绩单查看、班级成绩统计、错题回顾。成绩单查看就是查 exam_record 表按考试倒序排列这个没难度。班级成绩统计是典型的聚合查询SELECT AVG(total_score) AS avg_score, MAX(total_score) AS max_score, MIN(total_score) AS min_score, COUNT(*) AS exam_count FROM exam_record WHERE paper_id #{paperId} AND status 3错题回顾的实现逻辑是查answer_detail表找到is_correct 0的记录关联question表查出题干、正确答案、用户答案、解析。如果题目本身绑定了知识点字段可以再做一层按知识点分组的错题聚合这个功能做到位论文里可以写基于知识点的个性化错题推荐整个系统的高度就不一样了。教师批改主观题是管理端的高频操作。列表显示待批改记录点击进入批改页左侧题目右侧学生答案老师输入分数保存。这个模块要注意的是事务控制更新answer_detail的得分和批改状态同时重新计算该学生该试卷的总分并更新exam_record这两个操作必须在同一个事务里否则会出现明细改了、总分没变的数据不一致问题。4. Flask 辅助服务那些 Java 搞起来费劲的事4.1 Flask 在这个项目里到底负责什么如果你拿到源码后发现 Flask 工程很小别意外。它在这个项目里就是辅助角色但又是那个能让系统讲故事的亮点模块。我见过两种合理的用法第一种是主观题自动评分。简答题没法像选择题那样精确比对答案Flask 端提供接口接收学生的答案和标准答案返回一个 0 到 1 的相似度分数Java 端再把这个分数映射成实际得分。Python 做文本相似度处理优势太大了一个 jieba 分词加一个余弦相似度几十行代码就能出一个可用的评分结果用 Java 写光分词工具类就要折腾半天。第二种是考试成绩数据的图表分析。Java 端虽然也能做统计但 Flask 配合 Pandas 做数据聚合和格式化输出会更灵活。前端需要班级成绩分布直方图、分数段人数统计、难度系数分析等数据时Flask 返回 JSON前端直接用 ECharts 渲染。这种方式把分析逻辑和后端主业务剥离开职责清晰也方便后期单独扩展算法模型。4.2 用 Flask 实现一个简单的文本相似度评分接口我以主观题评分为例展示 Flask 端怎么实现一个可用的接口。依赖就三个flask、jieba、sklearn用 TfidfVectorizer 计算相似度。from flask import Flask, request, jsonify import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity app Flask(__name__) def tokenize(text): # 用 jieba 分词并用空格连接适配 TfidfVectorizer return .join(jieba.cut(text)) app.route(/api/similarity, methods[POST]) def similarity(): data request.get_json() student_answer data.get(student_answer, ) standard_answer data.get(standard_answer, ) if not student_answer or not standard_answer: return jsonify({code: 400, msg: 参数不能为空}) # 如果标准答案包含关键词打分规则走关键词匹配 keywords data.get(keywords, []) if keywords: hit_count sum(1 for kw in keywords if kw in student_answer) ratio hit_count / len(keywords) return jsonify({code: 200, score: ratio}) # 用 TF-IDF 余弦相似度 corpus [tokenize(student_answer), tokenize(standard_answer)] vectorizer TfidfVectorizer() tfidf_matrix vectorizer.fit_transform(corpus) similarity_score float(cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])[0][0]) # 做一次映射保证相似度在 0-1 之间且合理 return jsonify({code: 200, score: round(similarity_score, 4)}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)注意几个细节。debugFalse很重要生产/答辩部署不要开 debug 模式否则别人可以远程执行代码安全隐患。跨域问题要在 Flask 端解决不然前端浏览器直接调会报跨域错误加两行from flask_cors import CORS CORS(app)如果不想引入 flask-cors 依赖也可以自己在 after_request 里加响应头但既然用 Flask 就直接用这个最省事的库。4.3 SSM 怎么调 FlaskRestTemplate 与 HttpClientJava 端调用 Flask 服务我推荐用 Spring 自带的RestTemplate不需要额外引入包。写一个辅助类Component public class AiScoreClient { private final RestTemplate restTemplate new RestTemplate(); // Flask 服务地址建议配置到配置文件里 Value(${flask.similarity.url}) private String similarityUrl; public double getSimilarity(String studentAnswer, String standardAnswer) { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); MapString, String body new HashMap(); body.put(student_answer, studentAnswer); body.put(standard_answer, standardAnswer); HttpEntityMapString, String request new HttpEntity(body, headers); ResponseEntityMap response restTemplate.postForEntity(similarityUrl, request, Map.class); Map result response.getBody(); if (result ! null Integer.valueOf(200).equals(result.get(code))) { Object score result.get(score); return score null ? 0.0 : Double.parseDouble(score.toString()); } return 0.0; } }这里要说一个真实的教训Flask 接口必须做好异常兜底。如果 Flask 服务没启动Java 端调用会抛连接异常如果不捕获会导致整个交卷流程失败。所以调用的地方要 try-catch捕获异常后降级处理——比如返回一个默认相似度 0.6或者根据关键词命中数量直接打分而不是让整个考试提交挂掉。系统设计里这叫故障隔离答辩时能主动讲出来比背十个八股文都有用。Flask 和 SSM 的通信方式还有一个备选方案如果不想让 Java 端主动调 Python可以反过来让 Flask 监听数据库变化发现待批改记录就自动处理然后写回结果。这种异步解耦的方式适合更复杂的系统但不建议毕业设计这么做因为处理不好会出现数据竞争而且讲起来也更难让老师理解。同步调用的直白方式在演示阶段最稳妥。5. 从源码到论文调试文档和开题报告怎么组织5.1 源码里通常会附带哪些文档每份怎么用这类项目完整交付一般包含源码、LW论文、调试文档三件套。调试文档不是摆设它是你遇到环境跑不起来时的第一排查手册。我建议你拿到手先做一件事照着调试文档的步骤从零把环境重新搭一遍看哪些描述是准确的哪些是有遗漏的。很多二手项目的调试文档是复制粘贴的步骤对不上当前代码你要自己重新写一份排坑笔记。论文LW的组织结构通常就是软件工程的标准八章绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试、总结与展望、参考文献。这里有一个最容易拿高分的设计不要把相关技术介绍写成名词解释。比如介绍 SSM 框架千万别写Spring 是一个轻量级开源框架这种话老师看得都想打哈欠。正确写法是Spring 在本系统中承担 Bean 管理和事务管理职责例如在考试交卷业务中通过声明式事务确保分数明细和总分更新的一致性。把一个具体业务和技术特性挂钩这叫技术选型落地点是论文加分项。5.2 论文里需求分析和系统设计怎么写才不空洞需求分析章节最容易写成流水账。我的建议是先用用例图把三个角色的行为画清楚再用功能需求列表 每个模块的关键业务规则来细化。比如在线考试模块要写清楚这些规则考试只能进一次进入后倒计时不可暂停超时自动交卷考试中途页面刷新可恢复未提交答案连续两次交卷请求要幂等处理第二次直接返回成功不能报考试不存在。这些业务规则就是你代码里那些 if/else 的论文表达写清楚了证明你真的做过系统不是纯抄。系统设计章节除了数据库 ER 图这是热搜词里都出现的说明是高频考点还要画系统架构图。别画那种网上随便找的高大上分布式架构就画你真实部署的样子浏览器 - TomcatSSM- MySQLTomcat 通过 HTTP 调 FlaskFlask 可以直连同一个数据库。配合文字说明每个组件的作用和它们之间的通信协议反而显得真实可靠。有人为了显得厉害画了 Nginx、Redis、消息队列结果源码里一个都没有答辩追问三句就露馅。5.3 调试文档里一定要记录的三个实战坑我在帮人跑这种项目时遇到最多的问题就三类调试文档里必须有针对性说明第一类是IDEA 中 Lombok 插件不生效。这个项目如果用 Lombok 简化实体类新电脑经常会报java: You arent using a compiler supported by lombok, so lombok will not work with your project。原因是 IDEA 内置编译器版本和 Lombok 版本不兼容。解决方案通常是把 Lombok 依赖版本降到 1.18.20 左右或者在 IDEA 设置里启用 Annotation Processing。这类问题写进调试文档可以帮后面的人省几个小时。第二类是MySQL 8.0 连接报错。连接 URL 里必须写serverTimezoneAsia/Shanghai驱动类要写com.mysql.cj.jdbc.Driver否则会报时区异常或者 ClassNotFound。调试文档里要明确列出 MySQL 5.7 和 8.0 两套配置写法。第三类是Tomcat 端口占用。java.net.BindException: Address already in use是最常见的启动失败原因。解决办法是把 Tomcat 默认端口从 8080 改成 8088或其他同时改 SpringMVC 里前端请求的 baseURL 里的端口号。这个看似简单的问题曾经拦住了一个同学整整两天因为后端端口改了但前端没改页面一直报 404。6. 常见问题与排查技巧实录6.1 环境类项目启动前最先遇到的坑问题原因解决办法java 不是内部或外部命令JDK 没装或环境变量没配好检查 JAVA_HOME 和 Path重启命令行窗口再试Maven 下载依赖极慢或失败默认仓库在国外换成阿里云镜像配置 settings.xmlTomcat 启动秒退端口被占用或 JDK 版本不对改用 8088 端口确认 JDK 是 1.8Lombok 报错IDEA 编译器版本与 Lombok 不兼容降级 Lombok 到 1.18.20开启 Annotation Processing数据库连接 Access denied密码不对或 root 不允许远程用安装 MySQL 时设置的密码检查连接 URL 用户名密码环境问题占了这类项目 60% 以上的报错所以调试文档最重要的价值就在这里。我个人的经验是每装一个新环境就先把数据库脚本跑通。数据库通不了后面全部免谈。很多同学栽在代码明明没错就是连不上数据库这种问题上排查到最后发现是 MySQL 服务根本没启动。6.2 代码与业务逻辑运行时报错怎么定位运行期最常见的异常是NullPointerException和SQLSyntaxErrorException。前者多发生在从数据库查到对象后直接取属性没判空比如student.getGrade()在 student 为 null 时直接炸。后者一般是 SQL 语句里的表名或字段名拼错注意 MySQL 的关键字比如order、desc、key要加反引号。我的排查套路很固定先看控制台异常堆栈的第一行定位到哪个类的哪个方法再往前找Caused by那个才是根本原因最后看 SQL 语句把 SQL 复制到 Navicat 里手动执行一遍。90% 的 MyBatis 报错都是 SQL 拼接问题在数据库工具里一跑就现原形。还有一个排查技巧MyBatis 的日志要打开在application.properties里配置mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl这样控制台会打印完整 SQL 和参数排查问题效率直接翻倍。6.3 演示和答办现场常见的意外场景答辩演示是一次实弹演习有几个场景你最好提前模拟场景一学生提交答卷时 Flask 服务突然挂了。因为主观题评分请求恰好打不到 Flask整个交卷接口报错页面白屏。这个问题的设计解法还是前面说的异常降级。演示前也一定要确认 Flask 服务是启动状态curl http://localhost:5000/api/similarity测一下通不通。很多人在演示桌上发现 Flask 没启动是因为 Flask 是在自己电脑的后台跑的换了一台演示机器就忘了。场景二两个学生同时交卷成绩串了。如果你的并发处理不当A 学生的答卷可能会被 B 学生的学习成绩覆盖。原因是代码里用了全局变量保存了当前考试记录 ID在并发场景下互相污染。解决方案是所有用户相关的数据都从当前登录用户上下文拿不要用静态变量缓存。这个坑我在真实项目里踩过查了一晚上才定位到。场景三导出成绩 Excel 时中文文件名乱码。这个是因为 HTTP 响应头里文件名编码没设置。用下面这段代码能解决String fileName URLEncoder.encode(考试成绩单.xls, UTF-8).replaceAll(\\, %20); response.setHeader(Content-Disposition, attachment; filename*UTF-8 fileName);如果老师要求现场演示导出而乱码观感很差。提前准备一张导出的截图或者实际跑通一次都能避免这种尴尬。6.4 我整理的一份避坑清单最后把我这些年调试类似项目攒下来的经验浓缩成一份清单每一条都是真实踩过或者帮人排查过的实体类的日期字段用java.util.Date还是LocalDateTime取决于 MyBatis 版本混用会出现类型转换异常整张表统一到底用哪种。前端页面用的/api路径和拦截器放行的路径必须一一对应否则一会儿能访问一会儿 401最烦的就是这种间歇性问题。每次改完 Java 代码记得用mvn clean package重新打一次包再启动不然 Tomcat 跑的还是旧 class改了没生效心态容易崩。Flask 服务的端口和 Java 服务端口别重合一台电脑上默认 5000 和 8080 不冲突但如果你另起了其他服务占了端口记得用netstat -ano | findstr 5000查看占用。如果你需要把整个项目打包发给别人MySQL 初始化脚本要附带CREATE DATABASE IF NOT EXISTS exam_system DEFAULT CHARACTER SET utf8mb4;这样的语句保证对方导入不会因为字符集或者库不存在而失败。论文里的测试数据要造得真实一点别全是 admin/123456可以加几个正常的中文用户名、真实的答案文本截图也更好看。写到这里这套系统的主体实现思路基本就有了SSM 把业务吃下来Flask 做辅助计算数据库表设计撑起整个流程文档负责把设计讲明白。如果你真的照着这个思路去把源码过一遍、把每张表和每个接口的对应关系理清楚答辩时候老师无论追问技术选型、业务流程还是异常处理你都不会慌。我自己的体会是毕业设计这类项目最大的价值不在于代码量多少而在于你能不能把每个技术选择背后的理由讲清楚这也是我想写这篇长文的原因。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进