# 后端模块规范核查报告 v2

> 核查日期：2026-06-18
> 核查范围：`src/modules/` 下所有后端 `.ts` 文件
> 核查依据：
> - `.trae/rules/project_rules.md` 项目规则
> - `docs/standards/coding-standards.md` 编码规范
> - `docs/architecture/004_architecture_impact_map.md` 架构影响地图
> - Vercel React Best Practices 性能优化规则
> - v1 报告 `bugs/back_bug.md`（对照修复状态）
>
> 本报告相比 v1 的核心变化：
> - 对每个问题标注 **修复状态**（已修复/未修复/部分修复/新问题）
> - 汇总 v1→v2 的修复进度
> - 列出 v2 新发现的问题

---

## 目录

- [一、v1→v2 修复进度总览](#一v1v2-修复进度总览)
- [二、v2 当前问题汇总](#二v2-当前问题汇总)
- [三、仍需优先修复的问题](#三仍需优先修复的问题)
- [四、按模块详细核查](#四按模块详细核查)
- [五、v2 新发现问题清单](#五v2-新发现问题清单)
- [六、架构文档同步提醒](#六架构文档同步提醒)

---

## 一、v1→v2 修复进度总览

### 1.1 整体修复率

| 指标 | v1 问题数 | 已修复 | 部分修复 | 未修复 | 修复率 |
|------|----------|--------|----------|--------|--------|
| 数量 | 129 | 90 | 12 | 27 | 70% 已修复 + 9% 部分修复 |
| P0 | 14 | 14 | 0 | 0 | **100%** |
| P1 | 60 | 39 | 7 | 14 | 65% + 12% |
| P2 | 55 | 37 | 5 | 13 | 67% + 9% |

### 1.2 按模块修复率

| 模块 | v1 问题数 | 已修复 | 部分修复 | 未修复 | 修复率 |
|------|----------|--------|----------|--------|--------|
| homework | 6 | 6 | 0 | 0 | **100%** |
| parent | 3 | 3 | 0 | 0 | **100%** |
| proctoring | 9 | 9 | 0 | 0 | **100%** |
| settings | 9 | 9 | 0 | 0 | **100%** |
| dashboard | 0 | - | - | - | 标杆模块 |
| grades | 8 | 7 | 1 | 0 | 88% |
| questions | 5 | 4 | 0 | 1 | 80% |
| users | 7 | 6 | 0 | 1 | 86% |
| exams | 7 | 5 | 1 | 1 | 71% |
| messaging | 7 | 5 | 0 | 2 | 71% |
| notifications | 7 | 5 | 0 | 2 | 71% |
| audit | 5 | 2 | 0 | 3 | 40% |
| textbooks | 5 | 2 | 1 | 2 | 40% |
| classes | 10 | 7 | 2 | 1 | 70% |
| announcements | 6 | 5 | 1 | 0 | 83% |
| school | 3 | 2 | 0 | 1 | 67% |
| scheduling | 6 | 5 | 1 | 0 | 83% |
| attendance | 1 | 1 | 0 | 0 | 100% |
| course-plans | 3 | 1 | 1 | 1 | 33% |
| elective | 7 | 6 | 1 | 0 | 86% |
| diagnostic | 8 | 7 | 0 | 1 | 88% |
| files | 5 | 3 | 1 | 1 | 60% |
| layout | 2 | 0 | 0 | 2 | 0% |

### 1.3 P0 问题修复情况（全部已修复）

| 编号 | v1 P0 问题 | 修复状态 |
|------|-----------|---------|
| P0-1 | exams/data-access.ts persistAiGeneratedExamDraft 直写 questions 表 | ✅ 已修复：改用 createQuestionWithRelations |
| P0-2 | exams/data-access.ts getExams 等直查 classes 表 | ✅ 已修复：改用 getClassGradeIdsByClassIds |
| P0-3 | questions/schema.ts z.any() | ✅ 已修复：改为 z.unknown() |
| P0-4 | questions/actions.ts 未返回 ActionState | ✅ 已修复：包装为 ActionState<T> |
| P0-5 | textbooks 无 Zod 验证 + 14 处 as 断言 | ⚠️ 部分修复：as 断言已清理，但 Zod 验证仍未添加 |
| P0-6 | grades N+1 查询 | ✅ 已修复：改为 inArray 批量查询 + Map 分组 |
| P0-7 | classes 跨模块直查 homework/exams 表 | ✅ 已修复：改用 homework/data-access-classes |
| P0-8 | school actions 层直接 DB 操作 | ✅ 已修复：DB 操作下沉到 data-access |
| P0-9 | proctoring actions 层直接 DB 操作 | ✅ 已修复：下沉到 data-access |
| P0-10 | messaging↔notifications 循环依赖 | ✅ 已修复：表所有权移交 notifications |
| P0-11 | notifications/in-app-channel.ts 非法 as 断言 | ✅ 已修复：新增 mapPayloadTypeToNotificationType |
| P0-12 | users 硬编码弱密码 "123456" | ✅ 已修复：改用 randomBytes 生成 |
| P0-13 | users/updateUserProfile 绕过权限 | ✅ 已修复：改用 requirePermission + Zod + ActionState |
| P0-14 | scheduling 4 个函数缺返回类型 | ✅ 已修复：已添加返回类型标注 |

---

## 二、v2 当前问题汇总

### 2.1 按严重程度统计（v2 当前状态）

| 严重程度 | 未修复 v1 问题 | 部分修复 v1 问题 | v2 新发现问题 | 合计 |
|----------|---------------|------------------|--------------|------|
| P0 | 0 | 1（textbooks Zod） | 0 | 1 |
| P1 | 14 | 7 | 16 | 37 |
| P2 | 13 | 5 | 25 | 43 |
| **合计** | **27** | **12** | **41** | **80** |

### 2.2 按问题类别统计（v2 当前状态）

| 问题类别 | 数量 | 主要分布 |
|---------|------|---------|
| 架构违规 | 8 | 跨模块直查 DB（exams→school、questions→textbooks、classes→scheduling、messaging→classes、elective→school/users） |
| TS 规范 | 35 | as 断言、缺返回类型标注、隐式 any[]、非空断言 |
| Server Action 规范 | 6 | textbooks 无 Zod、notifications 无 Zod、school 用 parse 非 safeParse、course-plans 缺 revalidatePath |
| 性能 | 15 | 串行查询未并行化、循环内串行 await、未用 React.cache()、隐式 any[] |
| 代码质量 | 12 | try-catch 吞错误、重复 try/catch、死代码、重复代码 |
| 命名规范 | 3 | 布尔变量前缀、函数命名不一致 |
| 数据一致性 | 4 | elective selectCourse/dropCourse 缺事务 |
| 文件行数 | 2 | exams/ai-pipeline.ts 916 行、classes/data-access.ts 866 行 |

---

## 三、仍需优先修复的问题

### 3.1 P0 级别（立即修复）

#### P0-1：textbooks 模块仍无 Zod 验证（v1 未修复）

- **文件**：`src/modules/textbooks/actions.ts`
- **行号**：54-281（所有 Action）
- **问题**：v1 报告的 P0 问题"actions.ts 完全无 Zod 验证"未修复。所有 Action 仍使用手动 `if` 校验，textbooks 模块甚至没有 schema.ts 文件
- **修复建议**：新建 `textbooks/schema.ts`，定义 `CreateTextbookSchema`、`UpdateTextbookSchema`、`CreateChapterSchema`、`CreateKnowledgePointSchema` 等 Zod schema，所有 Action 改用 `schema.safeParse()`

### 3.2 P1 级别（尽快修复）

#### P1-1：exams/data-access.ts 直接查询 school 模块表（v1 未修复）

- **文件**：`src/modules/exams/data-access.ts`
- **行号**：2, 208-228, 519-524, 529-534
- **问题**：`resolveSubjectGradeNames`、`getExamSubjects`、`getExamGrades` 仍直接查询 `subjects`/`grades` 表（school 模块）
- **修复建议**：在 school 模块暴露 `getGradeOptions()`、`getSubjectNameById(id)`、`getGradeNameById(id)` 接口

#### P1-2：questions/data-access.ts 直接查询 textbooks 模块表（v1 未修复）

- **文件**：`src/modules/questions/data-access.ts`
- **行号**：4, 266-299
- **问题**：`getKnowledgePointOptions` 仍直接 LEFT JOIN 查询 `knowledgePoints`、`chapters`、`textbooks` 三张表
- **修复建议**：在 textbooks 模块暴露 `getKnowledgePointOptionsForQuestions()` 接口

#### P1-3：classes/data-access-schedule.ts 直接查询 classSchedule 表（v1 未修复）

- **文件**：`src/modules/classes/data-access-schedule.ts`
- **行号**：7-11, 31-46, 73-86
- **问题**：仍直接导入并查询 `classSchedule` 表（scheduling 模块的表）
- **修复建议**：在 scheduling 模块暴露只读查询函数 `getClassScheduleByClassIds`

#### P1-4：messaging/data-access.ts getRecipients 直接 JOIN 跨模块表（v1 未修复）

- **文件**：`src/modules/messaging/data-access.ts`
- **行号**：26-27, 173-188
- **问题**：`getRecipients` 直接 import 并 JOIN `classEnrollments`、`classes` 表
- **修复建议**：通过 classes 模块暴露 `getStudentIdsByClassIds`、`getStudentIdsByGradeIds` 接口

#### P1-5：notifications/actions.ts 参数未用 Zod 验证（v1 未修复）

- **文件**：`src/modules/notifications/actions.ts`
- **行号**：28-50, 60-110
- **问题**：`sendNotificationAction` 和 `sendClassNotificationAction` 仅使用 TypeScript 类型标注和手动 if 检查
- **修复建议**：新增 `NotificationPayloadSchema` 和 `ClassNotificationSchema`

#### P1-6：textbooks/actions.ts 本地定义 ActionState 类型（v1 未修复）

- **文件**：`src/modules/textbooks/actions.ts`
- **行号**：46-50
- **问题**：仍在本地定义 `ActionState` 类型，而非从 `@/shared/types/action-state` 导入
- **修复建议**：删除本地定义，改为 `import type { ActionState } from "@/shared/types/action-state"`

#### P1-7：elective/data-access.ts 跨模块直查（v2 新发现）

- **文件**：`src/modules/elective/data-access.ts`
- **行号**：77-106（`buildCourseSelect`）、231-242（`getSubjectOptions`）
- **问题**：`buildCourseSelect` 直接 `leftJoin(users/subjects/grades)`；本地 `getSubjectOptions` 直查 `subjects` 表且与 school 模块重复
- **修复建议**：移除 join，改为先查主表再用 `getUserNamesByIds`/`getSubjectOptions`/`getGradeOptions` 批量解析；删除本地 `getSubjectOptions` 改用 school 模块

#### P1-8：elective selectCourse/dropCourse 缺事务（v2 新发现）

- **文件**：`src/modules/elective/data-access-operations.ts`
- **行号**：97-172（selectCourse）、174-241（dropCourse）
- **问题**：FCFS 模式下 update + insert 两步无事务包裹；dropCourse 最多 5 个连续写操作无事务
- **修复建议**：用 `db.transaction` 包裹所有写操作

#### P1-9：classes/actions.ts as 断言（v2 新发现）

- **文件**：`src/modules/classes/actions.ts`
- **行号**：47, 521, 565
- **问题**：`v as ClassSubject`、`weekday as 1|2|3|4|5|6|7` 使用 as 断言
- **修复建议**：在 schema.ts 中使用 Zod transform 使解析后直接产出目标类型

#### P1-10：school/actions.ts 使用 .parse() 而非 .safeParse()（v2 新发现）

- **文件**：`src/modules/school/actions.ts`
- **行号**：33, 60, 98, 129, 171, 202, 256, 289
- **问题**：所有 action 使用 `Schema.parse()` 而非 `safeParse()`，验证失败抛 ZodError，无法返回结构化 fieldErrors
- **修复建议**：改用 `safeParse()`，失败时返回 `{ success: false, errors: parsed.error.flatten().fieldErrors }`

#### P1-11：多个模块函数缺返回类型标注（v2 新发现 + v1 部分未修复）

| 模块 | 文件 | 函数 |
|------|------|------|
| classes | data-access.ts:53,60,93,95,100,107 | isDuplicateInvitationCodeError, generateInvitationCode, normalizeSortText, parseFirstInt, compareGradeLabel, compareClassLike |
| school | data-access.ts:24 | toIso |
| attendance | data-access.ts:70 | resolveRecorderNames |
| exams | ai-pipeline.ts:68,177,309,453,480,499,571,712 | sanitizeJsonCandidate, normalizeScores, buildAiMessages, splitStructureItems, mapWithConcurrency, parseQuestionDetail, buildQuestionContent, previewToDraft |
| exams | data-access.ts:269 | buildOrderedQuestionsFromStructure |
| grades | data-access.ts:57, data-access-analytics.ts:34 | buildScopeClassFilter |
| textbooks | data-access.ts:19,25 | normalizeOptional, sortChapters |

#### P1-12：files/data-access.ts conditions 隐式 any[]（v1 未修复）

- **文件**：`src/modules/files/data-access.ts`
- **行号**：201
- **问题**：`const conditions = []` 无类型注解，推断为 `any[]`
- **修复建议**：改为 `const conditions: SQL[] = []`

#### P1-13：course-plans updateCoursePlanItemAction 缺 revalidatePath（v1 部分修复）

- **文件**：`src/modules/course-plans/actions.ts`
- **行号**：197-234
- **问题**：v1 指出的 deleteCoursePlanItemAction 和 toggleCoursePlanItemCompletedAction 已修复，但 `updateCoursePlanItemAction` 仍缺 `revalidatePath`
- **修复建议**：在 `await updateCoursePlanItem(id, parsed.data)` 后添加 `revalidatePlanPaths()` 调用

### 3.3 P2 级别（迭代优化）

#### 性能类

| 模块 | 文件 | 问题 |
|------|------|------|
| grades | data-access.ts:273,377,397 | 3 个查询函数未用 React.cache() |
| elective | data-access.ts:231 | getSubjectOptions 未用 cache() |
| course-plans | data-access.ts:302-307 | reorderCoursePlanItems 循环内串行 await |
| diagnostic | data-access.ts:119-140 | updateMasteryFromSubmission 循环内串行 await |
| proctoring | data-access.ts:271-287 | getStudentProctoringStatuses 串行查询未并行化 |
| diagnostic | data-access.ts:147-159 | getClassMasterySummary 串行查询未并行化 |
| grades | export.ts:129 | 循环内 find O(n) 查找，应改用 Map |
| school | data-access.ts:406-469 | isGradeHead/isGradeManager/findGradeIdByHeadAndName 未用 cache() |

#### 代码质量类

| 模块 | 文件 | 问题 |
|------|------|------|
| school | data-access.ts:26-206 | 8 个函数 try-catch 吞错误返回空数组 |
| files | data-access.ts | 10 处静默 catch 吞错误 |
| classes | data-access.ts:371-373, data-access-admin.ts:88-89,244-247, data-access-students.ts:159-160 | 多处 try-catch 吞错误 |
| course-plans | data-access.ts:143-162,166-189,312-323 | 3 处 try-catch 吞错误 |
| announcements | data-access.ts:88-91,119-122 | catch 已加 console.error 但仍吞错误 |
| announcements | actions.ts:26-230 | 6 处重复 try/catch 块未抽取公共 helper |
| diagnostic | data-access-reports.ts:22,207-208 | void round2 死代码 |
| scheduling | data-access.ts:113-114 | select 中 requesterName 字段冗余 |
| elective | data-access.ts:46-75, data-access-selections.ts:46-74 | mapCourseRow 重复定义 |

#### TS 规范类

| 模块 | 文件 | 问题 |
|------|------|------|
| users | import-export.ts:14 | as 断言未加注释 |
| users | import-export.ts:98 | conditions 隐式 any[] |
| messaging | data-access.ts:84 | conds 隐式 any[] |
| audit | data-access.ts:40,96,161 | conditions 隐式 any[] |
| classes | data-access.ts:675 | 非空断言 `!` |
| textbooks | data-access.ts:314 | 非空断言 `stack.pop()!` |
| notifications | external-sdk.d.ts | 多处 any（有 eslint-disable 注释） |
| notifications | wechat-channel.ts:106 | as 断言未加注释 |

#### 命名规范类

| 模块 | 文件 | 问题 |
|------|------|------|
| questions | actions.ts:26 | createNestedQuestion 命名不一致 |
| settings | actions.ts:60-63 | getAiProviderSummaries 返回非 ActionState |

#### 架构/类型位置类

| 模块 | 文件 | 问题 |
|------|------|------|
| layout | navigation.ts:30-31 | permission 字段为 string 而非 Permission 类型 |
| layout | navigation.ts:34 | Role 类型应迁移至 shared/types |
| audit | actions.ts:63-205 | Excel 导出逻辑内联在 actions 层 |

#### 文件行数类

| 模块 | 文件 | 行数 | 建议 |
|------|------|------|------|
| exams | ai-pipeline.ts | 916 行 | 拆分为 prompts/json-parser/schemas/index |
| classes | data-access.ts | 866 行 | 拆分 enrollment 相关函数到 data-access-enrollment.ts |

#### 数据一致性/业务逻辑类

| 模块 | 文件 | 问题 |
|------|------|------|
| elective | data-access-operations.ts:139-148 | FCFS 并发超卖风险（架构图 P2-15） |
| elective | data-access-operations.ts:49 | runLottery 使用 Math.random 不可复现（架构图 P2-14） |
| diagnostic | data-access-reports.ts:113 | 班级报告 studentId 字段复用（架构图 P2-16） |

---

## 四、按模块详细核查

### 4.1 exams 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: persistAiGeneratedExamDraft 直写 questions 表 | ✅ 已修复 | 改用 createQuestionWithRelations |
| P0: getExams 等直查 classes 表 | ✅ 已修复 | 改用 getClassGradeIdsByClassIds |
| P1: 直接查询 subjects/grades 表 | ❌ 未修复 | 仍直查 school 模块表 |
| P1: actions.ts as 断言 | ✅ 已修复 | 改用 as unknown + safeParse |
| P2: import { ActionState } 未用 import type | ✅ 已修复 | 已改为 import type |
| P2: ai-pipeline.ts 912 行超长 | ❌ 未修复 | 当前 916 行 |
| P2: data-access.ts as string[] 断言 | ✅ 已修复 | 改用 getStringArray 类型守卫 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | ai-pipeline.ts:68,177,309,453,480,499,571,712 | 8 个函数缺少显式返回类型标注 |
| P2 | data-access.ts:269 | buildOrderedQuestionsFromStructure 缺返回类型 |

### 4.2 homework 模块

#### v1 修复情况（100% 修复）

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: data-access.ts 直查 exams/classEnrollments/subjects/users 表 | ✅ 已修复 | 改用跨模块 data-access 接口 |
| P1: data-access-write.ts 直查 classes/classEnrollments/classSubjectTeachers/exams 表 | ✅ 已修复 | 改用跨模块接口 |
| P1: stats-service.ts 直查 classEnrollments/classes/exams/users 表 | ✅ 已修复 | 改用跨模块接口 |
| P1: data-access.ts:39 as 断言 | ✅ 已修复 | 改用 isHomeworkQuestionContent 类型守卫 |
| P2: data-access-write.ts 循环内串行 await 未用事务 | ✅ 已修复 | 已用 db.transaction 包裹 |
| P2: data-access.ts 使用 auth() 而非 getAuthContext() | ✅ 已修复 | 不再使用 auth() |

**v2 无新发现问题，模块状态良好。**

### 4.3 questions 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: schema.ts z.any() | ✅ 已修复 | 改为 z.unknown() |
| P0: actions.ts 未返回 ActionState | ✅ 已修复 | 包装为 ActionState<T> |
| P1: data-access.ts 直查 textbooks 模块表 | ❌ 未修复 | 仍直查 knowledgePoints/chapters/textbooks |
| P1: actions.ts import type | ✅ 已修复 | 已改为 import type |
| P2: createNestedQuestion 命名不一致 | ❌ 未修复 | 仍为 createNestedQuestion |

**v2 无新发现问题。**

### 4.4 grades 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: N+1 查询 | ✅ 已修复 | 改为 inArray 批量查询 + Map 分组 |
| P1: 跨模块直查 | ✅ 已修复 | 改用跨模块 data-access 接口 |
| P1: 动态 import | ✅ 已修复 | 改为静态 import |
| P1: 除零 bug | ✅ 已修复 | 添加 `if (fullScores[i] <= 0) continue` |
| P2: 未用 React.cache() | ⚠️ 部分修复 | 4 个函数已用 cache()，3 个仍未用 |
| P2: includes O(n) 查找 | ✅ 已修复 | 改用 Set.has() |
| P2: 重复 filter | ✅ 已修复 | 改用单次 reduce |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | data-access.ts:57, data-access-analytics.ts:34 | buildScopeClassFilter 缺返回类型 |
| P2 | export.ts:129 | 循环内 find O(n) 查找，应改用 Map |

### 4.5 textbooks 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: 无 Zod 验证 | ❌ 未修复 | 仍无 schema.ts，所有 Action 手动校验 |
| P0: 14 处 as 断言 | ✅ 已修复 | 已清理所有 as 断言 |
| P1: 本地定义 ActionState | ❌ 未修复 | 仍在本地定义 |
| P1: import type | ✅ 已修复 | 已改为 import type |
| P1: data-access.ts as 断言 | ✅ 已修复 | 改用 isChapterNode 类型守卫 |
| P2: 非空断言 | ⚠️ 部分修复 | 原位置已修复，但 314 行仍有 stack.pop()! |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | data-access.ts:19,25 | normalizeOptional、sortChapters 缺返回类型 |

### 4.6 classes 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: actions.ts 直接 DB 操作 | ✅ 已修复 | 已下沉到 data-access |
| P0: getTeacherClasses 混入 homework/scheduling | ⚠️ 部分修复 | 架构合规，但职责仍混合 |
| P0: data-access-stats.ts 直查 homework/exams | ✅ 已修复 | 改用 homework/data-access-classes |
| P0: data-access-students.ts 直查 homework/exams | ✅ 已修复 | 同上 |
| P1: 无 schema.ts | ✅ 已修复 | 已创建 schema.ts |
| P1: as 断言 | ✅ 已修复 | 原位置已清理 |
| P1: 箭头函数缺返回类型 | ⚠️ 部分修复 | 6 个同步箭头函数仍缺 |
| P1: data-access-schedule.ts 直查 classSchedule | ❌ 未修复 | 仍直查 scheduling 模块表 |
| P2: 不可达代码 | ✅ 已修复 | 已删除 |
| P2: 串行查询未并行化 | ✅ 已修复 | 已用 Promise.all |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P1 | actions.ts:47,521,565 | as 断言（v as ClassSubject、weekday as 1\|2\|...\|7） |
| P2 | data-access.ts:675 | 非空断言 `!` |
| P2 | data-access.ts:371-373 等 | 多处 try-catch 吞错误 |
| P2 | data-access.ts | 文件 866 行超 800 行建议上限 |

### 4.7 school 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: actions 层直接 DB 操作 | ✅ 已修复 | DB 操作下沉到 data-access |
| P2: try-catch 吞错误 | ❌ 未修复 | 8 个函数仍吞错误 |
| P2: logAudit 阻塞响应 | ✅ 已修复 | 已用 after() 异步执行 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P1 | actions.ts:33,60,98,129,171,202,256,289 | 使用 .parse() 而非 .safeParse() |
| P1 | data-access.ts:24 | toIso 缺返回类型 |
| P2 | data-access.ts:406-469 | 3 个跨模块查询函数未用 cache() |

### 4.8 scheduling 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: actions.ts 直查 users 表 | ✅ 已修复 | 改用 getUserNamesByIds |
| P0: 4 个函数缺返回类型 | ✅ 已修复 | 已添加返回类型 |
| P1: 非空断言（3 处） | ✅ 已修复 | 改用显式判空 |
| P2: auto-scheduler.ts 310 行 | ⚠️ 部分修复 | 311 行，多个函数超 40 行 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | data-access.ts:113-114 | select 中 requesterName 字段冗余 |
| P2 | data-access.ts:135-145 | 用户查询应使用 inArray 替代 or(...map(eq)) |

### 4.9 attendance 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: Record<string, unknown> 丢失类型安全 | ✅ 已修复 | 改用 Partial<typeof attendanceRecords.$inferSelect> |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P1 | data-access.ts:70 | resolveRecorderNames 缺返回类型 |

### 4.10 course-plans 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: 缺 revalidatePath | ⚠️ 部分修复 | delete/toggle 已修复，update 仍缺 |
| P1: as 断言 | ✅ 已修复 | 已清理 |
| P2: 循环内串行 await | ❌ 未修复 | 仍串行 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | data-access.ts:143-162,166-189,312-323 | 3 处 try-catch 吞错误 |

### 4.11 users 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: updateUserProfile 绕过权限 | ✅ 已修复 | 改用 requirePermission + Zod + ActionState |
| P0: 硬编码弱密码 | ✅ 已修复 | 改用 randomBytes 生成 |
| P1: actions 层直接 DB 操作 | ✅ 已修复 | 下沉到 data-access |
| P1: batchImportUsers 无事务 | ✅ 已修复 | 每个用户创建包裹在 db.transaction |
| P2: rolePriority 命名 | ✅ 已修复 | 已移除，改用 resolvePrimaryRole |
| P2: normalizeRoleName 重复 | ✅ 已修复 | 改用 shared |
| P2: conditions 隐式 any[] | ❌ 未修复 | 仍为 `const conditions = []` |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | import-export.ts:14 | as 断言未加注释 |

### 4.12 messaging 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: 循环依赖 | ✅ 已修复 | 表所有权移交 notifications |
| P1: 5 个 Action 用 requireAuth | ✅ 已修复 | 改用 requirePermission |
| P1: getRecipients 直查跨模块表 | ❌ 未修复 | 仍 JOIN classEnrollments/classes |
| P1: 无 Zod 验证 | ✅ 已修复 | 已用 UpdateNotificationPreferencesSchema |
| P2: 非空断言 | ✅ 已修复 | 改用 ?? null |
| P2: 缺返回类型 | ✅ 已修复 | 已添加 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | data-access.ts:84 | conds 隐式 any[] |

### 4.13 notifications 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: 反向依赖 messaging | ✅ 已修复 | 表所有权归 notifications |
| P0: in-app-channel 动态 import messaging | ✅ 已修复 | 改为静态 import |
| P0: 非法 as 断言 | ✅ 已修复 | 新增 mapPayloadTypeToNotificationType |
| P1: actions.ts 直查 classes 表 | ✅ 已修复 | 改用 classes data-access 函数 |
| P1: 参数无 Zod 验证 | ❌ 未修复 | 仍用手动 if 检查 |
| P2: 缺返回类型 | ✅ 已修复 | 已添加 |
| P2: external-sdk.d.ts any | ❌ 未修复 | 有 eslint-disable 注释 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | wechat-channel.ts:106 | as 断言未加注释 |

### 4.14 parent 模块

#### v1 修复情况（100% 修复）

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: getChildBasicInfo 直查跨模块表 | ✅ 已修复 | 改用各模块 data-access 函数 |
| P2: as 断言 | ✅ 已修复 | 改用 isWeekday 类型守卫 |
| P2: 串行查询 | ✅ 已修复 | 改用 Promise.all |

**v2 无新发现问题，模块状态良好，是跨模块通信的标杆实现。**

### 4.15 audit 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: 导出函数数据截断 | ✅ 已修复 | 改用分页循环拉取全部数据 |
| P2: as 断言 | ✅ 已修复 | 已清理 |
| P2: Excel 导出逻辑内联 | ❌ 未修复 | 仍内联在 actions |
| P2: conditions 隐式 any[] | ❌ 未修复 | 3 处仍为 `const conditions = []` |

### 4.16 elective 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: data-access-selections.ts 直查 classes 表 | ✅ 已修复 | 改用跨模块接口 |
| P1: runLottery 无事务 | ✅ 已修复 | 已用 db.transaction |
| P1: 循环内逐条 await | ✅ 已修复 | 改用 inArray 批量更新 |
| P1: as 断言 | ✅ 已修复 | 已清理 |
| P2: 串行查询未并行化 | ✅ 已修复 | 改用 Promise.all |
| P2: 未用 React.cache() | ⚠️ 部分修复 | 大部分已用，getSubjectOptions 仍未用 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P1 | data-access.ts:77-106 | buildCourseSelect 跨模块 join users/subjects/grades |
| P1 | data-access.ts:231-242 | 本地 getSubjectOptions 直查 subjects 表且与 school 重复 |
| P1 | data-access-operations.ts:97-172 | selectCourse 缺事务包裹 |
| P1 | data-access-operations.ts:174-241 | dropCourse 缺事务包裹 |
| P2 | data-access-operations.ts:139-148 | FCFS 并发超卖风险 |
| P2 | data-access-operations.ts:49 | runLottery 使用 Math.random 不可复现 |
| P2 | data-access.ts:46-75, data-access-selections.ts:46-74 | mapCourseRow 重复定义 |

### 4.17 proctoring 模块

#### v1 修复情况（100% 修复）

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P0: actions.ts 直接 DB 操作 | ✅ 已修复 | 下沉到 data-access |
| P1: import type | ✅ 已修复 | 已改为 import type |
| P1: as 断言 | ✅ 已修复 | 改用类型守卫 |
| P1: requireAuth | ✅ 已修复 | 改用 requirePermission |
| P1: 直查 exams/examSubmissions 表 | ✅ 已修复 | 改用跨模块函数 |
| P1: 多处 as 断言 | ✅ 已修复 | 改用 toExamMode/isSubmissionStatus |
| P2: 未调用 revalidatePath | ✅ 已修复 | 已添加 |
| P2: 串行查询 | ✅ 已修复 | 改用 Promise.all |
| P2: 重复 filter | ✅ 已修复 | 改用单次循环 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | data-access.ts:271-287 | getStudentProctoringStatuses 串行查询未并行化 |

### 4.18 diagnostic 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: 4 个 Action 无 Zod | ✅ 已修复 | 新增 schema.ts，6 个 Action 全用 Zod |
| P1: 直查跨模块表 | ✅ 已修复 | 改用跨模块 data-access |
| P1: as 断言 | ✅ 已修复 | 改用 isStringArray 类型守卫 |
| P2: 循环内 find | ✅ 已修复 | 改用 Map |
| P2: 循环内串行 await | ❌ 未修复 | updateMasteryFromSubmission 仍串行 |
| P2: 重复 filter | ✅ 已修复 | 改用单次循环 |
| P2: 动态 import | ✅ 已修复 | 改为静态 import |
| P2: void round2 死代码 | ❌ 未修复 | 仍存在 |
| P2: 未用 React.cache() | ✅ 已修复 | 全部用 cache() 包装 |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | data-access.ts:147-159 | getClassMasterySummary 串行查询未并行化 |

### 4.19 dashboard 模块

**v1 无违规问题，v2 仍为标杆模块。** 正确使用 Promise.all 并行获取多模块数据，正确使用 cache()，正确通过各模块 data-access 通信。

### 4.20 files 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: conditions 隐式 any[] | ❌ 未修复 | 仍为 `const conditions = []` |
| P1: or(...)! 非空断言 | ✅ 已修复 | 改用显式判断 |
| P2: 循环内串行 await | ⚠️ 部分修复 | 主路径已批量删除，catch 回退仍串行 |
| P2: 9 处静默 catch | ❌ 未修复 | 实际 10 处 |
| P2: 未用 React.cache() | ✅ 已修复 | 全部用 cache() 包装 |

### 4.21 announcements 模块

#### v1 修复情况

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: as string 断言 | ✅ 已修复 | 新增 toIso/toIsoRequired 工具函数 |
| P2: 冗余 as 断言 | ✅ 已修复 | 已清理 |
| P2: catch 吞错误 | ⚠️ 部分修复 | 已加 console.error 但仍吞错误 |
| P2: 类型重复定义 | ✅ 已修复 | 已修复 |
| P2: requireAuth | ✅ 已修复 | 改用 requirePermission |
| P2: 重复 try/catch | ❌ 未修复 | 6 处仍重复 |

### 4.22 settings 模块

#### v1 修复情况（100% 修复）

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P1: 无 data-access.ts | ✅ 已修复 | 新建 data-access.ts |
| P1: actions-password.ts 无 data-access | ✅ 已修复 | DB 操作下沉 |
| P1: 无 Zod 验证 | ✅ 已修复 | 新增 ChangePasswordSchema |
| P2: 类型定义位置 | ✅ 已修复 | 新建 types.ts |
| P2: 缺返回类型 | ✅ 已修复 | 已添加 |
| P2: 串行查询 | ✅ 已修复 | 改用 Promise.all |
| P2: 布尔命名 | ✅ 已修复 | 改为 hasDefault/isNextDefault/shouldMakeDefault |
| P2: requireAuth | ✅ 已修复 | 改用 requirePermission |
| P2: 串行查询 | ✅ 已修复 | 改用 Promise.all |

#### v2 新发现问题

| 严重程度 | 文件 | 问题 |
|---------|------|------|
| P2 | actions.ts:60-63 | getAiProviderSummaries 返回非 ActionState |

### 4.23 layout 模块

#### v1 修复情况（0% 修复）

| v1 问题 | 修复状态 | 说明 |
|---------|---------|------|
| P2: permission 字段为 string | ❌ 未修复 | 仍为 string |
| P2: Role 类型位置 | ❌ 未修复 | 仍在 navigation.ts |

---

## 五、v2 新发现问题清单

### 5.1 P1 级别新问题（16 个）

| 编号 | 模块 | 文件 | 问题 |
|------|------|------|------|
| N1 | elective | data-access.ts:77-106 | buildCourseSelect 跨模块 join users/subjects/grades |
| N2 | elective | data-access.ts:231-242 | 本地 getSubjectOptions 直查 subjects 表且与 school 重复 |
| N3 | elective | data-access-operations.ts:97-172 | selectCourse 缺事务包裹 |
| N4 | elective | data-access-operations.ts:174-241 | dropCourse 缺事务包裹 |
| N5 | classes | actions.ts:47,521,565 | as 断言（v as ClassSubject、weekday as 1\|2\|...\|7） |
| N6 | school | actions.ts:33 等 | 使用 .parse() 而非 .safeParse() |
| N7 | school | data-access.ts:24 | toIso 缺返回类型 |
| N8 | attendance | data-access.ts:70 | resolveRecorderNames 缺返回类型 |
| N9 | exams | ai-pipeline.ts | 8 个函数缺返回类型 |
| N10 | exams | data-access.ts:269 | buildOrderedQuestionsFromStructure 缺返回类型 |
| N11 | grades | data-access.ts:57, data-access-analytics.ts:34 | buildScopeClassFilter 缺返回类型 |
| N12 | textbooks | data-access.ts:19,25 | normalizeOptional、sortChapters 缺返回类型 |
| N13 | settings | actions.ts:60-63 | getAiProviderSummaries 返回非 ActionState |
| N14 | messaging | data-access.ts:84 | conds 隐式 any[] |
| N15 | users | import-export.ts:14 | as 断言未加注释 |
| N16 | notifications | wechat-channel.ts:106 | as 断言未加注释 |

### 5.2 P2 级别新问题（25 个）

| 编号 | 模块 | 文件 | 问题 |
|------|------|------|------|
| N17 | classes | data-access.ts:675 | 非空断言 `!` |
| N18 | classes | data-access.ts:371-373 等 | 多处 try-catch 吞错误 |
| N19 | classes | data-access.ts | 文件 866 行超 800 行建议上限 |
| N20 | school | data-access.ts:406-469 | 3 个跨模块查询函数未用 cache() |
| N21 | scheduling | data-access.ts:113-114 | select 中 requesterName 字段冗余 |
| N22 | scheduling | data-access.ts:135-145 | 用户查询应使用 inArray |
| N23 | course-plans | data-access.ts:143-162 等 | 3 处 try-catch 吞错误 |
| N24 | grades | export.ts:129 | 循环内 find O(n) 查找 |
| N25 | proctoring | data-access.ts:271-287 | getStudentProctoringStatuses 串行查询 |
| N26 | diagnostic | data-access.ts:147-159 | getClassMasterySummary 串行查询 |
| N27 | elective | data-access-operations.ts:139-148 | FCFS 并发超卖风险 |
| N28 | elective | data-access-operations.ts:49 | runLottery 使用 Math.random |
| N29 | elective | data-access.ts:46-75 等 | mapCourseRow 重复定义 |
| N30 | users | import-export.ts:98 | conditions 隐式 any[] |
| N31 | audit | data-access.ts:40,96,161 | conditions 隐式 any[] |

---

## 六、架构文档同步提醒

根据项目规则"改码必同步图"，以下架构图信息需更新：

### 6.1 需更新的架构文档

| 文档 | 需更新内容 |
|------|-----------|
| `docs/architecture/004_architecture_impact_map.md` | 1. exams/actions.ts 行数（v1 记录 691，实际 771）<br>2. exams/ai-pipeline.ts 行数（v1 记录 857，实际 916）<br>3. settings 导出函数列表（v1 记录 getAiProvidersAction 等，实际为 getAiProviderSummaries/upsertAiProviderAction/testAiProviderAction）<br>4. P2-11 死代码 void wasPublished 状态（已修复）<br>5. announcements 依赖 school 模块（仅 components，非后端）<br>6. elective 依赖关系需补充 classes/school/users<br>7. messaging↔notifications 循环依赖已解决<br>8. classes 跨模块直查 homework/exams 已解决 |

### 6.2 需同步的代码变更

本轮修复涉及大量模块结构调整，必须同步更新 004 和 005 架构文档：

- **新增模块文件**：classes/schema.ts、settings/data-access.ts、settings/types.ts、diagnostic/schema.ts
- **新增跨模块接口**：classes 暴露 getClassGradeIdsByClassIds、getStudentIdsByClassId 等；exams 暴露 getExamIdsByGradeIds、getExamWithQuestionsForHomework 等；users 暴露 getUserNamesByIds、getUserBasicInfo 等；school 暴露 getSubjectOptions、getGradeOptions 等
- **表所有权迁移**：messageNotifications、notificationPreferences 表所有权从 messaging 移交至 notifications
- **权限点新增**：USER_PROFILE_UPDATE、PASSWORD_SELF_CHANGE 等

---

## 七、总体评价与建议

### 7.1 修复成效

本次 v2 核查显示，项目在 v1 报告后进行了大规模且有成效的修复：

1. **所有 P0 问题已全部修复**（14/14）：包括跨模块直写 DB、循环依赖、硬编码弱密码、N+1 查询等高危问题
2. **P1 问题修复率 65%**：剩余 14 个未修复 + 7 个部分修复
3. **4 个模块达到 100% 修复率**：homework、parent、proctoring、settings
4. **架构层面显著改善**：
   - messaging↔notifications 循环依赖彻底消除
   - 跨模块直查 DB 大幅减少（exams、homework、grades、classes、proctoring、diagnostic 等模块已改用 data-access 接口）
   - parent 模块成为跨模块通信的标杆实现

### 7.2 仍需改进的领域

1. **textbooks 模块**：P0 问题（无 Zod 验证）仍未修复，是所有模块中唯一未实现输入验证的 Server Action 文件
2. **跨模块直查残留**：exams→school、questions→textbooks、classes→scheduling、messaging→classes 仍存在直查
3. **函数返回类型标注**：多个模块仍存在箭头函数缺返回类型的问题（classes、school、attendance、exams、grades、textbooks）
4. **隐式 any[]**：`const conditions = []` 在 users、messaging、audit、files 等模块普遍存在
5. **错误处理**：try-catch 吞错误在 school、files、classes、course-plans 等模块仍普遍存在
6. **elective 模块**：v2 新发现 selectCourse/dropCourse 缺事务、data-access.ts 跨模块直查等问题

### 7.3 下一阶段优先修复建议

**第一优先级（P0/P1 核心问题）**：
1. textbooks 模块新建 schema.ts，所有 Action 改用 Zod safeParse
2. textbooks/actions.ts 改用共享 ActionState 类型
3. exams、questions、classes、messaging 模块消除剩余跨模块直查
4. elective selectCourse/dropCourse 加事务包裹
5. school/actions.ts 改用 safeParse
6. 补齐所有函数返回类型标注

**第二优先级（P2 系统性优化）**：
1. 全项目统一修复 `const conditions = []` 隐式 any[]（改为 `SQL[]`）
2. 清理 try-catch 吞错误（至少加 console.error 或向上抛出）
3. 补齐 React.cache() 包装
4. 串行查询改用 Promise.all
5. 同步架构文档

**第三优先级（代码质量）**：
1. 抽取重复代码（mapCourseRow、handleActionError、buildExcelSheet 等）
2. 清理死代码（void round2 等）
3. 拆分超长文件（ai-pipeline.ts、classes/data-access.ts）
4. layout 模块类型规范修复
