在线考试系统本身不是一个新话题,但“在线考试 + AI 大模型”的组合值得展开聊一下。传统考试系统大多停留在客观题自动判分、人工批改主观题的阶段,组卷靠随机抽题,考后只有简单的分数统计。这个项目的核心变化在于:用 SpringBoot 搭建可靠的考试主链路,同时把大模型接入到自动组卷、智能阅卷、考情分析等环节,让系统具备“出题智能、批改智能、分析智能”的能力。
这篇文章主要解决四个问题:
- 在线考试系统的核心功能模块如何拆。
- SpringBoot 后端如何对接 AI 大模型接口,而不是把模型调用写死在业务代码里。
- 自动组卷和智能阅卷怎么落地,包含可参考的表结构、接口设计和算法思路。
- 开发过程中会遇到哪些坑,以及如何做好成本、稳定性和合规控制。
适合正在做在线教育、企业培训、认证考试平台的 Java 开发人员,也适合想了解“大模型 + 业务系统”如何深度融合的架构师。如果你只关心"怎么调用大模型 API",可以直接跳到第 5 章;如果关心系统能不能真正用于严肃考试,第 7、8 章值得认真看。
1. 核心能力速览
从整体设计看,这套系统具备以下能力特征:
| 能力项 | 说明 |
|---|---|
| 项目定位 | 基于 SpringBoot 构建的在线考试系统,集成 AI 大模型能力 |
| 核心模块 | 用户认证、题库管理、自动组卷、在线考试、智能阅卷、成绩分析 |
| AI 能力 | 自动生成试题、主观题智能评分、考情分析与学习建议 |
| 后端技术栈 | SpringBoot、MyBatis Plus 或 Spring Data JPA、MySQL、Redis |
| 队列与异步 | 建议引入 RabbitMQ 或 RocketMQ 处理批改任务和考试事件 |
| 模型接入方式 | HTTP 接口对接大模型 API,支持 OpenAI 兼容协议 |
| 扩展性 | 模型层做统一接口封装,可切换不同厂商大模型 |
| 部署形态 | 单体优先,按模块边界演进为微服务 |
| 合规要求 | 考生数据脱敏、人脸识别需用户授权、AI 判分需人工复核 |
这些能力不是一次性做完的,通常有一个主线:先完成题库、试卷、考试、成绩这些基础闭环,再加入 AI 组卷和 AI 批改。下面按这个顺序展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 适用场景与使用边界
2.1 适合的场景
- 校园线上期末考试、随堂测验。
- 企业入职考试、安全培训考核。
- 证书认证类模拟考试。
- 题库运营平台,需要快速产出大量试题。
- 培训机构需要给学员生成个性化练习题。
这类场景有一个共同特点:考试流程要标准化,AI 只负责提效,不能完全替代规则。
2.2 不适合的场景
- 高利害法定考试,例如高考、司法考试。这类考试对命题、评分、防作弊有极严格的法律要求,AI 不能作为最终判分依据。
- 需要严格监管审计的场景,AI 生成内容的可解释性还不够。
- 完全依赖 AI 主观题判分且不设人工复核的场景,风险不可控。
2.3 使用边界与合规提醒
- AI 生成试题时,要确保不侵犯第三方版权,尤其不要在提示词中直接要求“仿照某某教材原文出题”。
- 涉及考生人脸、声音、行为数据的采集,必须获得用户明确授权,并在隐私说明中写明用途。
- AI 评分会存在模型偏差,建议系统设计上强制人工抽检比例。
- 对接大模型 API 时,需要确认数据脱敏策略,避免把考生真实姓名、身份证号等敏感信息发送给模型服务。
3. 总体架构与模块设计
3.1 总体架构
这套系统的架构建议保持简单。初期不一定要拆微服务,单体应用 + 模块化分包,配合消息队列做异步任务,已经足够支撑中等规模的考试场景。
推荐分层方式:
| 模块 | 职责 |
|---|---|
| exam-core | 考试领域模型、基础实体、通用工具 |
| exam-question | 题库管理,题目导入导出,题目审核 |
| exam-paper | 手动组卷、自动组卷、试卷策略 |
| exam-online | 考试流程、防作弊、答案提交 |
| exam-ai | 大模型统一客户端、Prompt 管理、AI 评分服务 |
| exam-analysis | 成绩分析、知识点掌握度分析 |
text复制前端 H5/PC
|
| HTTPS / JSON
v
SpringBoot 网关 / Controller 层
|
+-- exam-online 在线考试流程
+-- exam-question 题库模块
+-- exam-paper 组卷模块
+-- exam-ai 大模型调用服务
| |
| +-- 大模型 API 1
| +-- 大模型 API 2
+-- exam-analysis 考情分析
|
v
MySQL Redis RabbitMQ OSS/MinIO
单体架构最大的好处是开发调试方便,事务边界清晰。当题库、考试、AI 批改并发量增长后,再按模块拆分为独立服务,这里的设计已经为拆分留好了边界。
3.2 核心流程
在线考试的核心流程如下:
- 教师创建题库,导入题目,设置知识点和难度。
- 教师配置组卷策略,系统自动生成试卷。
- 管理员发布考试,生成考试令牌。
- 考生进入考试,前端定时拉取试卷,提交答案。
- 系统客观题自动判分,主观题进入 AI 评分队列。
- AI 评分完成后,结合人工复核,生成最终成绩。
- 系统生成考试分析报告。
第 5 步是关键设计。客观题判分在事务内完成,主观题判分全部异步化,避免考生提交试卷时长时间等待。
4. 数据库设计与核心表
在线考试系统的表结构相对成熟,建议从以下核心表开始。
4.1 表结构概览
| 表名 | 用途 |
|---|---|
| user | 用户表,包含考生和教师 |
| question_bank | 题库表 |
| question | 题目表,包含题型、知识点、难度 |
| paper | 试卷表 |
| paper_question | 试卷题目关联表 |
| exam | 考试表 |
| exam_record | 考生考试记录 |
| exam_answer | 考生作答明细 |
| ai_review_record | AI 评分记录 |
4.2 核心建表语句
sql复制CREATE TABLE question (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
bank_id BIGINT NOT NULL COMMENT '题库ID',
question_type TINYINT NOT NULL COMMENT '1单选 2多选 3判断 4填空 5简答',
content TEXT NOT NULL COMMENT '题目内容',
options TEXT COMMENT '选项JSON',
answer TEXT COMMENT '参考答案',
analysis TEXT COMMENT '解析',
knowledge_point VARCHAR(255) COMMENT '知识点',
difficulty TINYINT COMMENT '难度1-5',
ai_generated TINYINT DEFAULT 0 COMMENT '是否AI生成',
status TINYINT DEFAULT 1 COMMENT '状态',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE exam_answer (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
exam_id BIGINT NOT NULL,
paper_question_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
answer_text TEXT COMMENT '考生答案',
objective_score DECIMAL(5,2) DEFAULT 0 COMMENT '客观题得分',
ai_score DECIMAL(5,2) COMMENT 'AI评分',
manual_score DECIMAL(5,2) COMMENT '人工复核评分',
final_score DECIMAL(5,2) COMMENT '最终得分',
review_status TINYINT DEFAULT 0 COMMENT '0未评 1AI完成 2人工复核',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
sql复制CREATE TABLE ai_review_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
answer_id BIGINT NOT NULL,
prompt_version VARCHAR(64) COMMENT 'Prompt 版本',
model_name VARCHAR(128) COMMENT '模型名称',
request_content TEXT COMMENT '发送给模型的请求内容',
response_content TEXT COMMENT '模型返回内容',
score DECIMAL(5,2) COMMENT '评分结果',
confidence DECIMAL(5,2) COMMENT '置信度',
status TINYINT DEFAULT 0 COMMENT '0处理中 1成功 2失败',
error_msg VARCHAR(500) COMMENT '错误信息',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
这里要重点说明 ai_review_record 这张表。AI 评分是高风险操作,每次调用都记录请求和响应,一方面方便追溯问题,另一方面可以统计 token 消耗。这里的 prompt_version 字段很重要——调试 AI 评分效果时,我们经常要对比不同 Prompt 版本的效果,没有这个字段,线上数据会一团乱。
5. AI 大模型接入设计
5.1 模型接入层
AI 接入最忌讳把模型调用代码散落在业务 Service 中。建议定义一个统一接口:
java复制public interface AiModelClient {
String chat(String prompt, String modelName);
String chatWithJson(String prompt, String modelName);
}
然后为不同厂商实现各自的客户端,通过 Spring 的 @ConditionalOnProperty 或配置中心动态切换。市面上主流的模型服务大多兼容 OpenAI 协议,所以一个基于 HTTP 的通用客户端即可覆盖大多数场景。
bash复制# application-ai.yml 配置示例
ai:
provider: openai-compatible
openai:
base-url: https://api.example.com/v1
api-key: ${AI_API_KEY}
model: gpt-4o-mini
timeout: 60000
注意,这里的 api.example.com 是示意,实际使用时要替换为具体模型服务商的域名。不要把 API Key 写在配置文件中提交到 Git,建议通过环境变量注入。
5.2 通用 HTTP 客户端
如果项目暂时不引入 Spring AI 等封装库,直接用 Spring 的 RestTemplate 或者 JDK 17 的 HttpClient 也能完成:
java复制@Component
public class OpenAiCompatibleClient implements AiModelClient {
private final RestTemplate restTemplate;
private final AiProperties properties;
public OpenAiCompatibleClient(RestTemplate restTemplate, AiProperties properties) {
this.restTemplate = restTemplate;
this.properties = properties;
}
@Override
public String chat(String prompt, String modelName) {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", modelName);
requestBody.put("messages", List.of(
Map.of("role", "system", "content", "You are a helpful assistant."),
Map.of("role", "user", "content", prompt)
));
requestBody.put("temperature", 0.2);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(properties.getApiKey());
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
ResponseEntity<Map> response = restTemplate.postForEntity(
properties.getBaseUrl() + "/chat/completions", entity, Map.class);
Map body = response.getBody();
List choices = (List) body.get("choices");
Map firstChoice = (Map) choices.get(0);
Map message = (Map) firstChoice.get("message");
return message.get("content").toString();
}
}
因为这个客户端是同步阻塞式调用,建议用 CompletableFuture 或消息队列异步执行 AI 批改任务。
5.3 提示词工程
AI 评分时,Prompt 设计决定了上限。一个稳妥的主观题评分 Prompt 应该包含以下内容:
- 题目内容。
- 参考答案。
- 评分标准,按点给分。
- 考生答案。
- 输出格式,强制要求 JSON。
- 分数范围。
text复制你是一名严格的考试阅卷教师。请根据题目和参考答案,对考生答案评分。
题目:
{题目内容}
参考答案:
{参考答案}
评分标准:
1. 每答对一个要点得 5 分,答错不倒扣分。
2. 全部答对得满分 20 分。
3. 与参考答案无关的内容不得分。
考生答案:
{考生答案}
要求:
1. 只输出 JSON,不要包含任何解释。
2. JSON 格式:{"score": 15, "reason": "考生答对3个要点,表述基本准确。"}
这里输出格式强制为 JSON,方便后端解析。实际效果要看模型版本,测试阶段建议人工核对 100 条答案,统计分数误差。误差较大的题目要调整 Prompt,或者只让 AI 给出评分理由和参考要点,由人工教师做最终打分。
6. 自动组卷引擎
6.1 组卷约束模型
自动组卷本质是一个组合优化问题。我们需要根据试卷总分、题型数量、知识点覆盖范围、难度分布和区分度要求,从题库中选择合适的题目。
定义组卷策略:
java复制public class PaperStrategy {
private int totalScore;
private Map<Integer, Integer> questionTypeCount;
private Map<String, Integer> knowledgePointCount;
private List<Integer> difficultyDistribution;
}
约束条件至少包括:
- 知识点不得重复覆盖过多。
- 高分题目占比要控制在合理范围。
- 同一场考试不能出现完全相同的题目组合。
- 题目之间不能存在提示关系,例如简答题的答案出现在选择题选项中。
6.2 组卷算法思路
一个实用的方案是贪心 + 随机回溯的混合方式。先按题型排序,逐题选择一个符合剩余约束的题目;如果找不到,则回退到上一题,重新选择。单张试卷生成逻辑可以在 1 秒内完成。
java复制public List<Question> generatePaper(PaperStrategy strategy) {
// 1. 按题型、知识点、难度查询候选题目
List<Question> candidates = questionMapper.selectCandidates(strategy);
// 2. 使用回溯算法选择题目,优先满足知识点的均匀覆盖
List<Question> selected = new ArrayList<>();
backtrack(strategy, candidates, selected, 0);
return selected;
}
对于大规模题库,查询条件建议走索引,避免每道题实时计算匹配。更稳妥的做法是把题库的元数据缓存在 Redis 中,组卷时基于缓存数据执行算法,组卷完成后再按题目 ID 回表查询完整题目内容。
6.3 与 AI 生成题目的联动
自动组卷不一定要从已有题库中选。当题库题目数量不足时,可以先用 AI 生成一批候选题目,再执行组卷算法。这里要注意:AI 生成的题目质量不稳定,必须设置教师审核环节。从项目安全角度,未经审核的题目不能进入正式试卷。
7. 在线考试主流程
7.1 考试令牌
考生开始考试时,系统要签发一个考试令牌:
- 令牌包含考试 ID、开始时间、结束时间、考生 ID。
- 令牌存储在 Redis 中,设置与考试结束时间一致的过期时间。
- 提交答案时必须校验令牌,防止未报名用户直接调接口提交。
java复制public ExamToken createToken(Long examId, Long userId) {
ExamToken token = new ExamToken();
token.setExamId(examId);
token.setUserId(userId);
token.setCreateTime(new Date());
token.setExpireTime(examService.getEndTime(examId));
String key = "exam:token:" + userId + ":" + examId;
redisTemplate.opsForValue()
.set(key, JSON.toJSONString(token),
token.getExpireTime().getTime() - System.currentTimeMillis(),
TimeUnit.MILLISECONDS);
return token;
}
7.2 答案提交
考生答案的提交接口建议设计成批次提交,而不是每题一个请求。避免考试结束时大量请求同时打入数据库,把数据库连接池打满。
json复制{
"examId": 1001,
"answers": [
{
"paperQuestionId": 50001,
"answer": "A"
},
{
"paperQuestionId": 50002,
"answer": "{\"content\":\"SpringBoot 核心组件...\"}"
}
]
}
客观题部分可以在提交时直接判分,主观题部分写入消息队列,由 AI 评分服务消费处理。这样做的好处是提交接口响应时间可控,考生体验更稳定。
7.3 防作弊辅助
在线考试系统要不要做防作弊,取决于应用场景。对于企业内部考试,通常至少需要以下能力:
- 进入考试前记录人脸信息,考试过程中定时抓拍。
- 交卷时比对考生中途离开时间,检测异常切屏行为。
- 切屏次数超过阈值时,标记为“作弊嫌疑”,由人工复核。
- 题目顺序随机打乱,同一道题的选项顺序也可以随机。
人脸识别部分可以考虑接入第三方服务,但必须获取用户授权。项目设计上,可以将这些能力做成可插拔模块,不放在核心考试链路中,这样不同客户可以按需求开启或关闭。
8. 智能阅卷与成绩分析
8.1 智能阅卷流程
整个批改进程设计如下:
- 考试结束,客观题已自动判分。
- 主观题答案进入批改队列。
- AI 评分服务分批消费任务,调用大模型接口。
- 解析模型返回的 JSON,计算得分。
- 低于置信度阈值的记录自动标记为“需要人工复核”。
- 教师登录系统查看 AI 评分结果,可修改分数并填写批注。
- 最终分数以人工复核后为准。
java复制@Service
public class AiReviewService {
@Autowired
private AiModelClient aiModelClient;
@Autowired
private ExamAnswerMapper examAnswerMapper;
public void review(Long answerId) {
ExamAnswer answer = examAnswerMapper.selectById(answerId);
Question question = questionMapper.selectById(answer.getPaperQuestionId());
String prompt = buildReviewPrompt(question, answer.getAnswerText());
String result = aiModelClient.chatWithJson(prompt, "gpt-4o-mini");
ReviewResult reviewResult = JSON.parseObject(result, ReviewResult.class);
answer.setAiScore(reviewResult.getScore());
answer.setReviewStatus(1);
examAnswerMapper.updateById(answer);
saveAiReviewRecord(answerId, reviewResult);
}
}
8.2 成绩分析与报告
考试结束后,系统可以基于成绩数据和大模型能力生成更丰富的报告。传统系统一般只输出平均分、最高分、及格率。AI 可以进一步输出:
- 知识点薄弱项分析。
- 学生常见误区总结。
- 个性化学习建议。
这类分析建议异步生成,因为需要组合多次大模型调用。可以先统计学生错题分布,再调用大模型生成对应建议,最后将报告存为 PDF 或 HTML。
8.3 人工复核的工程实现
AI 批改上线时,较稳妥的做法是让系统自动抽 30% 的主观题答案进入人工复核区,其余 70% 的分数也允许教师一键调整。人工复核页面需要展示三列:考生答案、AI 评分理由、AI 得分。这样教师不需要打开多个页面,复核效率更高。
9. 开发环境与部署
推荐开发环境如下:
| 组件 | 建议版本 |
|---|---|
| JDK | 17 或 21 |
| SpringBoot | 3.x |
| MySQL | 8.0 |
| Redis | 6.x |
| RabbitMQ | 3.x |
| Maven | 3.8+ |
以下是一个简单的 Docker Compose 部署参考,实际镜像版本以官方为准:
yaml复制version: "3.8"
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: exam123456
MYSQL_DATABASE: exam_system
ports:
- "3306:3306"
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
应用服务本身的部署建议用 Docker 镜像,CI 流程中完成 Maven 构建、镜像打包、环境变量注入。API Key 等敏感信息统一放在环境变量或密钥管理服务中。
10. AI 调用性能与成本考量
在线考试系统接入大模型 API,与本地部署大模型的资源考量不同,这里重点不是显存,而是调用延迟、并发和 token 消耗。
10.1 调用延迟
一次主观题批改的 API 调用可能耗时 3 到 10 秒,如果一张试卷有 3 道主观题,1000 人同时交卷,会产生 3000 个批改任务。如果全部走同步调用,接口会被拖垮。
建议方案:
- 设置批改线程池,控制最大并发调用数。
- 使用消息队列削峰,考试结束高峰期不会压垮 AI 服务。
- 对模型服务依赖做超时熔断,模型服务不可用时,降级为“待人工批改”。
10.2 Token 消耗控制
大模型服务通常按 token 计费,考试系统的 token 消耗大头在主观题批改和 AI 分析报告。降低消耗的措施:
- 提交答案时截断过长文本。
- 评分时只发送题目和参考答案,不发送全部知识点资料。
- 模型输出强制 JSON 精简模式,控制
max_tokens。 - 不保留无用的历史对话上下文,每次请求都独立完成。
- 批量题目合并在一个请求中请求模型评分,减少请求次数,但要遵守模型单次输出长度限制。
10.3 模型选型建议
考后批改场景建议选择推理能力较强的模型,尤其要注意长文本理解和评分稳定性问题。如果预算有限,可以在低风险题型上用轻量模型,在高风险的主观题上使用更强的模型。不同题型使用不同模型,这个配置要放到系统设置中,而不是写死在代码里。
11. 常见问题与排查方法
下面是这套系统开发调试过程中最常见的几类问题。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 考生交卷后长时间没有主观题成绩 | 消息队列积压或 AI 调用超时 | 查看 RabbitMQ 队列长度、查看 AI 调用日志 | 增加消费者数量;调整模型超时时间;失败任务重试 |
| AI 评分出现极端分数 | Prompt 中没有明确分数边界 | 打开 ai_review_record 查看 prompt 和模型返回 | 在 Prompt 中增加分数范围约束,增加置信度校验逻辑 |
| 自动组卷反复选不出符合条件的题目 | 题库题目数量不足,约束条件过强 | 打印组卷约束条件,测试最小约束 | 扩充题库;降低知识点覆盖率;允许部分题目重复使用 |
| 试卷提交后页面卡住 | 提交接口中存在同步 AI 调用 | 检查接口调用链 | 必须改为异步队列处理 |
| 大模型 API 调用出现 401 | API Key 配置错误或过期 | 查看环境变量和日志 | 通过密钥管理平台注入 Key |
| 同一道主观题,不同时间评分结果不一致 | 模型推理存在随机性 | 降低 temperature 参数;记录 prompt_version | 固定 temperature 为 0;评分进行多次采样取中位数 |
| 考试成绩泄露 | 考试令牌未校验或 Redis 过期时间设置错误 | 检查接口拦截器 | 在拦截器中校验令牌,交卷后立即删除令牌 |
| AI 生成题目出现事实错误 | 模型本身存在幻觉风险 | 抽查 AI 生成题目质量 | 强制教师审核后再入库;用题库事实库做校验 |
这里要强调一个开发习惯:AI 相关的调用必须在测试环境用真实数据验证三轮以上,不要第一次上线就直接接生产库。AI 评分的不稳定性只有通过数据才能发现。
12. 最佳实践与后续扩展方向
12.1 工程化建议
AI 接入在线考试系统,关键不在模型调得多花哨,而在工程控制是否到位。以下几点是实践中比较有效的做法:
- 所有 AI 调用必须异步化,不能阻塞主业务链路。
- 每次 AI 调用记录完整的请求和响应,便于问题追溯。
- Prompt 加入版本号,修改后留痕。
- AI 评分结果不能直接作为最终成绩,必须设计人工复核环节。
- 使用 Mock 服务测试考试流程,避免开发阶段消耗真实 token 费用。
- 涉及考生数据时,使用脱敏后的匿名 ID 调用 AI 服务,不要在 Prompt 中携带真实姓名和手机号。
- 考试高峰期要对 AI 服务做一次性容量评估,充分预估并发量。
12.2 可持续扩展方向
这套系统跑通之后,还可以继续扩展这些方向:
- 智能错题本:根据考生错题分布,生成个性化复习错题集。
- 相似度检测:用大模型判断主观题答案与历年满分答案的相似度,辅助查重。
- 口语考试评分:接入音频模型,实现英语口语自动评测。
- AI 助教:考生答题后触发生成解析,并推送相似题练习。
- 知识图谱:将题库知识点构建成图谱,支撑更精细的学情分析。
从开发优先级上看,建议先把智能阅卷做扎实,这是考试系统中最有价值的 AI 场景。自动出题可以作为辅助功能,用较低成本验证效果。等这两条链路稳定,再去做报告生成、口语评分等更复杂的场景。在线考试系统接入 AI 大模型并不是赶时髦,而是真正能减少教师重复劳动、缩短成绩反馈周期的需求。难点在于工程稳定性,而不是模型选型。如果你正在规划类似系统,优先把异步批改链路、评分记录表和人工复核流程这三个设计做好,系统上线后的维护成本会低很多。
