1. 项目概述:当Java后端遇上Vue前端
考试管理系统是教育信息化进程中不可或缺的一环。这个基于Java+Vue的全栈项目,完美融合了后端稳定性和前端灵活性。我见过太多学校还在用纸质考试或单机版系统,每次考试前后都要折腾Excel表格,教师手动统计分数到深夜。而这个系统彻底改变了这种低效模式——从题库管理、智能组卷到在线监考、自动阅卷,形成完整的数字化闭环。
技术栈选择上,Java 17作为后端主力(为什么不用旧版本?后面会详细解释),配合Spring Boot 3.x的自动配置魔法;前端用Vue 3组合式API,比Options API更灵活;数据库选用MySQL 8.0,看中的是其JSON字段支持和窗口函数。这套组合在最近三年的教育类项目中验证过稳定性,能承受500人同时在线考试的压力。
关键提示:系统设计时特别注意了"三高"要求——高并发(考试提交峰值)、高可用(断网续考)、高安全(防作弊)。这也是为什么没选PHP+JQuery这种传统方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心功能模块拆解
2.1 智能组卷引擎
组卷算法是系统的灵魂所在。我们实现了三种策略:
- 随机抽题算法:基于Fisher-Yates洗牌算法改进,确保题库中每道题被抽中的概率均等
java复制// 改进版随机算法示例
public List<Question> shuffleQuestions(List<Question> pool, int count) {
Random rnd = ThreadLocalRandom.current();
for (int i = pool.size() - 1; i > 0; i--) {
int index = rnd.nextInt(i + 1);
Collections.swap(pool, index, i);
}
return pool.subList(0, Math.min(count, pool.size()));
}
- 难度均衡算法:根据预设的难度系数(0.3-0.7区间最佳),自动调整各难度题目比例
- 知识点覆盖算法:使用Tagging系统标记题目知识点,确保试卷全面覆盖教学大纲
实测发现,组合使用2、3两种策略时,需要给知识点设置权重系数。我们的经验公式是:
code复制题目权重 = 难度系数 × 知识点重要度 × (1 - 近期使用频率)
2.2 实时防作弊监控
前端每隔10秒通过WebSocket向后端发送:
- 窗口焦点状态
- 屏幕截图哈希值(经Canvas处理)
- 操作行为日志
后端用规则引擎进行异常检测:
mermaid复制graph TD
A[接收监控数据] --> B{全屏状态?}
B -->|否| C[标记异常事件]
B -->|是| D{截图哈希匹配?}
D -->|否| E[触发二级验证]
D -->|是| F[正常记录]
踩坑记录:初期用setInterval做轮询,发现客户端时间不准会导致数据风暴。后来改用WebSocket+心跳机制才稳定。
2.3 自动阅卷子系统
支持四种题型自动批改:
- 客观题:正则匹配答案选项
- 填空题:使用SimHash算法计算文本相似度
- 编程题:基于Docker的沙箱执行
- 简答题:TF-IDF关键词匹配(需人工复核)
其中编程题判题最复杂,流程如下:
- 创建临时Docker容器(资源限制:1核/512MB)
- 注入考生代码和测试用例
- 运行并捕获stdout/stderr
- 对比预期输出与实际输出
- 根据AC(Accepted)/WA(Wrong Answer)规则评分
3. 数据库设计精要
3.1 核心表结构
| 表名 | 关键字段 | 索引设计 | 备注 |
|---|---|---|---|
| exam_info | exam_id(PK), exam_name, start_time, duration | idx_teacher_id | 分区表按年月划分 |
| question_bank | question_id(PK), type, difficulty, tags | idx_tags_fulltext | 启用MySQL全文索引 |
| exam_record | record_id(PK), exam_id, user_id, submit_time | idx_exam_user | 归档策略:3个月冷数据转OSS |
3.2 性能优化实践
分库分表策略:
- 垂直分库:用户数据与考试数据分离
- 水平分表:考试记录按月份分表(exam_record_202307)
缓存方案:
java复制@Cacheable(value = "questions", key = "#questionId",
unless = "#result.difficulty < 0.3")
public Question getQuestionById(String questionId) {
// 查询数据库
}
缓存淘汰策略采用TTL+LRU双机制,避免简单题目长期占用内存。
4. 前后端交互设计
4.1 API规范
采用RESTful+JSON格式,关键接口示例:
code复制POST /api/exam/submit
Headers:
Authorization: Bearer <jwt>
Content-Type: application/json
Body:
{
"examId": "EX2023001",
"answers": [
{
"qId": "Q123",
"content": "A"
}
]
}
4.2 Vuex状态管理
考试页面状态机设计:
javascript复制const examStore = {
state: {
timer: null,
currentQuestion: 1,
answers: new Map()
},
mutations: {
SAVE_ANSWER(state, {qId, answer}) {
state.answers.set(qId, answer)
localStorage.setItem('autosave', JSON.stringify([...state.answers]))
}
}
}
血泪教训:曾经直接操作state导致响应式失效,必须通过mutations修改状态。
5. 部署与监控方案
5.1 容器化部署
Docker Compose配置要点:
yaml复制services:
backend:
image: openjdk:17-jdk
environment:
- SPRING_PROFILES_ACTIVE=prod
deploy:
resources:
limits:
cpus: '2'
memory: 2G
frontend:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./dist:/usr/share/nginx/html
5.2 监控指标
Prometheus监控的关键Metrics:
- 考试提交成功率
- 平均阅卷耗时
- 数据库连接池使用率
- JVM内存压力
配置告警规则示例:
code复制groups:
- name: exam-alert
rules:
- alert: HighSubmissionFailure
expr: rate(exam_submit_failed_total[5m]) > 0.1
for: 10m
6. 踩坑实录与解决方案
跨域问题终极方案:
- 后端配置:
java复制@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://exam.yourdomain.com");
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
- Nginx添加Headers:
code复制add_header 'Access-Control-Allow-Origin' '$http_origin' always;
PDF导出内存泄漏:
使用iText渲染PDF时,发现JVM堆内存持续增长。解决方案:
- 启用分页生成模式
- 设置-XX:+UseG1GC -XX:MaxGCPauseMillis=200
- 添加内存监控钩子:
java复制Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logger.warn("Memory usage before exit: {}",
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
}));
7. 扩展能力设计
插件式架构:
java复制public interface ExamPlugin {
default void beforeExamStart(ExamContext context) {}
default void afterExamSubmit(ExamContext context) {}
}
// 实现示例:人脸识别插件
@Component
public class FaceRecognitionPlugin implements ExamPlugin {
@Override
public void beforeExamStart(ExamContext context) {
// 调用人脸识别SDK
}
}
多端适配方案:
- PC端:完整功能
- 移动端:基于Vant的轻量版
- 微信小程序:Taro跨平台开发
最后分享一个性能优化彩蛋:在监考页面使用Web Worker处理截图分析,避免阻塞UI线程。具体实现是把Canvas操作移到worker中:
javascript复制// worker.js
self.onmessage = function(e) {
const { imageData } = e.data;
const hash = computeHash(imageData);
self.postMessage({ hash });
}
