数据结构 · 设计稿
本次重构的核心目标,是把「题库」和「教材」两套内容体系纳入同一套底层数据结构,同时保持两者各自的业务边界。
在之前的讨论中已经明确:CourseBook 是「书」,不是「课」。它只回答「这本书有哪些内容、按什么顺序排」;而教学进度、班级、成绩、学时分配等教学过程属性,由将来独立的 Course / LearningPath 实体承载。
新的数据结构遵循三条原则:
owner_type / owner_id 区分是题库级公共资源,还是某本教材的内嵌内容。教材是内容出版物,不是教学过程。它承载书名、版次、版本号、出版状态、出版社信息等元数据。CourseBook 本身不直接包含内容,而是通过 StructureNode 树来表达内容结构。
| 字段 | 说明 |
|---|---|
| id | 主键 |
| title | 教材标题 |
| edition | 版次,如「第三版」 |
| version | 数据版本,用于版本控制 |
| status | draft / reviewing / published / archived |
| publisher_info | 出版社、ISBN、出版日期等 |
StructureNode 是教材的树形骨架。它不自持教学内容,只表达层级关系。通过 parent_id 自引用,可以构建任意深度的教材结构(Book → Volume → Unit → Lesson → Part → Section 等)。
关键约束:ContentBlock 只允许挂在叶子 StructureNode 下。如果某个节点既要包含子节点又要直接挂内容块,应拆分为「父节点 + 同名叶子节点」。
| 字段 | 说明 |
|---|---|
| id | 主键 |
| course_book_id | 所属教材 |
| parent_id | 父节点,根节点为 null |
| level | 层级深度,决定排序 |
| node_type | 节点类型标签,如 unit / lesson / section |
| title / seq | 节点标题与同层排序 |
| metadata | 课时建议、页码、教学目标摘要等 JSON |
ContentBlock 是教材的最小可教学单元,挂在 StructureNode 下。它通过 block_type 区分不同的教学角色:
ContentBlock 同时持有 material_id 和 question_block_id 两个可空外键,通过 block_type 决定哪个生效。当 block_type = question 时,is_assessed 为 true。
Material 存储题目或教学内容所依托的原文、音频、图片、视频等媒体资源。体裁、模态、来源等属性挂在材料层。
共用表设计:Material 通过 owner_type 区分两种归属:
owner_type = question_bank:题库级材料,过审、可复用owner_type = course_book:教材内嵌材料,不进入题库检索QuestionBlock 是教学和发布的最小评测单元,继承自题库原设计中的四层模型。题型、能力维度、难度、答题要求等分类信息挂在题组层。
与 Material 一样,QuestionBlock 也通过 owner_type 区分题库级引用与教材内嵌:
owner_type = question_bank:题库中已过审的题组,可被多本教材引用owner_type = course_book:教材编写时单独创建的题组,不污染题库Question 承载具体的作答形式(单选、填空、写作等)和题干结构。Point 是最小判分单元,存储答案、评分规则、批改方式等。
这一层完全沿用题库现有设计:QuestionBlock → Question → Point,结构不变。
| 关系 | 基数 | 说明 |
|---|---|---|
| CourseBook → StructureNode | 1:N | 一本教材有多个结构节点 |
| StructureNode → StructureNode | 1:N | 自引用层级树 |
| StructureNode → ContentBlock | 1:N | 每个叶子节点包含多个内容块 |
| ContentBlock → Material | N:0..1 | 材料类型内容块引用材料 |
| ContentBlock → QuestionBlock | N:0..1 | 题组类型内容块引用题组 |
| QuestionBlock ↔ Material | N:M | 题组可引用多份材料,材料可被多个题组引用 |
| QuestionBlock → Question | 1:N | 一个题组包含多个题目 |
| Question → Point | 1:N | 每道题目有至少一个计分点 |
如果题库级和内嵌级分别建表,Question 和 Point 的数据结构就要复制两套,评分、统计、导出逻辑也要维护两套。共用一张表,只通过 owner_type 区分,既保证了题库的封闭性,又复用了所有底层能力。
| 维度 | 引用型 | 内嵌型 |
|---|---|---|
| 所有者 | 题库 | 所在 CourseBook |
| 审核 | 必须已过审 | 随教材一起审核 |
| 跨教材复用 | 可以 | 不可以 |
| 版本管理 | 题库版本号 | CourseBook 版本号 |
| 是否污染题库 | 否(本来就是题库的) | 否(不进入题库) |
不同教材的层级命名不同:有的用 Unit → Part,有的用编 → 课 → 节。StructureNode 的 level + node_type 设计让系统无需改动代码就能适配各种教材结构。
is_assessed 让系统能快速区分「学习任务」和「评测任务」。一本教材里有多少评测内容、多少学习内容,统计时很直接。