# 备课模块审计报告 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) #### 问题 1:Parent/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,也未过滤 status(archived 课案也会被查出)** - **违反规则**:同问题 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 模块变更影响备课模块 #### 问题 9:admin/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` - `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 schema,data-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。