Files
NextEdu/docs/superpowers/specs/2026-06-22-knowledge-graph-design.md
SpecialX 58656da983 feat(textbooks): 知识图谱功能全面重构 — 前置依赖 + dagre 布局 + React Flow 可视化 + 师生双视角
将教材模块图谱从基本无用状态升级为完整知识图谱可视化系统。

数据层:新增 knowledgePointPrerequisites 表(复合主键+双外键 cascade);新增 data-access-graph.ts(server-only)知识点关联聚合、学生/班级掌握度查询;utils.ts 新增 hasCycleAfterAddingEdge(DFS 循环依赖检测)。

业务层:3 个新 Server Action(getKnowledgeGraphDataAction 三视图模式、createPrerequisiteAction 含循环检测、deletePrerequisiteAction);graph-layout.ts 重写为 dagre 分层有向图布局。

视图层:knowledge-graph.tsx 重写为 React Flow 主组件(全书视图+搜索高亮+关联节点高亮+章节着色);4 个新组件(graph-kp-node/graph-prerequisite-edge/graph-toolbar/graph-node-detail-panel);use-graph-data.ts 派生值模式避免 effect 中 setState。

架构:严格三层架构,客户端通过 Server Action 间接访问 server-only 数据层;权限校验+ i18n 全覆盖;架构文档 004/005 同步。

测试:utils.test.ts 新增 5 个循环检测测试,graph-layout.test.ts 重写 5 个 dagre 布局测试,全部 30 个教材模块单元测试通过。

附带提交 drizzle/0005 error-book 迁移文件以保持 journal 一致性。
2026-06-23 00:13:03 +08:00

