简介:这是一套基于SSM框架(Spring+SpringMVC+MyBatis)、JSP前端与MySQL数据库开发的在线考试系统,核心亮点在于集成LD(Levenshtein Distance)字符串编辑距离算法实现编程题自动判分,有效支撑代码相似度比对与基础逻辑验证,专为本科毕业设计、高校课程设计及中小型教学类项目开发量身打造。资源包共685个文件,涵盖73个Java业务逻辑类、47个JSP页面、32个XML配置文件、55个JS交互脚本及153个GIF动图(多用于后台管理界面操作示意),辅以101个Jar依赖库和73个编译后Class文件,结构完整、模块清晰,压缩包大小为57.77MB。目前已有67人学习下载,资源经严格测试可直接运行,提供含学生/科目管理、在线组卷、限时考试、自动阅卷(含编程题LD比对)、成绩统计等全链路功能源码,并附带ASP/ASHX等兼容性接口文件,便于拓展Web服务集成与跨平台调用。
1. 为什么毕业设计选“SSM+JSP+MySQL+LD算法的在线考试系统”能稳过答辩、还能真跑起来?
不是所有毕设都叫“能上线”。我带过三届计科/软工本科生做毕业设计,翻车最多的是两类:一类是“SpringBoot+Vue”看着时髦,结果前端跨域调不通、部署到Tomcat报404、答辩现场连登录页都打不开;另一类是“纯理论判分”,说“用Levenshtein Distance算代码相似度”,但没写一行判分逻辑,答辩老师问“你比对的是token还是AST?空格和换行怎么处理?”,当场哑火。而这个标题——基于SSM+JSP+MySQL+LD算法实现的在线考试系统+编程题自动判分——恰恰卡在“有技术深度、有工程闭环、有可演示亮点”的黄金交叉点上:SSM(Spring+SpringMVC+MyBatis)是Java Web最扎实的分层架构,JSP在Tomcat里零配置就能渲染页面,MySQL支撑高并发小事务毫无压力,最关键的是,LD算法(Levenshtein Distance)不是PPT里的名词,而是你能在JudgeService.java里亲手写出getEditDistance(String s1, String s2)并调试通过的真实判分引擎。它不追求AI大模型式炫技,但能让你在答辩时打开后台管理页,点开某道编程题的判分日志,指着distance=3, similarity=0.85说清楚:“学生答案比标准答案多写了两个空格、少了一个分号,所以扣1分”。适合想踏实做、能动手调、靠细节赢过答辩的开发者——尤其适合用IDEA新建传统Web项目、不碰Docker和Nginx、只靠一台Windows笔记本就能从零搭起全链路的同学。
2. 搭建SSM+JSP环境:绕开IDEA新建JSP项目的5个经典陷阱
2.1 用IDEA创建“正宗”SSM Web项目:不是选Spring Initializr!
很多同学第一步就错:在IDEA里点“New Project”→“Spring Initializr”,生成一个SpringBoot项目,再硬塞JSP进去。结果发现<%@ page contentType="text/html;charset=UTF-8" %>报红,webapp/WEB-INF/web.xml被忽略,@Controller返回的JSP路径死活404。SSM+JSP必须走传统Web Application路线。正确步骤如下:
File → New → Project → Java Enterprise → Web Application(勾选Create web.xml)- 在
Project SDK选你装好的JDK 1.8(注意:JDK 11+默认不支持JSP,毕设务必用JDK 1.8!) - 点击
Next,不要勾选任何框架模板,手动配SSM - 完成后,在
Project Structure → Modules → Dependencies里添加:spring-webmvc-5.3.39.jarmybatis-3.4.6.jarmysql-connector-java-5.1.47.jarjstl-1.2.jar(JSP标签库,否则<c:if>报错)
提示:所有jar包统一放
webapp/WEB-INF/lib/下,别放lib文件夹或Maven依赖里——JSP编译器只认WEB-INF/lib。
2.2 配置web.xml:让SpringMVC接管JSP请求的3个生死节点
webapp/WEB-INF/web.xml是整个项目的“宪法”,漏掉任意一行,JSP就是白纸。以下是精简但必填的配置:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 1. Spring容器监听器:加载applicationContext.xml --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 2. SpringMVC前端控制器:拦截所有*.do请求 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <!-- 3. JSP中文乱码过滤器:解决表单提交中文变?? --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>关键参数说明:
<url-pattern>*.do</url-pattern>:强制所有业务请求走.do后缀(如/exam/start.do),避免与静态资源冲突;<param-value>classpath:applicationContext.xml</param-value>:Spring核心配置必须放在src/main/resources/下,且文件名严格为applicationContext.xml;<param-value>UTF-8</param-value>:forceEncoding=true确保GET/POST请求全部UTF-8解码,否则学生姓名“张三”存进MySQL变成“å¼ ä¸‰”。
2.3 SSM三层结构落地:Controller→Service→Mapper的最小可行代码链
光有配置不行,得让代码真正跑通。以下是最小闭环示例(以“获取考试列表”为例):
Controller层(ExamController.java):
@Controller public class ExamController { @Autowired private ExamService examService; @RequestMapping("/exam/list.do") public String listExam(Model model) { List<Exam> exams = examService.getAllExams(); model.addAttribute("exams", exams); // 传给JSP return "exam/list"; // 对应 webapp/WEB-INF/jsp/exam/list.jsp } }逻辑说明:
@Controller标识这是MVC控制器;@RequestMapping绑定URL;model.addAttribute()把数据塞进JSP上下文;return "exam/list"是逻辑视图名,SpringMVC会自动拼成/WEB-INF/jsp/exam/list.jsp。
Service层(ExamService.java & ExamServiceImpl.java):
// 接口 public interface ExamService { List<Exam> getAllExams(); } // 实现类 @Service public class ExamServiceImpl implements ExamService { @Autowired private ExamMapper examMapper; // MyBatis Mapper接口 @Override public List<Exam> getAllExams() { return examMapper.selectAll(); // 调用Mapper方法 } }参数说明:
@Service让Spring管理该Bean;@Autowired自动注入ExamMapper;Service层不直接操作数据库,只协调业务逻辑。
Mapper层(ExamMapper.java & ExamMapper.xml):
// Mapper接口 public interface ExamMapper { List<Exam> selectAll(); }<!-- ExamMapper.xml --> <mapper namespace="com.example.mapper.ExamMapper"> <select id="selectAll" resultType="com.example.entity.Exam"> SELECT id, title, start_time, duration FROM exam </select> </mapper>关键点:
namespace必须与Mapper接口全限定名一致;resultType指向实体类(需有对应getter/setter);SQL写在XML里,不写在Java中——这是MyBatis的核心约定。
3. MySQL建库建表:按考试系统真实场景设计的5张核心表
3.1 数据库初始化:字符集、排序规则、用户权限三步到位
别用phpMyAdmin点点点。毕设MySQL必须命令行初始化,否则答辩时老师问“你用的什么字符集”,答不上来直接扣分。执行以下命令:
# 1. 登录MySQL(假设root密码为123456) mysql -u root -p123456 # 2. 创建数据库,显式指定字符集(关键!) CREATE DATABASE online_exam DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 3. 创建应用专用用户(比root安全,答辩时能体现工程素养) CREATE USER 'exam_user'@'localhost' IDENTIFIED BY 'exam123'; GRANT SELECT,INSERT,UPDATE,DELETE ON online_exam.* TO 'exam_user'@'localhost'; # 4. 刷新权限 FLUSH PRIVILEGES;注意:
utf8mb4支持emoji和生僻字(如学生姓名“䶮”),utf8在MySQL里实际是utf8mb3,不支持4字节Unicode;exam_user权限仅限online_exam库,符合最小权限原则。
3.2 五张核心表DDL:覆盖考试、题目、学生、作答、判分全链路
考试系统不是CRUD练习,表结构要反映真实业务约束。以下是经答辩验证的5张表(含注释):
-- 1. 考试主表(exam) CREATE TABLE exam ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT '考试标题', description TEXT COMMENT '考试说明', start_time DATETIME NOT NULL COMMENT '开始时间', duration INT NOT NULL COMMENT '持续分钟数,如90', status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2. 题目表(question),支持单选、多选、编程题混合 CREATE TABLE question ( id INT PRIMARY KEY AUTO_INCREMENT, exam_id INT NOT NULL COMMENT '所属考试ID', type TINYINT NOT NULL COMMENT '题型:1-单选,2-多选,3-编程题', content TEXT NOT NULL COMMENT '题目描述(含代码片段)', options JSON COMMENT '选项JSON:{"A":"int","B":"String"},编程题为空', answer VARCHAR(500) NOT NULL COMMENT '标准答案:单选存"A",编程题存标准代码', score INT DEFAULT 10 COMMENT '分值', FOREIGN KEY (exam_id) REFERENCES exam(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 3. 学生表(student) CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL COMMENT '学号', password VARCHAR(100) NOT NULL COMMENT 'BCRYPT加密密码', name VARCHAR(50) NOT NULL COMMENT '真实姓名', class_name VARCHAR(50) COMMENT '班级', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 4. 作答记录表(submission) CREATE TABLE submission ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL COMMENT '学生ID', question_id INT NOT NULL COMMENT '题目ID', answer TEXT NOT NULL COMMENT '学生提交的答案(编程题为完整代码)', submit_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '提交时间', score DECIMAL(5,2) DEFAULT 0.00 COMMENT '判分得分', is_judged TINYINT DEFAULT 0 COMMENT '是否已判分:0-未判,1-已判', FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (question_id) REFERENCES question(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 5. 判分日志表(judge_log)——答辩时展示判分过程的关键表 CREATE TABLE judge_log ( id INT PRIMARY KEY AUTO_INCREMENT, submission_id INT NOT NULL COMMENT '关联作答ID', standard_code TEXT NOT NULL COMMENT '标准代码(脱敏存储)', student_code TEXT NOT NULL COMMENT '学生代码(脱敏存储)', edit_distance INT NOT NULL COMMENT 'LD算法计算出的编辑距离', similarity DECIMAL(5,4) NOT NULL COMMENT '相似度 = 1 - distance/max(len1,len2)', judge_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (submission_id) REFERENCES submission(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;设计理由:
question.answer存标准代码而非ID:编程题答案就是代码本身,无需额外表关联;submission.answer用TEXT类型:学生可能提交上百行代码;judge_log独立建表:答辩时可直接SELECT * FROM judge_log WHERE submission_id=123展示判分依据;ON DELETE CASCADE:删除考试时自动清空题目和作答,避免脏数据。
3.3 连接池配置:Druid比C3P0更稳,且能监控SQL慢查询
别用DriverManager.getConnection()硬编码。毕设必须用连接池,否则并发一高就报Too many connections。在applicationContext.xml中配置Druid:
<!-- Druid数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/online_exam?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/> <property name="username" value="exam_user"/> <property name="password" value="exam123"/> <property name="initialSize" value="5"/> <property name="minIdle" value="5"/> <property name="maxActive" value="20"/> <property name="maxWait" value="60000"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 1"/> <property name="testWhileIdle" value="true"/> <property name="filters" value="stat,wall"/> </bean>参数说明:
maxActive=20足够应付毕设演示(50人并发);testWhileIdle=true保证空闲连接有效性;filters="stat,wall"开启Druid监控页(访问http://localhost:8080/druid看SQL执行时间)——答辩时打开这个页面,老师立刻信服你懂性能。
4. LD算法实现编程题自动判分:从字符串比对到生产级鲁棒性改造
4.1 基础LD算法:手写动态规划,理解比调包更重要
网上一堆apache-commons-text的LevenshteinDistance,但毕设答辩时老师会问:“你改过源码吗?空格和换行怎么处理?”。必须自己写,且要能讲清每行逻辑。以下是经过答辩验证的JudgeService.java核心方法:
@Service public class JudgeService { /** * 计算两段代码的Levenshtein Distance * @param s1 标准代码 * @param s2 学生代码 * @return 编辑距离 */ public int getEditDistance(String s1, String s2) { if (s1 == null || s2 == null) return Integer.MAX_VALUE; if (s1.equals(s2)) return 0; // 预处理:去除首尾空格,统一换行符为\n(关键!) s1 = normalizeCode(s1); s2 = normalizeCode(s2); int len1 = s1.length(); int len2 = s2.length(); // dp[i][j] 表示 s1[0..i-1] 与 s2[0..j-1] 的最小编辑距离 int[][] dp = new int[len1 + 1][len2 + 1]; // 初始化:s1变空串需删i次,s2变空串需删j次 for (int i = 0; i <= len1; i++) dp[i][0] = i; for (int j = 0; j <= len2; j++) dp[0][j] = j; // 状态转移 for (int i = 1; i <= len1; i++) { for (int j = 1; j <= len2; j++) { if (s1.charAt(i - 1) == s2.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1]; // 字符相同,不操作 } else { dp[i][j] = Math.min( Math.min(dp[i - 1][j] + 1, // 删除s1[i-1] dp[i][j - 1] + 1), // 插入s2[j-1] dp[i - 1][j - 1] + 1 // 替换s1[i-1]为s2[j-1] ); } } } return dp[len1][len2]; } /** * 代码标准化:去除多余空格、统一换行、忽略连续空白符 */ private String normalizeCode(String code) { if (code == null) return ""; // 1. 统一换行符为\n code = code.replace("\r\n", "\n").replace("\r", "\n"); // 2. 去除每行首尾空格,合并连续空白符为单个空格 String[] lines = code.split("\n"); StringBuilder sb = new StringBuilder(); for (String line : lines) { line = line.trim().replaceAll("\\s+", " "); if (!line.isEmpty()) { sb.append(line).append("\n"); } } return sb.toString().trim(); } }逻辑说明:
normalizeCode()是成败关键——学生复制代码时可能带IDE自动缩进、多余空行、制表符\t,不处理会导致distance虚高;dp[i][j]二维数组是动态规划标准写法,空间复杂度O(mn),毕设数据量小完全可接受。
4.2 相似度计算与分数映射:让LD结果变成可解释的“扣分项”
LD算法输出的是整数距离,但老师要看到“为什么扣2分”。需转换为百分制分数,并给出可读日志:
/** * 根据编辑距离计算相似度,并映射为得分 * @param standard 标准代码 * @param student 学生代码 * @param fullScore 题目满分 * @return 判分结果对象 */ public JudgeResult judgeCode(String standard, String student, int fullScore) { int distance = getEditDistance(standard, student); int maxLength = Math.max(standard.length(), student.length()); double similarity = maxLength == 0 ? 1.0 : 1.0 - (double) distance / maxLength; // 分数映射:相似度≥0.95得满分,0.8~0.95线性扣分,<0.8得0分 double score; if (similarity >= 0.95) { score = fullScore; } else if (similarity >= 0.8) { score = fullScore * (0.2 + (similarity - 0.8) * 4); // 0.8→0.2*full, 0.95→1.0*full } else { score = 0.0; } return new JudgeResult(distance, similarity, Math.round(score * 100.0) / 100.0); } // 判分结果封装类 public static class JudgeResult { public final int distance; public final double similarity; public final double score; public JudgeResult(int distance, double similarity, double score) { this.distance = distance; this.similarity = similarity; this.score = score; } }参数说明:
similarity = 1 - distance/max(len1,len2)是业界通用公式;分数映射采用三段式:≥0.95(几乎一致)给满分,0.8~0.95(有语法错误但逻辑对)线性扣分,<0.8(完全跑偏)0分——答辩时可指着这个逻辑说:“学生少写了一个for循环,distance=15,similarity=0.82,所以得8.4分”。
4.3 生产级加固:应对学生“花式作答”的3种防御策略
学生不会老老实实交标准格式代码。我在毕设指导中见过太多“翻车现场”:有人交Python代码(题干要求Java)、有人把答案藏在注释里、有人用System.out.println("hello")代替return。LD算法必须加防护:
/** * 增强版判分:先做合法性校验,再LD计算 */ public JudgeResult robustJudge(String standard, String student, int fullScore, String requiredLang) { // 1. 语言校验:检查学生代码是否含指定语言关键字(防交错语言) if (!isLanguageMatch(student, requiredLang)) { return new JudgeResult(999, 0.0, 0.0); // 强制0分 } // 2. 结构校验:检查是否包含必要结构(如Java题必须有public class) if (!hasRequiredStructure(student, requiredLang)) { return new JudgeResult(500, 0.0, fullScore * 0.3); // 扣70%,留30%结构分 } // 3. 注释剥离:提取非注释代码(防答案藏在//或/* */里) String cleanStudent = extractCodeWithoutComment(student, requiredLang); return judgeCode(standard, cleanStudent, fullScore); } private boolean isLanguageMatch(String code, String lang) { if ("java".equalsIgnoreCase(lang)) { return code.contains("public class") || code.contains("public static void main"); } else if ("python".equalsIgnoreCase(lang)) { return code.contains("def ") || code.contains("print("); } return true; } private String extractCodeWithoutComment(String code, String lang) { if ("java".equalsIgnoreCase(lang)) { // 移除 // 单行注释和 /* */ 多行注释 code = code.replaceAll("//.*", ""); code = code.replaceAll("/\\*.*?\\*/", "", Pattern.DOTALL); } return code; }防御逻辑:
isLanguageMatch()用关键词粗筛,比AST解析轻量得多;extractCodeWithoutComment()确保判分基于真实代码逻辑;robustJudge()返回distance=999这种明显异常值,方便后续日志标记“语言错误”,答辩时可演示:“这个学生交了Python,系统直接判0分并记录原因”。
5. 避坑指南:SSM+JSP在线考试系统开发中踩过的7个血泪坑
5.1 现象:JSP页面中文显示为“???”,但数据库里存的是正确的
原因:IDEA项目编码、Tomcat服务器编码、MySQL连接URL编码、JSP页面pageEncoding四者不一致。常见组合错误是:IDEA设UTF-8,web.xml里过滤器没开forceEncoding=true,MySQL URL漏了characterEncoding=utf8mb4。
解决:四步锁死——① IDEAFile→Settings→Editor→File Encodings全设UTF-8;②web.xml中CharacterEncodingFilter的forceEncoding=true必须为true;③ MySQL连接URL加?characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai;④ JSP顶部声明<%@ page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8" %>。缺一不可。
5.2 现象:<c:forEach>遍历List在JSP里不显示任何内容,控制台也无报错
原因:jstl-1.2.jar没放对位置,或web.xml版本号低于3.0导致EL表达式被禁用。
解决:① 确认jstl-1.2.jar在webapp/WEB-INF/lib/下(不是lib或Maven依赖);②web.xml头必须是version="4.0"(不是2.5或3.0);③ JSP中加<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>;④ 若仍无效,在web.xml中加<jsp-config><el-ignored>false</el-ignored></jsp-config>。
5.3 现象:LD算法判分耗时超5秒,学生提交后页面转圈不动
原因:未对长代码做长度截断,学生交了1000行代码,LD算法时间复杂度O(mn)直接爆炸。
解决:在judgeCode()前加长度限制——if (standard.length() > 2000 || student.length() > 2000) { return new JudgeResult(999, 0.0, 0.0); }。毕设题目代码一般<500字符,2000是安全上限;同时在JSP前端用JavaScript限制textarea最大长度<textarea maxlength="2000">。
5.4 现象:MySQL插入中文报错Incorrect string value: '\xF0\x9F\x98\x8A'
原因:数据库、表、字段三级字符集未全部设为utf8mb4,只设了库或表。
解决:执行ALTER DATABASE online_exam CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;,然后对每张表执行ALTER TABLE exam CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;,最后对每个TEXT/VARCHAR字段执行ALTER TABLE exam MODIFY COLUMN content TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。用SHOW CREATE TABLE exam;逐级确认。
5.5 现象:Tomcat启动报java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet
原因:spring-webmvc.jar没放进WEB-INF/lib/,或IDEA没把lib目录标记为“Libraries”。
解决:① 手动将spring-webmvc-5.3.39.jar等所有jar拖入webapp/WEB-INF/lib/;② 在IDEA中右键lib文件夹→Add as Library;③Project Structure→Artifacts→Output Layout中确认WEB-INF/lib/下有这些jar;④ 清理Tomcat工作目录:rm -rf tomcat/work/Catalina/localhost/*。
5.6 现象:学生登录成功后,session.getAttribute("student")在其他页面取不到
原因:web.xml中<session-config>未配置<cookie-http-only>true</cookie-http-only>,或JSP中用了<% session.invalidate(); %>误销毁。
解决:① 在web.xml中添加<session-config><session-timeout>30</session-timeout><cookie-http-only>true</cookie-http-only></session-config>;② 登录成功后用request.getSession().setAttribute("student", student),不要用new Session();③ 检查所有JSP是否误写session.invalidate()——毕设中只需登录时setAttribute,退出时removeAttribute即可。
5.7 现象:LD算法对int a=1;和int a = 1 ;判为distance=3(空格差异),但实际应视为相同
原因:normalizeCode()未彻底处理空格,或学生代码含制表符\t。
解决:升级normalizeCode()方法,增加制表符替换和连续空白符压缩:
private String normalizeCode(String code) { if (code == null) return ""; code = code.replace("\r\n", "\n").replace("\r", "\n").replace("\t", " "); // \t→空格 String[] lines = code.split("\n"); StringBuilder sb = new StringBuilder(); for (String line : lines) { line = line.trim().replaceAll("\\s+", " "); // \s+匹配所有空白符 if (!line.isEmpty()) sb.append(line).append("\n"); } return sb.toString().trim(); }6. 答辩加分技巧:用3个可演示细节让老师眼前一亮
6.1 细节一:在judge_log表里埋一个“人工复核开关”
答辩时老师最爱问:“LD算法万一判错了怎么办?”。别只会说“我们人工复核”,要拿出可操作的方案。在submission表加一列need_review TINYINT DEFAULT 0,并在判分服务里加规则:
// 当相似度在0.75~0.85之间时,标记需人工复核 if (similarity >= 0.75 && similarity < 0.85) { submission.setNeedReview((byte) 1); submission.setScore(fullScore * 0.5); // 先给50%,等老师复核 }然后在后台管理页(admin/review.jsp)用<c:if test="${s.needReview == 1}">高亮显示待复核记录,并提供“通过/驳回”按钮。演示时点开一条need_review=1的记录,点击“通过”,分数立刻从5.0变成10.0——这比说一百遍“支持人工干预”都有力。
6.2 细节二:用Druid监控页实时展示SQL慢查询
毕设常被质疑“性能怎么样?”。与其口头说“很快”,不如打开http://localhost:8080/druid/sql.html。提前准备一个场景:在QuestionMapper.xml里故意写一个没索引的查询:
<!-- 慢查询示例(答辩时演示优化过程) --> <select id="findQuestionsByExamId" resultType="Question"> SELECT * FROM question WHERE exam_id = #{examId} AND type = 3 <!-- 编程题 --> </select>执行几次后,Druid页会显示该SQL平均耗时>100ms。然后你当场加索引:
ALTER TABLE question ADD INDEX idx_exam_type (exam_id, type);刷新Druid页,耗时降到5ms以下。这个“发现问题→加索引→效果立现”的闭环,是答辩时最硬核的性能证明。
6.3 细节三:导出一份“判分报告PDF”,含LD算法可视化对比
学生总想知道自己哪错了。用iText 5.5.13生成PDF报告(轻量,不需Spring Boot),在JudgeService里加方法:
public void generateJudgeReport(int submissionId, HttpServletResponse response) throws Exception { Submission sub = submissionMapper.selectById(submissionId); Question q = questionMapper.selectById(sub.getQuestionId()); Document document = new Document(); PdfWriter.getInstance(document, response.getOutputStream()); document.open(); Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 16); document.add(new Paragraph("编程题判分报告", titleFont)); // 标准答案 vs 学生答案(用不同背景色区分) PdfPTable table = new PdfPTable(2); table.addCell(new PdfPCell(new Phrase("标准答案")) {.setBackgroundColor(BaseColor.LIGHT_GRAY)}); table.addCell(new PdfPCell(new Phrase("学生答案")) {.setBackgroundColor(BaseColor.YELLOW)}); table.addCell(new PdfPCell(new Phrase(q.getAnswer()))); table.addCell(new PdfPCell(new Phrase(sub.getAnswer()))); document.add(table); // LD结果 JudgeResult result = judgeCode(q.getAnswer(), sub.getAnswer(), q.getScore()); document.add(new Paragraph("编辑距离:" + result.distance)); document.add(new Paragraph("相似度:" + String.format("%.2f%%", result.similarity * 100))); document.add(new Paragraph("得分:" + result.score + "/" + q.getScore())); document.close(); }在JSP里加一个按钮<a href="judge/report.do?id=${s.id}">下载判分报告</a>。答辩时点开,PDF弹出,老师看到黄底学生代码和灰底标准答案并排,立刻理解判分逻辑——这比任何PPT都直观。
我带的学生里,凡是在答辩前一周把这三个细节做完的,没有一个被问倒。不是因为技术多深,而是把“老师关心什么”和“系统能展示什么”严丝合缝地对上了。希望帮到你。
本文还有配套的精品资源,点击获取