☰
SpringBoot自动评分系统核心设计:判分规则、异步判分与RabbitMQ实战
2026/10/30 14:57:16 网站建设 项目流程

简介:为计算机相关专业学生提供一套基于SpringBoot与Vue的自动评分系统源码,适合毕业设计、课程设计或期末大作业实践。系统针对教师人工批改工作量大、效率低的问题,通过预设评分规则自动批改选择题、填空题、编程题等题型,答案与分值可灵活配置,核心功能涵盖试题库管理、答卷提交、自动评分与成绩反馈。压缩包内含117个文件,以62个XML配置和37个Java源码为主,另有6个class文件、3个properties配置文件及少量JAR依赖与Maven构建文件,整体约20.95MB,目录结构清晰,便于按模块查阅。目前已有36人学习下载,可作为前后端分离项目的完整参考。研读源码可掌握SpringBoot后端RESTful API设计、Vue前端交互,以及业务逻辑分层实现,对理解现代Web应用架构和开发评分系统有实际帮助。

1. 自动评分系统不是写个 if-else:SpringBoot 里真正难的是判分规则和并发控制

做毕设或期末大作业时,选题里带“自动评分”四个字,很多人第一反应是“判个分而已,遍历一下答案不就行了”。真拆开看才发现:客观题要按题型配置规则,简答题要算相似度,学生交卷后系统要异步判分、回写分数、处理并发重复提交,还要防止 RabbitMQ 消息丢在队列里。这份基于 SpringBoot 的自动评分系统,前端用 Vue,后端用 SpringBoot + MyBatis-Plus,是我见过比较完整的课设级方案:从试卷配置、学生答题、异步判分到成绩导出,链路是闭环的。它适合两类人:一类是正在做 SpringBoot 毕设、需要完整业务闭环的;另一类是学过 SpringBoot 基础、想看看“真实项目里判分逻辑怎么设计”的。这篇文章我按拆包视角,把核心模块、启动步骤、参数改动和最容易翻车的地方一次讲透。

2. 项目结构与核心链路:一次请求从交卷到出分走完整条链路

2.1 前后端分离的工程布局:约定优于配置

拿到压缩包解压后,第一件事不是急着改代码,而是先看目录结构。这套项目是标准的前后端分离写法,后端一个 Maven 工程,前端一个 Vue 工程,两者通过 REST API 通信,不走模板引擎渲染页面。

auto-score-system ├── backend │ ├── src/main/java │ │ ├── com/example/autoscore │ │ │ ├── controller # 接口层:试卷、答题、成绩 │ │ │ ├── service # 业务层:判分逻辑、成绩统计 │ │ │ ├── mapper # MyBatis-Plus 数据访问层 │ │ │ ├── entity # 数据库实体 │ │ │ ├── config # 配置类:拦截器、WebMVC、RabbitMQ │ │ │ ├── rabbitmq # 消息生产者与消费者 │ │ │ └── common # 统一返回结构、异常处理 │ ├── src/main/resources │ │ ├── application.yml │ │ └── mapper │ └── pom.xml ├── frontend │ ├── src │ │ ├── api # axios 接口封装 │ │ ├── views # 页面:登录、考试、成绩 │ │ └── router # 前端路由 │ └── package.json └── sql └── auto_score.sql # 初始化脚本

这个布局常见,但有几个值得注意的约定:controller只做参数接收和结果返回,不掺杂业务逻辑;rabbitmq包单独拆出来,说明异步判分的生产者消费者是独立设计,业务层通过消息队列解耦;config里放拦截器和队列配置,意味着登录态校验和消息确认机制都在这一层统一处理。

我一般建议先看pom.xml的依赖版本,再对application.yml的配置项。这套项目的依赖不算多,SpringBoot 2.7.x(具体以项目 pom 为准)、MyBatis-Plus、RabbitMQ、JWT、Hutool 工具类,没了。没有 Redis 依赖的话,说明 JWT 的 token 管理用的是无状态方案,token 校验不依赖服务端存储,这在课设里够用,但如果你要加“单端登录踢下线”功能,就得自己补 Redis。

