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

325 lines
19 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.
# 备课模块审计报告 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。