1. 项目背景与核心需求
在线考试系统是教育信息化进程中的关键基础设施,它彻底改变了传统纸质考试的运作模式。我去年为某职业培训机构开发的这套系统,上线后成功支撑了单场3000人同时在线的认证考试。这种系统通常需要应对几个核心挑战:
- 高并发场景下的稳定性(考试开始时的集中访问)
- 复杂题型(客观题/主观题/编程题)的实时渲染
- 严格的防作弊机制(包括题目乱序、切屏检测等)
- 自动化阅卷与成绩分析
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型分析
2.1 后端选择Spring Boot的三大理由
- 自动装配机制:通过
@SpringBootApplication注解快速整合MyBatis Plus(数据库访问)、Redis(缓存)、RabbitMQ(异步处理)等组件。例如考试提交高峰期,我们用@EnableAsync配合线程池处理阅卷请求:
java复制@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "gradingExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("GradingThread-");
executor.initialize();
return executor;
}
}
-
Actuator监控:通过
/actuator/metrics实时监控JVM状态,特别关注http.server.requests指标应对考试开始时的流量洪峰。 -
Security防护:自定义
UserDetailsService实现多角色(考生/教师/管理员)鉴权,配合@PreAuthorize控制API访问权限。
2.2 前端选择Vue 3的关键优势
- Composition API:将考试计时器、题目导航等逻辑封装成可复用的hooks。例如倒计时功能:
javascript复制// useCountdown.js
export default function(initialTime) {
const timeLeft = ref(initialTime)
const isTimeout = computed(() => timeLeft.value <= 0)
const start = () => {
const timer = setInterval(() => {
timeLeft.value--
if(isTimeout.value) clearInterval(timer)
}, 1000)
}
return { timeLeft, isTimeout, start }
}
-
性能优化:
- 使用
<KeepAlive>缓存题目组件避免重复渲染 - 通过
v-memo记忆化复杂题型(如含公式的数学题)
- 使用
-
TypeScript支持:严格定义试题数据接口:
typescript复制interface Question {
id: number
type: 'single' | 'multiple' | 'essay'
content: string
options?: Option[]
correctAnswer: string | string[]
}
3. 核心模块实现细节
3.1 考试过程控制
采用状态机模式管理考试生命周期:
mermaid复制stateDiagram
[*] --> 未开始
未开始 --> 进行中: 到达开始时间
进行中 --> 已提交: 考生交卷
进行中 --> 已超时: 倒计时结束
已提交 --> 已批改: 自动/手动阅卷
已超时 --> 已批改
对应Spring Boot中的状态变更服务:
java复制public enum ExamStatus {
PENDING, ONGOING, SUBMITTED, TIMEOUT, GRADED
}
@Service
@Transactional
public class ExamStateService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void changeState(Long examId, ExamStatus newStatus) {
String sql = "UPDATE exams SET status = ? WHERE id = ?";
jdbcTemplate.update(sql, newStatus.name(), examId);
}
}
3.2 实时通信方案
- WebSocket广播:当教师端发布考试通知时:
java复制@RestController
@RequiredArgsConstructor
public class NotificationController {
private final SimpMessagingTemplate messagingTemplate;
@PostMapping("/announce")
public void sendAnnouncement(@RequestBody AnnouncementDTO dto) {
messagingTemplate.convertAndSend(
"/topic/exam/" + dto.examId(),
new AnnouncementMessage(dto.content())
);
}
}
- 前端订阅处理:
javascript复制const socket = new SockJS('/ws-endpoint')
const stompClient = Stomp.over(socket)
stompClient.connect({}, () => {
stompClient.subscribe(`/topic/exam/${examId}`, (message) => {
const announcement = JSON.parse(message.body)
useNotificationStore().showAlert(announcement.content)
})
})
4. 安全防护体系
4.1 防作弊技术组合
- 题目乱序算法:
java复制public List<Question> shuffleQuestions(List<Question> original) {
List<Question> shuffled = new ArrayList<>(original);
Collections.shuffle(shuffled,
new SecureRandom(SecureRandom.getSeed(20)));
return shuffled.stream()
.map(q -> q.withShuffledOptions())
.collect(Collectors.toList());
}
- 行为监控矩阵:
| 异常行为 | 检测方式 | 处理措施 |
|---|---|---|
| 切屏超过3次 | document.visibilityState API | 自动提交试卷并标记异常 |
| 答案修改频率过高 | 前端埋点+后端统计 | 触发人工审核流程 |
| IP地址变更 | Spring Session监听 | 强制下线并记录设备指纹 |
4.2 试卷安全存储
采用分段加密策略:
- 使用AES加密试题内容
- 用RSA加密AES密钥
- 数据库存储结构:
sql复制CREATE TABLE exam_papers (
id BIGINT PRIMARY KEY,
encrypted_key TEXT NOT NULL,
iv TEXT NOT NULL,
encrypted_content TEXT NOT NULL,
hash_value CHAR(64) NOT NULL
);
5. 性能优化实战
5.1 数据库分片策略
按考试场次水平分表:
yaml复制# application.yml
spring:
shardingsphere:
datasource:
names: ds0,ds1
sharding:
tables:
exam_records:
actual-data-nodes: ds$->{0..1}.exam_records_$->{2023..2024}
table-strategy:
standard:
precise-algorithm-class-name: com.example.ExamYearPreciseShardingAlgorithm
range-algorithm-class-name: com.example.ExamYearRangeShardingAlgorithm
5.2 前端资源优化
- 懒加载题目图片:
html复制<img
v-lazy="question.imageUrl"
:data-srcset="`${question.imageUrl}?w=400 400w, ${question.imageUrl}?w=800 800w`"
alt="题目插图"
/>
- Web Worker处理批量答案校验:
javascript复制// answer-checker.worker.js
self.onmessage = ({ data: { answers, solutions } }) => {
const results = answers.map(answer =>
deepCompare(answer, solutions[answer.questionId])
)
self.postMessage(results)
}
6. 部署架构设计
采用容器化混合部署:
code复制 +-----------------+
| CDN (静态资源) |
+--------+--------+
|
+----------------------------------------------------------------+
| Load Balancer |
| +---------------------+ +---------------------+ |
| | Spring Boot (Pod1) | | Spring Boot (Pod2) | ... |
| +----------+----------+ +----------+----------+ |
| | | |
| +----------v----------+ +----------v----------+ |
| | Redis (缓存集群) | | MySQL (主从复制) | |
| +---------------------+ +---------------------+ |
+----------------------------------------------------------------+
关键Kubernetes配置片段:
yaml复制# deployment.yaml
resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: "1"
memory: 1Gi
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
7. 踩坑实录与解决方案
-
Vue 3的响应式丢失问题:
现象:批量导入试题时界面不更新
原因:直接修改了数组的length属性
修复:javascript复制// 错误做法 questions.length = 0 // 正确做法 questions.value = [] -
Spring Boot缓存穿透:
现象:高频查询不存在的试卷ID导致DB压力
解决方案:布隆过滤器+空值缓存java复制@Cacheable(value = "papers", key = "#id", unless = "#result == null") public ExamPaper getPaper(Long id) { if(!bloomFilter.mightContain(id)) { return null; } return repository.findById(id).orElse(null); } -
Edge浏览器兼容性问题:
通过navigator.userAgent检测浏览器类型,动态加载polyfill:javascript复制if (/Edg/.test(navigator.userAgent)) { import('edge-polyfills').then(() => { initApp() }) } else { initApp() }
这套系统经过三个月的迭代开发,最终实现了98.7%的考试成功率。关键经验是:在考试开始前2小时进行全链路压测(使用JMeter模拟300%的预期流量),并建立完善的监控看板(包含Prometheus+Grafana监控体系)。对于需要快速上线的团队,建议先聚焦核心考试流程(注册-登录-考试-交卷),其他功能如数据分析模块可以后续迭代。
