Files
NextEdu/docs/architecture/audit/archive/error-book-audit-report.md

275 lines
16 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.
# 错题本模块审计报告
> 审计日期2026-06-24
> 审计范围:`src/modules/error-book/` 及 `src/app/(dashboard)/{student,teacher,parent,admin}/error-book/`
> 审计依据:项目规则 `docs/standards/coding-standards.md`、架构影响地图 `004`/`005`
---
## 一、现有实现概要
### 1.1 文件分布
错题本模块位于 `src/modules/error-book/`,包含以下文件:
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 341 | 9 个 Server Actions列表/详情/统计/增删改/复习/采集) |
| `data-access.ts` | **1029** | 数据访问层(学生 CRUD + 教师/管理员分析查询 + 跨模块接口) |
| `data-access-collection.ts` | 170 | 自动采集逻辑(考试/作业错题采集) |
| `schema.ts` | 51 | 4 个 Zod 验证 schema |
| `types.ts` | 245 | 11 个类型定义 + 状态映射常量 |
| `sm2-algorithm.ts` | 177 | SM-2 间隔重复算法(纯函数) |
| `sm2-algorithm.test.ts` | - | 39 个单元测试 |
| `components/` | 17 个文件 | UI 组件(学生卡片/列表/筛选/详情/图表等) |
### 1.2 路由分布
| 路由 | 角色 | 文件完整性 |
|------|------|-----------|
| `/student/error-book` | 学生 | page + loading + error ✅ |
| `/teacher/error-book` | 教师 | page + loading + error ✅ |
| `/parent/error-book` | 家长 | page + loading + error ✅ |
| `/admin/error-book` | 管理员 | page + loading + error ✅ |
### 1.3 架构图覆盖情况
架构影响地图 `004_architecture_impact_map.md` 第 2.28 节已覆盖该模块,记录了:
- 模块职责、导出函数、权限点、DataScope 行级权限
- SM-2 算法说明、自动采集机制
- 文件清单、路由清单、数据库表
**但存在以下不一致**
1. `005_architecture_data.json``uses.shared` 仍列出 `examSubmissions``submissionAnswers``homeworkSubmissions` 等表,实际上这些已通过跨模块 data-access 接口访问(`data-access-collection.ts`),不再直接查询
2. JSON 未记录 `adaptive-practice` 依赖,但 `error-book-detail-dialog.tsx` 直接 import 了 `createPracticeSessionAction`
3. JSON 未记录 `ai` 模块依赖,但 `error-book-detail-dialog.tsx` 直接 import 了 `AiErrorBookAnalysis` 组件
---
## 二、现存问题与原因分析
### 2.1 【P0】跨模块直接依赖违反三层架构规则
**项目规则**:「模块间只能通过对方 data-access 通信,禁止跨模块直接查询数据库表」「该模块必须作为独立功能单元」
| 位置 | 违规内容 | 原因 | 后果 |
|------|---------|------|------|
| [error-book-detail-dialog.tsx](file:///e:/Desktop/CICD/src/modules/error-book/components/error-book-detail-dialog.tsx#L40) | `import { AiErrorBookAnalysis } from "@/modules/ai/components/ai-error-book-analysis"` | error-book 组件直接 import ai 模块组件 | 模块强耦合,无法独立测试/部署 |
| [error-book-detail-dialog.tsx](file:///e:/Desktop/CICD/src/modules/error-book/components/error-book-detail-dialog.tsx#L41) | `import { createPracticeSessionAction } from "@/modules/adaptive-practice/actions"` | error-book 组件直接 import adaptive-practice 的 Server Action | 模块强耦合,违反依赖注入原则 |
| [add-error-book-dialog.tsx](file:///e:/Desktop/CICD/src/modules/error-book/components/add-error-book-dialog.tsx#L26) | `import { getQuestionsAction } from "@/modules/questions/actions"` | error-book 组件直接 import questions 模块的 Action | 应通过 data-access 或注入接口调用 |
### 2.2 【P0】i18n 国际化严重遗漏
**项目规则**:「所有用户可见文本必须适配 i18n使用 next-intl提取翻译键」
虽然 `error-book.json` 翻译文件已存在123 行),但**大量组件仍使用硬编码中文**
| 组件 | 硬编码文本示例 | 行数 |
|------|--------------|------|
| `error-book-item-card.tsx` | "题目内容"、"难度"、"掌握度"、"复习 X 次"、"需复习"、"下次"、"未学习"、"入门"... | 10+ 处 |
| `error-book-detail-dialog.tsx` | "题目"、"我的答案"、"正确答案"、"AI 智能分析"、"复习自评"、"学习笔记"、"错误原因标签"、"复习历史"、"归档"、"删除"、"添加于"... | 20+ 处 |
| `error-book-filters.tsx` | "搜索笔记内容..."、"状态"、"来源"、"复习"、"全部状态"、"全部来源"... | 8+ 处 |
| `add-error-book-dialog.tsx` | "手动添加"、"添加错题"、"选择题目"、"学习笔记(可选)"、"错误原因标签"、"取消"、"添加"... | 10+ 处 |
| `review-buttons.tsx` | "重来"、"困难"、"良好"、"简单" 及描述文案i18n 已有翻译但未使用) | 8 处 |
| `error-book-stats-cards.tsx` | "错题总数"、"待学习"、"学习中"、"已掌握"、"待复习" 及描述 | 10 处 |
| `analytics-stats-cards.tsx` | "覆盖学生"、"错题总数"、"平均掌握率"、"待复习"、"涉及知识点" 及子文案 | 10+ 处 |
| `subject-tabs.tsx` | "全部学科"、"待复习" | 2 处 |
| `class-filter.tsx` | "全部班级"、"错题"、"待复习" | 3 处 |
| `top-wrong-questions.tsx` | "高频错题"、"高频错题 Top 10"、"暂无高频错题"、"人错"、"人已掌握"、"掌握率" | 8 处 |
| `knowledge-point-weakness-chart.tsx` | "薄弱知识点 Top X"、"错题数"、"所属章节"、"错题数"、"已掌握"、"掌握率" | 8+ 处 |
| `chapter-weakness-chart.tsx` | "章节错题分布(哪些课在错)"、"错题数"、"已掌握"、"掌握率"、"知识点数"、"薄弱知识点" | 10+ 处 |
| `class-error-bar-chart.tsx` | "各班级错题数对比"、"错题总数"、"学生数"、"人均错题"、"平均掌握率"、"待复习" | 6+ 处 |
| `subject-distribution-chart.tsx` | "各学科错题分布"、"错题数"、"已掌握"、"掌握率" | 4+ 处 |
| `grouped-student-error-table.tsx` | "未分班"、"人"、"人有错题"、"错题总数"、"平均掌握率"、"学生" 及表头 | 15+ 处 |
| `class-error-overview.tsx` | "覆盖学生"、"错题总数"、"平均掌握率"、"薄弱知识点"、"学科错题分布" 等 | 15+ 处 |
| `error-book-list.tsx` | "错题本为空"、"查看详情" | 2 处 |
| `teacher/error-book/page.tsx` | "错题分析"、"按学科、班级查看学生的错题统计与薄弱知识点" 等 | 8+ 处 |
**总计约 150+ 处硬编码中文文本**,违反 i18n 规则。
### 2.3 【P0】类型安全问题违反 TypeScript 严格模式)
**项目规则**:「禁止 `any`、禁止 `as` 断言(除类型收窄外)」
| 文件 | 行号 | 违规代码 | 类型 |
|------|------|---------|------|
| `data-access.ts` | 77 | `row.sourceType as ErrorBookItem["sourceType"]` | `as` 断言 |
| `data-access.ts` | 82 | `row.knowledgePointIds as string[] \| null` | `as` 断言 |
| `data-access.ts` | 90 | `row.errorTags as string[] \| null` | `as` 断言 |
| `data-access.ts` | 133 | `or(...)!` | 非空断言 |
| `data-access.ts` | 175 | `row as unknown as Parameters<typeof mapRowToItem>[0]` | 双重断言 |
| `data-access.ts` | 222 | 同上 | 双重断言 |
| `data-access.ts` | 657 | `row.knowledgePointIds as string[] \| null` | `as` 断言 |
| `data-access.ts` | 792 | 同上 | `as` 断言 |
| `actions.ts` | 63 | `params.status as "new" \| "learning" \| ...` | `as` 断言 |
| `actions.ts` | 69 | `params.sourceType as "exam" \| "homework" \| ...` | `as` 断言 |
| `error-book-item-card.tsx` | 31 | `node as Record<string, unknown>` | `as` 断言 |
| `error-book-detail-dialog.tsx` | 61 | `content as Record<string, unknown>` | `as` 断言 |
| `add-error-book-dialog.tsx` | 48 | `node as Record<string, unknown>` | `as` 断言 |
| `top-wrong-questions.tsx` | 26 | `node as Record<string, unknown>` | `as` 断言 |
| `knowledge-point-weakness-chart.tsx` | 77 | `payload as unknown as {...}` | 双重断言 |
| `chapter-weakness-chart.tsx` | 74 | `payload as unknown as {...}` | 双重断言 |
| `class-error-bar-chart.tsx` | 76 | `payload as unknown as {...}` | 双重断言 |
| `subject-distribution-chart.tsx` | 77 | `payload as unknown as {...}` | 双重断言 |
### 2.4 【P1】data-access.ts 超过 1000 行硬性上限
**项目规则**:「硬性上限:任何文件不超过 1000 行,超过必须拆分」
`data-access.ts` 当前 **1029 行**,超出硬性上限。该文件混合了:
- 学生端 CRUD`getErrorBookItems``createErrorBookItem``recordReview` 等)
- 教师/管理员分析查询(`getStudentErrorBookSummaries``getKnowledgePointWeakness``getChapterWeakness``getClassErrorOverviews``getSubjectErrorOverviews` 等)
- 跨模块查询接口(`getStudentIdsByClassIdList``getAllStudentIds`
### 2.5 【P1】性能问题全量查询 + JS 端聚合
| 函数 | 问题 | 影响 |
|------|------|------|
| `getErrorBookStats` | 查询该学生**所有**错题行到内存,再 JS 循环统计 | 学生错题多时内存/CPU 浪费 |
| `getStudentErrorBookSummaries` | 查询所有学生的所有错题行,再 JS 聚合 | 班级学生多时性能差 |
| `getKnowledgePointWeakness` | 查询所有错题行JS 展开知识点数组再统计 | 同上 |
| `getChapterWeakness` | 同上 | 同上 |
| `getSubjectErrorDistribution` | 同上 | 同上 |
| `getClassErrorOverviews` | 同上 | 同上 |
| `getSubjectErrorOverviews` | 同上 | 同上 |
| `getTopWrongQuestionsByStudentIds` | 同上 | 同上 |
| `admin/page.tsx` | `allStudentIds.slice(0, 500)` 硬编码限制,无分页 | 超过 500 学生时数据不完整 |
**应使用 SQL `GROUP BY` + `COUNT` 聚合查询**,避免全量加载到内存。
### 2.6 【P1】a11y 可访问性缺失
**项目规则**「可访问性a11y语义化标签、ARIA 属性、键盘导航」
| 位置 | 问题 |
|------|------|
| `grouped-student-error-table.tsx:162` | 使用 `<a href>` 而非 Next.js `<Link>`(违反项目记忆中的 Link 规范) |
| `subject-tabs.tsx` | `<button>` 缺少 `role="tab"` / `aria-selected` / `aria-controls` |
| `class-filter.tsx` | 同上 |
| 所有图表组件 | 缺少 `aria-label` 描述图表内容 |
| `review-buttons.tsx` | 按钮缺少 `aria-label` 描述操作 |
| `grouped-student-error-table.tsx:96` | 可展开行缺少 `aria-expanded` |
| `error-book-detail-dialog.tsx` | `<textarea>` 缺少 `aria-label` |
### 2.7 【P1】错误边界不完整
**项目规则**:「每个独立的数据区块必须用 React Error Boundary 包裹」
| 位置 | 问题 |
|------|------|
| `parent/error-book/page.tsx` | 无 `Suspense` 包裹,整个页面同步渲染 |
| 所有页面 | 仅页面级 `error.tsx`,各数据区块(统计卡片/图表/表格)无独立 Error Boundary |
| 图表组件 | recharts 渲染失败时无 fallback |
### 2.8 【P2】组件复用问题
| 问题 | 位置 | 说明 |
|------|------|------|
| `extractQuestionPreview` 函数重复 3 次 | `error-book-item-card.tsx``add-error-book-dialog.tsx``top-wrong-questions.tsx` | 应提取到 shared 工具函数 |
| `extractQuestionText` 函数重复 | `error-book-detail-dialog.tsx` | 与上面类似但实现不同 |
| `MASTERY_LEVEL_LABELS` 硬编码 | `error-book-item-card.tsx:47` | 应使用 i18n |
| `QUESTION_TYPE_LABEL` 硬编码 | `top-wrong-questions.tsx:35` | 应使用 i18n |
| `class-error-overview.tsx` 导出 `StudentErrorTable` | 似乎已被 `GroupedStudentErrorTable` 取代 | 死代码 |
| `COMMON_ERROR_TAGS` 硬编码中文 | `types.ts:55` | 应使用 i18n 键 |
### 2.9 【P2】数据服务未抽象不可测试
**项目规则**:「可测试性:数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks与 UI 分离」
当前组件直接 import `actions``data-access`
- `error-book-detail-dialog.tsx` 直接 import `archiveErrorBookItemAction``deleteErrorBookItemAction``updateErrorBookNoteAction`
- `add-error-book-dialog.tsx` 直接 import `createErrorBookItemAction`
- `review-buttons.tsx` 直接 import `reviewErrorBookItemAction`
**无法在不启动整个应用的情况下 mock 这些依赖**,违反可测试性原则。
### 2.10 【P2】权限校验位置不统一
`getAllStudentIds()` 在 data-access 层通过 `roles.name === "student"` 查询,虽然不在前端,但角色名字符串硬编码在查询中。应使用 `shared/types/permissions` 中的角色常量。
---
## 三、行业差距对比
### 3.1 与优秀 K12 产品的差距
| 功能 | 我们 | 智学网 | 猿题库 | 钉钉教育 | 差距影响 |
|------|------|--------|--------|---------|---------|
| 智能复习队列 | ✅ SM-2 | ✅ | ✅ | ❌ | 基本持平 |
| 复习提醒通知 | ❌ | ✅ 推送 | ✅ 推送 | ✅ | 学生不知道何时复习 |
| 错题趋势图 | ❌(类型已定义未实现) | ✅ | ✅ | ❌ | 无法看到进步趋势 |
| 导出/打印错题 | ❌ | ✅ PDF | ✅ PDF | ❌ | 无法离线复习 |
| 批量操作 | ❌ | ✅ | ✅ | ❌ | 管理大量错题效率低 |
| 班级平均对比 | ❌ | ✅ | ✅ | ❌ | 学生不知道自己水平 |
| 复习日历视图 | ❌ | ✅ | ✅ | ❌ | 无法直观看到复习安排 |
| 错题来源详情跳转 | ❌ | ✅ | ✅ | ❌ | 无法回看原试卷/作业 |
| 知识点掌握度雷达图 | ❌ | ✅ | ✅ | ❌ | 无法多维度看薄弱点 |
| 游戏化激励(连续复习天数) | ❌ | ✅ | ✅ | ❌ | 学生缺乏复习动力 |
| 智能推题(基于错题变式) | ✅(已接入 adaptive-practice | ✅ | ✅ | ❌ | 基本持平 |
### 3.2 UI/UX 差距
| 差距 | 说明 | 影响角色 |
|------|------|---------|
| 学生端缺少复习仪表盘 | 当前只有列表+筛选,无"今日待复习"独立视图 | 学生 |
| 教师端缺少学生个体下钻 | 点击学生只能跳转带参数,无学生错题详情面板 | 教师 |
| 家长端无子女切换 | 多子女时用卡片展示,无 Tab 切换对比 | 家长 |
| 管理员端无年级维度 | 只有全校维度,无年级/班级下钻 | 管理员 |
| 无骨架屏一致性 | 各页面 loading.tsx 结构不一致 | 全部 |
---
## 四、改进优先级建议
### P0紧急影响架构合规与安全
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P0-1 | 跨模块直接依赖 | 将 `AiErrorBookAnalysis``createPracticeSessionAction``getQuestionsAction` 改为通过 props/Context 注入error-book 模块不直接 import 其他业务模块 |
| P0-2 | i18n 硬编码150+ 处) | 全部提取为翻译键,扩展 `error-book.json` 翻译文件 |
| P0-3 | 类型安全18 处 as 断言) | 使用类型守卫替代 `as`,图表 tooltip 使用泛型组件 |
### P1重要影响性能与可访问性
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P1-1 | data-access.ts 超 1000 行 | 拆分为 `data-access.ts`(学生 CRUD+ `data-access-analytics.ts`(教师/管理员分析) |
| P1-2 | 全量查询 + JS 聚合 | 改用 SQL `GROUP BY` + `COUNT` 聚合 |
| P1-3 | a11y 缺失 | 添加 ARIA 属性、语义化标签、`<Link>` 替代 `<a>` |
| P1-4 | 错误边界不完整 | 为各数据区块添加 Error Boundary |
| P1-5 | admin 无分页 | 实现分页查询或虚拟滚动 |
### P2优化提升可维护性与用户体验
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P2-1 | 重复函数 | 提取 `extractQuestionPreview``shared/lib/question-content.ts`(已存在) |
| P2-2 | 数据服务未抽象 | 定义 `ErrorBookService` 接口,通过 Context 注入 |
| P2-3 | 死代码 | 删除 `class-error-overview.tsx` 中未使用的 `StudentErrorTable` |
| P2-4 | 缺失功能 | 错题趋势图、复习提醒、导出、批量操作(中长期) |
| P2-5 | 角色字符串硬编码 | `getAllStudentIds` 使用角色常量 |
---
## 五、架构图同步说明
### 5.1 需要修改的节点
1. **`005_architecture_data.json``error-book.uses.shared`**
- 移除 `db.schema.examSubmissions``db.schema.submissionAnswers``db.schema.homeworkSubmissions``db.schema.homeworkAnswers``db.schema.examQuestions``db.schema.homeworkAssignmentQuestions`(这些已通过跨模块 data-access 访问)
2. **`005_architecture_data.json``error-book.dependsOn`**
- 新增 `ai`error-book-detail-dialog 直接依赖 ai 组件)
- 新增 `adaptive-practice`error-book-detail-dialog 直接依赖其 Action
- 新增 `questions`add-error-book-dialog 直接依赖其 Action—— 注意questions 已在 dependsOn 中,但 uses 中记录的是 `actions.getQuestionsAction` 而非 data-access
3. **`004_architecture_impact_map.md` 第 2.28 节**
- 文件清单中 `data-access.ts` 行数更新为拆分后的两个文件
- 依赖关系新增 ai / adaptive-practice 的直接依赖说明(标注为"待解耦"
### 5.2 架构图待补充项
- 拆分后的 `data-access-analytics.ts` 文件信息
- `ErrorBookService` 接口定义(如实施 P2-2
- i18n 翻译文件结构更新