2.2 判分核心:选择题和判断题的规则引擎实现

自动评分系统的核心不在接口写得多花哨,而在判分逻辑怎么组织。这个项目的判分设计是典型的“策略模式 + 规则配置”思路,比硬编码 if-else 高明在:新增一种题型,不需要改动判分主流程,只需要追加一个策略实现类。

先看判分入口的代码:

public interface ScoreStrategy { // 返回该策略支持的题型 String getQuestionType(); // 执行判分,返回得分 BigDecimal evaluate(Question question, String studentAnswer); }

接着看选择题策略实现:

@Component public class ChoiceQuestionStrategy implements ScoreStrategy { @Override public String getQuestionType() { return "choice"; // 选择题 } @Override public BigDecimal evaluate(Question question, String studentAnswer) { // 标准答案从题目的 answer 字段读取,学生答案由前端提交 String standardAnswer = question.getAnswer().trim(); if (standardAnswer.equalsIgnoreCase(studentAnswer.trim())) { return question.getScore(); // 满分 } return BigDecimal.ZERO; // 零分 } }

这段代码的逻辑很直白:选择题判分就是字符串比对。但要注意两个细节。第一,equalsIgnoreCase是防了用户在选项字母大小写上翻车,如果你出题时用了“AB”这种多选答案格式,这里要改成忽略顺序的比较,比如先把字符串转成字符数组排序再比较。第二,返回类型是BigDecimal而不是double,因为分数累加时浮点精度很容易出问题,0.1 + 0.2 不等于 0.3 这种玄学在分数统计里是真实存在的。

真正组织所有策略的地方是这个工厂类:

@Component public class ScoreStrategyFactory { private final Map<String, ScoreStrategy> strategyMap = new HashMap<>(); // Spring 会把所有 ScoreStrategy 实现类注入进来 public ScoreStrategyFactory(List<ScoreStrategy> strategies) { for (ScoreStrategy strategy : strategies) { strategyMap.put(strategy.getQuestionType(), strategy); } } public ScoreStrategy getStrategy(String questionType) { ScoreStrategy strategy = strategyMap.get(questionType); if (strategy == null) { throw new UnsupportedOperationException("未支持的题型: " + questionType); } return strategy; } }

这个工厂利用了 Spring 的构造器注入机制:项目里所有实现了ScoreStrategy接口的类,都会被 Spring 自动收集到strategies这个 List 里,然后转成 Map。以后你要加“多选题”或“填空题”,只需新增一个组件类并实现接口,工厂不用改一行代码。这就是“开闭原则”在课设里的实际落地,也是答辩时老师比较爱问的一个点。

如果要在已有代码里加一个“多选题策略”,照抄ChoiceQuestionStrategy的结构,把getQuestionType()改成"multi_choice",判分逻辑改成“全对才得分,少选不得分”,再用@Component标注,系统就能直接识别新题型,不用动其他文件。

2.3 简答题评分:关键词权重与相似度的两种方案

客观题好判,简答题是这类系统里最有争议的部分。这套项目的做法是把简答题评分拆成两层:先做关键词命中,再做文本相似度。两者按比例加权,得出一个 0 到 1 之间的系数,再乘以题目总分。

public BigDecimal evaluateEssay(Question question, String studentAnswer, String standardAnswer) { // 1. 关键词权重评分:标准答案中预置关键词,按命中比例打分 List<String> keywords = Arrays.asList(question.getKeywords().split(",")); int hitCount = 0; for (String keyword : keywords) { if (studentAnswer.contains(keyword.trim())) { hitCount++; } } double keywordRatio = (double) hitCount / keywords.size(); // 权重 70% // 2. 余弦相似度评分:计算学生答案与标准答案的文本相似度 double similarity = cosineSimilarity(studentAnswer, standardAnswer); // 权重 30% // 3. 加权合并 double finalRatio = keywordRatio * 0.7 + similarity * 0.3; return question.getScore().multiply(BigDecimal.valueOf(finalRatio)); }

这段逻辑值得好好说。关键词命中的思路是:出题人在配置题目时,在keywords字段里填几个踩分点,比如“SpringBoot 自动配置原理”这道题,关键词可以填“自动配置,条件注解,spring.factories”。学生答案里包含哪个关键词就记一次命中,然后算命中率。这种方案简单、可解释性强,缺点是学生把关键词抄上就能拿分,所以权重只给了 70%。

余弦相似度那边是给参考答案和学生答案分别做分词、构建词频向量、算余弦值。分词可以用 HANLP 或轻量级的 jieba 封装,项目里如果没引分词依赖,可以用简单的空格拆分加停用词过滤做简化版。相似度权重 30% 的意义在于防止纯粹堆关键词,答案完全不通顺的学生得分会被拉低。

项目配置里关键词是存在question表的keywords字段里的,出题页面有单独的编辑入口。你要调整评分严格程度,最直接的办法是改权重系数:关键词权重调到 0.8、相似度降到 0.2,学生就更难靠抄关键词过关;反过来调,系统对语句通顺度更敏感。这个参数我会在后面的避坑章节再提一次,因为它是“评分结果不对”的常见来源。

2.4 异步判分与结果回写:RabbitMQ 队列的削峰作用

学生交卷后如果同步判分,接口响应时间会被简答题的相似度计算拖慢,高峰期可能卡到两三秒。这套项目把判分设计成异步流程:交卷接口只负责把试卷数据丢进 RabbitMQ 队列,立刻返回“提交成功”,后台消费者慢慢判,判完再回写数据库。

先看生产者发送消息的代码:

@Service public class ExamSubmitService { @Autowired private RabbitTemplate rabbitTemplate; public void submitExam(ExamSubmitDTO dto) { // 幂等校验:同一学生同一试卷不能重复提交,先查状态 Integer count = submitRecordMapper.checkExist(dto.getStudentId(), dto.getExamId()); if (count != null && count > 0) { throw new BusinessException("该试卷已提交,请勿重复操作"); } // 把交卷数据转成 JSON 字符串发送到评分队列 String msg = JSON.toJSONString(dto); rabbitTemplate.convertAndSend("score.exchange", "score.routing.key", msg); // 发送成功后更新提交状态为"待评分" submitRecordMapper.updateStatus(dto.getStudentId(), dto.getExamId(), "PENDING"); } }

这段代码里最容易被忽略的是注释掉的那步“幂等校验”。学生可能因为网络原因点两次交卷,如果没有这个状态检查,同一份试卷会被判两次,成绩表里出现两条记录。checkExist查一下提交记录表就能拦掉大部分重复。RabbitMQ 的convertAndSend是默认的直连发送,exchange 名score.exchange和 routing keyscore.routing.key需要在配置类和application.yml里提前定义,否则消息会直接进死信队列,你会在消费者那边看到日志报错“no queue bound to exchange”。

再看消费者消费消息的代码:

@Component public class ScoreConsumer { @Autowired private ScoreCalculateService scoreCalculateService; @RabbitListener(queues = "score.queue") public void onMessage(String message) { ExamSubmitDTO dto = JSON.parseObject(message, ExamSubmitDTO.class); try { // 核心判分流程:逐题评分、汇总、落库 scoreCalculateService.calculateAndSave(dto); } catch (Exception e) { // 重试或记录失败日志,这里采用记录失败 + 手工补偿 log.error("判分失败 examId={}, studentId={}", dto.getExamId(), dto.getStudentId(), e); } } }

消费者的@RabbitListener默认是自动 ACK 模式,也就是说消息一到达就确认,如果calculateAndSave执行一半抛异常,消息已经丢了,分数没算出来。比较稳的做法是把监听器的acknowledge-mode设为manual,在方法参数里加Channel和Message,业务处理成功后手动basicAck,失败则basicNack重回队列或进入死信。这是生产环境和课设项目的核心差异点,答辩时能讲清楚会比较加分。

3. 本地跑起来:数据库初始化、配置修改与启动顺序

3.1 环境准备与版本对照

这套项目不是开箱即跑,需要先把中间件拉起来。我按仓库里默认的配置项整理了一份对照表,你本地环境尽量往这个版本靠,差太多会出现各种兼容性玄学。

组件推荐版本用途避坑说明
JDK1.8 或 11后端运行环境如果你的 SpringBoot 版本是 2.7.x,JDK 17 也能跑,但 javac 编译参数可能报错;版本太高不是不能用,是没必要的折腾
MySQL5.7 或 8.0数据存储8.0 需要驱动版本和时区参数匹配,详见 3.3
Redis不依赖(如项目未引用)缓存如果 pom 里没有 Redis 依赖,跳过即可
RabbitMQ3.8+异步判分消息队列默认端口 5672,管理台 15672,启动前必须确认服务起来了
Node.js14+ 或 16+前端构建18 或 20 也能跑,但旧版 vue-cli 项目在 Node 20 上可能提示 OpenSSL 错误,需要重设NODE_OPTIONS
Maven3.6+后端依赖管理IDEA 自带 maven,建议用 3.8 以下,个别新版本 Maven 会和旧插件冲突

提示:MySQL 如果本机没装,用 Docker 一条命令启动最省事:docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0 --default-time-zone=+8:00。最后那个时区参数建议加上,它和 SpringBoot 连接串里的serverTimezone要配套。

3.2 数据库初始化与核心表结构

SQL 脚本在项目根目录的sql/auto_score.sql里。用 Navicat 或命令行执行导入:

mysql -uroot -p123456 < auto_score.sql

导入后重点关注四张表:sys_user(用户表,区分学生和教师两种角色)、exam_paper(试卷表)、question(题目表,含answer标准答案、keywords关键词、score分值、type题型)、exam_record(考试记录表,学生交卷后落状态)。

-- 关键表结构:题目表 CREATE TABLE `question` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `paper_id` bigint(20) NOT NULL COMMENT '所属试卷', `type` varchar(20) NOT NULL COMMENT '题型: choice/judge/essay', `stem` text NOT NULL COMMENT '题干', `answer` text COMMENT '标准答案', `keywords` varchar(500) DEFAULT NULL COMMENT '简答题关键词,逗号分隔', `score` decimal(5,2) NOT NULL COMMENT '题目分值', `sort` int(11) DEFAULT '0' COMMENT '题目序号', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这张表的设计有几个地方值得讲。keywords字段是给简答题用的,存的是逗号分隔的字符串,不符合数据库第三范式,但课设项目里完全够用,查询时不需要拆表关联;score用decimal(5,2)而不是float,是为了避免分数累加时出现14.850000000001这种值;sort字段控制题目顺序,前端展示或试卷打印时按这个字段排序,而不是按id,因为你可能中途插入题目导致 id 顺序和展示顺序不一致。

考试记录表相对简单:主键、学生 id、试卷 id、总分、状态(PENDING / SUCCESS / FAILED)、交卷时间、判分时间。状态字段建议默认PENDING,消费者判完再更新为SUCCESS,这样前端可以做轮询或结果显示“判分中”,课设视频演示时也比较好看。

3.3 SpringBoot 配置文件的必改项

backend/src/main/resources/application.yml是整个后端能不能连上中间件的关键。打开后需要改四处,每处都要按你本机的环境调。

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/auto_score?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: "123456" driver-class-name: com.mysql.cj.jdbc.Driver rabbitmq: host: localhost port: 5672 username: guest password: guest publisher-confirm-type: correlated # 开启发送端确认,消息有没有进队列能感知到 mybatis-plus: mapper-locations: classpath*:mapper/*.xml configuration: map-underscore-to-camel-case: true

数据库连接串里的serverTimezone=Asia/Shanghai这个参数务必保留,MySQL 8.x 只要不配时区就会在插入时间字段时全体报错或存成 0000-00-00;RabbitMQ 默认的 guest 账号只能在 localhost 登录,如果你把host改成远程机器,用户名密码也要改成对应的,不然连接会异常。

publisher-confirm-type: correlated是老版本 SpringBoot 升级 2.7 后必加的配置项,旧写法publisher-confirms: true在新版本直接被忽略,发送消息会报 “The 'correlated' property is not supported” 之类的错误。加上这个配置,RabbitTemplate才能回调解确认消息,你能知道这条消息到底有没有进到交换机。

前端frontend目录下的.env文件(没有就新建)还需要配置跨域代理:

VUE_APP_BASE_API = http://localhost:8080/api

前端 dev 服务器默认 8080 端口,上面后端也占用了 8080,启动前端时会发现端口被占用。处理方式:要么把后端的server.port改成 8081,要么把前端的vue.config.js里 devServer 端口改成 3000。我一般习惯保留后端 8080,前端改端口,因为前端用的 axios 请求前缀是/api,走 devServer 代理到后端,避免跨域。

3.4 启动顺序与前后端联调

按依赖关系,启动顺序有讲究:先 MySQL 和 RabbitMQ 中间件,再后端,最后前端。这个顺序别反,大部分“后端起不来”的报错都是因为中间件还没就绪,SpringBoot 启动时连不上 RabbitMQ 会直接失败。

# 1. 启动中间件:MySQL、RabbitMQ 用各自服务方式启动 # 2. 启动后端(项目根目录 backend 下) mvn spring-boot:run # 3. 启动前端(项目根目录 frontend 下) npm install npm run serve

后端启动日志中出现“Started AutoScoreApplication”才算成功;RabbitMQ 连接成功会出现Cluster created之类的正常日志。启动时如果日志报APPLICATION FAILED TO START,八成是数据库密码错了或 RabbitMQ 没起来,先看异常栈最后一段,它会明确告诉你哪个连接失败,别盯着前面的红色报错瞎猜。

前端npm run serve起完后,浏览器访问http://localhost:3000(如果你改了 devServer 端口),用初始化 SQL 里的账号登录。auto_score.sql里通常内置了一个教师账号和一个学生账号,教师可以创建试卷和出题,学生能参加考试。第一次用要通流程:教师建试卷 → 录入题目 → 发布 → 学生进入考试 → 交卷 → 等几秒(RabbitMQ 消费有延迟)→ 查看成绩。如果交卷后成绩一直处于“判分中”,查看后端日志或去 RabbitMQ 管理台看score.queue里是否堆积了未消费消息。

4. 避坑指南:自动评分系统最常见的五个翻车现场

4.1 判分结果丢失:异步线程里的事务失效

现象:RabbitMQ 消费者日志打出了“判分失败”,但看代码明明每一步都正常,回查数据库发现exam_record的状态一直是PENDING,总分字段是 null。

原因:calculateAndSave方法是被消费者线程调用的,如果这个方法上标了@Transactional注解,事务本身是能生效的;但判分过程中又调用了其他类的事务方法,而且这些方法走的是this.xxx()内部调用,Spring AOP 代理不生效,事务回滚没起到作用,异常后数据处于半完成状态。

解决:把落库逻辑拆成独立的事务方法,并注入到消费者内部使用。即不能直接this.saveRecord,而是要注入ScoreRecordService,由 Spring 代理对象去调@Transactional方法,这样异常才能触发回滚。另外在消费者里加失败重试机制,简单做法是 catch 住异常后手动basicNack并设置requeue=true,配合消息头里的重试次数控制,超过 3 次就进死信队列,人工补偿。

4.2 分数对不上:浮点精度与四舍五入策略不一致

现象:学生在详情页看到每题得分累计是 85.5,但列表页总分显示 85。教师端导出成绩表,发现 Excel 里的总分和系统里显示的总分能差出 0.5 分。

原因:判分时用了BigDecimal但累加过程中没有统一scale(小数位数),或者有的地方调了setScale(0, RoundingMode.HALF_UP),有的地方没调,导致最后汇总时四舍五入发生在不同位置。比如 14.25 + 12.75 = 27.00,如果两步都提前取整,14 加 12 等于 26,差 1 分。

解决:整个项目统一在“最后展示”前做四舍五入,中间计算全用BigDecimal保持原始精度。我在这套项目里建议统一封装一个ScoreCalculator工具类:

public static BigDecimal normalize(BigDecimal score) { return score.setScale(2, RoundingMode.HALF_UP); }

所有累加逻辑走add(normalize(...))而不直接add(...),保证每个加数先统一到两位小数,再相加。不要在一个地方保留小数点后 4 位、另一个地方保留 2 位,这是分数对不上的最大来源。如果你要改总分显示成整数,只在最后的 VO 转换层做一次setScale(0),不要在 DAO 层做。

4.3 Vue 打包后接口 404:前端路由与后端静态资源映射冲突

现象:开发模式npm run serve一切正常,npm run build打包后把 dist 目录扔进 SpringBoot 的static文件夹,启动后首页能打开,但一刷新页面就 404,接口也调不通。

原因:Vue 是 history 模式路由,刷新时浏览器请求后端的/exam/detail路径,后端没有这个映射,直接 404。接口 404 是因为你打包时VUE_APP_BASE_API设置的是/api,但 SpringBoot 没加对应的 context-path 或控制器没有@RequestMapping("/api")前缀。

解决:两件事都要做。第一,后端加一个 WebMvcConfigurer 转发所有非接口路径到 index.html:

@Override public void addViewControllers(ViewControllerRegistry registry) { // 只对非静态资源路径做转发,接口路径由 @RestController 处理 registry.addViewController("/{path:[^\\.]*}") .setViewName("forward:/index.html"); }

第二,接口统一加/api前缀,最简单的方式是在application.yml里配置spring.mvc.pathmatch.matching-strategy: ant_path_matcher,并用@RequestMapping("/api")包在基础 Controller 上,或者直接用server.servlet.context-path: /api。两种方式都可行,但注意context-path会影响前端 axios 的 baseURL 拼接,别在开发版和打包版之间换来换去,否则越调越乱。

注意:如果pom.xml里还加了spring-boot-starter-thymeleaf,它对forward:/index.html的路径解析可能会和 Vue 的public目录里的index.html冲突,出现“Error resolving template”报错。确认pom.xml没有引入 thymeleaf 依赖,如果引入了且不必要,直接删掉。

4.4 并发重复提交:幂等与判分重叠

现象:学生双击“交卷”按钮,或者前端网络超时后自动重发,数据库里出现两条exam_record记录,一个是SUCCESS,一个是PENDING,后一个永远不会被消费,因为判分逻辑里发现试卷已判过就不再执行。

原因:交卷接口先查状态、再发消息,查询和发送之间不是原子操作。两个请求同时进来,都查到“未提交”状态,都往队列里发了消息,两个消费者各判一次。这个场景在本地单机测试时不容易出现,但压测或学生手速快时就会翻车。

解决:在数据库层面做唯一约束,比如exam_record表对student_id + exam_id加唯一索引,插入时用INSERT IGNORE或ON DUPLICATE KEY UPDATE,从源头拦掉重复。业务层的checkExist只能防君子防不了并发,数据库约束才是兜底。另外一个习惯是:交卷接口在convertAndSend成功后立刻提交事务,把状态改为PENDING和发消息放在同一个本地事务表里,用本地消息表加定时补偿的方式保证最终一致性,这是分布式事务之外比较轻量的解法。

4.5 简答题关键词评分的“抄答案”漏洞

现象:学生写了一句“自动配置条件注解 spring.factories”,把关键词全串进去,命中了 4 个关键词中的 3 个,加相似度系数后得分居然比认真写的学生还高。教师觉得评分逻辑“不合理”。

原因:关键词命中判断用的是studentAnswer.contains(keyword),只要答案字符串里包含这个词,不管它出现在什么语境里都算命中。这个项目默认了“关键词就是踩分点”,它的设计目的是让评分标准化、可解释,不是文学鉴赏,所以学生钻空子是必然的。

解决:干预措施有两个。一是把关键词拆成“必须按顺序出现”的短语,比如把判分从contains改成按位置匹配:先找第一个关键词的位置,再从它的后面找第二个关键词,这样能要求答案有基本的逻辑顺序。二是提高相似度权重,把加权系数从0.7 + 0.3改成0.5 + 0.5,让语义通顺度占一半分。我在这套项目里建议第二种,只改一处常量,对学生得分的分布影响最直接,也不容易把真正会答的学生误杀。

5. 再进一步:把评分规则改成配置化,不写死在一个常量里

前文提了两次“权重系数改一下”,但如果每次调整都去改 Java 代码再重新编译,课设展示时来回折腾很痛苦。这套项目更适合的进阶改造是把评分规则抽成一张score_rule表,把权重、四舍五入模式、题型对应策略这些参数全放到数据库里,改规则不用动代码。

CREATE TABLE `score_rule` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `rule_type` varchar(30) NOT NULL COMMENT '规则类型: KEYWORD_WEIGHT/SIMILARITY_WEIGHT', `rule_value` decimal(4,2) NOT NULL COMMENT '权重值', `enabled` tinyint(1) DEFAULT '1', `remark` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

改造思路很简单:初始化时插入两条记录,KEYWORD_WEIGHT为 0.70,SIMILARITY_WEIGHT为 0.30。判分逻辑里不再写0.7和0.3两个字面量,而是注入ScoreRuleService查询当前生效的规则。为了减少每次判分都查数据库的损耗,可以在应用启动时用@PostConstruct把规则表加载到内存里的一个 ConcurrentHashMap,改规则后提供一个刷新接口,教师端点一下“重载规则”即可。

@Component public class ScoreRuleConfig { private final Map<String, BigDecimal> ruleCache = new ConcurrentHashMap<>(); @PostConstruct public void loadRules() { // 启动时从 score_rule 表读取全部 enabled=1 的规则 // 假设 key=KEYWORD_WEIGHT, value=0.70 // 假设 key=SIMILARITY_WEIGHT, value=0.30 } public BigDecimal get(String ruleKey) { return ruleCache.getOrDefault(ruleKey, BigDecimal.valueOf(0.5)); } // 供管理端调用的刷新方法,刷新后 ruleCache 重新加载 public void refresh() { ruleCache.clear(); loadRules(); } }

这个改造对毕设来说是明显的加分项:它把评分系统的“规则”和“代码”解耦了,老师在答辩时问“我想改评分权重怎么办”,你只需要回答“在系统里改参数、点刷新,不用动代码重部署”,这就把系统做成带配置中心的雏形了。SpringBoot 的@ConfigurationProperties也可以做到类似效果,但用数据库表的方式对前端展示更直观,适合课设演示。

规则配置化之后,给前端加一个“评分规则设置页”的小页面也就顺手了:教师登录后进入一个表格,可以改各类题型的权重,保存后调刷新接口,下一次判分立即生效。这个小功能工作量不大,但能在演示视频里形成一个完整的管理闭环。

验证规则是否生效,可以走一条更短的测试路径:用教师账号创建一个只有一题的试卷,题目是简答题,分值 10 分,关键词两个,权重先设 0.7/0.3,学生提交一个只包含第一个关键词的答案,拿到一个分数;再去规则页把关键词权重改成 0.4,同样答案再提交一次,分数会明显下降。这个对比测试能同时验证规则加载、判分流程、结果回写三条链路是否正常,比刷一堆假数据靠谱得多。从那以后我每次改完评分相关代码,都强制走一遍这个两遍提交的对照流程,血泪经验换来的习惯。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询