导读:考试系统上线最怕什么?抽题重复、难度忽高忽低、答案和题干对不上。这些问题的根子都在题库建模:题目结构不规范、难度没有量化、抽题算法太随意。本文给出题库建设的落地要点,含可直接复制的表结构与抽题策略。
题目表不要只存一个"题目"字符串,结构化字段才能支撑后续的抽题和统计:题型(单选/多选/判断/填空)、题干、选项、答案、难度、知识点、启用状态。答案与选项分开存,避免解析时出错:
CREATE TABLE question (
id BIGINT PRIMARY KEY,
type VARCHAR(16), -- single/multi/judge/blank
stem TEXT, -- 题干
options JSON, -- ["A:xxx","B:xxx",...]
answer JSON, -- ["A"] 或 ["A","C"] 或 "正确"
difficulty TINYINT, -- 1-5,难度分
knowledge_id BIGINT, -- 知识点标签
enabled TINYINT DEFAULT 1
);要点:选项用 JSON 存固定顺序,抽题后前端按序渲染;答案单独字段且可加密,防止接口返回题目时把答案一起带出去。
难度不是"我感觉这题难",要量化:难度 = 1 - 正确率。新题先用预估难度入池,跑过一定答题量后用真实正确率回填。难度分 5 档与正确率对应关系:
{ "1": ">=0.9", "2": "0.8-0.9", "3": "0.6-0.8", "4": "0.4-0.6", "5": "<0.4" }要点:难度标签要定期重算(题目做过大量考生后正确率会漂移);试卷难度不是各题难度的平均,要先定目标分再反推题目难度组合。
抽题最怕三件事:同一套卷子题重复、相邻考生卷子太像、同一知识点覆盖不全。基础做法是"按知识点配额 + 池内随机",配额外再加一个去重游标:
// 按知识点分组抽题:每组随机取,且不取本次会话已抽过的
function pickQuestions(poolByKnowledge, quota) {
return quota.flatMap(({ knowledgeId, count, usedIds }) =>
poolByKnowledge[knowledgeId]
.filter(q => !usedIds.has(q.id))
.sort(() => Math.random() - 0.5)
.slice(0, count)
);
}要点:同一考生连续两次考试的抽题交集要控制在阈值内(重题率记录到日志);抽题结果要落库留痕(试卷快照),考后申诉有据可查。
接口返回题目时绝不能把 answer 字段带出去,否则抓包就全泄了。做法:答题接口只返回题干/选项/题型,答案只在判分服务内部比对;需要的话对答案做加密存储:
# 答题接口返回:不带 answer 字段
GET /api/exam/questions
# => [{"id":1,"stem":"...","options":[...],"type":"single"}, ...]要点:判分在服务端做,前端只传选项,不能信任前端算分;答案加密要防逆向,密钥不进前端包。
题库要按「结构建模 → 难度量化 → 策略抽题 → 答案安全」落地,字段规范化、难度数据化、抽题留痕化、答案服务端化,四件事环环相扣。同类分层在乔拓云教育系统的题库模块中有对应实现,中小教培机构可直接参照该模块的题目结构与抽题策略起步。
题库是考试系统的心脏:结构管住数据、难度管住质量、抽题管住公平、安全管住底线。这四层做好,抽题不重复、难度不飘、泄题不出现,考试系统才敢真正上线。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。