feat(content): 增加编辑树与在线查询投影的架构说明
Some checks failed
ci / release-gate (push) Has been cancelled

This commit is contained in:
2026-08-06 10:17:50 +08:00
parent 61164f665c
commit bb8a4deda5

View File

@@ -117,6 +117,53 @@ flowchart LR
不同大纲或节点有多个投放。
- **知识点**分为跨大纲语义 `KnowledgeConcept` 和具体大纲位置 `CurriculumNode`。二者允许关联,但不能互相替代。
### 编辑树与在线查询投影
租户录题和内容编辑界面仍应以树形结构展示大纲,例如“学科 → 章节 → 小节 → 知识点”,因为树适合人工定位、
拖动和维护内容位置。后端不会把完整路径复制到题目,而是用以下两种结构保存大纲:
- `CurriculumNode.ParentId` 保存直接父子关系,是编辑树的事实来源;
- `CurriculumNodeClosure` 保存 `(AncestorNodeId, DescendantNodeId, Depth)`,用于高效查询祖先、后代和整棵子树,
避免每次递归遍历邻接表。
题目导入和题目投放是两个独立用例:
```text
导入 / 录入题目
→ 查重、复用或创建 QuestionAsset
→ 创建不可变 QuestionRevision
选择大纲树节点
→ 创建 QuestionPlacement
→ 保存 CurriculumVersionId + CurriculumNodeId
→ 配置适用目标、评分政策和知识概念
```
因此前端可以把“上传 → 查重预览 → 选择树节点 → 完成导入”组织成一个连续流程,但后端不能把
`QuestionAsset` 的创建与某个树节点绑定为同一个不可拆分操作。同一道题可以导入一次,再通过多个 Placement
放入不同大纲或不同节点。
发布时,`ContentReleaseCompiler` 将树上的有效 Placement 编译为扁平的 `ContentReleaseQuestion` 候选行,
每行直接保存 `CurriculumNodeId``QuestionRevisionId``AudienceSegmentId`、评分政策和稳定 `Ordinal`
学生在线组题读取这些发布候选,不再从题目向上或向下遍历大纲树,也不在线重新计算目标适用规则。
```text
编辑视图:树形 CurriculumNode
↓ 选择节点并创建 Placement
发布阶段:编译适用规则和交付版本
查询视图:扁平 ContentReleaseQuestion 候选投影
```
当前 `ContentCandidateReader``CurriculumNodeId` 使用**精确匹配**。请求父章节时不会自动包含所有子节点题目。
如果产品定义“点击父章节即练习整个子树”,必须显式选择并实现以下策略之一:
1. 发布时把子树范围预展开到专用候选投影;
2. 查询前通过 `CurriculumNodeClosure` 得到后代 NodeId再批量过滤发布候选
3. 为章节策划并发布 `CollectionRelease`,在线按题集成员读取。
在方案确定前,不得默认父节点查询已经包含子节点,也不要在高频路径临时加入递归逐层查询。
## 关系图
```mermaid