Files
NextEdu/docs/architecture/audit/archive/question-bank-audit-report.md

317 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.
# 题库模块审计报告
> 审计日期2026-06-22
> 审计范围:`src/modules/questions/`、`src/app/(dashboard)/teacher/questions/`、共享组件 `src/shared/components/question/`
> 架构图参考:`docs/architecture/004_architecture_impact_map.md`、`docs/architecture/005_architecture_data.json`
---
## 一、现有实现概要
### 1.1 文件分布
| 层 | 文件 | 行数 | 职责 |
|------|------|------|------|
| app | `src/app/(dashboard)/teacher/questions/page.tsx` | 129 | 教师题库列表页Server Component |
| app | `src/app/(dashboard)/teacher/questions/loading.tsx` | 29 | 骨架屏 |
| module | `src/modules/questions/actions.ts` | 158 | 5 个 Server ActionCRUD + 查询) |
| module | `src/modules/questions/data-access.ts` | 357 | 数据访问层(含跨模块接口) |
| module | `src/modules/questions/types.ts` | 53 | 类型定义 |
| module | `src/modules/questions/schema.ts` | 18 | Zod 校验 |
| module | `src/modules/questions/components/question-filters.tsx` | 104 | 筛选栏(自有实现) |
| module | `src/modules/questions/components/question-columns.tsx` | 142 | 表格列定义 |
| module | `src/modules/questions/components/question-data-table.tsx` | 134 | 数据表格 |
| module | `src/modules/questions/components/create-question-button.tsx` | 20 | 创建按钮 |
| module | `src/modules/questions/components/create-question-dialog.tsx` | 453 | 创建/编辑对话框 |
| module | `src/modules/questions/components/question-actions.tsx` | 188 | 行操作菜单 |
| shared | `src/shared/components/question/question-bank-filters.tsx` | 137 | 共享筛选栏exams/lesson-prep 复用) |
### 1.2 数据流
```
teacher/questions/page.tsx (RSC)
├─ requirePermission(QUESTION_READ) ← 服务端权限校验
├─ getQuestions(params) ← 直接调用 data-access项目规则允许
├─ <QuestionFilters /> ← 客户端组件,调用 getKnowledgePointOptionsAction
└─ <QuestionDataTable columns data /> ← 客户端表格
└─ <QuestionActions question /> ← 行操作
├─ <CreateQuestionDialog /> ← 编辑(调用 updateQuestionAction
└─ AlertDialog ← 删除(调用 deleteQuestionAction
```
### 1.3 架构图记录情况
`005_architecture_data.json` 已记录 questions 模块的 actions / dataAccess / schema / types / components但存在以下遗漏
- **依赖矩阵不完整**`dependencyMatrix.questions.dependsOn` 仅列出 `["shared", "auth"]`**缺失 `textbooks`**。实际 `data-access.ts:8` 导入了 `@/modules/textbooks/data-access``getKnowledgePointOptions`,属于跨模块 data-access 通信,应在依赖矩阵中记录。
- **action 名称不一致**:架构图记录 action 名为 `createNestedQuestion`,实际代码为 `createQuestionAction`
- **跨模块接口未记录**`getKnowledgePointsForQuestions``getQuestionsContentForErrorCollection` 两个跨模块接口未在架构图中记录。
---
## 二、现存问题与原因分析
### P0 — 严重问题
#### P0-1筛选方式不符合 K12 教学场景(用户核心痛点)
- **位置**`question-filters.tsx``page.tsx``data-access.ts:getQuestions`
- **问题**:当前筛选仅支持「关键词搜索 + 题型 + 难度 + 扁平知识点下拉」。知识点下拉将所有教材的所有章节的所有知识点平铺在一个列表中,教师面对数百条选项无法快速定位。
- **违反规则**:项目规则「交互不符合多角色使用习惯」;用户明确要求「选择一个课本 → 一个章节 → 一个课文 → 知识点」级联筛选。
- **后果**:教师无法按教学进度精准筛选题目,题库实用性大打折扣,被迫退回手动翻找。
#### P0-2完全缺失 i18n 国际化
- **位置**:题库模块全部文件
- **问题**:所有用户可见文本均为硬编码英文("Question Bank"、"Add Question"、"Search questions..."、"All Types"、"Single Choice" 等),**不存在 `questions.json` 翻译文件**。
- **违反规则**:项目规则「所有用户可见文本必须适配 i18n使用 next-intl提取翻译键」。
- **后果**无法支持中文环境与项目其他模块exams、textbooks、attendance 等均已 i18n不一致。
#### P0-3缺失 error.tsx 错误边界
- **位置**`src/app/(dashboard)/teacher/questions/`
- **问题**:仅有 `loading.tsx`**无 `error.tsx`**。当 `getQuestions``requirePermission` 抛错时,错误会冒泡到上层 layout整个仪表盘白屏。
- **违反规则**:项目规则「所有 student routes 必须包含 loading.tsx 和 error.tsx」此规则虽针对 student 路由,但教师路由同样应遵循);架构原则「每个独立的数据区块必须用 React Error Boundary 包裹」。
- **后果**:单次查询失败导致整页不可用,无法恢复。
### P1 — 高优先级问题
#### P1-1`as` 类型断言违反 TypeScript 严格规范
- **位置**
- `create-question-dialog.tsx:62``(content as { text?: unknown }).text`
- `create-question-dialog.tsx:71``(content as { options?: unknown }).options`
- `create-question-dialog.tsx:78``(opt as { id?: unknown; value?: unknown }).id`
- `create-question-dialog.tsx:80``(opt as { text?: unknown; label?: unknown }).text`
- `create-question-dialog.tsx:82``(opt as { isCorrect?: unknown }).isCorrect`
- `question-columns.tsx:58``(content as { text?: unknown }).text`
- `question-bank-list.tsx:108-112``q.content as { text?: string }``parsed as unknown`
- **问题**:从 `unknown` 到具体类型使用了 `as` 断言而非类型守卫。
- **违反规则**:项目规则「禁止 `as` 断言(除非从 `unknown` 转换或测试中,需注释原因)」。虽然这些是从 `unknown` 转换,但未使用类型守卫函数,且未注释原因。
- **后果**:类型不安全,运行时可能访问不存在的属性。
#### P1-2筛选栏组件重复实现
- **位置**`question-filters.tsx`模块内自有实现vs `shared/components/question/question-bank-filters.tsx`(共享组件)
- **问题**:模块内的 `QuestionFilters` 没有复用共享 `QuestionBankFilters`,两者功能高度重叠(搜索 + 题型 + 难度),但实现完全独立。共享组件已被 exams 和 lesson-preparation 模块使用。
- **违反规则**项目规则「Shared components must be extracted when page duplication exceeds 90%」。
- **后果**:维护两套筛选 UI样式和行为不一致修改需同步两处。
#### P1-3客户端组件未做权限感知
- **位置**`create-question-button.tsx``question-actions.tsx`
- **问题**`CreateQuestionButton` 无条件渲染,不检查用户是否有 `QUESTION_CREATE` 权限。`QuestionActions` 的编辑/删除按钮也不检查 `QUESTION_UPDATE`/`QUESTION_DELETE` 权限。
- **违反规则**:项目规则「前端权限判断统一使用 `usePermission().hasPermission()`」。
- **后果**:无权限用户看到操作按钮,点击后才在 Server Action 层被拒绝,体验差。
#### P1-4题目内容 `content: unknown` 类型不安全
- **位置**`types.ts:27``data-access.ts``create-question-dialog.tsx``question-columns.tsx`
- **问题**`Question.content` 类型为 `unknown`,各处用 `as``typeof` 手动解析,没有统一的类型定义和解析函数。题目内容实际有明确结构(`{ text, options?, answer?, explanation? }`),但未类型化。
- **违反规则**:项目规则「禁止 `any`:未知类型用 `unknown` 并做类型守卫」——虽然用了 `unknown`,但缺少类型守卫。
- **后果**:解析逻辑分散在 4+ 个文件中,重复且易错。
#### P1-5无级联筛选的数据访问支持
- **位置**`data-access.ts:getQuestions` 参数仅有 `knowledgePointId`,无 `textbookId`/`chapterId` 过滤
- **问题**:要实现「课本 → 章节 → 知识点」级联筛选,需要 `getQuestions` 支持按 `textbookId``chapterId` 过滤,以及提供 `getTextbookOptions``getChaptersByTextbookId` 等级联数据接口。当前均缺失。
- **违反规则**:架构原则「可扩展性」。
- **后果**:无法实现用户要求的级联筛选。
### P2 — 中优先级问题
#### P2-1仅支持教师角色无多角色复用
- **位置**:仅 `src/app/(dashboard)/teacher/questions/` 存在
- **问题**admin 角色无法管理题库(虽然 admin 有 `QUESTION_*` 全部权限parent/student 无法查看题目。组件未抽象为可配置化多角色复用。
- **违反规则**:架构原则「最大化复用:识别四个角色共用的 UI 块」。
- **后果**:新增角色需复制整个页面。
#### P2-2无题目预览渲染
- **位置**`question-columns.tsx:52-72``question-actions.tsx:160-163`
- **问题**:题目内容预览仅截取前 80 字符的纯文本或 `JSON.stringify`,无结构化渲染(选项列表、正确答案高亮等)。
- **违反规则**:架构原则「交互不符合多角色使用习惯」。
- **后果**:教师无法快速判断题目内容,需逐个点开查看。
#### P2-3无批量操作
- **位置**`question-data-table.tsx` 有行选择 checkbox但无批量操作 UI
- **问题**:表格支持多选但无批量删除、批量导出、批量移动知识点等操作。
- **违反规则**:架构原则「可扩展性」。
- **后果**:教师需逐条操作,效率低。
#### P2-4无题目导入/导出
- **位置**:缺失
- **问题**:不支持从 Excel/Word 导入题目,也不支持导出。
- **违反规则**:行业差距(见第三节)。
- **后果**:教师无法迁移已有题库。
#### P2-5create-question-dialog.tsx 接近行数上限
- **位置**`create-question-dialog.tsx`453 行)
- **问题**:接近 500 行组件上限。包含表单、知识点选择、选项编辑三块逻辑。
- **违反规则**项目规则「React 组件:建议 ≤ 500 行」。
- **后果**:维护困难,难以测试。
#### P2-6无监控埋点
- **位置**:缺失
- **问题**:无题目创建/删除/搜索的操作埋点接口。
- **违反规则**:架构原则「监控:方案中预留关键操作埋点接口」。
- **后果**:无法分析题库使用情况。
#### P2-7架构图 action 名称与代码不一致
- **位置**`005_architecture_data.json:4708` 记录 `createNestedQuestion`,实际代码为 `createQuestionAction`
- **问题**:架构图与代码不同步。
- **违反规则**:项目规则「改码必同步图」。
- **后果**:架构图不可信。
---
## 三、行业差距对比
### 3.1 与主流 K12 产品的差距
| 能力 | 主流产品(组卷网/学科网/智学网) | 当前实现 | 差距影响 |
|------|------|------|------|
| **级联筛选** | 教材→章节→课时→知识点四级级联 | 扁平知识点下拉 | 教师无法按教学进度选题 |
| **题目预览** | 结构化渲染(题干+选项+答案+解析) | 截取 80 字符纯文本 | 无法快速判断题目内容 |
| **批量操作** | 批量删除/移动/导出 | 仅有多选 UI 无操作 | 效率低 |
| **导入导出** | Excel/Word 模板导入导出 | 无 | 无法迁移已有题库 |
| **题目难度标签** | 易/中/难 + 星级 | 数字 1-5 | 不够直观 |
| **题目来源标注** | 自编/教材/网络 | 仅记录 author | 无法追溯题目来源 |
| **题目使用统计** | 被引用次数/正确率 | 仅 `childrenCount` | 无法评估题目质量 |
| **题目版本管理** | 修改历史 | 无 | 无法回溯修改 |
| **富文本编辑** | 公式/图片/表格 | 纯文本 textarea | 无法编辑理科题目 |
| **多角色支持** | 教师建题/admin管理/学生练习 | 仅教师 | admin 无法管理 |
### 3.2 关键差距分析
1. **级联筛选是最大痛点**K12 教学严格按教材章节进度推进,教师选题时必然先定位到当前教学进度对应的章节,再在该章节的知识点下筛选题目。扁平列表完全不符合此工作流。
2. **题目预览缺失影响效率**:教师在组卷/备课选题时,需要快速浏览多道题目的完整内容(含选项和答案),当前仅显示截断文本,必须逐个点开查看,严重影响效率。
3. **无导入导出限制迁移**学校通常有存量题库Word/Excel无法导入意味着教师需手动重新录入所有题目阻力极大。
---
## 四、改进优先级建议
### P0 — 立即实施
| 编号 | 问题 | 改进方向 |
|------|------|------|
| P0-1 | 筛选方式不符合教学场景 | 新增「教材→章节→知识点」三级级联筛选组件data-access 层新增 `getTextbookOptionsForQuestions``getChaptersByTextbookId` 接口,`getQuestions` 支持 `textbookId`/`chapterId` 参数 |
| P0-2 | 缺失 i18n | 创建 `questions.json` 翻译文件(中/英),所有组件使用 `useTranslations("questions")` |
| P0-3 | 缺失 error.tsx | 新增 `error.tsx` 错误边界 |
### P1 — 本轮实施
| 编号 | 问题 | 改进方向 |
|------|------|------|
| P1-1 | `as` 类型断言 | 提取 `parseQuestionContent` 类型守卫函数到 `utils/parse-content.ts`,替换所有 `as` 断言 |
| P1-2 | 筛选栏重复 | 模块内 `QuestionFilters` 改为复用共享 `QuestionBankFilters` + 新增级联筛选扩展 |
| P1-3 | 客户端权限感知 | `CreateQuestionButton``QuestionActions` 使用 `usePermission().hasPermission()` |
| P1-4 | content 类型不安全 | 定义 `QuestionContent` 类型,替换 `unknown` |
| P1-5 | 无级联筛选数据支持 | data-access 新增级联数据接口 |
### P2 — 中长期计划
| 编号 | 问题 | 改进方向 |
|------|------|------|
| P2-1 | 仅教师角色 | 抽象角色配置,新增 admin 题库页面 |
| P2-2 | 无题目预览 | 新增 `QuestionPreview` 组件,结构化渲染 |
| P2-3 | 无批量操作 | 新增批量操作工具栏 |
| P2-4 | 无导入导出 | 中长期Excel 模板导入导出 |
| P2-5 | dialog 行数 | 拆分为 `QuestionFormFields` + `KnowledgePointSelector` + `OptionsEditor` |
| P2-6 | 无监控埋点 | 预留 `trackQuestionEvent` 接口 |
| P2-7 | 架构图不同步 | 同步 action 名称、依赖矩阵、跨模块接口 |
---
## 五、架构图同步说明
需要同步更新以下内容:
### 5.1 `005_architecture_data.json`
1. **依赖矩阵**`dependencyMatrix.questions.dependsOn` 添加 `"textbooks"``uses` 添加 `textbooks: ["data-access.getKnowledgePointOptions"]`
2. **action 名称**`createNestedQuestion``createQuestionAction`
3. **跨模块接口**:在 `dataAccess` 中补充 `getKnowledgePointsForQuestions``getQuestionsContentForErrorCollection`
4. **新增 data-access 函数**`getTextbookOptionsForQuestions``getChaptersForCascade``getQuestions` 参数新增 `textbookId`/`chapterId`
5. **新增组件**`QuestionCascadeFilter``QuestionPreview``QuestionContentRenderer`
6. **新增 utils**`utils/parse-content.ts`
### 5.2 `004_architecture_impact_map.md`
1. 题库模块章节:更新筛选方式说明(级联筛选)、新增 i18n 说明
2. 依赖关系:补充 questions → textbooks 依赖
3. 组件清单:新增级联筛选、预览组件
---
## 六、实施记录
以下改进已在本次审计中实施(含中长期计划,全部完成):
### P0 修复
- [x] P0-1新增「教材→章节→知识点」三级级联筛选data-access 支持 `textbookId`/`chapterId` 参数
- [x] P0-2创建 `questions.json` i18n 翻译文件(中/英),所有组件迁移到 `useTranslations`
- [x] P0-3新增 `error.tsx` 错误边界teacher + admin
### P1 修复
- [x] P1-1提取 `parseQuestionContent` 类型守卫,替换所有 `as` 断言
- [x] P1-2模块筛选栏复用共享组件 + 级联扩展QuestionCascadeFilter
- [x] P1-3客户端组件添加 `usePermission()` 权限感知
- [x] P1-4定义 `QuestionContent` 类型
- [x] P1-5data-access 新增级联数据接口getTextbookOptions/getChapterOptions/getKnowledgePointOptionsByChapter
### P2 修复
- [x] P2-5拆分 create-question-dialog.tsx453→281 行,拆分 KnowledgePointSelector + OptionsEditor
- [x] P2-6预留 `trackQuestionEvent` 埋点接口no-op 实现)
- [x] P2-7同步架构图004 + 005
### P2 中长期计划(已全部实施)
- [x] P2-1多角色支持 — 新增 `admin/questions` 页面page.tsx + error.tsx + loading.tsx复用全部 questions 模块组件,权限感知自动适配
- [x] P2-2题目结构化预览 — 新增 `QuestionContentRenderer` 组件,支持题干/选项/答案/解析结构化渲染,单选圆点/多选方框视觉区分,正确答案高亮,已集成到查看详情对话框
- [x] P2-3批量操作 — 新增 `deleteQuestionsBatch` data-access + `deleteQuestionsBatchAction` Server Action + `BatchOperations` 组件,表格行选择后显示批量删除工具栏,权限感知,事务保证原子性
- [x] P2-4导入导出 — 新增 `exportQuestions`/`importQuestions` data-access + `exportQuestionsAction`/`importQuestionsAction` Server Action + `ImportExportButtons` 组件,支持 JSON 格式导出下载和文件上传导入,权限感知,导入前预览确认
### 新增文件清单
| 文件 | 行数 | 职责 |
|------|------|------|
| `src/app/(dashboard)/admin/questions/page.tsx` | 136 | 管理员题库页面(复用 questions 模块组件) |
| `src/app/(dashboard)/admin/questions/error.tsx` | 29 | 管理员题库错误边界 |
| `src/app/(dashboard)/admin/questions/loading.tsx` | 29 | 管理员题库加载骨架屏 |
| `src/modules/questions/components/question-content-renderer.tsx` | 110+ | 题目结构化渲染(题干/选项/答案/解析) |
| `src/modules/questions/components/batch-operations.tsx` | 110+ | 批量操作工具栏(批量删除) |
| `src/modules/questions/components/import-export-buttons.tsx` | 180+ | 导入导出组件JSON 格式) |
### 修改文件清单
| 文件 | 变更 |
|------|------|
| `src/modules/questions/data-access.ts` | 新增 `deleteQuestionsBatch`/`exportQuestions`/`importQuestions` + 类型定义 |
| `src/modules/questions/actions.ts` | 新增 `deleteQuestionsBatchAction`/`exportQuestionsAction`/`importQuestionsAction`,所有 Action 增加 `revalidatePath("/admin/questions")` |
| `src/modules/questions/components/question-data-table.tsx` | 集成 BatchOperations 组件,支持 getRowId + 行选择 |
| `src/modules/questions/components/question-bank-results-client.tsx` | 传入 getRowId 支持批量操作 |
| `src/modules/questions/components/question-actions.tsx` | 查看详情改用 QuestionContentRenderer 结构化渲染 |
| `src/app/(dashboard)/teacher/questions/page.tsx` | 新增 ImportExportButtons |
| `src/app/(dashboard)/admin/questions/page.tsx` | 新增 ImportExportButtons |
| `src/shared/i18n/messages/zh-CN/questions.json` | 新增 batch + importExport 命名空间 |
| `src/shared/i18n/messages/en/questions.json` | 新增 batch + importExport 命名空间 |
### 验证结果
- ✅ TypeScriptquestions 模块零错误(`npx tsc --noEmit` 仅余其他模块预存错误)
- ✅ ESLintquestions 模块 + admin/questions + teacher/questions 零错误零警告(`--max-warnings=0`
- ✅ 架构图同步004 + 005 已更新