19 KiB
19 KiB
备课模块审计报告 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.jsonmodules.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项)vsRICH_TEXT_TYPES(2项)vsBLOCK_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 XxxBlockDatalesson-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 类型"直接硬编码为中文,整个文件未 importuseTranslations - 违反规则:项目规则 "所有用户可见文本必须适配 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 硬编码中文 |
参数化注入 |
五、架构图同步说明
本次审计发现架构图需补充以下内容:
- 依赖关系补充:
node-edit-panel.tsx对@/modules/ai的直接依赖需在005_architecture_data.json的lesson_preparation.dependsOn中记录 - auditFixes 补充:本次 V3 续续审计的修复记录(V3-23 ~ V3-35)需添加到
005_architecture_data.json的auditFixes对象 - 文件清单更新:若
use-lesson-plan-editor.ts拆分为多个 slice,需更新004文件清单和005files 数组 - 配置冲突修复:
constants.ts与block-registry.tsx的富文本配置统一后,需更新004中两者的描述
六、实施计划
本报告所有问题将按 P0 → P1 → P2 顺序完整实施,实施过程中同步更新架构图 004/005。