339 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 知识图谱重构设计文档
- **日期**2026-06-22
- **模块**textbooks
- **范围**:知识图谱功能全面重构
- **状态**:已批准,待实现
## 1. 背景与动机
### 1.1 当前问题
教材模块的知识图谱功能([knowledge-graph.tsx](file:///e:/Desktop/CICD/src/modules/textbooks/components/knowledge-graph.tsx))处于"基本无用"状态:
- **静态 SVG 树状布局**:仅 `parentId` 父子关系,无前置依赖
- **仅显示当前章节**:无法跨章节/全书查看知识体系
- **交互单一**:点击节点仅高亮正文,无缩放/平移/拖拽/键盘导航
- **信息密度低**:节点只有名称,无关联题目数、无掌握度
- **无师生区分**:教师和学生看到相同的图,无学情数据
### 1.2 同类平台调研
| 平台 | 核心设计 | 可借鉴点 |
|------|---------|---------|
| 人教数字教材 | 按学科/章节聚合的层级图,节点关联资源 | 跨章节聚合 + 资源关联 |
| Khan Academy | 力导向图,前置依赖边,节点显示掌握度 | 前置依赖 + 掌握度 + 跳转练习 |
| 学而思/猿辅导 | 学习路径图,红/黄/绿表示掌握度 | 掌握度色彩 + 路径推荐 |
| ClassIn/Seewo | 师生双视角,教师看班级整体 | 师生双视角 |
| 洋葱学院 | 章节→知识点→题目三级下钻 | 下钻交互 + 缩放 |
**共性特征**:① 跨章节/全书视图 ② 前置依赖关系 ③ 掌握度可视化 ④ 关联题目/资源 ⑤ 缩放平移 ⑥ 师生双视角
### 1.3 已有可复用数据
- `questionsToKnowledgePoints` 关联表(题目↔知识点多对多)
- `knowledgePointMastery` 表(学生掌握度,已被 `diagnostic` 模块填充)
- `questions` 模块已支持按 `knowledgePointId` 筛选
- `@xyflow/react`React Flow 12已在 `lesson-preparation` 模块使用
## 2. 设计目标
1. **跨章节全书视图**:支持单章节和全书两种范围切换
2. **前置依赖关系**:新增数据模型,支持声明任意知识点间的前置依赖
3. **掌握度可视化**:学生看个人,教师看班级,红/黄/绿/灰着色
4. **关联题目预览**:节点显示关联题目数,详情面板可跳转题目库
5. **缩放平移交互**React Flow 内置画布交互
6. **师生双视角**:同一组件,通过 prop 注入不同数据源
7. **侧边栏详情面板**:点击节点显示详情,不离开当前页面
## 3. 数据模型扩展
### 3.1 新增表knowledge_point_prerequisites
```typescript
export const knowledgePointPrerequisites = mysqlTable("knowledge_point_prerequisites", {
id: id("id").primaryKey(),
knowledgePointId: varchar("knowledge_point_id", { length: 128 }).notNull()
.references(() => knowledgePoints.id, { onDelete: "cascade" }),
prerequisiteKpId: varchar("prerequisite_kp_id", { length: 128 }).notNull()
.references(() => knowledgePoints.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (table) => ({
kpPairPk: primaryKey({ columns: [table.knowledgePointId, table.prerequisiteKpId] }),
kpIdx: index("kp_prereq_kp_idx").on(table.knowledgePointId),
prereqIdx: index("kp_prereq_prereq_idx").on(table.prerequisiteKpId),
}))
```
**设计说明**
- 多对多自关联表,表达"学习 KP_B 前应先掌握 KP_A"
- `knowledgePointId` = 目标知识点,`prerequisiteKpId` = 前置知识点
- 联合主键防止重复声明
- 级联删除:知识点删除时自动清理关联
- 循环依赖检测由 Server Action 层 DFS 校验,拒绝形成环的声明
### 3.2 不修改的表
- `knowledgePoints`:已有 `parentId`(树归属)和 `chapterId`(章节归属),不变
- `knowledgePointMastery`:已有 `masteryLevel`/`totalQuestions`/`correctQuestions`,不变
- `questionsToKnowledgePoints`:不变
## 4. 架构与模块结构
### 4.1 新增文件清单
```
src/modules/textbooks/
├─ data-access-graph.ts # 新增:图谱专用数据访问
├─ components/
│ ├─ knowledge-graph.tsx # 重写React Flow 渲染器
│ ├─ graph-node-detail-panel.tsx # 新增:节点详情侧边栏
│ ├─ graph-kp-node.tsx # 新增React Flow 自定义节点
│ ├─ graph-prerequisite-edge.tsx # 新增React Flow 自定义边
│ └─ graph-toolbar.tsx # 新增:视图切换/筛选/搜索工具栏
└─ hooks/
└─ use-graph-data.ts # 新增:图谱数据加载与缓存 Hook
```
### 4.2 修改的文件
| 文件 | 修改内容 |
|------|---------|
| `src/shared/db/schema.ts` | 新增 `knowledgePointPrerequisites` 表定义 |
| `data-access.ts` | 新增 prerequisite CRUD 函数 |
| `actions.ts` | 新增 3 个 Server Action |
| `schema.ts` | 新增 prerequisite 声明的 Zod 校验 |
| `types.ts` | 新增 `GraphNodeData` / `GraphViewMode` / `KpWithRelations` 等类型 |
| `graph-layout.ts` | 重写:调用 dagre保留纯函数签名 |
| `components/textbook-reader.tsx` | 图谱 Tab 接入新组件 |
| `i18n/messages/zh-CN/textbooks.json` | 新增 graph.* 翻译键 |
| `i18n/messages/en/textbooks.json` | 新增 graph.* 翻译键 |
### 4.3 数据访问层data-access-graph.ts
```typescript
// 全书知识点 + 前置依赖 + 关联题目数,一次查询聚合
export async function getKnowledgePointsWithRelations(
textbookId: string
): Promise<KpWithRelations[]>
// 学生个人掌握度(按教材范围)
export async function getStudentKpMastery(
studentId: string,
textbookId: string
): Promise<Map<string, MasteryInfo>>
// 班级平均掌握度(教师视角)
export async function getClassKpMastery(
teacherId: string,
textbookId: string
): Promise<Map<string, MasteryInfo>>
// 单个知识点的前置列表
export async function getPrerequisitesForKp(
kpId: string
): Promise<KnowledgePoint[]>
```
**性能考量**
- 全书知识点通常 50-300 个,一次查询无压力
- 关联题目数用子查询 `COUNT` 聚合,避免 N+1
- 掌握度查询走索引(`mastery_kp_idx` + `mastery_student_idx`
### 4.4 Server Actionsactions.ts
```typescript
// 图谱数据懒加载入口
export async function getKnowledgeGraphDataAction(
textbookId: string,
viewMode: GraphViewMode
): Promise<ActionState<KnowledgeGraphData>>
// 声明前置依赖(含循环检测)
export async function createPrerequisiteAction(
input: CreatePrerequisiteInput
): Promise<ActionState<void>>
// 删除前置依赖
export async function deletePrerequisiteAction(
input: DeletePrerequisiteInput
): Promise<ActionState<void>>
```
**权限**
- `getKnowledgeGraphDataAction``requirePermission(Permissions.TEXTBOOK_READ)`,掌握度数据按当前用户角色过滤
- `createPrerequisiteAction` / `deletePrerequisiteAction``requirePermission(Permissions.TEXTBOOK_UPDATE)`
**循环检测**`createPrerequisiteAction` 中做 DFS若声明 `A→B` 后从 B 可达 A 则拒绝。
## 5. 图谱视图与交互
### 5.1 视图模式
| 模式 | 数据源 | 节点着色 | 适用角色 |
|------|--------|---------|---------|
| `structure` | 全书知识点 + parentId + prerequisites | 按章节分色 | 教师/学生 |
| `student-mastery` | + 学生个人掌握度 | 红(<60%)/黄(60-85%)/绿(>85%)/灰(未测) | 学生 |
| `class-mastery` | + 班级平均掌握度 | 同上但聚合班级数据 | 教师 |
### 5.2 节点设计graph-kp-node.tsx
- 矩形卡片,宽度 180px高度自适应
- 内容:知识点名称 + 关联题目数徽章 + 掌握度进度条mastery 模式下)
- 双击节点 → 打开右侧详情面板
- 单击节点 → 高亮关联节点(前置+后置),其余节点降低透明度
- 节点支持拖拽(位置不持久化,切换章节后重新布局)
### 5.3 边设计
- `parentId` 关系:实线,无箭头(树归属)
- `prerequisite` 关系:虚线 + 箭头(依赖方向)
- 选中节点时:关联边高亮,其余边降低透明度
### 5.4 画布交互
- React Flow 内置缩放滚轮、平移拖拽空白、小地图、键盘导航Tab/方向键)
- 工具栏(`graph-toolbar.tsx`
- 视图模式切换structure / student-mastery / class-mastery
- 学生角色:仅显示 `structure` + `student-mastery`
- 教师角色:显示 `structure` + `class-mastery`(教师看班级整体,不看个人)
- 章节筛选(多选下拉,默认全选)
- 关键词搜索(高亮匹配节点,非匹配节点降低透明度)
- 重置视图按钮
### 5.5 详情面板graph-node-detail-panel.tsx
- 知识点描述
- 掌握度详情(个人/班级,含答题数/正确率)
- 关联题目列表(前 5 条 + "查看全部"跳转题目库,带 `?kp=<id>` 查询参数)
- 前置知识点列表(可点击跳转)
- 后置知识点列表(可点击跳转)
- 教师/有权限者:编辑前置依赖入口(添加/删除前置)
## 6. 数据流与性能
### 6.1 数据加载策略
- 图谱数据按教材维度一次性加载(全书知识点 + 依赖 + 题目数聚合)
- 掌握度数据按 viewMode 懒加载:切换到 mastery 模式时才请求
- 使用 Server Action `getKnowledgeGraphDataAction(textbookId, viewMode)` 统一入口
- 客户端缓存:`use-graph-data.ts``useState` + `useRef` 防重复请求(复用 P2-3 模式)
### 6.2 布局计算
- dagre 布局在客户端 `useMemo` 中执行
- 知识点量级50-300下 dagre 计算时间 <10ms
- 布局参数:`rankdir=TB`(从上到下)、`nodesep=40``ranksep=90`
### 6.3 错误处理
- 图谱数据加载失败 → 复用 `TextbookSectionErrorBoundary`
- 掌握度数据缺失 → 节点显示"未测评"灰色状态,不阻断图谱渲染
- 前置依赖循环检测 → Server Action 返回结构化错误,前端 toast 提示
## 7. 权限与 i18n
### 7.1 权限
- `TEXTBOOK_READ` — 查看图谱
- `TEXTBOOK_UPDATE` — 编辑前置依赖
- 掌握度查看:学生只能看自己,教师看班级(由 data-access 层按 `getCurrentStudentUser`/`getCurrentTeacherUser` 过滤)
### 7.2 i18n 新增键
```json
{
"graph": {
"viewMode": {
"structure": "结构图",
"studentMastery": "个人掌握度",
"classMastery": "班级掌握度"
},
"node": {
"questions": "题目",
"mastery": "掌握度",
"prerequisite": "前置",
"successor": "后置"
},
"detail": {
"title": "知识点详情",
"noDescription": "暂无描述",
"viewAllQuestions": "查看全部题目",
"editPrerequisite": "编辑前置依赖",
"addPrerequisite": "添加前置",
"removePrerequisite": "移除",
"noPrerequisites": "暂无前置知识点",
"noSuccessors": "暂无后置知识点",
"masteryNotAssessed": "未测评",
"correctRate": "正确率"
},
"toolbar": {
"search": "搜索知识点",
"filterByChapter": "按章节筛选",
"resetView": "重置视图"
},
"empty": {
"noPrerequisites": "暂无前置依赖关系",
"noData": "暂无图谱数据"
},
"error": {
"cyclicDependency": "不能添加循环依赖",
"loadFailed": "图谱加载失败"
}
}
}
```
## 8. 测试策略
### 8.1 单元测试
- `graph-layout.ts`dagre 集成后布局正确性、空数据、循环容错
- `data-access-graph.ts`:聚合查询正确性、权限过滤
- 循环依赖检测DFS 算法单测
### 8.2 组件测试
- `graph-kp-node.tsx`:节点渲染、掌握度进度条、徽章
- `graph-node-detail-panel.tsx`:详情展示、前置/后置列表、跳转链接
- `graph-toolbar.tsx`:视图切换、搜索、筛选
### 8.3 集成测试
- 图谱数据加载 → 渲染 → 节点点击 → 详情面板
- 视图模式切换 → 掌握度数据懒加载
- 前置依赖 CRUD → 图谱边更新
## 9. 依赖变更
### 9.1 新增依赖
- `@dagrejs/dagre` — 分层有向图布局算法(~50KB gzip
### 9.2 复用依赖
- `@xyflow/react`(已在项目中使用)
## 10. 架构图同步
实现完成后需同步以下架构文档:
- `docs/architecture/004_architecture_impact_map.md` — §2.5 教材模块章节
- 更新文件清单(新增 6 个文件)
- 更新导出函数(新增 4 个 data-access + 3 个 actions
- 更新 knownIssues移除"P2 图谱方向键导航未实现"
- `docs/architecture/005_architecture_data.json` — modules.textbooks 节点
- 更新 exports、dbTables新增 knowledge_point_prerequisites、dependencyMatrix
## 11. 非目标YAGNI
以下功能不在本次范围内,后续迭代考虑:
- 节点位置持久化(拖拽后保存布局)
- 学习路径自动推荐
- 关联视频/课件资源(当前仅关联题目)
- 教材阅读进度跟踪
- 学生笔记/标注
- 教材导入/导出
- 多版本教材对比