# 后端模块规范核查报告

> 核查日期：2026-06-18
> 核查范围：`src/modules/` 下所有后端 `.ts` 文件（actions / data-access / schema / types / services 等非 components、非 hooks 文件）
> 核查依据：
> - `.trae/rules/project_rules.md` 项目规则
> - `docs/standards/coding-standards.md` 编码规范
> - `docs/architecture/004_architecture_impact_map.md` 架构影响地图
> - Vercel React Best Practices 性能优化规则（react-best-practices 技能）
>
> 严重程度定义：
> - **P0**：严重违规，破坏架构分层 / 安全漏洞 / 数据正确性问题，必须立即修复
> - **P1**：明确违规，影响可维护性 / 类型安全 / 性能，应尽快修复
> - **P2**：轻微违规或代码异味，可在迭代中优化

---

## 目录

- [一、汇总统计](#一汇总统计)
- [二、P0 严重问题清单（立即修复）](#二p0-严重问题清单立即修复)
- [三、按模块详细核查](#三按模块详细核查)
  - [3.1 exams（考试模块）](#31-exams考试模块)
  - [3.2 homework（作业模块）](#32-homework作业模块)
  - [3.3 questions（题库模块）](#33-questions题库模块)
  - [3.4 grades（成绩模块）](#34-grades成绩模块)
  - [3.5 textbooks（教材模块）](#35-textbooks教材模块)
  - [3.6 classes（班级模块）](#36-classes班级模块)
  - [3.7 school（学校模块）](#37-school学校模块)
  - [3.8 scheduling（排课模块）](#38-scheduling排课模块)
  - [3.9 attendance（考勤模块）](#39-attendance考勤模块)
  - [3.10 course-plans（教学计划模块）](#310-course-plans教学计划模块)
  - [3.11 users（用户模块）](#311-users用户模块)
  - [3.12 messaging（消息模块）](#312-messaging消息模块)
  - [3.13 notifications（通知模块）](#313-notifications通知模块)
  - [3.14 parent（家长模块）](#314-parent家长模块)
  - [3.15 audit（审计模块）](#315-audit审计模块)
  - [3.16 elective（选修课模块）](#316-elective选修课模块)
  - [3.17 proctoring（监考模块）](#317-proctoring监考模块)
  - [3.18 diagnostic（诊断模块）](#318-diagnostic诊断模块)
  - [3.19 dashboard（仪表盘模块）](#319-dashboard仪表盘模块)
  - [3.20 files（文件模块）](#320-files文件模块)
  - [3.21 announcements（公告模块）](#321-announcements公告模块)
  - [3.22 settings（设置模块）](#322-settings设置模块)
  - [3.23 layout（布局模块）](#323-layout布局模块)
- [四、跨模块共性问题](#四跨模块共性问题)
- [五、性能优化专项（react-best-practices）](#五性能优化专项react-best-practices)
- [六、优先修复路线图](#六优先修复路线图)

---

## 一、汇总统计

### 1.1 按严重程度

| 严重程度 | 数量 | 典型问题 |
|---------|------|---------|
| **P0** | 14 | 跨模块直查/直写 DB 表、循环依赖、`z.any()`、N+1 查询、未返回 ActionState、无 Zod 验证、硬编码弱密码 |
| **P1** | 60+ | `as` 断言滥用、`requireAuth` 替代 `requirePermission`、缺少 `import type`、动态 import、无事务、除零 bug、导出数据截断 |
| **P2** | 100+ | 非空断言 `!`、未用 `React.cache()`、串行查询未并行化、重复 filter、静默吞异常、缺返回类型标注、文档不同步 |

### 1.2 按问题类别

| 问题类别 | 涉及文件数 | 关键发现 |
|---------|-----------|---------|
| 架构违规 | 25+ | 跨模块直查 DB 是最普遍问题；messaging↔notifications 循环依赖；actions 层直接 DB 操作 |
| TS 规范 | 30+ | `as` 断言大量存在；`z.any()`；`import type` 未用；非空断言 `!`；隐式 `any[]` |
| Server Action 规范 | 12+ | `requireAuth` 替代 `requirePermission`；未返回 `ActionState`；无 Zod 验证；缺 `revalidatePath` |
| 性能 | 20+ | N+1 查询；动态 import；未用 `cache()`；串行 await 循环；重复 filter 遍历 |
| 安全 | 2 | 硬编码弱密码 "123456"；`as` 断言掩盖类型不兼容 |
| 行数 | 1 | `exams/ai-pipeline.ts` 912 行（超 800 建议，未超 1000 硬上限） |

### 1.3 模块问题分布

| 模块 | P0 | P1 | P2 | 总体评价 |
|------|----|----|-----|---------|
| exams | 3 | 5 | 6 | 跨模块直查/直写严重 |
| homework | 0 | 8 | 5 | 跨模块直查普遍 |
| questions | 2 | 2 | 3 | `z.any()` + 未返回 ActionState |
| grades | 1 | 6 | 7 | N+1 查询 + 跨模块直查 |
| textbooks | 2 | 2 | 4 | 无 Zod 验证 + `as` 断言 |
| classes | 3 | 8 | 6 | 耦合最严重，混入多模块逻辑 |
| school | 1 | 1 | 2 | actions 层直接 DB 操作 |
| scheduling | 0 | 4 | 3 | 缺返回类型标注 |
| attendance | 0 | 1 | 0 | 整体规范 |
| course-plans | 0 | 3 | 2 | 缺 revalidatePath |
| users | 1 | 3 | 4 | 硬编码弱密码 + 无事务 |
| messaging | 1 | 6 | 6 | 循环依赖 + requireAuth |
| notifications | 3 | 3 | 8 | 循环依赖 + as 断言类型不兼容 |
| parent | 0 | 2 | 2 | 跨模块直查 |
| audit | 0 | 4 | 6 | 导出数据截断 |
| elective | 0 | 3 | 5 | 无事务 + 串行更新 |
| proctoring | 1 | 6 | 3 | actions 层直接 DB 操作 |
| diagnostic | 0 | 2 | 5 | 跨模块直查 + 性能问题 |
| dashboard | 0 | 0 | 0 | 标杆模块 |
| files | 0 | 2 | 3 | 隐式 any[] + 非空断言 |
| announcements | 0 | 1 | 5 | as 断言 + 错误吞没 |
| settings | 0 | 3 | 9 | 缺 data-access 层 + 无 Zod |
| layout | 0 | 0 | 2 | 类型松散 |

---

## 二、P0 严重问题清单（立即修复）

### P0-1：exams 模块直写 questions 表

- **文件**：`src/modules/exams/data-access.ts`
- **行号**：2, 318
- **问题**：`persistAiGeneratedExamDraft` 直接 `db.insert(questions)` 写入 questions 表（属 questions 模块），破坏模块封装
- **修复**：改为调用 `questions/data-access.createQuestionWithRelations()` 或在 questions 模块暴露批量插入接口

### P0-2：exams 模块直查 classes 表

- **文件**：`src/modules/exams/data-access.ts`
- **行号**：2, 72-80, 156-163, 357-364
- **问题**：`getExams`/`getExamById`/`getExamsDashboardStats` 直接查询 `classes` 表获取 gradeId
- **修复**：在 classes 模块暴露 `getGradeIdsByClassIds(classIds)` 接口

### P0-3：questions/schema.ts 使用 `z.any()`

- **文件**：`src/modules/questions/schema.ts`
- **行号**：6
- **问题**：`content: z.any()` 违反"禁止 any"规则
- **修复**：改为 `z.unknown()`

### P0-4：questions/actions.ts 未返回 ActionState

- **文件**：`src/modules/questions/actions.ts`
- **行号**：154, 166-175
- **问题**：`getQuestionsAction` 和 `getKnowledgePointOptionsAction` 直接返回原始结果，未包装为 `ActionState<T>`，错误时 throw 而非返回失败状态
- **修复**：改为 `Promise<ActionState<...>>` 返回 `{ success: true, data: result }`

### P0-5：textbooks 模块无 Zod 验证 + 大量 `as` 断言

- **文件**：`src/modules/textbooks/actions.ts`
- **行号**：全文件，特别 54-58, 92-98, 154, 219-221, 259-261
- **问题**：缺少 `schema.ts` 文件，所有 Action 均无 Zod 验证，使用手动 `if (!rawData.title)` 检查；14 处 `formData.get("title") as string` 断言
- **修复**：新建 `schema.ts`，为每个 Action 定义 Zod schema；改用 `typeof` 守卫或 `getStringValue()` 辅助函数

### P0-6：grades 模块 N+1 查询

- **文件**：`src/modules/grades/data-access-analytics.ts`
- **行号**：142-185
- **问题**：`getClassComparison` 中 `for (const cls of classRows) { ... await db.select(...) }` 循环内串行查询，N+1 问题
- **修复**：用 `inArray` 一次性查询所有班级的成绩，再在内存分组

### P0-7：classes 模块跨模块直接查询 DB 表

- **文件**：`src/modules/classes/data-access-stats.ts`、`src/modules/classes/data-access-students.ts`
- **行号**：data-access-stats.ts: 11-18；data-access-students.ts: 10-14
- **问题**：直接导入并查询 `homeworkAssignments`、`homeworkSubmissions`、`homeworkAssignmentTargets`、`homeworkAssignmentQuestions`、`exams` 等其他模块的表
- **修复**：通过 homework 模块的 data-access 暴露的函数获取数据

### P0-8：school 模块 actions 层直接操作 DB

- **文件**：`src/modules/school/actions.ts`
- **行号**：7-8, 26-30, 53-59, 73, 96-108, 133-147, 161, 182-186, 211-217, 233, 261-268, 294-303, 317
- **问题**：actions 层直接导入 `db` 和所有 schema，所有 mutation 直接操作 DB，未下沉到 data-access 层
- **修复**：在 `data-access.ts` 中新增 `createDepartment`、`updateDepartment`、`deleteDepartment`、`createSchool` 等函数

### P0-9：proctoring 模块 actions 层直接查询 DB

- **文件**：`src/modules/proctoring/actions.ts`
- **行号**：11-13, 79-84
- **问题**：actions.ts（编排层）直接导入 `db`、`examSubmissions` 并执行 DB 查询，违反三层架构
- **修复**：将 submission 归属校验逻辑移至 `data-access.ts`

### P0-10：messaging↔notifications 循环依赖

- **文件**：`src/modules/messaging/data-access.ts`、`src/modules/notifications/data-access.ts`、`src/modules/notifications/channels/in-app-channel.ts`
- **行号**：messaging/data-access.ts: 192-203；notifications/data-access.ts: 20-21；in-app-channel.ts: 50
- **问题**：messaging 写 `messageNotifications` 表，notifications 反向依赖 messaging，形成循环依赖
- **修复**：将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块

### P0-11：notifications/in-app-channel.ts 非法 as 断言

- **文件**：`src/modules/notifications/channels/in-app-channel.ts`
- **行号**：54
- **问题**：`payload.type as "message" | "announcement" | "homework" | "grade"` 源类型 `"info"|"warning"|"error"|"success"` 与目标类型不兼容，as 断言非法
- **修复**：重新设计类型映射函数，或统一 messaging/notifications 的 type 定义

### P0-12：users 模块硬编码弱密码

- **文件**：`src/modules/users/user-service.ts`
- **行号**：13
- **问题**：`const DEFAULT_PASSWORD = "123456"` 硬编码弱密码，批量导入用户使用极弱默认密码
- **修复**：改为随机生成密码或要求首次登录强制修改密码，至少使用 `crypto.randomBytes` 生成

### P0-13：users/actions.ts updateUserProfile 绕过权限校验

- **文件**：`src/modules/users/actions.ts`
- **行号**：29-51
- **问题**：`updateUserProfile` 使用 `requireAuth()` 而非 `requirePermission()`，且在 actions 层直接执行 `db.update(users)`，返回 `Promise<void>` 而非 `ActionState<T>`
- **修复**：改为 `requirePermission(Permissions.USER_MANAGE)` 或新增 `USER_PROFILE_UPDATE` 权限点；DB 操作下沉到 data-access；返回 `ActionState<void>`

### P0-14：scheduling/data-access.ts 缺返回类型标注

- **文件**：`src/modules/scheduling/data-access.ts`
- **行号**：239, 246, 255, 262
- **问题**：`getAdminClassesForScheduling()`、`getTeachersForScheduling()`、`getClassroomsForScheduling()`、`getClassSubjectsForScheduling()` 缺少返回类型标注，违反"函数返回值必须显式标注，特别是 Promise<T>"
- **修复**：添加返回类型 `: Promise<Array<{ id: string; name: string; grade: string }>>` 等

---

## 三、按模块详细核查

### 3.1 exams（考试模块）

#### `src/modules/exams/actions.ts`（767 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 270 | TS规范 | P1 | `formData.get("questionsJson") as string \| null` — 使用 `as` 断言，非从 unknown 转换 | 改用 `typeof` 守卫：`const raw = formData.get("questionsJson"); const rawQuestions = typeof raw === "string" ? raw : null` |
| 349-351 | TS规范 | P1 | 三处 `as string \| null` 断言（`questionsJson`/`aiQuestionsJson`/`structureJson`） | 统一使用 `getStringValue()` 辅助函数 |
| 4 | TS规范 | P2 | `import { ActionState }` — 类型未使用 `import type` | 改为 `import type { ActionState }` |
| 564-565 | Server Action规范 | P2 | `JSON.parse(rawQuestions)` 直接 parse 后传入 Zod，parse 异常未捕获 | 用 try-catch 包裹或先 `as unknown` 再 safeParse |
| 全文件 | 行数 | P2 | 实际 767 行，架构文档记录为 691 行，文档与代码不同步 | 同步更新 004 和 005 架构文档 |

**合规项**：所有 10 个 Action 均调用 `requirePermission()` ✓；均使用 Zod 验证 ✓；均返回 `ActionState<T>` ✓；均使用 `revalidatePath` ✓。

#### `src/modules/exams/ai-pipeline.ts`（912 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 全文件 | 行数 | P2 | 912 行，超出 800 行建议（架构文档记录 857 行，已不同步）。混合 4 类职责 | 按职责拆分为 `ai-json-parser.ts`、`ai-prompts.ts`、`ai-requests.ts`、`ai-preview-builder.ts` |
| 464 | TS规范 | P2 | `draft.sections!.forEach` — 非空断言 | 改用 `if (draft.sections) { draft.sections.forEach(...) }` |
| 657, 665, 666, 671 | TS规范 | P2 | 多处 `aiParsed.sections!.flatMap` / `aiParsed.sections!.map` — 非空断言 | 同上，用条件守卫替代 |
| 505, 508, 545, 879 | TS规范 | P2 | `as Record<string, unknown>` — 虽然从 unknown 转换允许，但缺少注释说明原因 | 添加注释 `// safe: validated by isRecord` |
| 557-558 | 代码质量 | P2 | `catch {}` 空捕获块，静默吞掉错误 | 至少记录 `console.warn` |
| 503-524 | 代码质量 | P2 | `normalizeQuestionCandidate` 嵌套定义在 `parseQuestionDetail` 内部，每次调用都重新创建闭包 | 提取为模块级函数 |

**合规项**：使用 `mapWithConcurrency` 实现并发控制 ✓；使用 `Promise.all` ✓；`env.AI_MODEL` 服务端环境变量无 `NEXT_PUBLIC_` 前缀 ✓。

#### `src/modules/exams/data-access.ts`（524 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 2, 318 | 架构违规 | **P0** | `persistAiGeneratedExamDraft` 直接 `db.insert(questions)` 写入 questions 表 | 改为调用 `questions/data-access.createQuestionWithRelations()` |
| 2, 72-80, 156-163, 357-364 | 架构违规 | **P0** | `getExams`/`getExamById`/`getExamsDashboardStats` 直接查询 `classes` 表 | 在 classes 模块暴露 `getGradeIdsByClassIds(classIds)` 接口 |
| 2, 206-226, 509-524 | 架构违规 | P1 | `resolveSubjectGradeNames`/`getExamSubjects`/`getExamGrades` 直接查询 `subjects`/`grades` 表 | 在 school 模块暴露 `getSubjectOptions()`/`getGradeOptions()` 接口 |
| 76, 160, 361 | TS规范 | P1 | `teacherGradeIds.map(g => g.gradeId).filter(Boolean) as string[]` — `as` 断言 | 使用类型守卫：`.filter((id): id is string => Boolean(id))` |
| 108, 172 | TS规范 | P2 | `(exam.status as ExamStatus)` — `as` 断言从 string 转换 | 使用 `z.enum()` 验证或类型守卫函数 |
| 182 | TS规范 | P2 | `exam.structure as unknown` — 转换到 unknown 允许，但无注释 | 添加注释说明 |

**合规项**：使用 `cache()` 包装查询函数 ✓；使用 `Promise.all` 并行查询 ✓；使用 `Map` 做 O(1) 查找 ✓。

#### `src/modules/exams/types.ts`（31 行）

无违规问题。类型定义规范，接口命名 PascalCase 无 `I` 前缀 ✓。

---

### 3.2 homework（作业模块）

#### `src/modules/homework/actions.ts`（282 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 247 | TS规范 | P1 | `formData.get("answersJson") as string \| null` — `as` 断言 | 改用 `typeof` 守卫 |
| 54 | TS规范 | P2 | `JSON.parse(targetStudentIdsJson) as unknown` — 转换到 unknown 允许，但无注释 | 添加注释 |

**合规项**：所有 5 个 Action 均调用 `requirePermission()` ✓；使用 Zod 验证 ✓；返回 `ActionState` ✓；使用 `revalidatePath` ✓；使用 `Set` 做查找 ✓。

#### `src/modules/homework/data-access.ts`（681 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 8-20, 101-105, 162-166, 283-287, 337-340, 518-519 | 架构违规 | P1 | 直接查询 `exams` 表（5 处），属 exams 模块 | 在 exams 模块暴露 `getExamIdsByGradeIds(gradeIds)` 接口 |
| 8-20, 66-77, 86-95, 149-158, 242-254, 275-281, 347-351 | 架构违规 | P1 | 直接查询 `classEnrollments` 表（多处），属 classes 模块 | 在 classes 模块暴露 `getStudentIdsByClassId(classId)` / `getStudentIdsByClassIds(classIds)` 接口 |
| 8-20, 515-519 | 架构违规 | P1 | 直接查询 `subjects` 表，属 school 模块 | 通过 school data-access 获取 |
| 8-20, 480-486 | 架构违规 | P1 | 直接查询 `users`/`roles`/`usersToRoles` 表，属 users 模块 | 在 users 模块暴露 `getUserWithRole(userId, roleName)` 接口 |
| 475-490 | 架构违规 | P2 | `getDemoStudentUser` 使用 `auth()` 而非 `getAuthContext()`，绕过 auth-guard 标准模式 | 改用 `getAuthContext()` 获取 userId |
| 39 | TS规范 | P1 | `return v as HomeworkQuestionContent` — 从 `Record<string, unknown>` 断言 | 使用类型守卫或 `z.safeParse()` 验证 |
| 124, 226, 314, 392, 467, 645 | TS规范 | P2 | 多处 `(status as HomeworkAssignmentStatus)` — `as` 断言 | 使用类型守卫函数或 Zod 验证 |
| 451-455 | 性能 | P2 | `getHomeworkSubmissionDetails` 获取全部 submissions 仅为计算 prev/next 导航 | 改用 SQL `LAG`/`LEAD` 窗口函数或仅查询相邻 ID |

**合规项**：使用 `cache()` ✓；使用 `Map`/`Set` 聚合 ✓；`"server-only"` 导入 ✓。

#### `src/modules/homework/data-access-write.ts`（317 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 8-17, 70-76, 125-130 | 架构违规 | P1 | 直接查询 `classes` 表（`getClassTeacherById`） | 在 classes 模块暴露 `getClassTeacherById(classId)` 接口 |
| 8-17, 132-143 | 架构违规 | P1 | 直接查询 `classEnrollments` 表（`getActiveClassStudentIdsForHomework`） | 在 classes 模块暴露 `getActiveStudentIdsByClassId(classId)` 接口 |
| 8-17, 107-116 | 架构违规 | P1 | 直接查询 `classSubjectTeachers` 表 | 在 classes 模块暴露 `getTeacherSubjectIdsByClass(classId, teacherId)` 接口 |
| 8-17, 81-88 | 架构违规 | P1 | 直接查询 `exams` 表（`getExamWithQuestionsForHomework`） | 在 exams 模块暴露 `getExamForHomeworkCreation(examId)` 接口 |
| 305-311 | 性能 | P2 | `gradeHomeworkAnswers` 中 `for (const ans of answers) { await db.update(...) }` — 循环内串行 await，未使用事务 | 用 `db.transaction` 包裹，或用 `Promise.all` 并行化无依赖更新 |

**合规项**：`"server-only"` 导入 ✓；所有函数显式标注返回类型 ✓；无 `as`/`any` ✓。

#### `src/modules/homework/schema.ts`（29 行）

无违规问题。Zod 验证完整 ✓。

#### `src/modules/homework/stats-service.ts`（483 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 8-16, 313-323, 320-323 | 架构违规 | P1 | 直接查询 `classEnrollments` 表 | 通过 classes data-access 获取 |
| 8-16, 437-441 | 架构违规 | P1 | 直接查询 `classes` 表 | 通过 classes data-access 获取 |
| 8-16, 425-429, 443-447 | 架构违规 | P1 | 直接查询 `exams` 表 | 通过 exams data-access 获取 |
| 8-16, 366-369 | 架构违规 | P1 | 直接查询 `users` 表 | 在 users 模块暴露 `getUserNamesByIds(ids)` 接口 |
| 220 | TS规范 | P2 | `(assignment.status as HomeworkAssignmentStatus)` — `as` 断言 | 使用类型守卫 |
| 346 | 性能 | P2 | `limit: 5000` 查询班级所有 graded submissions，大班级可能性能问题 | 考虑分批查询或 SQL 聚合 |

**合规项**：使用 `cache()` ✓；使用 `Promise.all` 并行查询 ✓；使用 `Map` 聚合 ✓。

#### `src/modules/homework/types.ts`（205 行）

无违规问题。类型定义规范 ✓。

---

### 3.3 questions（题库模块）

#### `src/modules/questions/actions.ts`（176 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 154 | Server Action规范 | **P0** | `getQuestionsAction` 未返回 `ActionState<T>`，直接返回 `getQuestions(params)` 的原始结果 `{ data, meta }`，错误时 throw | 改为 `Promise<ActionState<...>>` 返回 `{ success: true, data: result }` |
| 166-175 | Server Action规范 | **P0** | `getKnowledgePointOptionsAction` 未返回 `ActionState<T>`，直接返回 `KnowledgePointOption[]` | 同上，包装为 ActionState |
| 154 | TS规范 | P1 | `getQuestionsAction` 缺少显式返回类型标注 | 添加 `: Promise<ActionState<...>>` |
| 7 | TS规范 | P2 | `import { ActionState }` — 类型导入未用 `import type` | 改为 `import type { ActionState }` |
| 158-163, 170-175 | 代码质量 | P2 | catch 块两个分支都 `throw e`，是死代码 | 简化为单个 `throw e` 或返回失败 ActionState |
| 20 | 命名规范 | P2 | 函数名 `createNestedQuestion` 与其他模块的 `createXxxAction` 命名模式不一致 | 改为 `createQuestionAction` |

**合规项**：所有 Action 调用 `requirePermission()` ✓；create/update/delete 使用 Zod 验证 ✓；写操作使用 `revalidatePath` ✓。

#### `src/modules/questions/data-access.ts`（299 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 4, 266-298 | 架构违规 | P1 | `getKnowledgePointOptions` 直接查询 `knowledgePoints`/`chapters`/`textbooks` 表，属 textbooks 模块 | 在 textbooks 模块暴露 `getKnowledgePointOptions()` 接口 |
| 108 | 代码质量 | P2 | 局部变量名 `knowledgePoints` 与 import 的 `knowledgePoints` 表名冲突 | 重命名为 `kpRows` 或 `kps` |
| 151-184, 231-242 | 性能 | P2 | `insertQuestionWithRelations` / `deleteQuestionRecursive` 递归内串行 await | 对于删除可改为先收集所有 ID 再批量删除 |

**合规项**：使用 `cache()` ✓；`"server-only"` 导入 ✓；无 `as`/`any` ✓；显式返回类型 ✓。

#### `src/modules/questions/schema.ts`（18 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 6 | TS规范 | **P0** | `content: z.any()` — 使用 `any`，违反"禁止 any"规则 | 改为 `z.unknown()` |

#### `src/modules/questions/types.ts`（34 行）

无违规问题。`content: unknown` 正确使用 unknown ✓。

---

### 3.4 grades（成绩模块）

#### `src/modules/grades/actions.ts`（312 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 93 | 代码质量 | P2 | `JSON.parse(recordsJson)` 未用 try-catch 包裹，parse 失败会 500 | 用 try-catch 或先 `as unknown` 再 safeParse |

**合规项**：所有 Action 调用 `requirePermission()` ✓；使用 Zod 验证 ✓；返回 `ActionState<T>` ✓；使用 `revalidatePath` ✓；scope 二次校验 ✓。

#### `src/modules/grades/actions-analytics.ts`（133 行）

**合规项**：所有 Action 调用 `requirePermission()` ✓；返回 `ActionState<T>` ✓；scope 二次校验 ✓。无违规问题。

#### `src/modules/grades/data-access.ts`（419 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 6-12, 100, 382-386 | 架构违规 | P1 | 直接查询 `classes` 表 | 通过 classes data-access 获取 |
| 6-12, 365-368, 408-411 | 架构违规 | P1 | 直接查询 `classEnrollments` 表 | 通过 classes data-access 获取 |
| 6-12, 102, 276, 280 | 架构违规 | P1 | 直接查询 `subjects` 表 | 通过 school data-access 获取 |
| 6-12, 100, 109-112, 269, 342 | 架构违规 | P1 | 直接查询 `users` 表 | 通过 users data-access 获取 |
| 148, 172 | 性能 | P1 | `const { createId } = await import("@paralleldrive/cuid2")` — 函数内动态 import，每次调用都有开销 | 改为文件顶部静态 `import { createId } from "@paralleldrive/cuid2"` |
| 249 | 代码质量/Bug | P1 | `const ratio = scores[i] / fullScores[i]` — `fullScores[i]` 可能为 0，导致 `Infinity`/`NaN` | 添加 `if (fullScores[i] <= 0) continue` 守卫 |
| 全文件 | 性能 | P2 | 查询函数未使用 `React.cache()` 包装 | 用 `cache()` 包装查询函数 |
| 248-252 | 性能 | P2 | `for` 循环内两次独立 `if` 判断遍历同一数组 | 合并为单次循环同时计算 passCount 和 excellentCount |

**合规项**：`"server-only"` 导入 ✓；使用 `Map`/`Set` ✓；无 `as`/`any` ✓；显式返回类型 ✓。

#### `src/modules/grades/data-access-analytics.ts`（293 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 6-10, 79, 125-128 | 架构违规 | P1 | 直接查询 `classes` 表 | 通过 classes data-access 获取 |
| 6-10, 80, 211 | 架构违规 | P1 | 直接查询 `subjects` 表 | 通过 school data-access 获取 |
| 22, 27, 32 | 代码质量 | P2 | `toNumber`/`normalize`/`buildScopeClassFilter` 在 data-access.ts 和 data-access-ranking.ts 中重复定义 | 提取到 `grades/utils.ts` |
| 142-185 | 性能 | **P0** | `getClassComparison` 中 `for (const cls of classRows) { ... await db.select(...) }` — 循环内串行查询，N+1 问题 | 用 `inArray` 一次性查询所有班级的成绩，再在内存分组 |
| 133-136 | 性能 | P2 | `classRows.filter((c) => scope.classIds.includes(c.id))` — `includes` 是 O(n) 查找 | 将 `scope.classIds` 转为 `Set` 后用 `.has()` |
| 180-181, 238-239 | 性能 | P2 | `normalized.filter((s) => s >= 60).length` 和 `normalized.filter((s) => s >= 85).length` — 两次 filter 遍历同一数组 | 合并为单次 `reduce` 同时统计两个计数 |
| 全文件 | 性能 | P2 | 查询函数未使用 `React.cache()` | 用 `cache()` 包装 |

**合规项**：`"server-only"` ✓；无 `as`/`any` ✓；显式返回类型 ✓。

#### `src/modules/grades/data-access-ranking.ts`（121 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 6-10, 44-53 | 架构违规 | P1 | 直接查询 `classEnrollments` 表 | 通过 classes data-access 获取 |
| 6-10, 37-41 | 架构违规 | P1 | 直接查询 `users` 表 | 通过 users data-access 获取 |
| 17, 22 | 代码质量 | P2 | `toNumber`/`normalize` 重复定义 | 提取到共享 utils |
| 100, 102 | 性能 | P2 | `sorted.findIndex(...)` 和 `sorted.find(...)` 两次 O(n) 查找同一元素 | 合并为一次遍历 |
| 全文件 | 性能 | P2 | 查询函数未使用 `React.cache()` | 用 `cache()` 包装 |

#### `src/modules/grades/export.ts`（214 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 6-11, 116-120 | 架构违规 | P1 | 直接查询 `classes` 表 | 通过 classes data-access 获取 |
| 6-11, 124-132 | 架构违规 | P1 | 直接查询 `subjects` 表 | 通过 school data-access 获取 |
| 6-11, 135-144 | 架构违规 | P1 | 直接查询 `users` 表 | 通过 users data-access 获取 |
| 154 | TS规范 | P2 | `subjMap.get(r.studentId)!` — 非空断言 | 改用 `const subjMap = scoreMap.get(r.studentId); if (!subjMap) continue;` |

#### `src/modules/grades/schema.ts`（52 行）& `types.ts`（176 行）

无违规问题。Zod 验证完整，类型定义规范 ✓。

---

### 3.5 textbooks（教材模块）

#### `src/modules/textbooks/actions.ts`（276 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 全文件 | Server Action规范 | **P0** | **缺少 `schema.ts` 文件**，所有 Action 均无 Zod 验证，使用手动 `if (!rawData.title)` 检查 | 新建 `schema.ts`，为每个 Action 定义 Zod schema |
| 41-45 | 架构违规 | P1 | 本地定义 `export type ActionState`，未使用 `@/shared/types/action-state` 的统一类型 | 删除本地定义，改用 `import type { ActionState } from "@/shared/types/action-state"` |
| 18 | TS规范 | P1 | `import { CreateTextbookInput, UpdateTextbookInput }` — 类型导入未用 `import type` | 改为 `import type { ... }` |
| 54-58, 92-98, 154, 219-221, 259-261 | TS规范 | **P0** | 多处 `formData.get("title") as string` 等 `as` 断言（共 14 处） | 改用 `typeof` 守卫或 `getStringValue()` 辅助函数 |
| 20, 47 | 代码质量 | P2 | `// ... existing code ...` 残留注释 | 删除 |
| 164 | 代码质量 | P2 | `order: 0 // Default order` 魔法数字 | 提取为常量 `const DEFAULT_CHAPTER_ORDER = 0` |

**合规项**：所有 Action 调用 `requirePermission()` ✓；使用 `revalidatePath` ✓。

#### `src/modules/textbooks/data-access.ts`（444 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 42 | TS规范 | P2 | `byId.get(pid)!.children.push(ch)` — 非空断言 | 改用 `const parent = byId.get(pid); if (parent) parent.children.push(ch)` |
| 51 | TS规范 | P1 | `sortRecursive(n.children as Array<Chapter & { children: Chapter[] }>)` — `as` 断言 | 使用类型守卫或在 `Chapter` 类型中明确 `children` 字段 |
| 71 | TS规范 | P2 | `or(...)!` — 对 `or()` 返回值使用非空断言 | 改用条件判断 |
| 110, 128 | 代码质量 | P2 | `getTextbookById` 返回 `Textbook \| undefined`，与其他模块返回 `null` 的模式不一致 | 统一返回 `Textbook \| null` |
| 368, 381 | 代码质量 | P2 | `createKnowledgePoint`/`updateKnowledgePoint` 参数使用内联类型 | 将输入类型移至 `types.ts` |

**合规项**：使用 `cache()` ✓；使用 `Promise.all` ✓；`"server-only"` ✓；无 `any` ✓；显式返回类型 ✓。这是架构文档中的"标杆模块"，无跨模块 DB 访问 ✓。

#### `src/modules/textbooks/types.ts`（83 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 1-3 | 代码质量 | P2 | 残留注释 `// In a real app, we would infer these from the schema...` | 删除过时注释 |

---

### 3.6 classes（班级模块）

#### `src/modules/classes/actions.ts`（765 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 8-9 | 架构违规 | **P0** | actions 层直接导入 `db` 和 `grades, classes` schema 并执行 DB 查询 | 将权限校验查询下沉到 `data-access-admin.ts` |
| 48-53, 109-111, 140-142, 175-183, 369-377, 518-533, 636-644 | Server Action规范 | P1 | 输入验证使用手动 `typeof` 检查而非 Zod，classes 模块完全没有 schema.ts 文件 | 新建 `schema.ts`，定义 Zod schema |
| 538 | TS规范 | P1 | `weekdayNum as 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7` — `as` 断言 | 用类型守卫 `isValidWeekday(weekdayNum): weekdayNum is 1\|2\|3\|4\|5\|6\|7` 替换 |
| 582 | TS规范 | P1 | `weekdayNum as 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| undefined` — 同上 | 同上 |
| 287, 706 | TS规范 | P2 | `JSON.parse(subjectTeachers) as unknown` 后续用 `as { subject?: unknown }` | 用 Zod schema 解析 |

#### `src/modules/classes/data-access.ts`（656 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 26-27, 182-206 | 架构违规 | **P0** | `getTeacherClasses` 中调用 `getClassHomeworkInsights`（homework 逻辑）和 `getClassSchedule`（scheduling 逻辑） | 将 homework insights 聚合逻辑移至 homework 模块 |
| 47 | TS规范 | P1 | `isDuplicateInvitationCodeError` 箭头函数缺少返回类型标注 | 添加 `: boolean` |
| 54 | TS规范 | P1 | `generateInvitationCode` 箭头函数缺少返回类型标注 | 添加 `: string` |
| 87 | TS规范 | P1 | `normalizeSortText` 箭头函数缺少返回类型标注 | 添加 `: string` |
| 89 | TS规范 | P1 | `parseFirstInt` 箭头函数缺少返回类型标注 | 添加 `: number \| null` |
| 94 | TS规范 | P1 | `compareGradeLabel` 箭头函数缺少返回类型标注 | 添加 `: number` |
| 101-104 | TS规范 | P1 | `compareClassLike` 箭头函数缺少返回类型标注 | 添加 `: number` |
| 240 | TS规范 | P1 | `r.subject as ClassSubject` — `as` 断言 | 用类型守卫 `isClassSubject(r.subject)` 过滤 |
| 266 | TS规范 | P1 | `r.name as ClassSubject` — 同上 | 同上 |
| 287 | TS规范 | P2 | `idByName.get(name)!` — 非空断言 | 用 `const id = idByName.get(name); if (!id) return null` 显式处理 |
| 297-299 | 代码质量 | P1 | `throw new Error("Failed to create class")` 后有 `return id` 不可达代码 | 删除 `return id` |
| 561 | TS规范 | P1 | `r.name as ClassSubject` — `as` 断言 | 用类型守卫替换 |
| 567 | TS规范 | P2 | `idByName.get(name)!` — 非空断言 | 显式判空处理 |
| 120-127 | 性能 | P2 | `getAccessibleClassIdsForTeacher` 中两个独立 DB 查询串行执行 | 用 `Promise.all` 并行化 |

#### `src/modules/classes/data-access-admin.ts`（441 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 82-117, 237-239 | 代码质量 | P1 | try-catch 吞掉错误返回空数组或 fallback 查询 | 移除 try-catch 或记录错误日志后 rethrow |
| 135 | TS规范 | P1 | `r.subject as ClassSubject` — `as` 断言 | 用类型守卫替换 |
| 259 | TS规范 | P1 | `r.subject as ClassSubject` — 同上 | 同上 |
| 303 | TS规范 | P1 | `getManagedGrades` 缺少返回类型标注 | 添加 `: Promise<GradeListItem[]>` |
| 349 | TS规范 | P1 | `r.name as ClassSubject` — `as` 断言 | 用类型守卫替换 |
| 369 | TS规范 | P2 | `idByName.get(name)!` — 非空断言 | 显式判空 |
| 380-382 | 代码质量 | P1 | `throw new Error("Failed to create class")` 后有 `return id` 不可达代码 | 删除 `return id` |

#### `src/modules/classes/data-access-schedule.ts`（230 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 7-11, 33-48 | 架构违规 | P1 | 直接导入并查询 `classSchedule` 表（该表归属 scheduling 模块） | 将读取也委托给 scheduling 模块的 data-access |
| 54 | TS规范 | P1 | `r.weekday as StudentScheduleItem["weekday"]` — `as` 断言 | 用类型守卫或 Zod 校验 weekday 范围 |
| 93 | TS规范 | P1 | `r.weekday as ClassScheduleItem["weekday"]` — 同上 | 同上 |

#### `src/modules/classes/data-access-stats.ts`（604 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 11-18 | 架构违规 | **P0** | 直接导入并查询 `homeworkAssignmentQuestions`、`homeworkAssignmentTargets`、`homeworkAssignments`、`homeworkSubmissions`、`exams` 等 homework/exams 模块的 DB 表 | 通过 homework 模块的 data-access 暴露的函数获取数据 |
| 250 | TS规范 | P1 | `(s.status ?? "started") as string` — `as` 断言 | `s.status` 已是 string 类型，无需断言 |
| 261 | TS规范 | P1 | `(a.status as string) ?? "draft"` — `as` 断言 | 同上 |
| 512 | TS规范 | P1 | `(s.status ?? "started") as string` — 同上 | 同上 |
| 523 | TS规范 | P1 | `(a.status as string) ?? "draft"` — 同上 | 同上 |
| 130-151, 175-191 | 性能 | P2 | 多个早期返回中重复构造相同的返回对象结构 | 提取 `buildEmptyInsights(classRow, studentCounts)` 工厂函数 |

#### `src/modules/classes/data-access-students.ts`（280 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 10-14 | 架构违规 | **P0** | 直接导入并查询 `homeworkAssignmentTargets`、`homeworkAssignments`、`homeworkSubmissions`、`exams` 等 homework/exams 模块的 DB 表 | 通过 homework 模块 data-access 获取数据 |
| 102 | TS规范 | P2 | `studentScores.get(s.studentId)!` — 非空断言 | 显式判空处理 |
| 168-204 | 代码质量 | P2 | `getStudentClasses` 中 try-catch fallback 查询使用 `sql\`NULL\`.as(...)` 模式，吞掉错误 | 移除 try-catch |

#### `src/modules/classes/types.ts`（201 行）

基本符合规范，无违规问题。

---

### 3.7 school（学校模块）

#### `src/modules/school/actions.ts`（325 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 7-8, 26-30, 53-59, 73, 96-108, 133-147, 161, 182-186, 211-217, 233, 261-268, 294-303, 317 | 架构违规 | **P0** | actions 层直接导入 `db` 和所有 schema，所有 mutation 直接操作 DB，完全没有下沉到 data-access 层 | 在 `data-access.ts` 中新增 `createDepartment`、`updateDepartment`、`deleteDepartment`、`createSchool` 等函数 |
| 188, 219, 235 | 性能 | P2 | `await logAudit(...)` 阻塞响应，审计日志写入是非关键路径 | 使用 Next.js `after()` 函数将 `logAudit` 改为非阻塞执行 |

#### `src/modules/school/data-access.ts`（186 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 13, 27, 44, 59, 112, 133 | 代码质量 | P2 | 所有函数都用 try-catch 包裹并返回空数组 `[]` 吞掉错误 | 移除 try-catch 或记录错误日志后 rethrow |

#### `src/modules/school/schema.ts`（51 行）& `types.ts`（42 行）

符合规范。

---

### 3.8 scheduling（排课模块）

#### `src/modules/scheduling/actions.ts`（300 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 10-11, 112-116 | 架构违规 | **P0** | actions 层直接导入 `db` 和 `users` schema，在 `autoScheduleAction` 中直接查询 `users` 表获取教师信息 | 在 data-access.ts 中新增 `getTeachersByIds(teacherIds: string[])` 函数 |
| 115 | TS规范 | P1 | `teacherIds[0]!` — 非空断言 | 用条件表达式 `eq(users.id, teacherIds[0] ?? "")` |

#### `src/modules/scheduling/auto-scheduler.ts`（310 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 144-145 | TS规范 | P1 | `schedule[i]!`、`schedule[j]!` — 非空断言 | 用局部变量 + 显式判空 |
| 全文件 | 行数 | P2 | 工具函数规范要求 ≤ 40 行，此文件 310 行 | 拆分为 `auto-scheduler/` 目录下的多个文件 |

#### `src/modules/scheduling/data-access.ts`（368 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 239 | TS规范 | **P0** | `getAdminClassesForScheduling()` 缺少返回类型标注 | 添加返回类型 |
| 246 | TS规范 | **P0** | `getTeachersForScheduling()` 缺少返回类型标注 | 添加返回类型 |
| 255 | TS规范 | **P0** | `getClassroomsForScheduling()` 缺少返回类型标注 | 添加返回类型 |
| 262 | TS规范 | **P0** | `getClassSubjectsForScheduling(classId: string)` 缺少返回类型标注 | 添加返回类型 |
| 113-114 | 代码质量 | P2 | `requesterName: users.name` 与 `originalTeacherName: users.name` 选中同一列，`requesterName` 在 map 中被 `userMap.get()` 覆盖，select 中的字段是死代码 | 删除 select 中的 `requesterName: users.name` |
| 140 | TS规范 | P1 | `userIds[0]!` — 非空断言 | 显式判空 |
| 221-222 | TS规范 | P1 | `rows[i]!`、`rows[j]!` — 非空断言 | 用局部变量 + 显式判空 |

#### `src/modules/scheduling/schema.ts`（81 行）& `types.ts`（124 行）

符合规范。

---

### 3.9 attendance（考勤模块）

#### `src/modules/attendance/actions.ts`（271 行）

基本符合规范。所有 action 调用了 `requirePermission`、使用 Zod 验证、返回 `ActionState`、使用 `revalidatePath`。

#### `src/modules/attendance/data-access.ts`（271 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 195 | TS规范 | P1 | `const update: Record<string, unknown> = { updatedAt: new Date() }` — 使用宽泛的 `Record<string, unknown>` 丢失类型安全 | 使用 `Partial<typeof attendanceRecords.$inferSelect>` 类型 |

#### `src/modules/attendance/data-access-stats.ts`（145 行）& `schema.ts`（43 行）& `types.ts`（103 行）

符合规范。

---

### 3.10 course-plans（教学计划模块）

#### `src/modules/course-plans/actions.ts`（265 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 236-246 | Server Action规范 | P1 | `deleteCoursePlanItemAction` 未调用 `revalidatePath` | 添加 `revalidatePlanPaths()` 调用 |
| 248-264 | Server Action规范 | P1 | `toggleCoursePlanItemCompletedAction` 未调用 `revalidatePath` | 同上 |

#### `src/modules/course-plans/data-access.ts`（320 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 149 | TS规范 | P1 | `params.status as CoursePlanStatus` — `as` 断言 | 用类型守卫或 Zod 校验 |
| 158, 183 | 代码质量 | P2 | `getCoursePlans` 和 `getCoursePlanById` 用 try-catch 返回空数组/null 吞掉错误 | 移除 try-catch 或记录日志后 rethrow |
| 300-305 | 性能 | P2 | `reorderCoursePlanItems` 中循环内串行 `await db.update()`，N 次 DB 往返 | 用 `db.transaction` 批量更新，或用 `Promise.all` 并行化 |

#### `src/modules/course-plans/schema.ts`（148 行）& `types.ts`（60 行）

符合规范。

---

### 3.11 users（用户模块）

#### `src/modules/users/actions.ts`（151 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 29-51 | Server Action规范 | **P0** | `updateUserProfile` 使用 `requireAuth()` 而非 `requirePermission()` | 改为 `requirePermission(Permissions.USER_MANAGE)` 或新增 `USER_PROFILE_UPDATE` 权限点 |
| 29-51 | 架构违规 | P1 | `updateUserProfile` 在 actions 层直接执行 `db.update(users)` | 将写操作下沉到 `data-access.ts` |
| 29-51 | Server Action规范 | P1 | `updateUserProfile` 返回 `Promise<void>` 而非 `ActionState<T>` | 改为返回 `Promise<ActionState<void>>` |
| 29 | TS规范 | P2 | `updateUserProfile(data: UpdateUserProfileInput)` 缺少显式返回类型标注 | 标注为 `Promise<ActionState<void>>` |
| 29-51 | Server Action规范 | P2 | `updateUserProfile` 输入未使用 Zod 验证 | 新增 Zod schema |
| 76-125 | Server Action规范 | P2 | `importUsersAction` 输入验证使用手动逻辑，未使用 Zod | 将 `parseUserImportData` 改用 Zod 实现 |

#### `src/modules/users/class-registration.ts`（27 行）

合规，无违规问题。正确通过 `classes/data-access.enrollStudentByInvitationCode` 跨模块通信。

#### `src/modules/users/data-access.ts`（152 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 24 | 命名规范 | P2 | `const rolePriority` 常量未使用 UPPER_SNAKE_CASE | 重命名为 `ROLE_PRIORITY` |
| 26-31 | 架构违规 | P2 | `normalizeRoleName` 与 `shared/lib/role-utils.ts` 的 `normalizeRole` 重复 | 改为 `import { normalizeRole } from "@/shared/lib/role-utils"` 复用 |
| 26 | TS规范 | P2 | `normalizeRoleName` 缺少显式返回类型标注 | 标注为 `(value: string) => string` |
| 33 | TS规范 | P2 | `resolvePrimaryRole` 缺少显式返回类型标注 | 标注为 `(roleNames: string[]) => string` |

#### `src/modules/users/import-export.ts`（176 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 98 | TS规范 | P2 | `const conditions = []` 隐式推断为 `any[]`，违反"禁止 any"规则 | 标注类型 `const conditions: ReturnType<typeof eq>[] = []` |
| 100-114 | 性能 | P2 | `exportUsersToExcel` 中 role 查询 → userIds 查询 → users 查询为串行 | 可接受（有依赖关系），但建议合并为子查询减少往返 |

#### `src/modules/users/user-service.ts`（99 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 13 | 安全规范 | **P0** | `const DEFAULT_PASSWORD = "123456"` 硬编码弱密码 | 改为随机生成密码或要求首次登录强制修改密码 |
| 15-19 | 架构违规 | P2 | `normalizeBcryptHash` 与 `shared/lib/bcrypt-utils.ts` 重复 | 改为 `import { normalizeBcryptHash } from "@/shared/lib/bcrypt-utils"` 复用 |
| 15 | TS规范 | P2 | `normalizeBcryptHash` 缺少显式返回类型标注 | 标注为 `(value: string) => string` |
| 30-92 | 架构违规 | P1 | `batchImportUsers` 注释标注"批量导入用户（事务）"，但实际未使用 `db.transaction` 包裹 | 用 `await db.transaction(async (tx) => { ... })` 包裹 |
| 50-92 | 性能 | P2 | 循环内串行 `await db.insert`，N 条记录需 N 次 DB 往返 | 可批量插入用户和角色关联 |

---

### 3.12 messaging（消息模块）

#### `src/modules/messaging/actions.ts`（247 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 163 | Server Action规范 | P1 | `getNotificationsAction` 使用 `requireAuth()` 而非 `requirePermission()` | 改为 `requirePermission(Permissions.MESSAGE_READ)` |
| 177 | Server Action规范 | P1 | `markNotificationAsReadAction` 使用 `requireAuth()` 而非 `requirePermission()` | 同上 |
| 190 | Server Action规范 | P1 | `markAllNotificationsAsReadAction` 使用 `requireAuth()` 而非 `requirePermission()` | 同上 |
| 203 | Server Action规范 | P1 | `getNotificationPreferencesAction` 使用 `requireAuth()` 而非 `requirePermission()` | 同上 |
| 218 | Server Action规范 | P1 | `updateNotificationPreferencesAction` 使用 `requireAuth()` 而非 `requirePermission()` | 同上 |
| 213-247 | Server Action规范 | P2 | `updateNotificationPreferencesAction` 未使用 Zod 验证 | 新增 Zod schema 校验 8 个布尔字段 |
| 87, 101, 129 | Server Action规范 | P2 | `markMessageAsReadAction`/`deleteMessageAction`/`getMessageDetailAction` 参数 `messageId` 未使用 Zod 验证 | 新增 `z.string().min(1)` 校验 messageId |

#### `src/modules/messaging/data-access.ts`（252 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 12-14 | 架构违规 | P1 | 导入 `classEnrollments, classes`，`getRecipients`（227-251 行）直接 JOIN 跨模块表 | 通过 `classes/data-access` 暴露 `getStudentsByClassIds(classIds)` 接口 |
| 94 | TS规范 | P2 | `const conds = []` 隐式 `any[]` | 标注 drizzle 条件类型 |
| 97 | TS规范 | P2 | `or(...)!` 使用非空断言 | 改为显式处理 null |
| 117 | TS规范 | P2 | `or(...)!` 使用非空断言 | 同上 |
| 163 | TS规范 | P2 | `or(...)!` 使用非空断言 | 同上 |
| 63 | TS规范 | P2 | `mapMessage` 缺少显式返回类型标注 | 标注返回类型 |
| 77 | TS规范 | P2 | `mapNotification` 缺少显式返回类型标注 | 标注返回类型 |
| 80 | TS规范 | P2 | `r.type as NotificationType` 使用 as 断言 | 使用类型守卫 |
| 74, 85 | TS规范 | P2 | `toIso(r.createdAt) as string` 使用 as 断言 | 处理 null 情况或修改 toIso 返回类型 |
| 192-203 | 架构违规 | **P0** | `createNotification` 写 `messageNotifications` 表，但 notifications 模块反向调用此函数，形成循环依赖 | 将 `messageNotifications` 表所有权移交 notifications 模块 |

#### `src/modules/messaging/notification-preferences.ts`（166 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 14 | TS规范 | P2 | `toIso` 缺少显式返回类型标注 | 标注为 `(d: Date) => string` |
| 16 | TS规范 | P2 | `mapRow` 缺少显式返回类型标注 | 标注返回类型 |

#### `src/modules/messaging/schema.ts`（18 行）& `types.ts`（108 行）

合规，无违规问题。

---

### 3.13 notifications（通知模块）

#### `src/modules/notifications/actions.ts`（119 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 14-17 | 架构违规 | P1 | actions 层直接导入 `db`、`classEnrollments`、`classes` | 将 DB 查询下沉到 `data-access.ts` |
| 83-96 | 架构违规 | P1 | `sendClassNotificationAction` 直接查询 `classes` 和 `classEnrollments` 表 | 通过 `classes/data-access` 暴露查询接口 |
| 30-52 | Server Action规范 | P2 | `sendNotificationAction` 参数 `payload` 未使用 Zod 验证 | 新增 `NotificationPayloadSchema` |
| 62-119 | Server Action规范 | P2 | `sendClassNotificationAction` 参数 `classId`/`payload` 未使用 Zod 验证 | 新增 Zod schema 校验 |

#### `src/modules/notifications/data-access.ts`（86 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 20 | 架构违规 | **P0** | `import { getNotificationPreferences } from "@/modules/messaging/notification-preferences"` 反向依赖 messaging 模块 | 将 `notificationPreferences` 表所有权移交 notifications 模块 |
| 21 | 架构违规 | **P0** | `import type { NotificationPreferences } from "@/modules/messaging/types"` 反向依赖 messaging 模块 | 类型定义应迁移到 notifications 模块 |
| 71-77 | 架构违规 | P2 | `logNotificationSend` 仅 `console.info`，无 DB 持久化 | 新增 `notification_logs` 表并写入发送结果 |

#### `src/modules/notifications/dispatcher.ts`（152 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 44 | TS规范 | P2 | `getSenders` 缺少显式返回类型标注 | 标注为 `() => SenderRegistry` |
| 59 | TS规范 | P2 | `selectChannels` 缺少显式返回类型标注 | 标注返回类型 |

#### `src/modules/notifications/external-sdk.d.ts`（47 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 12, 20, 25, 33, 42, 43 | TS规范 | P2 | 多处使用 `any`（`const _default: any`、`config: any`、`options: any`） | 安装实际 SDK 后用其自带类型覆盖；或使用 `unknown` |

#### `src/modules/notifications/index.ts`（38 行）& `types.ts`（70 行）& `channels/types.ts`（38 行）

合规，无违规问题。

#### `src/modules/notifications/channels/email-channel.ts`（183 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 29 | TS规范 | P2 | `getEmailConfig` 缺少显式返回类型标注 | 标注返回类型 |
| 45 | TS规范 | P2 | `getTypeColor` 缺少显式返回类型标注 | 标注为 `(type: NotificationPayload["type"]) => string` |
| 60 | TS规范 | P2 | `buildHtmlContent` 缺少显式返回类型标注 | 标注为 `(payload: NotificationPayload) => string` |
| 79 | TS规范 | P2 | `escapeHtml` 缺少显式返回类型标注 | 标注为 `(text: string) => string` |

#### `src/modules/notifications/channels/in-app-channel.ts`（88 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 50 | 架构违规 | **P0** | `await import("@/modules/messaging/data-access")` 动态 import messaging 模块，运行时形成循环 | 将 `messageNotifications` 表所有权移交 notifications |
| 54 | TS规范 | P1 | `payload.type as "message" \| "announcement" \| "homework" \| "grade"` 使用 as 断言，且源类型与目标类型不兼容，as 断言非法 | 重新设计类型映射函数 |

#### `src/modules/notifications/channels/sms-channel.ts`（236 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 30 | TS规范 | P2 | `getSmsConfig` 缺少显式返回类型标注 | 标注返回类型 |
| 32 | TS规范 | P2 | `(process.env.SMS_PROVIDER ?? "mock") as "aliyun" \| "tencent" \| "mock"` 使用 as 断言 | 使用类型守卫或 Zod 校验环境变量 |
| 44 | TS规范 | P2 | `buildTemplateParams` 缺少显式返回类型标注 | 标注返回类型 |

#### `src/modules/notifications/channels/wechat-channel.ts`（208 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 40 | TS规范 | P2 | `getWechatConfig` 缺少显式返回类型标注 | 标注返回类型 |
| 99 | TS规范 | P2 | `buildTemplateData` 缺少显式返回类型标注 | 标注返回类型 |

---

### 3.14 parent（家长模块）

#### `src/modules/parent/data-access.ts`（234 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 59-105 | 架构违规 | P1 | `getChildBasicInfo` 直接查询 `users`、`grades`、`classEnrollments`、`classes` 表 | 通过 `users/data-access.getUserProfile`、`classes/data-access` 等接口查询 |
| 58 | TS规范 | P2 | `getChildBasicInfo` 缺少显式返回类型标注 | 标注返回类型 |
| 30 | TS规范 | P2 | `(day === 0 ? 7 : day) as 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7` 使用 as 断言 | 使用类型守卫 `function isWeekday(n: number): n is 1\|2\|3\|4\|5\|6\|7` |
| 58-117 | 性能 | P2 | `getChildBasicInfo` 串行查询 student → grade → enrollment → class | 使用 JOIN 合并为单次查询，或用 Promise.all 并行化 |

#### `src/modules/parent/types.ts`（57 行）

合规，无违规问题。

---

### 3.15 audit（审计模块）

#### `src/modules/audit/actions.ts`（212 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 6, 70-100, 120-146, 166-192 | 架构违规 | P2 | Excel 导出逻辑内联在 actions 层 | 新建 `audit/export.ts`，将 Excel 构建逻辑迁移 |
| 63-205 | 架构违规 | P2 | 三个导出 Action 结构高度重复 | 抽取通用 `buildExcelSheet(name, columns, rows)` 辅助函数 |
| 207-212 | 架构违规 | P2 | `formatDateForFile` 工具函数定义在 actions.ts 中 | 迁移到 `shared/lib/utils.ts` 或 `audit/utils.ts` |
| 24-61 | Server Action规范 | P2 | `getDataChangeLogsAction` 参数 `params` 未使用 Zod 验证 | 新增 `DataChangeLogQueryParamsSchema` |
| 63-205 | Server Action规范 | P2 | 三个导出 Action 参数未使用 Zod 验证 | 新增对应 Zod schema |

#### `src/modules/audit/data-access.ts`（260 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 18 | TS规范 | P2 | `toIso` 缺少显式返回类型标注 | 标注为 `(d: Date) => string` |
| 23 | TS规范 | P2 | `clampPageSize` 缺少显式返回类型标注 | 标注为 `(size?: number) => number` |
| 28 | TS规范 | P2 | `clampPage` 缺少显式返回类型标注 | 标注为 `(page?: number) => number` |
| 40, 95, 158 | TS规范 | P2 | `const conditions = []` 隐式 `any[]` | 标注 drizzle 条件类型 |
| 75 | TS规范 | P2 | `r.status as "success" \| "failure"` 使用 as 断言 | 使用类型守卫 |
| 122 | TS规范 | P2 | `r.action as "signin" \| "signout" \| "signup"` 使用 as 断言 | 使用类型守卫 |
| 123 | TS规范 | P2 | `r.status as "success" \| "failure"` 使用 as 断言 | 使用类型守卫 |
| 186 | TS规范 | P2 | `r.action as "create" \| "update" \| "delete"` 使用 as 断言 | 使用类型守卫 |
| 50-86, 104-137, 168-202 | 安全/质量 | P2 | `getAuditLogs`/`getLoginLogs`/`getDataChangeLogs` 使用 try-catch 吞错误返回空数组 | 至少 `console.error` 记录错误 |
| 235-240 | 逻辑错误 | P1 | `getAuditLogsForExport` 注释标注 "no pagination cap"，但实际调用 `getAuditLogs({ page: 1, pageSize: 100 })`，最多只返回 100 条，导出数据不完整 | 实现真正的无分页查询，或循环分页拉取全部数据 |
| 245-250 | 逻辑错误 | P1 | `getLoginLogsForExport` 同上问题，最多 100 条 | 同上 |
| 255-260 | 逻辑错误 | P1 | `getDataChangeLogsForExport` 同上问题，最多 100 条 | 同上 |

#### `src/modules/audit/types.ts`（91 行）

合规，无违规问题。

---

### 3.16 elective（选修课模块）

#### `src/modules/elective/data-access.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 117 | TS规范 | P1 | `params.status as ElectiveCourseStatus` 使用 `as` 断言 | 直接使用 `params.status`，无需断言 |
| 78 | TS规范 | P2 | `buildCourseSelect = () =>` 箭头函数缺少显式返回类型标注 | 添加返回类型标注 |
| 136-138, 150-152 | 代码质量 | P2 | `catch { return [] / null }` 静默吞掉异常 | 至少 `console.error` 记录错误 |

#### `src/modules/elective/data-access-selections.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 102 | TS规范 | P1 | `r.status as CourseSelectionStatus` 使用 `as` 断言 | 应通过类型守卫或直接赋值 |
| 114 | TS规范 | P1 | `r.courseStatus as ElectiveCourseStatus \| null` 同上 | 同上 |
| 59, 117 | TS规范 | P2 | `buildCourseSelect` 和 `selectionDetailSelect` 箭头函数缺少显式返回类型 | 添加返回类型标注 |
| 7-14 | 架构违规 | P1 | 直接导入并查询 `classes`、`classEnrollments` 表 | 通过 `@/modules/classes/data-access` 暴露的函数获取 |
| 全文件 | 性能 | P2 | 均未用 `React.cache()` 包裹 | 对读函数用 `cache()` 包裹 |

#### `src/modules/elective/data-access-operations.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 18-34 | 性能 | P2 | `runLottery` 中先查 course 再查 selections，两次独立查询串行 `await` | 用 `Promise.all` 并行 |
| 46-71 | 性能 | P1 | `for` 循环内逐条 `await db.update()`，N 名学生产生 N 次 DB 往返 | 拆分为两组 ID，用 `inArray` 执行 2 次批量 UPDATE |
| 46-76 | 安全/数据完整性 | P1 | `runLottery` 多步更新未包裹事务 | 使用 `db.transaction()` 包裹整个摇号流程 |
| 86-112 | 性能 | P2 | `selectCourse` 中查 course 和查 existing selection 两次独立查询串行 | 用 `Promise.all` 并行 |
| 158-182 | 性能 | P2 | `dropCourse` 中查 existing 和查 course 串行 | 并行化独立查询 |

#### `src/modules/elective/schema.ts`（20-24 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 20-24 | TS规范 | P2 | `emptyToNull` 和 `optionalStringToNull` 箭头函数缺少显式返回类型 | 添加返回类型 |

#### `src/modules/elective/types.ts`

未发现违规问题。

#### `src/modules/elective/actions.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 278-294 | Server Action规范 | P2 | `getStudentSelectionsAction(studentId: string)` 直接接收参数，未经 Zod 校验 | 对 `studentId` 用 Zod 校验 |

---

### 3.17 proctoring（监考模块）

#### `src/modules/proctoring/actions.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 11-13, 79-84 | 架构违规 | **P0** | actions.ts（编排层）直接导入 `db`、`examSubmissions` 并执行 DB 查询 | 将 submission 归属校验逻辑移至 `data-access.ts` |
| 3 | TS规范 | P1 | `import { ActionState }` 应为 `import type { ActionState }` | 改为 `import type { ActionState }` |
| 39 | TS规范 | P1 | `as z.ZodType<ProctoringEventType>` 使用 `as` 断言 | 直接使用 `z.enum([...])` 推导 |
| 63 | Server Action规范 | P1 | `recordProctoringEventAction` 使用 `requireAuth()` 而非 `requirePermission()` | 使用 `requirePermission(Permissions.EXAM_SUBMIT)` |
| 全文件 | Server Action规范 | P2 | `recordProctoringEventAction` 未调用 `revalidatePath` | 添加 `revalidatePath` 刷新监考面板相关路径 |

#### `src/modules/proctoring/data-access.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 127 | TS规范 | P1 | `row.event.eventType as ProctoringEventType` 使用 `as` 断言 | drizzle enum 列类型已是字面量联合，应直接匹配 |
| 152 | TS规范 | P1 | `row.eventType as ProctoringEventType` 同上 | 同上 |
| 208 | TS规范 | P1 | `stat.eventType as ProctoringEventType` 同上 | 同上 |
| 290 | TS规范 | P1 | `row.eventType as ProctoringEventType` 同上 | 同上 |
| 313 | TS规范 | P1 | `(row.submission.status ?? null) as StudentProctoringStatus["submissionStatus"]` 使用 `as` 断言 | 用类型守卫函数校验 status 值 |
| 378 | TS规范 | P1 | `row.event.eventType as ProctoringEventType` 同上 | 同上 |
| 5-9 | 架构违规 | P1 | 直接导入 `exams`、`examSubmissions` 表 | 通过 `@/modules/exams/data-access` 暴露的函数获取 |
| 188-193 | 性能 | P2 | `getExamProctoringSummary` 中对 `submissions` 数组调用两次 `.filter()` | 用单次 `for` 循环同时统计两个计数 |
| 178-223 | 性能 | P2 | `getExamProctoringSummary` 中 submissions 查询、eventStats 查询、studentEventCounts 查询三者相互独立，却串行 `await` | 用 `Promise.all` 并行执行 |

#### `src/modules/proctoring/types.ts`

未发现违规问题。

---

### 3.18 diagnostic（诊断模块）

#### `src/modules/diagnostic/actions.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 26-33, 54-61, 82-85, 105-108 | Server Action规范 | P1 | `generateStudentReportAction`、`generateClassReportAction`、`publishReportAction`、`deleteReportAction` 均使用手动 `typeof` + `length` 校验，未使用 Zod schema | 新建 `schema.ts`，定义 Zod schema |
| 121-133, 136-148 | Server Action规范 | P2 | `getDiagnosticReportsAction(params)` 和 `getDiagnosticReportByIdAction(id)` 直接接收参数，未经 Zod 校验 | 对入参用 Zod 校验 |
| 123, 138 | TS规范 | P2 | `Promise<ActionState<Awaited<ReturnType<typeof getDiagnosticReports>>>>` 类型表达式过于复杂 | 在 types.ts 中定义具名返回类型并导入 |

#### `src/modules/diagnostic/data-access.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 9, 13 | 架构违规 | P1 | 直接导入 `examSubmissions`、`submissionAnswers`、`questionsToKnowledgePoints` 表 | 通过对应模块的 data-access 函数获取数据 |
| 116 | 性能 | P2 | `answers.find((a) => a.questionId === link.questionId)` 在 `for` 循环内调用，O(n×m) 复杂度 | 先用 `new Map(answers.map(a => [a.questionId, a]))` 构建 Map |
| 125-146 | 性能 | P2 | `for` 循环内逐条 `await db.insert().onDuplicateKeyUpdate()`，N 个知识点产生 N 次 DB 往返 | 用 `Promise.all` 并行化，或构建批量 upsert |
| 80-81 | 性能 | P2 | `allMastery.filter(m => m.masteryLevel >= 80)` 和 `allMastery.filter(m => m.masteryLevel < 60)` 两次遍历同一数组 | 合并为单次循环 |
| 全文件 | 性能 | P2 | 所有读函数均未用 `React.cache()` 包裹 | 用 `cache()` 包裹读函数 |

#### `src/modules/diagnostic/data-access-reports.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 29-31 | TS规范 | P1 | `(r.strengths as string[] \| null)`、`(r.weaknesses as string[] \| null)`、`(r.recommendations as string[] \| null)` 三处 `as` 断言 | 编写类型守卫 `isStringArray(v: unknown): v is string[]` |
| 59, 103 | 性能/规范 | P2 | `const { createId } = await import("@paralleldrive/cuid2")` 在函数内部动态导入 | 改为顶层静态导入 |
| 201-202 | 代码质量 | P2 | `void round2` — 为抑制未使用警告而保留无用函数 | 直接删除 `round2` 函数定义及 `void round2` |
| 172-180 | 性能 | P2 | `getDiagnosticReportById` 中先查 report，再单独查 generator 名称，两次串行查询 | 将 generator 查询合并到主查询的 JOIN 中 |
| 全文件 | 性能 | P2 | `getDiagnosticReports`、`getDiagnosticReportById` 未用 `React.cache()` 包裹 | 用 `cache()` 包裹 |
| 107 | 代码质量 | P2 | `studentId: generatedBy` 班级报告将生成者 ID 存入 studentId 字段 | 考虑将 studentId 改为可空，或使用单独的 classId 字段 |

#### `src/modules/diagnostic/types.ts`

未发现违规问题。

---

### 3.19 dashboard（仪表盘模块）

#### `src/modules/dashboard/data-access.ts` & `types.ts`

**无违规问题**。正确使用 `Promise.all` 并行获取多模块数据，正确使用 `cache()`，正确通过各模块 data-access 通信。是除 textbooks 外的另一个标杆模块。

---

### 3.20 files（文件模块）

#### `src/modules/files/data-access.ts`

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 190 | TS规范 | P1 | `const conditions = []` 缺少类型标注，TypeScript 推断为 `never[]` 或 `any[]` | 改为 `const conditions: SQL[] = []` |
| 204 | TS规范 | P1 | `or(...)!` 使用非空断言 | 显式处理 undefined |
| 160-170 | 性能 | P2 | `deleteFileAttachments` 回退逻辑中 `for` 循环内逐条 `await db.delete()` | 用 `Promise.allSettled` 并行删除 |
| 53-55, 70-72, 95-97, 114-116, 131-133, 143-145, 219-221, 248-250, 264-266 | 代码质量 | P2 | 大量 `catch { return null / [] / false }` 静默吞掉异常，共 9 处 | 至少 `console.error` 记录错误信息 |
| 全文件 | 性能 | P2 | 所有读函数均未用 `React.cache()` 包裹 | 用 `cache()` 包裹读函数 |

#### `src/modules/files/types.ts`

未发现违规问题。

---

### 3.21 announcements（公告模块）

#### `src/modules/announcements/data-access.ts`（186 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 46-47 | TS规范 | P1 | `toIso(row.createdAt) as string` — `as` 断言掩盖潜在 null | 让 `toIso` 对非空入参返回 `string`，拆分为两个函数 |
| 59, 62 | TS规范 | P2 | `params.status as AnnouncementStatus` — 冗余 `as` 断言 | 直接使用收窄后的变量 |
| 88-90, 118-120 | 性能/可靠性 | P2 | `catch {}` 静默吞掉所有异常 | 至少记录错误日志 |
| 25-26 | 命名/DRY | P2 | `mapRow` 参数中内联定义类型，与 types.ts 重复 | 导入并复用 types.ts 中的类型 |

#### `src/modules/announcements/actions.ts`（231 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 217-231 | Server Action规范 | P2 | `getAnnouncementsAction` 使用 `requireAuth()` 而非 `requirePermission(Permissions.ANNOUNCEMENT_READ)` | 改为 `requirePermission(Permissions.ANNOUNCEMENT_READ)` |
| 73-79, 137-143, 159-165, 185-191, 208-214, 224-230 | 性能/可维护性 | P2 | 6 个 Action 的 try/catch 错误处理块完全相同 | 提取共享错误处理函数 `handleActionError(e)` |

#### `src/modules/announcements/schema.ts`（45 行）& `types.ts`（50 行）

符合规范。

---

### 3.22 settings（设置模块）

#### `src/modules/settings/actions.ts`（205 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 8, 64-76, 95-100, 104-113, 123-139, 154-169 | 架构违规 | P1 | 模块无 `data-access.ts` 文件，actions.ts 直接 `import { db }` 并执行 DB 查询 | 新建 `data-access.ts`，将所有 DB 操作下沉 |
| 62-77 | Server Action规范 | P2 | `getAiProviderSummaries` 返回 `Promise<AiProviderSummary[]>` 而非 `ActionState<T>` | 移至 data-access.ts 或包装为 ActionState |
| 38-46 | 架构违规 | P2 | `AiProviderSummary` 类型定义在 actions.ts 中 | 新建 `types.ts`，将类型移入 |
| 48-51 | TS规范 | P2 | `ensureUser` 是 async 箭头函数，未显式标注返回类型 | 添加 `: Promise<{ id: string }>` |
| 53-60 | TS规范 | P2 | `normalizeBaseUrl` 未显式标注返回类型 | 添加 `: string \| null` |
| 95-113 | 性能 | P2 | `upsertAiProviderAction` 的更新分支中，`count()` 查询与 `select` 查询是两个独立查询，串行执行 | 用 `Promise.all` 并行化 |
| 120, 152 | 命名规范 | P2 | `nextIsDefault`、`makeDefault` 布尔变量前缀不规范 | 改为 `isNextDefault`、`shouldMakeDefault` |

#### `src/modules/settings/actions-password.ts`（113 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 7, 58-62, 78-81, 83-87, 89-105 | 架构违规 | P1 | actions-password.ts 直接 `import { db }` 并执行 DB 操作，无 data-access.ts | 将 DB 操作下沉至 `data-access.ts` |
| 39-51 | Server Action规范 | P1 | 输入通过 `String(formData.get(...))` 手动读取，未使用 Zod schema 验证 | 定义 Zod schema 并 safeParse |
| 30 | Server Action规范 | P2 | `changePasswordAction` 使用 `requireAuth()` 而非 `requirePermission()` | 新增 `PASSWORD_SELF_CHANGE` 权限点并赋给所有角色 |
| 58-87 | 性能 | P2 | 查询 user 和查询 passwordSecurity 是两个独立表的查询，当前串行执行 | 重构为 `Promise.all` 并行获取 |
| 14-18 | TS规范 | P2 | `normalizeBcryptHash` 未显式标注返回类型 | 添加 `: string` |

---

### 3.23 layout（布局模块）

#### `src/modules/layout/config/navigation.ts`（317 行）

| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|---------|---------|---------|---------|---------|
| 30-31 | TS规范 | P2 | `NavItem.permission` 和子项的 `permission` 均为 `string` 类型，但项目已有 `Permission` 类型 | 导入 Permission 类型并使用 |
| 34 | 架构违规 | P2 | `export type Role = "admin" \| "teacher" \| "student" \| "parent"` 定义在 config 文件中 | 考虑将 Role 类型提升至 `@/shared/types` |

---

## 四、跨模块共性问题

### 4.1 架构违规（P0/P1，最高优先级）

#### 4.1.1 跨模块直接查询 DB 表（最普遍问题）

| 调用方模块 | 被查模块表 | 严重程度 |
|-----------|-----------|---------|
| exams | questions（直写）、classes、subjects、grades | P0/P1 |
| homework | exams（5 处）、classes、classEnrollments、subjects、users | P1 |
| questions | knowledgePoints、chapters、textbooks | P1 |
| grades | classes、classEnrollments、subjects、users | P1 |
| classes | homeworkAssignments、homeworkSubmissions、exams、classSchedule | P0 |
| school | （actions 层直接 DB 操作） | P0 |
| scheduling | users（actions 层） | P0 |
| messaging | classEnrollments、classes | P1 |
| notifications | classes、classEnrollments、messaging（循环） | P0/P1 |
| parent | users、grades、classEnrollments、classes | P1 |
| proctoring | exams、examSubmissions | P0/P1 |
| diagnostic | examSubmissions、submissionAnswers、questionsToKnowledgePoints | P1 |
| elective | classes、classEnrollments | P1 |

**统一修复方案**：在各模块 data-access 暴露跨模块查询接口，消除直查。

#### 4.1.2 messaging↔notifications 循环依赖

- `messaging/data-access.ts` 写 `messageNotifications` 表
- `notifications/data-access.ts` 反向 import messaging 的 `notification-preferences` 和 `types`
- `notifications/channels/in-app-channel.ts` 动态 import messaging 的 `data-access`

**修复方案**：将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块。

#### 4.1.3 actions 层直接操作 DB

| 模块 | 文件 | 问题 |
|------|------|------|
| school | actions.ts | 所有 mutation 直接操作 DB |
| scheduling | actions.ts | 直接查询 users 表 |
| proctoring | actions.ts | 直接查询 examSubmissions 表 |
| classes | actions.ts | 直接查询 grades、classes 表做权限校验 |
| settings | actions.ts、actions-password.ts | 无 data-access.ts，直接 DB 操作 |
| users | actions.ts | updateUserProfile 直接 db.update |

**统一修复方案**：DB 操作必须下沉到 data-access.ts。

### 4.2 TypeScript 规范违规（P1）

#### 4.2.1 `as` 断言滥用（非从 unknown 转换）

| 模块 | 文件 | 行号 |
|------|------|------|
| exams | data-access.ts | 76, 160, 361, 108, 172 |
| exams | actions.ts | 270, 349-351 |
| homework | data-access.ts | 39, 124, 226, 314, 392, 467, 645 |
| homework | stats-service.ts | 220 |
| textbooks | actions.ts | 54-58, 92-98, 154, 219-221, 259-261（14 处） |
| textbooks | data-access.ts | 51 |
| classes | data-access.ts | 240, 266, 561 |
| classes | data-access-admin.ts | 135, 259, 349 |
| classes | data-access-schedule.ts | 54, 93 |
| classes | data-access-stats.ts | 250, 261, 512, 523 |
| classes | actions.ts | 538, 582 |
| course-plans | data-access.ts | 149 |
| messaging | data-access.ts | 80, 74, 85 |
| notifications | channels/in-app-channel.ts | 54（非法） |
| notifications | channels/sms-channel.ts | 32 |
| audit | data-access.ts | 75, 122, 123, 186 |
| elective | data-access.ts | 117 |
| elective | data-access-selections.ts | 102, 114 |
| proctoring | data-access.ts | 127, 152, 208, 290, 313, 378 |
| proctoring | actions.ts | 39 |
| diagnostic | data-access-reports.ts | 29-31 |
| parent | data-access.ts | 30 |
| announcements | data-access.ts | 46-47, 59, 62 |
| attendance | data-access.ts | 195（Record<string, unknown>） |

**统一修复方案**：用类型守卫函数或 Zod 校验替换 `as` 断言。

#### 4.2.2 非空断言 `!`

| 模块 | 文件 | 行号 |
|------|------|------|
| exams | ai-pipeline.ts | 464, 657, 665, 666, 671 |
| homework | export.ts | 154 |
| textbooks | data-access.ts | 42, 71 |
| classes | data-access.ts | 287, 567 |
| classes | data-access-admin.ts | 369 |
| classes | data-access-students.ts | 102 |
| scheduling | data-access.ts | 140, 221, 222 |
| scheduling | actions.ts | 115 |
| scheduling | auto-scheduler.ts | 144, 145 |
| messaging | data-access.ts | 97, 117, 163 |
| files | data-access.ts | 204 |

**统一修复方案**：用显式判空处理替换非空断言。

#### 4.2.3 函数返回值未显式标注

| 模块 | 文件 | 函数 |
|------|------|------|
| classes | data-access.ts | isDuplicateInvitationCodeError, generateInvitationCode, normalizeSortText, parseFirstInt, compareGradeLabel, compareClassLike |
| classes | data-access-admin.ts | getManagedGrades |
| scheduling | data-access.ts | getAdminClassesForScheduling, getTeachersForScheduling, getClassroomsForScheduling, getClassSubjectsForScheduling |
| users | data-access.ts | normalizeRoleName, resolvePrimaryRole |
| users | user-service.ts | normalizeBcryptHash |
| messaging | data-access.ts | mapMessage, mapNotification |
| messaging | notification-preferences.ts | toIso, mapRow |
| notifications | dispatcher.ts | getSenders, selectChannels |
| notifications | channels/email-channel.ts | getEmailConfig, getTypeColor, buildHtmlContent, escapeHtml |
| notifications | channels/sms-channel.ts | getSmsConfig, buildTemplateParams |
| notifications | channels/wechat-channel.ts | getWechatConfig, buildTemplateData |
| audit | data-access.ts | toIso, clampPageSize, clampPage |
| parent | data-access.ts | getChildBasicInfo |
| announcements | — | — |
| settings | actions.ts | ensureUser, normalizeBaseUrl |
| settings | actions-password.ts | normalizeBcryptHash |
| elective | data-access.ts | buildCourseSelect |
| elective | data-access-selections.ts | buildCourseSelect, selectionDetailSelect |
| elective | schema.ts | emptyToNull, optionalStringToNull |
| questions | actions.ts | getQuestionsAction |

**统一修复方案**：所有函数（特别是箭头函数）必须显式标注返回类型。

#### 4.2.4 `import type` 未使用

| 模块 | 文件 | 行号 |
|------|------|------|
| exams | actions.ts | 4 |
| questions | actions.ts | 7 |
| textbooks | actions.ts | 18 |
| proctoring | actions.ts | 3 |

**统一修复方案**：仅用于类型的导入必须使用 `import type`。

### 4.3 Server Action 规范违规

#### 4.3.1 `requireAuth()` 替代 `requirePermission()`

| 模块 | 文件 | Action |
|------|------|--------|
| users | actions.ts | updateUserProfile |
| messaging | actions.ts | getNotificationsAction, markNotificationAsReadAction, markAllNotificationsAsReadAction, getNotificationPreferencesAction, updateNotificationPreferencesAction |
| proctoring | actions.ts | recordProctoringEventAction |
| announcements | actions.ts | getAnnouncementsAction |
| settings | actions-password.ts | changePasswordAction |

**统一修复方案**：改为 `requirePermission(Permissions.XXX)`。

#### 4.3.2 缺少 Zod 验证

| 模块 | 文件 | 问题 |
|------|------|------|
| textbooks | actions.ts | 完全无 schema.ts，所有 Action 无 Zod 验证 |
| classes | actions.ts | 完全无 schema.ts，使用手动 typeof 检查 |
| diagnostic | actions.ts | 4 个 Action 使用手动 typeof 校验 |
| settings | actions-password.ts | 输入手动读取，无 Zod |
| messaging | actions.ts | 多个 Action 参数未 Zod 校验 |
| notifications | actions.ts | 多个 Action 参数未 Zod 校验 |
| audit | actions.ts | 多个 Action 参数未 Zod 校验 |
| users | actions.ts | updateUserProfile 无 Zod |
| elective | actions.ts | getStudentSelectionsAction 无 Zod |

**统一修复方案**：新建/补充 schema.ts，所有 Action 输入用 Zod 验证。

#### 4.3.3 缺少 `revalidatePath`

| 模块 | 文件 | Action |
|------|------|--------|
| course-plans | actions.ts | deleteCoursePlanItemAction, toggleCoursePlanItemCompletedAction |
| proctoring | actions.ts | recordProctoringEventAction |

#### 4.3.4 未返回 `ActionState<T>`

| 模块 | 文件 | Action |
|------|------|--------|
| questions | actions.ts | getQuestionsAction, getKnowledgePointOptionsAction |
| users | actions.ts | updateUserProfile（返回 Promise<void>） |
| settings | actions.ts | getAiProviderSummaries（返回原始数组） |

### 4.4 重复代码

| 重复内容 | 出现位置 | 修复方案 |
|---------|---------|---------|
| `normalizeRoleName` | users/data-access.ts、shared/lib/role-utils.ts | 复用 shared |
| `normalizeBcryptHash` | users/user-service.ts、settings/actions-password.ts、shared/lib/bcrypt-utils.ts | 复用 shared |
| `toNumber`/`normalize`/`buildScopeClassFilter` | grades/data-access.ts、data-access-analytics.ts、data-access-ranking.ts | 提取到 grades/utils.ts |
| `serializeDate` | scheduling/data-access.ts、attendance/data-access.ts、attendance/data-access-stats.ts | 提取到 shared |
| `toIso` | announcements/data-access.ts、messaging/data-access.ts、messaging/notification-preferences.ts、audit/data-access.ts | 提取到 shared |
| try/catch 错误处理块 | announcements/actions.ts（6 处） | 提取 `handleActionError(e)` |
| Excel 导出逻辑 | audit/actions.ts（3 处） | 抽取 `buildExcelSheet` |

### 4.5 错误吞没（静默 catch）

| 模块 | 文件 | 处数 |
|------|------|------|
| school | data-access.ts | 6 |
| classes | data-access-admin.ts、data-access-students.ts | 多处 |
| course-plans | data-access.ts | 2 |
| announcements | data-access.ts | 2 |
| files | data-access.ts | 9 |
| audit | data-access.ts | 3 |
| elective | data-access.ts | 2 |

**统一修复方案**：至少 `console.error` 记录错误，或向上抛出由调用方处理。

### 4.6 不可达代码

| 模块 | 文件 | 行号 |
|------|------|------|
| classes | data-access.ts | 297-299（throw 后 return id） |
| classes | data-access-admin.ts | 380-382（throw 后 return id） |
| questions | actions.ts | 158-163, 170-175（catch 块两分支都 throw e） |

---

## 五、性能优化专项（react-best-practices）

依据 Vercel React Best Practices 规则集，识别以下性能优化机会：

### 5.1 CRITICAL：消除瀑布流（async-*）

#### 5.1.1 `async-parallel`：独立操作用 Promise.all()

| 模块 | 文件 | 行号 | 问题 | 修复 |
|------|------|------|------|------|
| classes | data-access.ts | 120-127 | `getAccessibleClassIdsForTeacher` 两个独立 DB 查询串行 | `Promise.all` |
| settings | actions.ts | 95-113 | `upsertAiProviderAction` count 与 select 串行 | `Promise.all` |
| settings | actions-password.ts | 58-87 | user 与 passwordSecurity 查询串行 | `Promise.all` |
| elective | data-access-operations.ts | 18-34 | `runLottery` course 与 selections 串行 | `Promise.all` |
| elective | data-access-operations.ts | 86-112 | `selectCourse` course 与 existing 串行 | `Promise.all` |
| elective | data-access-operations.ts | 158-182 | `dropCourse` existing 与 course 串行 | `Promise.all` |
| proctoring | data-access.ts | 178-223 | `getExamProctoringSummary` 三个独立查询串行 | `Promise.all` |
| diagnostic | data-access-reports.ts | 172-180 | `getDiagnosticReportById` report 与 generator 串行 | 合并 JOIN 或 `Promise.all` |
| parent | data-access.ts | 58-117 | `getChildBasicInfo` 4 个串行查询 | JOIN 或 `Promise.all` |

#### 5.1.2 `async-defer-await`：将 await 移到实际使用的分支

无明显违规，多数文件已正确处理。

### 5.2 HIGH：服务端性能（server-*）

#### 5.2.1 `server-cache-react`：用 React.cache() 做请求内去重

| 模块 | 文件 | 缺失 cache() 的函数 |
|------|------|---------------------|
| grades | data-access.ts | getGradeRecords, getGradeRecordById, getClassGradeStats |
| grades | data-access-analytics.ts | getClassComparison, getGradeTrends |
| grades | data-access-ranking.ts | getClassRanking, getStudentRanking |
| elective | data-access-selections.ts | getCourseSelections, getStudentSelections, getStudentGradeId, getAvailableCoursesForStudent |
| diagnostic | data-access.ts | getStudentMastery, getStudentMasterySummary, getClassMasterySummary, getKnowledgePointStats |
| diagnostic | data-access-reports.ts | getDiagnosticReports, getDiagnosticReportById |
| files | data-access.ts | getFileAttachment, getFileAttachmentsByTarget, getFileAttachmentsByUploader, getAllFileAttachments, getFileAttachmentsWithFilters, getFileStats, getFileAttachmentsByIds |
| proctoring | data-access.ts | 多个查询函数 |

**标杆**：exams、homework、questions、textbooks、dashboard、announcements 已正确使用 `cache()` ✓。

#### 5.2.2 `server-after-nonblocking`：用 after() 做非阻塞操作

| 模块 | 文件 | 行号 | 问题 |
|------|------|------|------|
| school | actions.ts | 188, 219, 235 | `await logAudit(...)` 阻塞响应 |

**修复**：使用 Next.js `after()` 将审计日志改为非阻塞。

#### 5.2.3 `server-serialization`：最小化传给客户端组件的数据

需结合前端组件审查，本次后端核查未深入。

### 5.3 MEDIUM：JavaScript 性能（js-*）

#### 5.3.1 `js-set-map-lookups`：用 Set/Map 做 O(1) 查找

| 模块 | 文件 | 行号 | 问题 |
|------|------|------|------|
| grades | data-access-analytics.ts | 133-136 | `scope.classIds.includes(c.id)` — O(n) 查找 |
| diagnostic | data-access.ts | 116 | `answers.find(...)` 在 for 循环内 — O(n×m) |

**修复**：将数组转为 Set/Map 后用 `.has()` / `.get()`。

#### 5.3.2 `js-combine-iterations`：合并多次 filter/map 为一次循环

| 模块 | 文件 | 行号 | 问题 |
|------|------|------|------|
| grades | data-access.ts | 248-252 | 两次 if 判断遍历同一数组 |
| grades | data-access-analytics.ts | 180-181, 238-239 | 两次 filter 遍历同一数组 |
| grades | data-access-ranking.ts | 100, 102 | findIndex 和 find 查找同一元素 |
| diagnostic | data-access.ts | 80-81 | 两次 filter 遍历同一数组 |
| proctoring | data-access.ts | 188-193 | 两次 filter 统计 submissions |

#### 5.3.3 `js-early-exit`：函数早返回

多数文件已正确处理。

#### 5.3.4 动态 import 改静态 import

| 模块 | 文件 | 行号 | 问题 |
|------|------|------|------|
| grades | data-access.ts | 148, 172 | `await import("@paralleldrive/cuid2")` 函数内动态导入 |
| diagnostic | data-access-reports.ts | 59, 103 | 同上 |

**修复**：改为文件顶部静态 `import { createId } from "@paralleldrive/cuid2"`。

### 5.4 性能问题汇总（按影响排序）

| 优先级 | 问题 | 影响范围 |
|--------|------|---------|
| **CRITICAL** | grades N+1 查询（getClassComparison） | 大班级成绩对比性能差 |
| **HIGH** | 循环内串行 await db.update/insert（homework/data-access-write.ts、elective/data-access-operations.ts、diagnostic/data-access.ts、course-plans/data-access.ts） | 批量操作慢 |
| **HIGH** | 大量读函数未用 React.cache()（grades、elective、diagnostic、files、proctoring） | 请求内重复查询 |
| **MEDIUM** | 独立查询未并行化（9 处） | 响应时间累加 |
| **MEDIUM** | 动态 import（3 处） | 每次调用有开销 |
| **LOW** | 重复 filter 遍历（5 处） | 微小性能损失 |
| **LOW** | O(n) 查找代替 O(1)（2 处） | 微小性能损失 |

---

## 六、优先修复路线图

### 第一阶段：P0 严重问题（立即修复）

1. **exams/data-access.ts**：`persistAiGeneratedExamDraft` 改为调用 questions data-access
2. **exams/data-access.ts**：`getExams`/`getExamById` 改为通过 classes data-access 获取 gradeId
3. **questions/schema.ts**：`z.any()` → `z.unknown()`
4. **questions/actions.ts**：`getQuestionsAction` 和 `getKnowledgePointOptionsAction` 包装为 ActionState
5. **textbooks/actions.ts**：新建 schema.ts，添加 Zod 验证；清理 14 处 `as` 断言
6. **grades/data-access-analytics.ts**：`getClassComparison` N+1 查询重构为 `inArray` 批量查询
7. **classes/data-access-stats.ts、data-access-students.ts**：移除对 homework/exams 表的直接查询
8. **school/actions.ts**：所有 DB 操作下沉到 data-access.ts
9. **proctoring/actions.ts**：DB 查询下沉到 data-access.ts
10. **messaging↔notifications**：将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块
11. **notifications/channels/in-app-channel.ts:54**：修复非法 as 断言
12. **users/user-service.ts:13**：移除硬编码弱密码 "123456"
13. **users/actions.ts**：`updateUserProfile` 整改（权限+下沉+返回类型）
14. **scheduling/data-access.ts**：4 个函数补充返回类型标注

### 第二阶段：P1 重要问题（尽快修复）

1. **跨模块直查 DB**：在各模块 data-access 暴露跨模块查询接口，消除直查（涉及 exams、homework、questions、grades、classes、messaging、notifications、parent、proctoring、diagnostic、elective）
2. **`as` 断言清理**：全局替换为类型守卫（涉及 20+ 文件，60+ 处）
3. **`requireAuth` → `requirePermission`**：users、messaging、proctoring、announcements、settings 共 8 处
4. **`import type` 修正**：exams、questions、textbooks、proctoring 共 4 处
5. **动态 import 改静态**：grades、diagnostic 共 3 处
6. **grades/data-access.ts:249**：除零 bug 守卫
7. **audit/data-access.ts**：导出函数数据截断问题（3 处）
8. **elective/data-access-operations.ts**：`runLottery` 加事务包裹 + 批量更新
9. **diagnostic/actions.ts**：新建 schema.ts，用 Zod 替代手动校验
10. **classes 模块**：新建 schema.ts，用 Zod 替换 actions.ts 中的手动校验
11. **course-plans/actions.ts**：补齐 revalidatePath 调用（2 处）
12. **homework/data-access-write.ts:305-311**：`gradeHomeworkAnswers` 加事务包裹
13. **users/user-service.ts**：`batchImportUsers` 加事务包裹
14. **不可达代码清理**：classes/data-access.ts:297-299、classes/data-access-admin.ts:380-382

### 第三阶段：P2 优化（迭代修复）

1. **React.cache() 包装**：grades、elective、diagnostic、files、proctoring 读函数添加 cache()
2. **Promise.all 并行化**：9 处独立查询串行执行
3. **`after()` 非阻塞审计日志**：school/actions.ts（3 处）
4. **错误吞没清理**：school、classes、course-plans、announcements、files、audit、elective 共 30+ 处静默 catch
5. **非空断言 `!` 清理**：20+ 处替换为显式判空
6. **函数返回类型补齐**：30+ 个箭头函数
7. **重复代码提取**：normalizeRoleName、normalizeBcryptHash、toIso、serializeDate、toNumber/normalize、handleActionError、buildExcelSheet
8. **合并 filter 遍历**：5 处
9. **Set/Map 查找优化**：2 处
10. **架构文档同步**：exams/actions.ts 行数、settings 导出函数列表、P2-11 状态、announcements 依赖关系
11. **exams/ai-pipeline.ts 拆分**：912 行按职责拆分为 4 个文件
12. **layout/navigation.ts**：permission 字段改用 Permission 类型
13. **notifications/external-sdk.d.ts**：安装实际 SDK 后用其自带类型覆盖 any

---

## 附录：核查方法说明

### 核查流程

1. **架构图优先**：先阅读 `docs/architecture/004_architecture_impact_map.md`，理解模块依赖关系和已知问题
2. **并行核查**：启动 5 个并行搜索代理，分别核查核心教学、教学管理、用户沟通、扩展功能、其他模块
3. **逐文件审查**：每个代理读取所有后端 `.ts` 文件，对照项目规范逐条核查
4. **性能规则应用**：应用 Vercel React Best Practices 的 65 条规则，识别性能优化机会
5. **结果汇总**：合并 5 个代理的核查结果，按严重程度和模块分类整理

### 核查依据

- `.trae/rules/project_rules.md`：项目规则（架构分层、TS规范、命名、组件、Server Action、Tailwind、安全、提交）
- `docs/standards/coding-standards.md`：编码规范（详细规范）
- `docs/architecture/004_architecture_impact_map.md`：架构影响地图（模块清单、依赖关系、已知问题）
- `docs/architecture/005_architecture_data.json`：AI 友好格式的结构化数据
- Vercel React Best Practices：65 条性能优化规则（8 个类别）

### 未核查范围

- `src/modules/*/components/` 下的前端组件文件
- `src/modules/*/hooks/` 下的 Hook 文件
- `src/shared/` 下的基础设施文件
- `src/app/` 下的路由层文件
- 测试文件（`*.test.ts`）

如需核查上述范围，请另行指派任务。
