Files
NextEdu/bugs/back_bug.md
SpecialX 49291fcc31 refactor: fix all P0/P1/P2 bugs and architecture issues
Bug fixes (from bugs/ directory):

- Fix cross-module DB queries in 9 modules (homework, grades, parent, diagnostic, elective, proctoring, notifications, scheduling, classes) by routing through data-access functions

- Fix shared/lib <-> auth circular dependency via new session.ts module

- Fix divide-by-zero guard in grades data-access

- Fix audit export data truncation (paginated fetch for full datasets)

- Fix missing transactions in homework grading and elective lottery

- Fix missing revalidatePath in course-plans actions

- Fix frontend permission checks using requirePermission instead of requireAuth

- Fix dashboard role routing using session.user.roles

- Fix student auth pattern (migrate getDemoStudentUser to users module)

- Fix ActionState return type handling in components

Code quality fixes:

- Remove 60+ as type assertions (replace with type guards)

- Remove non-null assertions (use optional chaining or explicit checks)

- Convert dynamic imports to static imports (grades, diagnostic)

- Add React.cache() wrapping for read functions

- Parallelize independent queries with Promise.all

- Add explicit return types to 30+ arrow functions

- Replace any with unknown + type guards

- Fix import type for type-only imports

- Add Zod validation schemas for classes and diagnostic modules

- Extract duplicate code (normalizeRoleName, normalizeBcryptHash, logger IP extraction)

- Add console.error to silent catch blocks

- Fix permission naming consistency (exam:proctor_read -> exam:proctor:read)

Architecture doc sync:

- Update 004_architecture_impact_map.md and 005_architecture_data.json

- Update management-modules-audit.md for P0-7 cross-module fix

Moved deleted proctoring event route to deletes/ folder.
2026-06-19 05:13:34 +08:00

1435 lines
89 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端模块规范核查报告
> 核查日期2026-06-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-1exams 模块直写 questions 表
- **文件**`src/modules/exams/data-access.ts`
- **行号**2, 318
- **问题**`persistAiGeneratedExamDraft` 直接 `db.insert(questions)` 写入 questions 表(属 questions 模块),破坏模块封装
- **修复**:改为调用 `questions/data-access.createQuestionWithRelations()` 或在 questions 模块暴露批量插入接口
### P0-2exams 模块直查 classes 表
- **文件**`src/modules/exams/data-access.ts`
- **行号**2, 72-80, 156-163, 357-364
- **问题**`getExams`/`getExamById`/`getExamsDashboardStats` 直接查询 `classes` 表获取 gradeId
- **修复**:在 classes 模块暴露 `getGradeIdsByClassIds(classIds)` 接口
### P0-3questions/schema.ts 使用 `z.any()`
- **文件**`src/modules/questions/schema.ts`
- **行号**6
- **问题**`content: z.any()` 违反"禁止 any"规则
- **修复**:改为 `z.unknown()`
### P0-4questions/actions.ts 未返回 ActionState
- **文件**`src/modules/questions/actions.ts`
- **行号**154, 166-175
- **问题**`getQuestionsAction``getKnowledgePointOptionsAction` 直接返回原始结果,未包装为 `ActionState<T>`,错误时 throw 而非返回失败状态
- **修复**:改为 `Promise<ActionState<...>>` 返回 `{ success: true, data: result }`
### P0-5textbooks 模块无 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-6grades 模块 N+1 查询
- **文件**`src/modules/grades/data-access-analytics.ts`
- **行号**142-185
- **问题**`getClassComparison``for (const cls of classRows) { ... await db.select(...) }` 循环内串行查询N+1 问题
- **修复**:用 `inArray` 一次性查询所有班级的成绩,再在内存分组
### P0-7classes 模块跨模块直接查询 DB 表
- **文件**`src/modules/classes/data-access-stats.ts``src/modules/classes/data-access-students.ts`
- **行号**data-access-stats.ts: 11-18data-access-students.ts: 10-14
- **问题**:直接导入并查询 `homeworkAssignments``homeworkSubmissions``homeworkAssignmentTargets``homeworkAssignmentQuestions``exams` 等其他模块的表
- **修复**:通过 homework 模块的 data-access 暴露的函数获取数据
### P0-8school 模块 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-9proctoring 模块 actions 层直接查询 DB
- **文件**`src/modules/proctoring/actions.ts`
- **行号**11-13, 79-84
- **问题**actions.ts编排层直接导入 `db``examSubmissions` 并执行 DB 查询,违反三层架构
- **修复**:将 submission 归属校验逻辑移至 `data-access.ts`
### P0-10messaging↔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-203notifications/data-access.ts: 20-21in-app-channel.ts: 50
- **问题**messaging 写 `messageNotifications`notifications 反向依赖 messaging形成循环依赖
- **修复**:将 `messageNotifications``notificationPreferences` 表所有权移交 notifications 模块
### P0-11notifications/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-12users 模块硬编码弱密码
- **文件**`src/modules/users/user-service.ts`
- **行号**13
- **问题**`const DEFAULT_PASSWORD = "123456"` 硬编码弱密码,批量导入用户使用极弱默认密码
- **修复**:改为随机生成密码或要求首次登录强制修改密码,至少使用 `crypto.randomBytes` 生成
### P0-13users/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-14scheduling/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 后传入 Zodparse 异常未捕获 | 用 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` 检查而非 Zodclasses 模块完全没有 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 | exams5 处、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 | usersactions 层) | 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-26114 处) |
| 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 | 195Record<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.ts6 处) | 提取 `handleActionError(e)` |
| Excel 导出逻辑 | audit/actions.ts3 处) | 抽取 `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-299throw 后 return id |
| classes | data-access-admin.ts | 380-382throw 后 return id |
| questions | actions.ts | 158-163, 170-175catch 块两分支都 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 MEDIUMJavaScript 性能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/inserthomework/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** | 动态 import3 处) | 每次调用有开销 |
| **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.ts3 处)
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 Practices65 条性能优化规则8 个类别)
### 未核查范围
- `src/modules/*/components/` 下的前端组件文件
- `src/modules/*/hooks/` 下的 Hook 文件
- `src/shared/` 下的基础设施文件
- `src/app/` 下的路由层文件
- 测试文件(`*.test.ts`
如需核查上述范围,请另行指派任务。