Files
NextEdu/docs/architecture/audit/archive/textbooks-audit-report-v3.md

27 KiB
Raw Blame History

教材Textbooks模块审计报告 v3

审计日期2026-06-24 审计范围:src/modules/textbooks/**src/app/(dashboard)/teacher/textbooks/**src/app/(dashboard)/student/learning/textbooks/** 对比基准:textbooks-audit-report-v2.mdv2 参照规则:docs/architecture/004_architecture_impact_map.mddocs/architecture/005_architecture_data.json.trae/rules/project_rules.md


一、现有实现概要

1.1 模块定位

教材模块是 K12 系统的"标杆模块"(架构图 004 第 2.5 节明确标注),承担教材与知识体系管理职责:

  • 教材/章节树形结构 CRUD
  • 知识点 CRUD + Markdown 内容编辑
  • 知识图谱可视化React Flow + dagre 布局)
  • 知识点前置依赖管理(含循环检测)
  • 学生/班级掌握度聚合查询

1.2 文件分布与规模

文件数 总行数 最大文件 备注
模块核心modules/textbooks 26 ~3,800 data-access.ts 662 行 含 5 个 hooks、16 个组件
App 层 - 教师 6 ~350 [id]/page.tsx 66 行 含 loading/error
App 层 - 学生 6 ~240 page.tsx 86 行 含 loading/error
测试 2 ~200 utils.test.ts 纯函数单测

1.3 数据流概览

┌─────────────────────────────────────────────────────────────────┐
│  App 层RSC                                                    │
│  teacher/textbooks/page.tsx  ──┐                                 │
│  student/learning/textbooks/page.tsx ──┤                         │
└────────────────────────────────┼─────────────────────────────────┘
                                 │ 直接调用 data-access
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│  modules/textbooks                                               │
│  ┌──────────────┐   ┌──────────────┐   ┌────────────────────┐  │
│  │ actions.ts   │──▶│ data-access.ts│──▶│ data-access-graph.ts│  │
│  │ (14 Actions) │   │ (662 行)      │   │ (207 行, 图谱只读) │  │
│  └──────────────┘   └──────────────┘   └────────────────────┘  │
│         │                  │                      │              │
│         │                  ▼                      ▼              │
│         │          ┌──────────────┐      ┌──────────────┐       │
│         │          │ utils.ts     │      │ graph-layout │       │
│         │          │ (225 行纯函数)│      │ (121 行纯函数)│       │
│         │          └──────────────┘      └──────────────┘       │
│         │                                                        │
│         ▼                                                        │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │ components/16 个组件)+ hooks/5 个 Hook              │  │
│  │ textbook-reader / knowledge-graph / chapter-sidebar-list │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
         │
         ▼ 跨模块依赖(通过 data-access合规
┌─────────────────────────────────────────────────────────────────┐
│  users.data-accessgetCurrentStudentUser                      │
│  classes.data-access-studentsgetClassStudents                │
│  school.data-accessgetGradeNameById                          │
└─────────────────────────────────────────────────────────────────┘

1.4 架构图记录完整性评估

架构图节点 记录状态 差异
004 § 2.5 textbooks 完整 导出函数、依赖、已知问题、文件清单均已记录
005 textbooks 节点 完整 actions/dataAccess/hooks/types/components/files 均已记录
已知问题记录 ⚠️ 部分过期 005 knownIssues 仍写"i18n 覆盖率约 98%"实际审计发现残留硬编码英文异常文案、a11y 缺失等问题未记录

结论:架构图对教材模块的覆盖度约 90%,主要差距在 knownIssues 字段未同步最新审计发现。


二、现存问题与原因分析

2.1 安全与权限维度P0

问题 T-AUDIT-01 学生端页面缺少 requirePermission 显式校验

  • 位置student/learning/textbooks/page.tsxstudent/learning/textbooks/[id]/page.tsx
  • 现象:两个学生端页面均未调用 requirePermission(Permissions.TEXTBOOK_READ),仅靠 getCurrentStudentUser() 隐式校验角色。对比教师端 teacher/textbooks/page.tsx 第 18 行显式调用 requirePermission
  • 违反规则project_rules.md → "Server Action 规范" → "每个 Action 必须调用 requirePermission() 进行权限校验";以及"安全规范" → "所有敏感数据查询必须在 data-access 层结合当前用户权限过滤Server Action 二次校验"
  • 原因:学生端页面是 RSC 直接调用 data-access非 Server Action开发者认为 getCurrentStudentUser 已隐含角色校验,但权限点 TEXTBOOK_READ 未被显式校验,无法通过权限矩阵灵活调整
  • 后果:若未来权限矩阵调整(如临时禁止某学生查看教材),学生端无法响应;权限审计日志缺失"教材阅读"事件

问题 T-AUDIT-02 getKnowledgeGraphDataAction 缺少数据范围过滤

  • 位置actions.ts 第 373-413 行
  • 现象getKnowledgeGraphDataAction 仅校验 TEXTBOOK_READ 权限,未按学生年级做 scope 过滤(对比 getTextbooksWithScope 有 scope.grade 过滤)。学生可传任意 textbookId 查看跨年级教材的图谱数据
  • 违反规则project_rules.md → "安全规范" → "所有敏感数据查询必须在 data-access 层结合当前用户权限过滤"
  • 原因:图谱查询是 Task 7 新增功能,开发时复用了教师端逻辑,未区分学生数据范围
  • 后果:学生可越权查看非本年级教材的知识图谱与掌握度数据

问题 T-AUDIT-03 class-mastery 视图缺少教师-班级归属校验

  • 位置actions.ts 第 393-403 行
  • 现象getKnowledgeGraphDataActionclass-mastery 分支调用 getClassStudents({ status: "active" }) 获取全量班级学生 ID未校验当前教师是否拥有这些班级
  • 违反规则project_rules.md → "安全规范" → "Server Action 二次校验"
  • 原因:直接调用 classes 模块的 getClassStudents 未传入教师 scope
  • 后果:任何持有 TEXTBOOK_READ 权限的教师可查看全校所有班级的掌握度数据

2.2 数据完整性维度P0

问题 T-AUDIT-04 deleteChapter 未清理孤儿前置依赖记录

  • 位置data-access.ts 第 259-298 行
  • 现象:递归删除章节及其知识点时,未删除 knowledgePointPrerequisites 表中引用被删知识点的边(包括作为 prerequisiteKpId 的记录)
  • 违反规则:数据完整性最佳实践
  • 原因:删除逻辑只关注了 knowledgePoints 表本身,遗漏了外键关联表
  • 后果:图谱渲染时出现指向已删除知识点的孤儿边,循环检测算法 hasCycleAfterAddingEdge 可能因引用不存在节点而异常

2.3 类型安全维度P1

问题 T-AUDIT-05 as 断言违反规范

  • 位置
  • 违反规则project_rules.md → "TypeScript 规则" → "禁止 as 断言(除非从 unknown 转换或测试中,需注释原因)"
  • 原因React Flow 的 NodeProps 类型与自定义数据结构不匹配,开发者用 as unknown as 绕过类型检查
  • 后果:类型安全被削弱,运行时可能因数据形状不符而崩溃

问题 T-AUDIT-06 类型与 DB Schema 不一致

  • 位置types.ts
  • 现象
    • 第 42 行 KnowledgePoint.chapterId?: string(可选),但 DB schema 中该字段非空
    • 第 13 行 Textbook.grade: string | null(可空),但 CreateTextbookSchema.grade 要求 min(1) 非空
  • 违反规则project_rules.md → "TypeScript 规则" → "禁止 any"
  • 原因:类型手动定义而非从 Drizzle schema 推断types.ts 第 1-3 行注释承认)
  • 后果:下游需大量 ?? undefined 兜底data-access.ts 第 320、347 行),类型契约不可信

2.4 错误处理维度P0/P1

问题 T-AUDIT-07 前置依赖操作无 try/catchloading 状态卡死

  • 位置knowledge-graph.tsx 第 202-219 行(handleAddPrerequisite)、第 222-235 行(handleRemovePrerequisite
  • 现象:两个函数均无 try/catch若 action 抛异常,setIsSavingPrereq(false) 不会执行
  • 违反规则project_rules.md → "错误处理" 隐含要求
  • 原因:开发者假设 Server Action 不会抛异常(实际网络异常、服务端 500 均会抛)
  • 后果:用户点击"添加前置依赖"失败后,按钮永久 loading需刷新页面

问题 T-AUDIT-08 textbook-reader.tsx onCreateKnowledgePoint 无 try/catch

  • 位置textbook-reader.tsx 第 177-181 行
  • 现象onCreateKnowledgePoint 回调中调用 handleCreateKnowledgePoint,无 try/catch异常时 setIsCreating(false) 不执行
  • 后果创建知识点失败时UI 永久显示创建中状态

问题 T-AUDIT-09 section-error-boundary.tsxcomponentDidCatch

  • 位置section-error-boundary.tsx
  • 现象:仅实现 getDerivedStateFromError,未实现 componentDidCatch,错误被吞没未上报
  • 违反规则React Error Boundary 最佳实践
  • 后果:生产环境无法追踪章节内容渲染错误

问题 T-AUDIT-10 4 个 error.tsx 完全重复且未记录日志

  • 位置4 个 error.tsx 文件(教师/学生 × 列表/详情)
  • 现象:内容完全相同(仅函数名不同),error 参数接收但未 console.error 记录、未展示 error.digest、未区分错误类型
  • 违反规则DRY 原则、Next.js error.tsx 最佳实践
  • 后果错误无法追踪4 份重复代码维护成本高

2.5 国际化维度P1

问题 T-AUDIT-11 data-access 层硬编码英文异常文案

  • 位置data-access.ts 第 188、244、383 行
  • 现象throw new Error("Textbook not found") / "Chapter not found" 等英文硬编码
  • 违反规则project_rules.md → "所有用户可见文本必须适配 i18n"
  • 原因data-access 层无法直接调用 getTranslations(需 async context
  • 后果:错误冒泡到 handleActionError 后,非英文用户看到英文错误信息

问题 T-AUDIT-12 use-graph-data.ts 错误状态值类型不一致

  • 位置use-graph-data.ts 第 60、66 行
  • 现象error 状态有时是服务端已翻译文案(result.message),有时是原始 i18n key 字符串("graph.error.loadFailed"),有时是 JS Error.message
  • 后果:下游组件无法判断该直接显示还是再 t() 翻译

2.6 可访问性维度P1

问题 T-AUDIT-13 章节项可点击 div 无键盘支持

  • 位置chapter-sidebar-list.tsx 第 100-110 行
  • 现象:章节项点击区是 <div onClick>,缺少 role="button"tabIndex={0}onKeyDown
  • 违反规则WCAG 2.1 AA → 键盘可达性
  • 后果:键盘用户无法选中章节

问题 T-AUDIT-14 图标按钮缺 aria-label

  • 位置graph-node-detail-panel.tsx 第 45、139-146 行
  • 现象关闭按钮X 图标、删除前置按钮Trash2 图标)无 aria-label
  • 后果:屏幕阅读器用户无法识别按钮用途

问题 T-AUDIT-15 loading.tsx 缺少 ARIA 标记

  • 位置4 个 loading.tsx 文件
  • 现象:根容器缺少 role="status"aria-busy="true"
  • 后果:屏幕阅读器无法感知加载状态

问题 T-AUDIT-16 error.tsx 缺少 role="alert"

  • 位置4 个 error.tsx 文件
  • 现象:错误容器缺少 role="alert",屏幕阅读器不会自动播报错误

2.7 跨模块耦合维度P1

问题 T-AUDIT-17 跨模块 URL 硬编码

  • 位置
  • 现象:组件内硬编码角色前缀 URL/teacher/...),无法复用于学生/家长端
  • 违反规则project_rules.md → "前端组件禁止使用 role === "xxx" 硬编码"URL 硬编码角色前缀同理)
  • 原因:组件未通过 props 注入路由前缀
  • 后果:组件无法跨角色复用;新增角色需修改组件源码

2.8 代码质量维度P2

问题 T-AUDIT-18 use-kp-crud.ts 行数超限

  • 位置hooks/use-kp-crud.ts 122 行
  • 现象:超过 Hook 上限 80 行
  • 违反规则project_rules.md → "自定义 Hook建议 ≤ 80 行"
  • 原因CRUD 操作集中在一个 Hook
  • 后果:维护困难

问题 T-AUDIT-19 表单字段代码重复

问题 T-AUDIT-20 死代码

  • 位置
  • 违反规则:代码整洁
  • 后果:增加维护负担

问题 T-AUDIT-21 textbook-content-panel.tsx props 膨胀

  • 位置textbook-content-panel.tsx 17 个 props
  • 现象props 过多,违反组合优先原则
  • 违反规则project_rules.md → "组合优先"
  • 原因:编辑态、内容态、选区态未分离
  • 后果:组件难以复用和测试

问题 T-AUDIT-22 函数返回类型未显式标注

  • 位置use-knowledge-point-actions.tsuse-kp-dialog-state.tsuse-text-selection.tsuse-graph-data.tsconstants.tsgetSubjectColor 等)
  • 违反规则project_rules.md → "函数返回值必须显式标注,特别是 Promise<T>"
  • 后果:类型推导不透明

2.9 性能与可测试性维度P2

问题 T-AUDIT-23 textbook-reader.tsx 命令式 DOM 操作

  • 位置textbook-reader.tsx 第 229-235 行
  • 现象document.querySelector(\[data-kp-id="${highlightedKpId}"]`)+el.classList.add/remove`
  • 风险:模板字符串选择器存在选择器注入风险;命令式 DOM 操作与 React 数据流冲突
  • 后果:难以测试、潜在 XSS

问题 T-AUDIT-24 graph-layout.ts O(n²) 性能

  • 位置graph-layout.ts 第 56、64、89、103 行
  • 现象:多次 knowledgePoints.some(...) 线性查找
  • 后果:大图谱(>100 节点)性能差

问题 T-AUDIT-25 cache() 包裹影响可测试性


三、行业差距对比

基于对 PowerSchool、Veracross、钉钉教育、智学网、班级小管家、ClassIn、智慧树等 K12 产品的调研,教材模块相比行业优秀实践存在以下差距:

3.1 功能差距

差距项 行业实践 当前实现 影响角色
多角色视图缺失 admin 全校教材管理、parent 查看子女教材 仅 teacher/student 两端 admin、parent
教材版本管理 支持多版本教材对比、历史版本回滚 无版本概念 teacher、admin
协作编辑 多教师协作编辑同一教材(锁机制/CRDT 单人编辑,无锁 teacher
离线阅读 移动端缓存章节内容离线访问 无离线缓存 student
PDF/打印导出 章节导出为 PDF、打印友好版式 无导出功能 teacher、student
阅读进度追踪 记录学生阅读进度、停留时长 无进度追踪 student、teacher
教材资源附件 章节关联视频、音频、PDF 附件 仅 Markdown 文本 teacher、student
知识点掌握度联动 图谱节点点击跳转至相关题目/作业练习 仅展示掌握度,无跳转练习入口 student
教材评论/批注 教师可在章节内添加批注,学生可见 无批注功能 teacher、student
智能推荐 基于掌握度推荐复习路径 analytics.tsx 仅预留接口,未实现 student

3.2 UI/UX 差距

差距项 行业实践 当前实现 影响
骨架屏布局不匹配 骨架屏与实际页面结构一致 [id]/loading.tsx 渲染 grid+sidebar实际是阅读器布局 用户体验割裂
空状态引导 空状态提供"创建教材"CTA 按钮 仅文字提示 转化率低
筛选持久化 筛选条件持久化到 URLnuqs 部分持久化,不一致 刷新丢失筛选
移动端阅读体验 上下滑动阅读、字号调节、夜间模式 有 Sheet 抽屉,无字号/主题调节 阅读体验差
图谱交互 节点拖拽、缩放、小地图、方向键导航 有拖拽/缩放,缺方向键导航 键盘用户不可达

3.3 架构差距

差距项 行业实践 当前实现 影响
配置驱动 角色配置决定渲染哪些 Widget 组件内硬编码角色 URL 新增角色需改组件
依赖注入 通过 Context 注入数据服务 组件直接调用 data-access 难以 mock 测试
错误恢复 错误边界提供重试按钮 + 自动上报 仅 router.refresh无上报 错误不可追踪
监控埋点 关键操作(创建/删除/阅读)埋点 analytics.tsx 仅 stub 无行为数据

四、改进优先级建议

4.1 P0紧急安全与数据完整性

编号 问题 改进方向 影响范围
T-FIX-01 T-AUDIT-01 学生端缺 requirePermission 学生端两个页面添加 requirePermission(Permissions.TEXTBOOK_READ) 2 文件
T-FIX-02 T-AUDIT-02 图谱缺 scope 过滤 getKnowledgeGraphDataAction 学生端按年级 scope 过滤 textbookId actions.ts
T-FIX-03 T-AUDIT-03 class-mastery 缺教师-班级校验 传入教师 scope校验班级归属 actions.ts
T-FIX-04 T-AUDIT-04 deleteChapter 孤儿记录 级联删除 knowledgePointPrerequisites 表相关记录 data-access.ts
T-FIX-05 T-AUDIT-07/08 前置依赖/创建知识点无 try/catch 补 try/catch/finallyfinally 中复位 loading knowledge-graph.tsx、textbook-reader.tsx
T-FIX-06 T-AUDIT-09 Error Boundary 缺 componentDidCatch 添加 componentDidCatch 记录错误 section-error-boundary.tsx

4.2 P1重要规范合规

编号 问题 改进方向 影响范围
T-FIX-07 T-AUDIT-05 as 断言 NodeProps<GraphLayoutNodeData> 泛型替代双重断言data-access 用 satisfies 3 文件
T-FIX-08 T-AUDIT-06 类型与 DB Schema 不一致 chapterId 改为必填grade 与 schema 对齐 types.ts
T-FIX-09 T-AUDIT-10 error.tsx 重复 抽取 shared/components/error-boundary.tsx 共享组件 4 文件 → 1 共享
T-FIX-10 T-AUDIT-11 data-access 硬编码英文 改用错误码actions 层翻译 data-access.ts
T-FIX-11 T-AUDIT-12 错误状态值类型不一致 统一为 i18n key 或错误码 use-graph-data.ts
T-FIX-12 T-AUDIT-13 章节项无键盘支持 添加 role="button"/tabIndex/onKeyDown chapter-sidebar-list.tsx
T-FIX-13 T-AUDIT-14 图标按钮缺 aria-label 添加 aria-label graph-node-detail-panel.tsx
T-FIX-14 T-AUDIT-15/16 loading/error 缺 ARIA 添加 role="status"/aria-busy/role="alert" 8 文件
T-FIX-15 T-AUDIT-17 跨模块 URL 硬编码 通过 props 注入 hrefBase,移除角色前缀 4 文件
T-FIX-16 T-AUDIT-18 use-kp-crud 行数超限 按 create/update/delete 拆为 3 个子 Hook use-kp-crud.ts

4.3 P2优化代码质量

编号 问题 改进方向 影响范围
T-FIX-17 T-AUDIT-19 表单字段重复 抽取 TextbookFormFields 共享组件 2 文件
T-FIX-18 T-AUDIT-20 死代码 删除 verifyKnowledgePointBelongsToChapterReorderChaptersSchemacanEdit prop 3 文件
T-FIX-19 T-AUDIT-21 props 膨胀 拆分为编辑态/内容态/选区态 Context textbook-content-panel.tsx
T-FIX-20 T-AUDIT-22 返回类型未标注 显式标注所有 Hook/工具函数返回类型 5 文件
T-FIX-21 T-AUDIT-23 命令式 DOM 操作 改用 React 状态驱动高亮 textbook-reader.tsx
T-FIX-22 T-AUDIT-24 O(n²) 性能 预建 Set 替代 some() graph-layout.ts
T-FIX-23 T-AUDIT-25 cache() 可测试性 导出非 cache 版本供测试 data-access.ts

4.4 中长期计划P3功能增强

编号 功能 优先级 说明
T-FEAT-01 admin/parent 端教材视图 复用现有组件,新增配置驱动
T-FEAT-02 教材版本管理 新增 textbook_versions
T-FEAT-03 PDF/打印导出 服务端生成 PDF
T-FEAT-04 阅读进度追踪 新增 reading_progress
T-FEAT-05 教材资源附件 章节关联 files 模块
T-FEAT-06 智能推荐学习路径 接入 AI 模块
T-FEAT-07 图谱方向键导航 React Flow 键盘交互
T-FEAT-08 监控埋点接入 实现 analytics.tsx 真实逻辑

五、架构图同步说明

5.1 需要更新的节点

文档 节点 修改内容
004 § 2.5 已知问题 新增 v3 审计发现:学生端缺 requirePermission、图谱缺 scope 过滤、deleteChapter 孤儿记录、error.tsx 重复、a11y 缺失等
004 § 2.5 文件清单 更新行数(如 use-kp-crud 拆分后行数变化)
005 textbooks.knownIssues knownIssues 同步上述新发现
005 textbooks.files files 同步行数变化

5.2 无需修改的部分

  • 导出函数清单:本次审计未发现新增/删除/重命名导出函数
  • 依赖关系:未新增跨模块依赖
  • 权限点:未新增权限点
  • 数据库表:未新增表(中长期计划中的新表待实施时再同步)

附录:审计方法说明

  • 审计基准2026-06-24 代码库快照
  • 审计工具:人工阅读全部 26 个模块文件 + 13 个 app 层文件
  • 覆盖维度:安全/权限、数据完整性、类型安全、错误处理、i18n、a11y、跨模块耦合、代码质量、性能、可测试性
  • 未覆盖E2E 测试、性能压测、安全渗透测试(建议后续补充)