数据结构 · 设计稿

CourseBook + QuestionBank 统一数据模型

zigzagYang 头像
zigzagYang
NJUP·2026 年 8 月 18 日

一、设计目标与边界

本次重构的核心目标,是把「题库」和「教材」两套内容体系纳入同一套底层数据结构,同时保持两者各自的业务边界。

在之前的讨论中已经明确:CourseBook 是「书」,不是「课」。它只回答「这本书有哪些内容、按什么顺序排」;而教学进度、班级、成绩、学时分配等教学过程属性,由将来独立的 Course / LearningPath 实体承载。

新的数据结构遵循三条原则:

  1. 节点与内容块分离:StructureNode 只表达教材的树形层级;ContentBlock 才是可教学的最小单元,挂在叶子节点下。
  2. 题库与教材共用核心实体:Material 与 QuestionBlock 共用一张表,通过 owner_type / owner_id 区分是题库级公共资源,还是某本教材的内嵌内容。
  3. 内嵌内容不突破教材边界:教材内嵌的 Material 与 QuestionBlock 不进入题库检索范围,审核随教材一起完成。

二、ER 图总览

CourseBook + QuestionBank 统一数据模型 CourseBook id: PK title edition version status publisher_info StructureNode id: PK course_book_id: FK parent_id: FK level: int node_type title seq: int metadata: JSON ContentBlock id: PK structure_node_id: FK seq: int block_type: enum title is_assessed: bool visible_to material_id: FK question_block_id: FK payload: JSON Material id: PK genre: enum modality: enum[] content resources_url: JSON source: enum owner_type: enum owner_id review_status: enum QuestionBlock_Material question_block_id: FK material_id: FK role_in_block QuestionBlock id: PK block_type: enum primary_skill: enum secondary_skills: enum[] instruction: rich text difficulty: int owner_type: enum owner_id review_status: enum tags: JSON Question id: PK question_block_id: FK seq: int question_type: enum content: JSON source: enum Point id: PK question_id: FK seq: int position answer hint: JSON scoring: enum 1:N 1:N 1:N N:0..1 N:0..1 1:N 1:N 1:N 1:N 说明:Material 与 QuestionBlock 共用一张表,通过 owner_type 区分题库级(question_bank)与教材内嵌级(course_book)。
图 1CourseBook + QuestionBank 统一数据模型 ER 图

三、实体详解

3.1 CourseBook(教材)

教材是内容出版物,不是教学过程。它承载书名、版次、版本号、出版状态、出版社信息等元数据。CourseBook 本身不直接包含内容,而是通过 StructureNode 树来表达内容结构。

CourseBook 核心字段
字段说明
id主键
title教材标题
edition版次,如「第三版」
version数据版本,用于版本控制
statusdraft / reviewing / published / archived
publisher_info出版社、ISBN、出版日期等

3.2 StructureNode(结构节点)

StructureNode 是教材的树形骨架。它不自持教学内容,只表达层级关系。通过 parent_id 自引用,可以构建任意深度的教材结构(Book → Volume → Unit → Lesson → Part → Section 等)。

关键约束:ContentBlock 只允许挂在叶子 StructureNode 下。如果某个节点既要包含子节点又要直接挂内容块,应拆分为「父节点 + 同名叶子节点」。

StructureNode 核心字段
字段说明
id主键
course_book_id所属教材
parent_id父节点,根节点为 null
level层级深度,决定排序
node_type节点类型标签,如 unit / lesson / section
title / seq节点标题与同层排序
metadata课时建议、页码、教学目标摘要等 JSON

3.3 ContentBlock(内容块)

ContentBlock 是教材的最小可教学单元,挂在 StructureNode 下。它通过 block_type 区分不同的教学角色:

ContentBlock 同时持有 material_idquestion_block_id 两个可空外键,通过 block_type 决定哪个生效。当 block_type = question 时,is_assessed 为 true。

3.4 Material(材料)

Material 存储题目或教学内容所依托的原文、音频、图片、视频等媒体资源。体裁、模态、来源等属性挂在材料层。

共用表设计:Material 通过 owner_type 区分两种归属:

3.5 QuestionBlock(题组)

QuestionBlock 是教学和发布的最小评测单元,继承自题库原设计中的四层模型。题型、能力维度、难度、答题要求等分类信息挂在题组层。

与 Material 一样,QuestionBlock 也通过 owner_type 区分题库级引用与教材内嵌:

3.6 Question(题目)与 Point(计分点)

Question 承载具体的作答形式(单选、填空、写作等)和题干结构。Point 是最小判分单元,存储答案、评分规则、批改方式等。

这一层完全沿用题库现有设计:QuestionBlock → Question → Point,结构不变。

四、实体关系说明

实体关系总览
关系基数说明
CourseBook → StructureNode1:N一本教材有多个结构节点
StructureNode → StructureNode1:N自引用层级树
StructureNode → ContentBlock1:N每个叶子节点包含多个内容块
ContentBlock → MaterialN:0..1材料类型内容块引用材料
ContentBlock → QuestionBlockN:0..1题组类型内容块引用题组
QuestionBlock ↔ MaterialN:M题组可引用多份材料,材料可被多个题组引用
QuestionBlock → Question1:N一个题组包含多个题目
Question → Point1:N每道题目有至少一个计分点

五、关键设计决策

5.1 为什么 Material 与 QuestionBlock 共用一张表?

如果题库级和内嵌级分别建表,Question 和 Point 的数据结构就要复制两套,评分、统计、导出逻辑也要维护两套。共用一张表,只通过 owner_type 区分,既保证了题库的封闭性,又复用了所有底层能力。

5.2 引用型与内嵌型 QuestionBlock 的区别

两种 QuestionBlock 的对比
维度引用型内嵌型
所有者题库所在 CourseBook
审核必须已过审随教材一起审核
跨教材复用可以不可以
版本管理题库版本号CourseBook 版本号
是否污染题库否(本来就是题库的)否(不进入题库)

5.3 教材结构为什么不做死在字段里?

不同教材的层级命名不同:有的用 Unit → Part,有的用编 → 课 → 节。StructureNode 的 level + node_type 设计让系统无需改动代码就能适配各种教材结构。

5.4 ContentBlock 为什么需要 is_assessed?

is_assessed 让系统能快速区分「学习任务」和「评测任务」。一本教材里有多少评测内容、多少学习内容,统计时很直接。

六、源文件下载


zigzagYang 头像
zigzagYang
CourseBook + QuestionBank 统一数据模型 · 设计稿
DOC-COURSEBOOK-ER-20260818