docs(audit): add audit reports for grades, homework, lesson-preparation, messaging, permissions, question-bank, settings, textbooks

- Add grades-audit-report

- Add homework-audit-report and homework-exams-audit-report

- Add lesson-preparation-audit-report-v3 and v4

- Add messaging-audit-report

- Add permissions-audit-report

- Add question-bank-audit-report

- Add settings-profile-audit-report-v3

- Add textbooks-audit-report-v3
This commit is contained in:
SpecialX
2026-07-03 10:23:34 +08:00
parent 365c36d97b
commit 89b9e181d2
29 changed files with 13009 additions and 230 deletions

View File

@@ -0,0 +1,324 @@
# 备课模块审计报告 v3
> 审计日期2026-06-24
> 审计范围:`src/modules/lesson-preparation/` 全部文件 + `src/app/(dashboard)/{teacher,admin,student,parent}/lesson-plans/` 全部页面
> 审计依据:`e:\Desktop\CICD\.trae\rules\project_rules.md`、`docs/architecture/004_architecture_impact_map.md` §2.27、`docs/architecture/005_architecture_data.json` modules.lesson_preparation
> 前序报告:`lesson-preparation-audit-report-v2.md`
---
## 一、现有实现概要
### 1.1 文件分布
备课模块共 45 个文件,分布如下:
| 层级 | 文件数 | 主要文件 |
|------|--------|----------|
| types/schema/constants | 3 | types.ts(345行)、schema.ts(63行)、constants.ts(117行) |
| data-access | 4 | data-access.ts(593行)、data-access-versions.ts(181行)、data-access-templates.ts(129行)、data-access-knowledge.ts(95行) |
| actions | 4 | actions.ts(391行)、actions-publish.ts(82行)、actions-ai.ts(46行)、actions-kp.ts(47行) |
| services | 2 | publish-service.ts(198行)、ai-suggest.ts(82行) |
| lib | 6 | type-guards.ts(213行)、i18n-errors.ts(50行)、document-migration.ts(330行)、anchor-injector.ts(304行)、node-summary.ts(133行)、rf-mappers.ts(172行) |
| config | 1 | block-registry.tsx(194行) |
| providers | 2 | lesson-plan-provider.tsx(336行)、lesson-plan-provider-setup.tsx(29行) |
| services | 1 | default-data-service.ts(165行) |
| hooks | 1 | use-lesson-plan-editor.ts(303行) |
| components | 18 | 含 11 个 block 组件 + 4 个 node 组件 + 7 个业务组件 |
| seed | 1 | seed-templates.ts(9行) |
| 页面 | 13 | teacher(3) + admin(2) + student(2) + parent(2) + loading/error(4) |
### 1.2 数据流
```
页面(Server Component) → getAuthContext() → data-access → DB
LessonPlanProviderSetup → LessonPlanProvider
LessonPlanEditor(LessonPlanList) → useLessonPlanContextSafe()
default-data-service → Server Actions → requirePermission → data-access → DB
```
### 1.3 架构图完整性
架构影响地图 §2.27 记录的导出函数、文件清单、依赖关系与实际代码**基本一致**,但存在以下遗漏:
- `providers/lesson-plan-provider-setup.tsx` 已在 V3 续审计中补充
- `components/nodes/anchor-node-selector.tsx``textbook-segments.tsx` 已在 V3 续审计中补充
- `node-edit-panel.tsx``@/modules/ai` 的直接依赖**未在架构图依赖关系中记录**
---
## 二、现存问题与原因分析
### 2.1 安全与权限问题P0
#### 问题 1Parent/Student 路由未校验孩子/班级归属 — 信息泄露漏洞
- **位置**`data-access.ts` 第 197-204 行 `buildScopeCondition`
- **问题**`children``class_members` 类型的 DataScope 仅过滤 `status = 'published'`**完全未使用 `scope.childrenIds`/`scope.classIds`/`scope.gradeIds` 进行归属过滤**
- **违反规则**:项目规则 "Parent routes must include permission checks with both parentId and studentId to prevent information leakage"
- **后果**:任何家长可查看全校所有已发布课案;任何学生可查看全校所有已发布课案,构成信息泄露
#### 问题 2`createLessonPlanVersion` 未校验 planId 归属
- **位置**`data-access-versions.ts` 第 56-82 行
- **问题**:函数接收 `planId``userId`,但事务内只查询 `lessonPlanVersions` 表的 max(versionNo)**未校验该 planId 是否属于 userId**
- **违反规则**:项目规则 "All Server Actions must call requirePermission() for permission verification" + "软删除/权限过滤在 data-access 层结合 userId 过滤"
- **后果**:调用方传入任意 planId 即可为他人课案创建版本记录,越权写入
#### 问题 3`pruneAutoVersions` 完全无权限校验
- **位置**`data-access-versions.ts` 第 152-181 行
- **问题**:函数签名 `pruneAutoVersions(planId, keep = 50)` **没有 userId 参数**,任何调用方传入 planId 即可删除该课案的自动版本记录
- **违反规则**:同问题 2
- **后果**:越权删除他人课案的版本历史
#### 问题 4`getLessonPlansByKnowledgePoint`/`getLessonPlansByQuestion` 无权限过滤
- **位置**`data-access-knowledge.ts` 第 11-95 行
- **问题**:两个函数直接 `db.select().from(lessonPlans)` 查询全表,**未过滤 creatorId也未过滤 statusarchived 课案也会被查出)**
- **违反规则**:同问题 2
- **后果**:任意调用方可获取所有课案(含他人 draft、archived 状态)的列表,严重越权
#### 问题 5`saveLessonPlanVersionAction` 的 schema 不包含 content 字段
- **位置**`actions.ts` 第 154-178 行、`schema.ts` 第 26-28 行
- **问题**`saveVersionSchema` 仅校验 `planId``label`**完全不包含 `content` 字段**`input.content`(类型为 `LessonPlanDocument`)未经任何运行时校验直接持久化
- **违反规则**:项目规则 "输入使用 Zod 验证,验证失败返回结构化错误"
- **后果**:恶意或畸形文档结构可被写入数据库
#### 问题 6`publishLessonPlanHomeworkAction` 中 homeworkTitle 传入 planId
- **位置**`actions-publish.ts` 第 35 行
- **问题**`homeworkTitle = t("publish.homeworkTitle", { title: parsed.data.planId })``title` 参数传入的是 `planId`UUID而非课案标题
- **违反规则**:业务逻辑正确性
- **后果**:作业标题将包含 UUID 而非有意义的课案名称
#### 问题 7`getLessonPlansAction` 的 params 未经验证
- **位置**`actions.ts` 第 43-53 行
- **问题**`params`(含 `query`/`textbookId`/`chapterId`/`subjectId`/`status`**未经过 Zod 验证**直接传入 `getLessonPlans()`
- **违反规则**:项目规则 "输入使用 Zod 验证"
- **后果**:若 `query` 用于 SQL LIKE 查询且未调用 `escapeLikePattern()`,存在 LIKE 通配符注入风险
### 2.2 架构违规问题P0/P1
#### 问题 8`node-edit-panel.tsx` 直接 import `@/modules/ai`
- **位置**`node-edit-panel.tsx` 第 11-12 行
- **问题**:直接 import `AiLessonContentGenerator``useAiClientOptional`,形成模块间紧耦合
- **违反规则**:项目规则 "模块内部组件绝不直接 import 其他业务模块的 actions 或 data-access只能通过注入的接口调用"
- **后果**模块间紧耦合无法独立测试AI 模块变更影响备课模块
#### 问题 9admin/student/parent 列表页未包裹 `LessonPlanProviderSetup`
- **位置**`admin/lesson-plans/page.tsx``student/lesson-plans/page.tsx``parent/lesson-plans/page.tsx`
- **问题**`LessonPlanList` 调用 `useLessonPlanContextSafe()` 获取 `service`,当未在 Provider 内使用时 `service``null`,导致 `handleFilter` 静默返回 — **筛选功能在 admin/student/parent 页面完全失效**
- **违反规则**:项目规则 "Provider 是否正确注入数据服务"
- **后果**:筛选 UI 仍可见但点击无反应,用户体验差
#### 问题 10`publish-service.ts` 多步写操作未包裹事务
- **位置**`publish-service.ts` 第 56-197 行
- **问题**`publishLessonPlanHomework` 包含 5 个写操作步骤(创建题目、创建 exam 草稿、插入 exam 题目关联、下发作业、回写溯源标记),**均未包裹在 `db.transaction` 中**
- **违反规则**:项目规则 "需要原子性的操作必须包裹在 db.transaction 中"
- **后果**:任一步骤失败将导致数据不一致(孤儿题目、草稿残留、溯源断裂)
### 2.3 类型安全问题P1
#### 问题 11`rf-mappers.ts` 颜色映射 Bug
- **位置**`rf-mappers.ts` 第 113 行
- **问题**`getNodeColor(anchor.nodeId)` 传入 nodeId 而非 node type`getNodeColor` 期望接收节点类型(如 `"objective"`),传入 nodeId 必然找不到匹配项
- **违反规则**:功能性 Bug
- **后果**:锚点边始终使用默认灰色 `#9e9e9e`,注释声称的"P1-4 修复"实际未生效
#### 问题 12富文本类型配置三处不一致
- **位置**`constants.ts` 第 25-36 行、`block-registry.tsx` 第 50、56-69 行
- **问题**`RICH_TEXT_BLOCK_TYPES`10项vs `RICH_TEXT_TYPES`2项vs `BLOCK_REGISTRY.isRichText`2项三处定义语义冲突
- **违反规则**:配置一致性
- **后果**`isRichTextBlock()``RICH_TEXT_BLOCK_TYPES` 行为完全相反,调用方混用会产生矛盾结果
#### 问题 13多处 `as` 断言违规
- **位置**
- `node-summary.ts` 第 28-55 行:`as { html?: string; ... }` 将联合类型转为内联接口
- `rf-mappers.ts` 第 81、95 行:`as Record<string, unknown>`
- `block-renderer.tsx` 第 107、115、121、125 行4 处 `as XxxBlockData`
- `lesson-plan-readonly-view.tsx` 第 91 行:`as Edge[]`
- `inline-question-editor.tsx` 第 34 行:`as typeof QUESTION_TYPES[number]`
- `data-access-knowledge.ts` 第 24、39、67、82 行4 处 `as` 断言
- `use-lesson-plan-editor.ts` 第 137、138、154、155 行4 处 `as` 断言
- 4 个页面文件中 `findChapter``as typeof chapters`(冗余断言)
- `actions.ts` 第 144 行:`as unknown as LessonPlanDocument` 双重断言
- **违反规则**:项目规则 "禁止 `as` 断言(除类型收窄外)"
- **后果**:类型安全被绕过,潜在运行时错误
#### 问题 14`actions-ai.ts`/`actions-kp.ts` 隐式 any
- **位置**`actions-ai.ts` 第 34 行 `Array.isArray(doc.nodes)` 收窄为 `any[]``actions-kp.ts` 第 30 行 `let kps;` 隐式 any
- **违反规则**:项目规则 "禁止 `any`"
- **后果**:类型安全缺失
### 2.4 i18n 与错误处理问题P1
#### 问题 15`actions-ai.ts`/`actions-kp.ts` 未使用 `handleActionError` 和 `translateFieldErrors`
- **位置**`actions-ai.ts` 第 23、41-45 行;`actions-kp.ts` 第 23、42-46 行
- **问题**Zod 错误未调用 `translateFieldErrors()` 翻译catch 块未使用 `handleActionError()`,非权限错误被静默吞掉,无日志
- **违反规则**:项目规则 "所有 Server Action catch 块改用 handleActionError"
- **后果**:错误不可观测,用户看到未翻译的 i18n 键
#### 问题 16`block-renderer.tsx` 硬编码中文
- **位置**`block-renderer.tsx` 第 130 行
- **问题**`"未知 block 类型"` 直接硬编码为中文,整个文件未 import `useTranslations`
- **违反规则**:项目规则 "所有用户可见文本必须适配 i18n"
- **后果**:多语言环境下显示中文
#### 问题 17`schema.ts` 多个 schema 缺少 i18n 错误消息
- **位置**`schema.ts` 第 14-32 行
- **问题**:仅 `createLessonPlanSchema``publishLessonPlanHomeworkSchema` 部分字段使用 i18n 键,其他 6 个 schema 均未使用
- **违反规则**:项目规则 "Zod schema 错误消息应使用 i18n 键"
- **后果**Zod 校验错误返回英文默认消息
### 2.5 a11y 与 UX 问题P1/P2
#### 问题 18`inline-question-editor.tsx` Modal 缺少焦点陷阱
- **位置**`inline-question-editor.tsx` 第 72-227 行
- **问题**:模态框设置了 `role="dialog"``aria-modal="true"`但未实现焦点陷阱、Escape 关闭、焦点管理
- **违反规则**a11y 键盘导航
- **后果**:键盘用户无法正常使用模态框
#### 问题 19多处缺少 aria-label
- **位置**`lesson-plan-editor.tsx` 第 239-243 行(标题输入框)、第 330-335 行(添加节点按钮);`block-renderer.tsx` 第 71-102 行4 个图标按钮);`lesson-plan-readonly-view.tsx` 第 89-103 行(画布缺少 aria
- **违反规则**a11y 语义化标签
- **后果**:屏幕阅读器无法识别元素用途
#### 问题 20`getLessonPlanStats` 的 archived 恒为 0
- **位置**`data-access.ts` 第 553-566 行、`admin/lesson-plans/page.tsx` 第 63-72 行
- **问题**WHERE 子句排除 archived 后硬编码返回 `archived: 0`,但 admin 页面仍渲染"已归档"统计卡片
- **违反规则**:数据准确性
- **后果**:统计卡片永远显示 0具有误导性
### 2.6 性能与代码质量问题P2
#### 问题 21`use-lesson-plan-editor.ts` 303 行超标
- **位置**`hooks/use-lesson-plan-editor.ts`
- **问题**:文件 303 行,远超 Hook 80 行建议上限
- **违反规则**:项目规则 "自定义 Hook建议 ≤ 80 行"
- **后果**:可维护性差
#### 问题 22`findChapter` 在 4 个页面重复
- **位置**teacher edit、admin view、student view、parent view
- **问题**4 个页面文件中存在完全相同的 `findChapter` 递归函数实现
- **违反规则**DRY 原则
- **后果**:代码重复,维护成本高
#### 问题 23`lesson-plan-card.tsx` 4 个异步函数未使用 useCallback
- **位置**`lesson-plan-card.tsx` 第 72、89、105、122 行
- **问题**`handleArchive`/`handleDuplicate`/`handlePublish`/`handleUnpublish` 每次渲染重新创建
- **违反规则**:性能最佳实践
- **后果**:不必要渲染
#### 问题 24多处 Tailwind 任意值
- **位置**`lesson-plan-editor.tsx`4处`node-edit-panel.tsx`2处`inline-question-editor.tsx`3处
- **违反规则**:项目规则 "禁止使用任意值(`w-[137px]`),除非有充分理由并注释"
---
## 三、行业差距对比
### 3.1 与优秀 K12 产品的差距
| 维度 | 优秀实践 | 当前实现 | 差距影响 |
|------|----------|----------|----------|
| **权限隔离** | 家长仅能查看自己孩子班级的课案,学生仅能查看自己班级课案 | 家长/学生可查看全校已发布课案 | 信息泄露风险,违反 FERPA/GDPR 等隐私法规 |
| **数据一致性** | 发布作业等关键操作使用事务保证原子性 | 多步写操作无事务 | 部分失败导致数据不一致,需要人工修复 |
| **版本管理** | 版本创建/删除有严格的归属校验 | 版本创建/删除可越权操作 | 用户可篡改他人课案历史 |
| **a11y** | 模态框实现完整焦点管理,所有交互元素有 aria-label | Modal 无焦点陷阱,多处缺少 aria-label | 残障用户无法使用 |
| **错误可观测性** | 所有异常记录日志,有统一错误处理 | 部分Action 静默吞掉错误 | 生产问题无法排查 |
| **配置一致性** | 单一数据源定义 Block 类型属性 | 三处富文本配置冲突 | 运行时行为不可预测 |
| **模块解耦** | 通过接口抽象依赖,支持独立测试 | 直接 import AI 模块组件 | 无法独立测试,变更影响扩散 |
### 3.2 缺失的功能
- **课案协作**:优秀产品支持多位教师协作编辑同一课案,当前仅支持单教师
- **课案评课**:教研组长可对课案添加评课意见,当前缺失
- **课案资源库**:跨学期/学年的课案资源库检索,当前仅按教材章节组织
- **AI 智能备课**:基于课标自动生成教学目标/重难点,当前仅有知识点推荐
---
## 四、改进优先级建议
### P0严重 — 安全/正确性,必须立即修复)
| # | 问题 | 改进方向 |
|---|------|----------|
| P0-1 | Parent/Student 路由未校验归属关系 | 修复 `buildScopeCondition``children`/`class_members` 分支,使用 `scope.classIds`/`scope.gradeIds`/`scope.childrenIds` 过滤;修复 `getLessonPlanById` 增加归属校验 |
| P0-2 | `createLessonPlanVersion` 未校验 planId 归属 | 事务开头增加 `lessonPlans.creatorId = userId` 校验 |
| P0-3 | `pruneAutoVersions` 无 userId 参数 | 增加 `userId` 参数,删除前校验 planId 归属 |
| P0-4 | `getLessonPlansByKnowledgePoint`/`getLessonPlansByQuestion` 无权限过滤 | 增加 `userId` 参数,过滤 `status != 'archived'`,按 creatorId/DataScope 限制 |
| P0-5 | `saveLessonPlanVersionAction` schema 不含 content | 定义 `lessonPlanDocumentSchema` 递归校验文档结构 |
| P0-6 | `publishLessonPlanHomeworkAction` homeworkTitle 传入 planId | 先查询课案标题再传入 |
| P0-7 | `getLessonPlansAction` params 未验证 | 定义 Zod schemadata-access 使用 `escapeLikePattern` |
| P0-8 | `rf-mappers.ts` 颜色映射 Bug | 传入节点 type 而非 nodeId |
| P0-9 | 富文本配置三处不一致 | 统一为单一数据源,删除冗余定义 |
| P0-10 | `block-renderer.tsx` 硬编码中文 + as 断言 | 使用 i18n + 类型守卫 |
| P0-11 | `node-edit-panel.tsx` 直接 import @/modules/ai | 通过 props 注入 AI 组件 |
| P0-12 | `publish-service.ts` 多步写操作无事务 | 包裹 `db.transaction` |
| P0-13 | admin/student/parent 列表页未包裹 Provider | 包裹 `LessonPlanProviderSetup` |
### P1高 — 类型安全/规范一致性)
| # | 问题 | 改进方向 |
|---|------|----------|
| P1-1 | 多处 `as` 断言node-summary/rf-mappers/block-renderer/readonly-view/inline-question-editor/data-access-knowledge/use-lesson-plan-editor/4页面 | 使用类型守卫替代 |
| P1-2 | `actions-ai.ts`/`actions-kp.ts` 隐式 any | 显式类型标注 |
| P1-3 | `actions-ai.ts`/`actions-kp.ts` 未用 handleActionError/translateFieldErrors | 统一错误处理 |
| P1-4 | `schema.ts` 多个 schema 缺 i18n 错误消息 | 补全 i18n 键 |
| P1-5 | `getLessonPlanStats` archived 恒为 0 | 修复统计逻辑 |
| P1-6 | `findChapter` 4 处重复 | 提取为 textbooks 共享工具 |
| P1-7 | `inline-question-editor.tsx` Modal 缺焦点陷阱 | 实现焦点管理 |
| P1-8 | 多处缺少 aria-label | 补全 a11y 属性 |
| P1-9 | `data-access-knowledge.ts` LIKE 未用 escapeLikePattern | 统一转义 |
| P1-10 | `actions-publish.ts` 重复 requirePermission | 复用 AuthContext |
### P2中 — 性能/代码质量)
| # | 问题 | 改进方向 |
|---|------|----------|
| P2-1 | `use-lesson-plan-editor.ts` 303 行超标 | 拆分为多个 slice |
| P2-2 | `lesson-plan-card.tsx` 4 函数未 useCallback | 包裹 useCallback |
| P2-3 | 多处 Tailwind 任意值 | 提取设计令牌 |
| P2-4 | `lesson-plan-list.tsx` 缺筛选加载状态 | 增加 isLoading |
| P2-5 | `block-renderer.tsx` 废弃文件 | 评估是否可删除 |
| P2-6 | `ai-suggest.ts` prompt 硬编码中文 | 参数化注入 |
---
## 五、架构图同步说明
本次审计发现架构图需补充以下内容:
1. **依赖关系补充**`node-edit-panel.tsx``@/modules/ai` 的直接依赖需在 `005_architecture_data.json``lesson_preparation.dependsOn` 中记录
2. **auditFixes 补充**:本次 V3 续续审计的修复记录V3-23 ~ V3-35需添加到 `005_architecture_data.json``auditFixes` 对象
3. **文件清单更新**:若 `use-lesson-plan-editor.ts` 拆分为多个 slice需更新 `004` 文件清单和 `005` files 数组
4. **配置冲突修复**`constants.ts``block-registry.tsx` 的富文本配置统一后,需更新 `004` 中两者的描述
---
## 六、实施计划
本报告所有问题将按 P0 → P1 → P2 顺序完整实施,实施过程中同步更新架构图 004/005。