1. 项目背景与核心需求
奥数竞赛作为数学拔尖人才培养的重要途径,长期面临组织效率低下、资源管理混乱等痛点。我在参与某省数学学会的竞赛系统升级项目时,深刻体会到传统纸质考试的三大困境:一是试卷印刷分发成本高达每次竞赛3-5万元;二是2000份试卷的阅卷工作需要20名教师连续工作3天;三是成绩统计错误率超过5%。这促使我们开发基于SpringBoot的在线评测系统。
系统设计遵循三个核心原则:
- 全流程数字化:从组卷到成绩分析实现闭环管理
- 智能防作弊:通过题目乱序+选项随机+切屏检测保障公平性
- 数据沉淀:构建可追溯的选手成长档案
2. 技术架构设计
2.1 整体技术栈选型
采用前后端分离架构,主要技术组件如下表所示:
| 层级 | 技术选型 | 选型理由 |
|---|---|---|
| 前端 | Vue 2.x + Element UI | 组件库丰富适合管理后台开发,与后端JSON交互顺畅 |
| 后端 | SpringBoot 2.5.6 | 约定优于配置,快速集成MyBatis-Plus等组件 |
| 数据库 | MySQL 8.0 | 支持JSON字段存储复杂题型结构,事务性能满足高并发考试提交 |
| 缓存 | Redis 6.2 | 存储考试临时数据(如考生答题进度),降低数据库压力 |
| 消息队列 | RabbitMQ 3.9 | 异步处理批量导入、成绩计算等耗时操作 |
| 监控 | Prometheus + Grafana | 实时监控服务器资源使用情况,保障考试期间系统稳定性 |
特别注意:MySQL必须配置为严格模式(sql_mode=STRICT_TRANS_TABLES),避免因数据类型不匹配导致 silent error
2.2 核心业务模块设计
系统采用DDD领域驱动设计,主要聚合根包括:
java复制// 考试聚合根示例
public class Examination {
private Long id;
private String title;
private LocalDateTime startTime;
private Integer duration; // 分钟
private List<QuestionGroup> questionGroups;
// 考试状态机
public enum Status {
DRAFT, PUBLISHED, STARTED, FINISHED
}
}
关键设计决策:
- 最终一致性设计:考试成绩计算采用事件溯源模式,通过RabbitMQ延迟队列处理异常情况
- 读写分离:考试期间查询走从库,提交操作走主库
- 分布式锁:使用Redisson实现题目分配锁,避免并发问题
3. 核心功能实现
3.1 智能组卷系统
3.1.1 题库结构设计
采用多级分类体系:
code复制数学题库
├── 代数
│ ├── 不等式(难度:★)
│ └── 多项式(难度:★★★)
└── 几何
├── 平面几何
└── 立体几何
数据库表设计关键字段:
sql复制CREATE TABLE `question` (
`id` bigint NOT NULL AUTO_INCREMENT,
`stem` text COMMENT '题干(支持LaTeX)',
`question_type` enum('SINGLE','MULTI','JUDGE','FILL','SUBJECTIVE') NOT NULL,
`answer` json NOT NULL COMMENT '参考答案',
`analysis` text COMMENT '解析',
`difficulty` decimal(3,1) DEFAULT '5.0',
`knowledge_points` json DEFAULT NULL COMMENT '知识点标签'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.1.2 组卷算法实现
采用遗传算法实现智能组卷,核心参数:
- 试卷总分:100分
- 难度系数:0.6-0.7
- 知识点覆盖率:≥80%
java复制public Paper generatePaper(PaperRule rule) {
// 初始化种群
List<Chromosome> population = initPopulation(rule);
for (int i = 0; i < MAX_GENERATION; i++) {
// 计算适应度
population.forEach(chromosome ->
chromosome.setFitness(calculateFitness(chromosome, rule)));
// 选择、交叉、变异
population = geneticOperation(population);
}
return getBestPaper(population);
}
3.2 在线考试引擎
3.2.1 考试流程控制
状态转换设计:
mermaid复制stateDiagram
[*] --> DRAFT
DRAFT --> PUBLISHED: 发布
PUBLISHED --> STARTED: 到达开始时间
STARTED --> FINISHED: 到达结束时间
STARTED --> FINISHED: 所有考生交卷
关键接口实现:
java复制@Transactional
public Response submitAnswer(Long examId, List<AnswerDTO> answers) {
// 1. 检查考试状态
Examination exam = examinationRepository.findById(examId);
if (exam.getStatus() != Status.STARTED) {
throw new IllegalStateException("考试未在进行中");
}
// 2. 保存答案(Redis+MySQL双写)
answerService.saveAnswers(answers);
// 3. 触发自动批改
if (exam.isAutoGrading()) {
gradingQueue.publish(new GradingTask(examId, getCurrentUserId()));
}
return Response.success();
}
3.2.2 防作弊方案
采用五层防护体系:
- 题目随机:每个考生获取不同的题目顺序
- 选项乱序:选择题选项随机排列
- 切屏检测:超过3次切屏自动交卷
- 活体检测:考前人脸识别验证
- 行为分析:实时监控答题速度异常
4. 关键问题与解决方案
4.1 高并发提交优化
在模拟测试中,500人同时提交时出现数据库连接耗尽问题。解决方案:
- 连接池优化:
yaml复制# application.yml
spring:
datasource:
hikari:
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
- 批量插入改造:
java复制@Transactional
public void batchSave(List<Answer> answers) {
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
AnswerMapper mapper = session.getMapper(AnswerMapper.class);
answers.forEach(mapper::insert);
session.commit();
} finally {
session.close();
}
}
4.2 主观题批改方案
对于证明题等主观题型,采用"AI初评+教师复核"模式:
- 使用BERT模型提取答题要点
- 与参考答案向量进行相似度计算
- 差异超过阈值时转入人工批改队列
python复制# 评分模型核心逻辑
def calculate_similarity(answer, reference):
answer_embedding = model.encode(answer)
ref_embedding = model.encode(reference)
return cosine_similarity([answer_embedding], [ref_embedding])[0][0]
5. 部署与运维实践
5.1 服务器配置建议
根据实际负载测试结果推荐配置:
| 并发人数 | CPU | 内存 | 服务器数量 | 数据库配置 |
|---|---|---|---|---|
| <500 | 4核 | 8G | 1 | RDS MySQL 2核4G |
| 500-2000 | 8核 | 16G | 2+负载均衡 | RDS MySQL 4核8G+读副本 |
5.2 监控指标设置
关键Prometheus监控项:
- 接口响应时间:
http_server_requests_seconds_max - 数据库连接池使用率:
hikaricp_connections_active - JVM内存:
jvm_memory_used_bytes
告警规则示例:
yaml复制- alert: HighLatency
expr: http_server_requests_seconds_max{uri="/api/submit"} > 5
for: 2m
labels:
severity: critical
6. 踩坑经验分享
-
LaTeX渲染性能:
初始方案直接在前端渲染MathJax,导致100题以上时页面卡顿。优化方案:- 后端预渲染为SVG
- 缓存渲染结果
- 懒加载非可视区公式
-
考试时间同步:
发现不同客户端时间差异可达3秒以上,最终方案:- 开考前同步服务器时间
- 使用WebSocket定期校准
- 倒计时基于服务器事件触发
-
Excel导入内存溢出:
处理5000+题目导入时出现OOM,解决步骤:- 改用Apache POI的SAX模式
- 分批次提交(每500条)
- 增加文件头校验
这个项目让我深刻体会到,教育类系统的核心不是技术复杂度,而是对业务场景的深度理解。比如在防作弊设计中,我们最初的技术方案很完善,但后来发现最大的漏洞其实是"代考"——这促使我们增加了活体检测环节。建议开发类似系统时,一定要先跟着组织方实地观察几次真实考试流程。