# 考试(exams)模块审计报告 > 审计时间:2026-06-25 > 审计范围:`src/modules/exams/**`(35+ 文件)+ `src/app/(dashboard)/teacher/exams/**`(10 个页面)+ 跨模块依赖面 > 审计依据:`docs/architecture/004_architecture_impact_map.md`、`docs/architecture/005_architecture_data.json`、项目 `project_rules.md` --- ## 一、现有实现概要 ### 1.1 文件分布与体量 exams 模块按职责已做较细粒度拆分,体量基本符合规范: | 子目录/文件 | 行数(参考架构图) | 职责 | |-------------|------|------| | `actions.ts` | 633 | 11 个核心 Server Action(已从 1525 行拆分) | | `actions-helpers.ts` | 96 | 跨 Action 共享纯函数(prepareExamCreateContext 等) | | `actions-rich-editor.ts` | 250 | 富文本编辑器 Server Action(create/update) | | `ai-pipeline/auto-mark.ts` | 356 | AI 自动标记 Server Action + 纯转换函数 | | `ai-pipeline/{index,parse,request,structure}.ts` | — | AI 调用/解析/结构化 | | `data-access.ts` | 542 | 考试 CRUD(已从 1036 行拆分) | | `data-access-cross-module.ts` | 511 | 13 个跨模块查询/写接口 | | `data-access-error-collection.ts` | — | 错题采集相关跨模块接口 | | `stats-service.ts` | 158 | 考试分析数据聚合 | | `types.ts` | 93 | 类型定义 | | `utils/normalize-structure.ts` | 57 | exam.structure 运行时归一化 | | `components/` | 24 个文件 | 表单/组卷/预览/分析/卡片/筛选/表格 | | `editor/` | 14 个文件 | Tiptap 富文本编辑器(extensions/utils/转换) | | `hooks/` | 4 个文件 | use-exam-preview 主组合器 + 3 个子 Hook | **架构图覆盖情况**:004/005 已记录 exams 模块的职责、依赖、被依赖、文件清单、P0/P1 修复历史、V3 增强项。本次审计对照架构图核对,覆盖基本完整,但以下细节需补全(见第五节): - `data-access-error-collection.ts` 未在 005 JSON 的 modules.exams.exports 中列出 - `utils/normalize-structure.ts` 已记录但未在 005 的 dependencyMatrix 中明确标注被 `[id]/build/page.tsx` 与 `[id]/edit-rich/page.tsx` 引用 ### 1.2 主要数据流 - **创建**:`/teacher/exams/create` → `createExamAction` → `persistExamDraft` → `db.insert(exams)` - **AI 创建**:`/teacher/exams/create` → `createAiExamAction` → `loadAiDraftQuestionsAndStructure` → `persistAiGeneratedExamDraft` → 通过 `questions/data-access.createQuestionWithRelations` 创建题目 → 事务写 exams + examQuestions - **富文本创建**:`/teacher/exams/new` → `createExamFromRichEditorAction` → `editorDocToStructure` → `persistAiGeneratedExamDraft` - **组卷**:`/teacher/exams/[id]/build` → `ExamAssembly` + `getExamById` - **预览**:`previewAiExamAction` / `getExamPreviewAction` - **分析**:`/teacher/exams/[id]/analytics` → `getExamAnalytics`(聚合 homework 提交数据) ### 1.3 跨模块依赖(合规项) 以下跨模块调用均通过对方 data-access,符合三层架构规则: - `questions/data-access.createQuestionWithRelations`(P0-1 已修复) - `classes/data-access.getClassGradeIdsByClassIds`(P0-2 已修复) - `school/data-access.{getSubjectNameById,getGradeNameById,getSubjectOptions,getGradeOptions}`(P1-1 已修复) - `homework/data-access.{getHomeworkAssignmentsByExamId,getGradedSubmissionsByExamId}`(V3-8 新增) - `homework/data-access-utils.getQuestionText` ### 1.4 已修复的历史问题(架构图记录) P0-1/P0-2/P0-4/P0-8/P1-1 等历史违规已修复,详见 004 文档第 2.2 节。 --- ## 二、现存问题与原因分析 ### 🔴 2.1【架构违规·P0】跨模块直接 JOIN questions 表 **位置**:[data-access-cross-module.ts](file:///e:/Desktop/CICD/src/modules/exams/data-access-cross-module.ts#L4-L5) 第 4 行 import、第 480-490 行 `getExamForGradeEntry` **问题**: ``` 第 4 行:import { exams, examQuestions, examSubmissions, submissionAnswers, questions } from "@/shared/db/schema" 第 488 行:.innerJoin(questions, eq(examQuestions.questionId, questions.id)) ``` `getExamForGradeEntry` 为了获取题目 `type` 字段,直接 JOIN 了 questions 模块的核心表 `questions`。 **违反规则**:项目规则"模块间只能通过对方 data-access 通信,**禁止跨模块直接查询数据库表**"。 **原因**:成绩录入表格表头需要题目类型,但实现时未在 questions 模块暴露按 ID 批量获取类型的接口,于是直接 JOIN。 **直接后果**:questions 模块若重构表结构(如将 type 拆分到独立表),exams 模块会编译失败或运行时错误;模块封装性被破坏,违反可测试性与可替换性。 --- ### 🟢 2.2【已确认合规】submissionAnswers 表归属与直查 **位置**: - [data-access-cross-module.ts](file:///e:/Desktop/CICD/src/modules/exams/data-access-cross-module.ts#L4) 导入 `submissionAnswers` - [data-access-cross-module.ts](file:///e:/Desktop/CICD/src/modules/exams/data-access-cross-module.ts#L212-L218) `getExamSubmissionWithAnswers` 直查 `submissionAnswers` - [data-access-error-collection.ts](file:///e:/Desktop/CICD/src/modules/exams/data-access-error-collection.ts#L6) 导入并查询 `submissionAnswers`(第 62-69 行) **结论**:经核对 `src/shared/db/schema.ts:575-578`,`submissionAnswers` 表的 `submissionId` 外键引用 `examSubmissions.id`,**该表属于 exams 模块自身域**(exam submissions 的答题记录)。exams 模块查询自己的表合规,`getExamSubmissionWithAnswers` 与 `getExamSubmissionDataForErrorCollection` 通过 data-access-cross-module 暴露给 diagnostic/error-book 模块调用,符合"模块间通过对方 data-access 通信"规则。 **无违规,无需修复。** --- ### 🟠 2.3【i18n 缺失·P1】11 个组件未接入 useTranslations **位置**: | 文件 | 硬编码样本 | |------|-----------| | [components/exam-card.tsx](file:///e:/Desktop/CICD/src/modules/exams/components/exam-card.tsx#L78-L91) | "Lvl"、"min"、"pts"、"Questions" | | [components/exam-filters.tsx](file:///e:/Desktop/CICD/src/modules/exams/components/exam-filters.tsx#L33-L59) | "Search exams..."、"Status"、"Any Status"、"Draft"、"Published"、"Archived"、"Difficulty"、"Easy (1)" 等 | | [components/exam-preview-dialog.tsx](file:///e:/Desktop/CICD/src/modules/exams/components/exam-preview-dialog.tsx#L89-L199) | "Section"、"未命名题目"、"未命名子题"、"Exam Preview"、"Generating preview..."、"完整试卷预览"、"题 · 科目 · 年级 · 分钟 · 总分"、"No preview available"、"Confirm & Create" | | [components/exam-viewer.tsx](file:///e:/Desktop/CICD/src/modules/exams/components/exam-viewer.tsx#L95-L197) | "Section"、"Group"、"Score:"、"No questions available." | | [components/question-options-editor.tsx](file:///e:/Desktop/CICD/src/modules/exams/components/question-options-editor.tsx) | 选项编辑器中文硬编码 | | [editor/extensions/blank-node.tsx](file:///e:/Desktop/CICD/src/modules/exams/editor/extensions/blank-node.tsx) | aria-label="填空" | | [editor/extensions/group-block.tsx](file:///e:/Desktop/CICD/src/modules/exams/editor/extensions/group-block.tsx) | placeholder 与统计文案硬编码 | | [editor/extensions/question-block.tsx](file:///e:/Desktop/CICD/src/modules/exams/editor/extensions/question-block.tsx) | 题型 `