调研报告 · 题库分类体系
开源题库系统数据结构调研:
TAO × 学之思 × Spring Boot Online Exam
调研日期:2026-08-14。方法:shallow clone 三个仓库(及 TAO 相关扩展),直接阅读 SQL 建表语句与 Java/PHP 源码,核对实体、字段与判分逻辑。所有结论均来自源码,非文档转述。
〇、三家系统定位速览
| 系统 | 仓库 | 技术栈 | 定位 | 许可证 | 题库在系统中的地位 |
|---|---|---|---|---|---|
| TAO | github.com/oat-sa(tao-core + extension-tao-item / -itemqti / -test / -testqti / -delivery / -outcome) | PHP + RDF 本体(generis),QTI/LTI 标准 | 商业级开源测评平台(卢森堡大学发起,OAT 维护) | AGPL/GPL 系 | 核心:题库(Item Bank)+ 试卷(Test)+ 投放(Delivery)+ 结果(Outcome)四段式 |
| 学之思 | github.com/mindskip/xzs | Java SpringBoot + Vue + 微信小程序 | 在线考试系统(教育机构/企业) | AGPL-3.0 | 子模块:题库管理 → 组卷 → 考试 → 判分 → 错题本 |
| Spring Boot Online Exam | github.com/lsgwr/spring-boot-online-exam | Java SpringBoot + Vue | 通用在线考试系统(演示型) | 无明确开源协议(README 声明开源)2 | 子模块:题库 → 固定卷考试 → 记录 |
一句话:TAO 是「测评数据模型」的行业天花板;学之思是「国产考试系统」里工程最完整的参考;sboe 是 2019 年的教学演示项目,结构简单、反例也多。
一、题目分类体系对比
1.1 学之思:4 个标量字段,题型即分类
t_subject(学科,自带 level 年级 1-12)+ t_question 上的 4 个字段:
| 字段 | 取值 | 语义 |
|---|---|---|
| subject_id | 学科 id(语文/数学/英语…) | 学科维度 |
| grade_level | 1-12 | 年级 |
| question_type | 1 单选 / 2 多选 / 3 判断 / 4 填空 / 5 简答(Java 枚举 QuestionTypeEnum 写死在代码里) | 作答形式,兼当「题型」 |
| difficult | 难度档 | 难度 |
- 关键事实:question_type 一个字段同时承担「作答形式」和「题型」两种职责;新增题型必须改 Java 枚举 + 前端组件 + 判分逻辑,无法配置化扩展。
- 无材料维度、无输入模态维度、无技能维度。听力/阅读材料没有实体,只能塞进题干富文本,多题共享靠复制。
1.2 sboe:三张字典表,题型只有 3 种
question_category(学科字典:天文/数学/物理/生物/地理/化学/英语/历史/人文/生活)、question_level(难度 high/middle/low)、question_type(只有 single/multi/judge 三种,没有填空和简答)。题目通过 3 个外键引用。
- 字典表化是进步(加行即可扩展学科/难度),但试卷结构按题型固定列(见 2.3),题型枚举仍写死。
- 试卷也只能组单选/多选/判断,主观题完全缺席。
1.3 TAO:没有内置分类体系,分类=类树+属性,自由度最高但零约束
- TAO 的数据层是 RDF 本体(generis):一切资源都是类的实例,题库就是一棵用户自定义的类树(Item 类的子类可以随便建,如「听力/阅读/语法」),Item 再挂任意属性/元数据。
- 读 extension-tao-item/models/ontology/taoitem.rdf:Item 类(subClassOf AssessmentContentObject)只有 ItemContent(内容文件)、ItemModel 等基础设施属性,没有任何学科/题型/难度之类的内置分类字段。
- 结论:TAO 给的是「自由分类引擎」,你要什么分面自己建属性,但不强制、不校验——数据质量完全靠使用方的流程约定。
1.4 三家 vs 你的分面体系
| 你的维度 | 学之思 | sboe | TAO |
|---|---|---|---|
| A 技能领域(L/R/W/T/S/K) | ✗ | ✗ | ✗(需自建属性) |
| B 作答形式(b_form) | 部分:question_type(且与题型混在一起) | 部分:question_type(3 种) | 结构化:QTI interaction 类型(20+ 种,见 3.1) |
| C 材料体裁 | ✗ | ✗ | ✗(无材料实体,见 3.2) |
| D 输入模态(TEXT/AUDIO/VIDEO/IMAGE) | ✗(图片可塞富文本) | ✗(图片 base64 塞题干 HTML) | 部分:媒体作为 item 附件,但非独立实体 |
| 题型(受控词表) | 与 question_type 合一 | 与 question_type 合一 | 无题型概念(题型=interaction 组合的自由度) |
| 评分方式(MACHINE/HUMAN/AI/HYBRID) | 隐式:判分逻辑按枚举分支 | 隐式:只有机器可判题型 | 显式:responseDeclaration + responseProcessing(见 3.1) |
核心结论:三家开源系统里没有任何一家实现了「材料/题组/题原子/计分点」四级实体,也没有「正交分面分类」——你的设计在开源参照物里是独一份,参考价值主要来自局部做法。
- 表 3 中「✗」表示在建表 SQL、实体类与本体文件中均未找到对应字段或实体;「部分」表示有相近机制但不完整。↩
本节注释
二、数据结构逐家拆解
2.1 学之思(xzs-postgresql.sql)
实体关系1:
t_subject(学科+年级)
└─ t_question(题型/学科/分值(千分制)/年级/难度/正确答案文本)
├─ info_text_content_id → t_text_content(题干富文本:题面/填空/解析/答案全在这里)
t_exam_paper(固定卷/时段卷)
├─ frame_text_content_id → t_text_content(试卷结构:JSON,见下)
t_exam_paper_answer(一次作答的卷面快照:paper_name/paper_type/subject_id/system_score/user_score/question_correct/question_count/do_time/status)
t_exam_paper_question_customer_answer(逐题作答快照)
├─ question_id / question_type / subject_id / customer_score / question_score
├─ question_text_content_id(题干快照)/ text_content_id(填空简答的学生作答文本)
└─ answer / do_right / item_order
t_task_exam + t_task_exam_customer_answer(任务组卷 + 任务作答)
要点:
- 试卷结构是 JSON:ExamPaperServiceImpl 把「每个题型 × 题数 × 分值」序列化成 JSON 存入 t_text_content(frame_text_content_id)——组卷模板与内容解耦,这是亮点,但塞进富文本表是权宜。
- 作答全快照:试卷名字、题型、分值、题干、答案全部在作答时冗余复制进 customer_answer 表——题目后续改版不影响历史记录。
- 判分在提交端:ExamPaperAnswerServiceImpl 里 doRight = customerScore.equals(questionScore),customer_score 来自提交报文;填空的作答以 contentArray JSON 保存(needSaveTextContent = 填空+简答),简答没有人工/AI 评分管线,展示参考答案让学生自评。
- 题干/解析/答案混在一个富文本(infoTextContentId),图片内嵌 HTML——机器不可解析,统计/检索困难。
2.2 sboe(doc/sql/exam.sql)
question_category(学科字典) question_level(难度字典) question_type(题型字典)
question(题干=longtext HTML(含 base64 图片)、question_option_ids 用"-"拼接、question_answer_option_ids 正确选项"-"拼接)
└─ question_option(独立选项表:内容 + 解析)
exam(试卷:exam_question_ids 全部题号"-"拼接 + 按题型分列 radio/check/judge 题号 + 各题型单题分值列 + 时限/有效期)
exam_record(answer_option_ids varchar(4096):questionId@True/False_选项id串 用 $ 连接的一条大字符串)
exam_record_level(优秀/良好/一般/及格/不及格)
要点与反例:
- 选项独立表 ✓(对比 xzs 把选项藏进题干富文本,这是更好的做法)。
- 题目 ID、答案、作答记录全部用
-/$/@拼接的 varchar 大字符串 ✗——无法做任何关系查询、统计、版本管理。 - 试卷按题型固定列(radio/check/judge 三列)+ 固定单题分值 ✗——新增题型要改表结构。
- 题干富文本直接内嵌 base64 图片 ✗——数据膨胀、无法复用。
- 作答记录里冗余存了每题对错标志(questionId@True/False_...),判分是提交时字符串比对。
2.3 TAO(RDF 本体 + QTI XML)
数据模型是「资源-属性」三元组,核心四段:
Item(题库,class 树自由分类)
└─ ItemContent:QTI XML 文件(itemBody + interactions + responseDeclaration + responseProcessing)
Test(试卷库,class 树)
└─ TestContent:QTI assessmentTest XML
├─ testPart → assessmentSection(可嵌套)→ assessmentItemRef(引用 Item)
└─ itemSessionControl(最大尝试次数/是否可跳过/何时显示反馈)+ outcomeProcessing(分数规则)
Delivery(投放:把 Test 分发给 TestTaker 的会话)
Outcome / Results(逐题结果变量流:每个 interaction 的响应值 + 分数)
- Item 与 Test 彻底分离:Item 是独立资源(QTI 文件),Test 通过 assessmentItemRef 引用——一道题可被多张试卷复用,且题改版通过 revision 管理。
- 答案结构精确声明(ResponseDeclaration):每个交互对应一个 responseDeclaration,声明 identifier、baseType(identifier/integer/float/string/point/pair…)、cardinality(single/multiple/ordered/container)、correctResponse、mapping(部分给分:选项到分值的映射表)、areaMapping、howMatch。
- 判分规则 = responseProcessing:机器判分在题目 XML 内声明(如 matchCorrect 后按 mapping 给分);开放题(extendedTextInteraction / uploadInteraction)不声明自动判分,留给外部评分器/人工。
- 题型即交互:20+ 种 interaction(Choice/Order/Match/GapMatch/Associate/InlineChoice/TextEntry/ExtendedText/Hotspot/Hottext/Graphic 系列/SelectPoint/Slider/Upload/Media/EndAttempt/Custom)2——作答形式的表达力远超「单选/多选/判断」枚举,且新增交互=新插件(PCI),不用改核心。
- 没有材料实体:音频/视频/图片作为 item 的媒体附件(MediaInteraction/<object>)挂在单题上;同一篇听力材料被 5 道题共用时,是 5 份附件,不是 1 个共享实体。
三系统「题目 → 试卷 → 作答记录」实体关系对照
按各仓库建表 SQL 与本体文件归纳 · 仅保留主干实体
三、对本平台设计的参考价值
对照四级实体 + 分面分类 + scoring 映射逐条评估。
3.1 值得抄的
- TAO:答案结构声明(responseDeclaration 的 baseType/cardinality)→ 压缩进你的 blank 层。你的 blank(计分点)目前存「占位信息 + 参考答案」。建议增加一个「作答形态」描述(如 blank_kind: choice_single / choice_multi / text_exact / text_fuzzy / ordering / matching / gap…),等价于 QTI 的 baseType×cardinality。好处:b_form → scoring 的 mapping_rule 有了机器可读的输入,部分推导可自动化(choice_single + 精确匹配 → MACHINE;text_fuzzy / essay → HUMAN/AI),这正是 TAO 里 responseDeclaration 干的事。
- TAO:mapping 部分给分。responseDeclaration 支持「选项→分值」映射表(选 B 给 0.5 分)。你的 mapping_rule 目前是「(exam, paper, qtype, b_form) → scoring 默认值」,建议加上部分给分规则的字段,多选/匹配题的半对给分是真实刚需。
- TAO:Item 与 Test 分离 + 题可多卷复用。你的 item 层天然支持(item_group 可挂多卷),无需改动;但 TAO 证明了「题 = 独立可导入导出的资源文件(QTI)」这条路,建议你的 item 层设计一个序列化格式(哪怕先不接 QTI),未来与外系统互通有标准可循。
- 学之思:作答全快照(t_exam_paper_question_customer_answer)。作答时把题干、分值、答案、学生作答全部冗余复制。对应你的设计:item 允许改版/停用,但历史作答必须保留出题时的原样——在你的 item 上加 version,并在作答记录里存 item 快照(或 version_id),别只存 item_id 引用。
- 学之思:组卷模板 JSON(frame_text_content)。「题型 × 题数 × 分值」的组卷规则与具体题目解耦。你的平台如果要组卷,建议做成结构化的「组卷模板表」(比塞 text_content 更规范,sboe 的按题型固定列则是明确的反例)。
- sboe:选项独立表。单选/多选的选项应该是独立实体(可加解析、可排序、可复用),学之思把选项塞进题干富文本是劣势。你的 blank 层如果面对 choice 类题型,可以借鉴「选项实体化」的粒度。
- TAO:QTI/LTI 标准互通。TAO、QST、Moodle 都支持 QTI 导入导出。你的题库如果未来要接外部系统(或导数据给出版社合作方),QTI 是事实标准——至少把 QTI 的「题型-答案声明-判分规则」三段式思想吸收进数据结构,而不是自造一个无法互通的格式。
3.2 要避的坑
- sboe:一切 ID/答案用
-/$拼接的 varchar 大字符串——无法查询、统计、版本化。你的 blank 参考答案务必结构化(JSON 或独立表)。 - sboe:试卷按题型固定列——新增题型要改表。你的平台题型是受控词表可配置,试卷结构必须动态。
- sboe:base64 图片直接塞题干 HTML——数据膨胀、无法去重复用。你的 material 层 + 媒体引用 URL/对象存储是正解。
- 学之思:判分逻辑放在提交端(customer_score 由前端报文给出,服务端只校验)——可作弊、不可审计。你的评分方式(MACHINE/HUMAN/AI/HYBRID)应该由服务端规则引擎执行,映射表 + 复核队列的方向是对的。
- 学之思:题型枚举写死在 Java 代码里,新增题型要改代码 + 前端 + 判分——你定的「新题型走配置化新增字典值流程」正是它的补集。
- TAO:零约束分类——类树随便建、属性随便挂,数据质量靠流程纪律。你的「必填单值分面 + 受控词表 + 冲突复核队列」是工程上更可控的选择,不要因为 TAO 自由就放松约束。
- 三家共同:没有材料实体,听力/阅读材料靠复制——这是你四级模型的最大差异化,也是主要复杂度来源(引用/版本/权限)。工程建议:material_id 允许为空,单题内嵌材料也放行,两级并存,避免「必须建材料才能录题」的强约束吓退内容团队。
四级实体模型对照:本平台 vs 三家开源系统
按建表 SQL 与本体文件核对 · 虚线空框 = 该层实体缺失
3.3 一个重要认知校正
三家都没有「材料」「题组」概念,但都活得很好——说明不做材料层系统也能跑;你要做的材料/题组层,价值在「多模态材料复用 + 大规模题目共享 + 统计口径」,收益在规模上来之后才体现。所以分级落地是对的:第一阶段 item 单飞(material_id 可空、item_group 可退化为一题一组),第二阶段再强化材料共享。
四、结论清单(可直接进设计评审的条目)
- blank 层增加「作答形态」字段(对齐 QTI baseType×cardinality),作为 scoring 推导的机器可读输入。
- scoring 映射表增加「部分给分」能力(对齐 TAO mapping)。
- item 增加 version;作答记录存 item 快照(对齐学之思 customer_answer 快照)。
- 组卷模板独立结构化(对齐学之思 frame JSON,但入库为表)。
- choice 类题目的选项独立实体化(对齐 sboe question_option,但用正规外键)。
- item 层预留序列化格式(长期对接 QTI)。
- 材料层可选(material_id 可空 + 内嵌路径放行),避免强约束。
- 判分规则放服务端规则引擎,前端只提交原始作答(避开学之思提交端判分的坑)。
- 题型/分面全部字典表化,试卷结构动态生成(避开 sboe 固定列)。
- 评分管线(HUMAN/AI/HYBRID)自研:三家均无开放题评分,QTI 的「答题结构标准化 + 评分器可插拔」架构值得照抄思路。
五、资料来源(本次实际阅读的文件)
- 学之思:github.com/mindskip/xzs — sql/xzs-postgresql.sql;domain/Question.java;domain/enums/QuestionTypeEnum.java;service/impl/ExamPaperServiceImpl.java;service/impl/ExamPaperAnswerServiceImpl.java;listener/CalculateExamPaperAnswerListener.java
- sboe:github.com/lsgwr/spring-boot-online-exam — doc/sql/exam.sql(question / question_category / question_type / question_level / question_option / exam / exam_record / exam_record_level / user / role)
- TAO:github.com/oat-sa/tao-core(core,RDF 本体层);extension-tao-item(models/ontology/taoitem.rdf、category.rdf);extension-tao-itemqti(model/qti/interaction/ 20+ 交互类、model/qti/ResponseDeclaration.php、OutcomeDeclaration.php);extension-tao-test(models/ontology/taotest.rdf);extension-tao-testqti(models/classes/test/、cat/)
- 标准:IMS QTI 2.1/2.2(imsglobal.org/question/qtiv2p1);TAO 官方文档 taotesting.com
—— 全文完 ——