# 后端模块规范核查报告 v2 > 核查日期:2026-06-18 > 核查范围:`src/modules/` 下所有后端 `.ts` 文件 > 核查依据: > - `.trae/rules/project_rules.md` 项目规则 > - `docs/standards/coding-standards.md` 编码规范 > - `docs/architecture/004_architecture_impact_map.md` 架构影响地图 > - Vercel React Best Practices 性能优化规则 > - v1 报告 `bugs/back_bug.md`(对照修复状态) > > 本报告相比 v1 的核心变化: > - 对每个问题标注 **修复状态**(已修复/未修复/部分修复/新问题) > - 汇总 v1→v2 的修复进度 > - 列出 v2 新发现的问题 --- ## 目录 - [一、v1→v2 修复进度总览](#一v1v2-修复进度总览) - [二、v2 当前问题汇总](#二v2-当前问题汇总) - [三、仍需优先修复的问题](#三仍需优先修复的问题) - [四、按模块详细核查](#四按模块详细核查) - [五、v2 新发现问题清单](#五v2-新发现问题清单) - [六、架构文档同步提醒](#六架构文档同步提醒) --- ## 一、v1→v2 修复进度总览 ### 1.1 整体修复率 | 指标 | v1 问题数 | 已修复 | 部分修复 | 未修复 | 修复率 | |------|----------|--------|----------|--------|--------| | 数量 | 129 | 90 | 12 | 27 | 70% 已修复 + 9% 部分修复 | | P0 | 14 | 14 | 0 | 0 | **100%** | | P1 | 60 | 39 | 7 | 14 | 65% + 12% | | P2 | 55 | 37 | 5 | 13 | 67% + 9% | ### 1.2 按模块修复率 | 模块 | v1 问题数 | 已修复 | 部分修复 | 未修复 | 修复率 | |------|----------|--------|----------|--------|--------| | homework | 6 | 6 | 0 | 0 | **100%** | | parent | 3 | 3 | 0 | 0 | **100%** | | proctoring | 9 | 9 | 0 | 0 | **100%** | | settings | 9 | 9 | 0 | 0 | **100%** | | dashboard | 0 | - | - | - | 标杆模块 | | grades | 8 | 7 | 1 | 0 | 88% | | questions | 5 | 4 | 0 | 1 | 80% | | users | 7 | 6 | 0 | 1 | 86% | | exams | 7 | 5 | 1 | 1 | 71% | | messaging | 7 | 5 | 0 | 2 | 71% | | notifications | 7 | 5 | 0 | 2 | 71% | | audit | 5 | 2 | 0 | 3 | 40% | | textbooks | 5 | 2 | 1 | 2 | 40% | | classes | 10 | 7 | 2 | 1 | 70% | | announcements | 6 | 5 | 1 | 0 | 83% | | school | 3 | 2 | 0 | 1 | 67% | | scheduling | 6 | 5 | 1 | 0 | 83% | | attendance | 1 | 1 | 0 | 0 | 100% | | course-plans | 3 | 1 | 1 | 1 | 33% | | elective | 7 | 6 | 1 | 0 | 86% | | diagnostic | 8 | 7 | 0 | 1 | 88% | | files | 5 | 3 | 1 | 1 | 60% | | layout | 2 | 0 | 0 | 2 | 0% | ### 1.3 P0 问题修复情况(全部已修复) | 编号 | v1 P0 问题 | 修复状态 | |------|-----------|---------| | P0-1 | exams/data-access.ts persistAiGeneratedExamDraft 直写 questions 表 | ✅ 已修复:改用 createQuestionWithRelations | | P0-2 | exams/data-access.ts getExams 等直查 classes 表 | ✅ 已修复:改用 getClassGradeIdsByClassIds | | P0-3 | questions/schema.ts z.any() | ✅ 已修复:改为 z.unknown() | | P0-4 | questions/actions.ts 未返回 ActionState | ✅ 已修复:包装为 ActionState | | P0-5 | textbooks 无 Zod 验证 + 14 处 as 断言 | ⚠️ 部分修复:as 断言已清理,但 Zod 验证仍未添加 | | P0-6 | grades N+1 查询 | ✅ 已修复:改为 inArray 批量查询 + Map 分组 | | P0-7 | classes 跨模块直查 homework/exams 表 | ✅ 已修复:改用 homework/data-access-classes | | P0-8 | school actions 层直接 DB 操作 | ✅ 已修复:DB 操作下沉到 data-access | | P0-9 | proctoring actions 层直接 DB 操作 | ✅ 已修复:下沉到 data-access | | P0-10 | messaging↔notifications 循环依赖 | ✅ 已修复:表所有权移交 notifications | | P0-11 | notifications/in-app-channel.ts 非法 as 断言 | ✅ 已修复:新增 mapPayloadTypeToNotificationType | | P0-12 | users 硬编码弱密码 "123456" | ✅ 已修复:改用 randomBytes 生成 | | P0-13 | users/updateUserProfile 绕过权限 | ✅ 已修复:改用 requirePermission + Zod + ActionState | | P0-14 | scheduling 4 个函数缺返回类型 | ✅ 已修复:已添加返回类型标注 | --- ## 二、v2 当前问题汇总 ### 2.1 按严重程度统计(v2 当前状态) | 严重程度 | 未修复 v1 问题 | 部分修复 v1 问题 | v2 新发现问题 | 合计 | |----------|---------------|------------------|--------------|------| | P0 | 0 | 1(textbooks Zod) | 0 | 1 | | P1 | 14 | 7 | 16 | 37 | | P2 | 13 | 5 | 25 | 43 | | **合计** | **27** | **12** | **41** | **80** | ### 2.2 按问题类别统计(v2 当前状态) | 问题类别 | 数量 | 主要分布 | |---------|------|---------| | 架构违规 | 8 | 跨模块直查 DB(exams→school、questions→textbooks、classes→scheduling、messaging→classes、elective→school/users) | | TS 规范 | 35 | as 断言、缺返回类型标注、隐式 any[]、非空断言 | | Server Action 规范 | 6 | textbooks 无 Zod、notifications 无 Zod、school 用 parse 非 safeParse、course-plans 缺 revalidatePath | | 性能 | 15 | 串行查询未并行化、循环内串行 await、未用 React.cache()、隐式 any[] | | 代码质量 | 12 | try-catch 吞错误、重复 try/catch、死代码、重复代码 | | 命名规范 | 3 | 布尔变量前缀、函数命名不一致 | | 数据一致性 | 4 | elective selectCourse/dropCourse 缺事务 | | 文件行数 | 2 | exams/ai-pipeline.ts 916 行、classes/data-access.ts 866 行 | --- ## 三、仍需优先修复的问题 ### 3.1 P0 级别(立即修复) #### P0-1:textbooks 模块仍无 Zod 验证(v1 未修复) - **文件**:`src/modules/textbooks/actions.ts` - **行号**:54-281(所有 Action) - **问题**:v1 报告的 P0 问题"actions.ts 完全无 Zod 验证"未修复。所有 Action 仍使用手动 `if` 校验,textbooks 模块甚至没有 schema.ts 文件 - **修复建议**:新建 `textbooks/schema.ts`,定义 `CreateTextbookSchema`、`UpdateTextbookSchema`、`CreateChapterSchema`、`CreateKnowledgePointSchema` 等 Zod schema,所有 Action 改用 `schema.safeParse()` ### 3.2 P1 级别(尽快修复) #### P1-1:exams/data-access.ts 直接查询 school 模块表(v1 未修复) - **文件**:`src/modules/exams/data-access.ts` - **行号**:2, 208-228, 519-524, 529-534 - **问题**:`resolveSubjectGradeNames`、`getExamSubjects`、`getExamGrades` 仍直接查询 `subjects`/`grades` 表(school 模块) - **修复建议**:在 school 模块暴露 `getGradeOptions()`、`getSubjectNameById(id)`、`getGradeNameById(id)` 接口 #### P1-2:questions/data-access.ts 直接查询 textbooks 模块表(v1 未修复) - **文件**:`src/modules/questions/data-access.ts` - **行号**:4, 266-299 - **问题**:`getKnowledgePointOptions` 仍直接 LEFT JOIN 查询 `knowledgePoints`、`chapters`、`textbooks` 三张表 - **修复建议**:在 textbooks 模块暴露 `getKnowledgePointOptionsForQuestions()` 接口 #### P1-3:classes/data-access-schedule.ts 直接查询 classSchedule 表(v1 未修复) - **文件**:`src/modules/classes/data-access-schedule.ts` - **行号**:7-11, 31-46, 73-86 - **问题**:仍直接导入并查询 `classSchedule` 表(scheduling 模块的表) - **修复建议**:在 scheduling 模块暴露只读查询函数 `getClassScheduleByClassIds` #### P1-4:messaging/data-access.ts getRecipients 直接 JOIN 跨模块表(v1 未修复) - **文件**:`src/modules/messaging/data-access.ts` - **行号**:26-27, 173-188 - **问题**:`getRecipients` 直接 import 并 JOIN `classEnrollments`、`classes` 表 - **修复建议**:通过 classes 模块暴露 `getStudentIdsByClassIds`、`getStudentIdsByGradeIds` 接口 #### P1-5:notifications/actions.ts 参数未用 Zod 验证(v1 未修复) - **文件**:`src/modules/notifications/actions.ts` - **行号**:28-50, 60-110 - **问题**:`sendNotificationAction` 和 `sendClassNotificationAction` 仅使用 TypeScript 类型标注和手动 if 检查 - **修复建议**:新增 `NotificationPayloadSchema` 和 `ClassNotificationSchema` #### P1-6:textbooks/actions.ts 本地定义 ActionState 类型(v1 未修复) - **文件**:`src/modules/textbooks/actions.ts` - **行号**:46-50 - **问题**:仍在本地定义 `ActionState` 类型,而非从 `@/shared/types/action-state` 导入 - **修复建议**:删除本地定义,改为 `import type { ActionState } from "@/shared/types/action-state"` #### P1-7:elective/data-access.ts 跨模块直查(v2 新发现) - **文件**:`src/modules/elective/data-access.ts` - **行号**:77-106(`buildCourseSelect`)、231-242(`getSubjectOptions`) - **问题**:`buildCourseSelect` 直接 `leftJoin(users/subjects/grades)`;本地 `getSubjectOptions` 直查 `subjects` 表且与 school 模块重复 - **修复建议**:移除 join,改为先查主表再用 `getUserNamesByIds`/`getSubjectOptions`/`getGradeOptions` 批量解析;删除本地 `getSubjectOptions` 改用 school 模块 #### P1-8:elective selectCourse/dropCourse 缺事务(v2 新发现) - **文件**:`src/modules/elective/data-access-operations.ts` - **行号**:97-172(selectCourse)、174-241(dropCourse) - **问题**:FCFS 模式下 update + insert 两步无事务包裹;dropCourse 最多 5 个连续写操作无事务 - **修复建议**:用 `db.transaction` 包裹所有写操作 #### P1-9:classes/actions.ts as 断言(v2 新发现) - **文件**:`src/modules/classes/actions.ts` - **行号**:47, 521, 565 - **问题**:`v as ClassSubject`、`weekday as 1|2|3|4|5|6|7` 使用 as 断言 - **修复建议**:在 schema.ts 中使用 Zod transform 使解析后直接产出目标类型 #### P1-10:school/actions.ts 使用 .parse() 而非 .safeParse()(v2 新发现) - **文件**:`src/modules/school/actions.ts` - **行号**:33, 60, 98, 129, 171, 202, 256, 289 - **问题**:所有 action 使用 `Schema.parse()` 而非 `safeParse()`,验证失败抛 ZodError,无法返回结构化 fieldErrors - **修复建议**:改用 `safeParse()`,失败时返回 `{ success: false, errors: parsed.error.flatten().fieldErrors }` #### P1-11:多个模块函数缺返回类型标注(v2 新发现 + v1 部分未修复) | 模块 | 文件 | 函数 | |------|------|------| | classes | data-access.ts:53,60,93,95,100,107 | isDuplicateInvitationCodeError, generateInvitationCode, normalizeSortText, parseFirstInt, compareGradeLabel, compareClassLike | | school | data-access.ts:24 | toIso | | attendance | data-access.ts:70 | resolveRecorderNames | | exams | ai-pipeline.ts:68,177,309,453,480,499,571,712 | sanitizeJsonCandidate, normalizeScores, buildAiMessages, splitStructureItems, mapWithConcurrency, parseQuestionDetail, buildQuestionContent, previewToDraft | | exams | data-access.ts:269 | buildOrderedQuestionsFromStructure | | grades | data-access.ts:57, data-access-analytics.ts:34 | buildScopeClassFilter | | textbooks | data-access.ts:19,25 | normalizeOptional, sortChapters | #### P1-12:files/data-access.ts conditions 隐式 any[](v1 未修复) - **文件**:`src/modules/files/data-access.ts` - **行号**:201 - **问题**:`const conditions = []` 无类型注解,推断为 `any[]` - **修复建议**:改为 `const conditions: SQL[] = []` #### P1-13:course-plans updateCoursePlanItemAction 缺 revalidatePath(v1 部分修复) - **文件**:`src/modules/course-plans/actions.ts` - **行号**:197-234 - **问题**:v1 指出的 deleteCoursePlanItemAction 和 toggleCoursePlanItemCompletedAction 已修复,但 `updateCoursePlanItemAction` 仍缺 `revalidatePath` - **修复建议**:在 `await updateCoursePlanItem(id, parsed.data)` 后添加 `revalidatePlanPaths()` 调用 ### 3.3 P2 级别(迭代优化) #### 性能类 | 模块 | 文件 | 问题 | |------|------|------| | grades | data-access.ts:273,377,397 | 3 个查询函数未用 React.cache() | | elective | data-access.ts:231 | getSubjectOptions 未用 cache() | | course-plans | data-access.ts:302-307 | reorderCoursePlanItems 循环内串行 await | | diagnostic | data-access.ts:119-140 | updateMasteryFromSubmission 循环内串行 await | | proctoring | data-access.ts:271-287 | getStudentProctoringStatuses 串行查询未并行化 | | diagnostic | data-access.ts:147-159 | getClassMasterySummary 串行查询未并行化 | | grades | export.ts:129 | 循环内 find O(n) 查找,应改用 Map | | school | data-access.ts:406-469 | isGradeHead/isGradeManager/findGradeIdByHeadAndName 未用 cache() | #### 代码质量类 | 模块 | 文件 | 问题 | |------|------|------| | school | data-access.ts:26-206 | 8 个函数 try-catch 吞错误返回空数组 | | files | data-access.ts | 10 处静默 catch 吞错误 | | classes | data-access.ts:371-373, data-access-admin.ts:88-89,244-247, data-access-students.ts:159-160 | 多处 try-catch 吞错误 | | course-plans | data-access.ts:143-162,166-189,312-323 | 3 处 try-catch 吞错误 | | announcements | data-access.ts:88-91,119-122 | catch 已加 console.error 但仍吞错误 | | announcements | actions.ts:26-230 | 6 处重复 try/catch 块未抽取公共 helper | | diagnostic | data-access-reports.ts:22,207-208 | void round2 死代码 | | scheduling | data-access.ts:113-114 | select 中 requesterName 字段冗余 | | elective | data-access.ts:46-75, data-access-selections.ts:46-74 | mapCourseRow 重复定义 | #### TS 规范类 | 模块 | 文件 | 问题 | |------|------|------| | users | import-export.ts:14 | as 断言未加注释 | | users | import-export.ts:98 | conditions 隐式 any[] | | messaging | data-access.ts:84 | conds 隐式 any[] | | audit | data-access.ts:40,96,161 | conditions 隐式 any[] | | classes | data-access.ts:675 | 非空断言 `!` | | textbooks | data-access.ts:314 | 非空断言 `stack.pop()!` | | notifications | external-sdk.d.ts | 多处 any(有 eslint-disable 注释) | | notifications | wechat-channel.ts:106 | as 断言未加注释 | #### 命名规范类 | 模块 | 文件 | 问题 | |------|------|------| | questions | actions.ts:26 | createNestedQuestion 命名不一致 | | settings | actions.ts:60-63 | getAiProviderSummaries 返回非 ActionState | #### 架构/类型位置类 | 模块 | 文件 | 问题 | |------|------|------| | layout | navigation.ts:30-31 | permission 字段为 string 而非 Permission 类型 | | layout | navigation.ts:34 | Role 类型应迁移至 shared/types | | audit | actions.ts:63-205 | Excel 导出逻辑内联在 actions 层 | #### 文件行数类 | 模块 | 文件 | 行数 | 建议 | |------|------|------|------| | exams | ai-pipeline.ts | 916 行 | 拆分为 prompts/json-parser/schemas/index | | classes | data-access.ts | 866 行 | 拆分 enrollment 相关函数到 data-access-enrollment.ts | #### 数据一致性/业务逻辑类 | 模块 | 文件 | 问题 | |------|------|------| | elective | data-access-operations.ts:139-148 | FCFS 并发超卖风险(架构图 P2-15) | | elective | data-access-operations.ts:49 | runLottery 使用 Math.random 不可复现(架构图 P2-14) | | diagnostic | data-access-reports.ts:113 | 班级报告 studentId 字段复用(架构图 P2-16) | --- ## 四、按模块详细核查 ### 4.1 exams 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: persistAiGeneratedExamDraft 直写 questions 表 | ✅ 已修复 | 改用 createQuestionWithRelations | | P0: getExams 等直查 classes 表 | ✅ 已修复 | 改用 getClassGradeIdsByClassIds | | P1: 直接查询 subjects/grades 表 | ❌ 未修复 | 仍直查 school 模块表 | | P1: actions.ts as 断言 | ✅ 已修复 | 改用 as unknown + safeParse | | P2: import { ActionState } 未用 import type | ✅ 已修复 | 已改为 import type | | P2: ai-pipeline.ts 912 行超长 | ❌ 未修复 | 当前 916 行 | | P2: data-access.ts as string[] 断言 | ✅ 已修复 | 改用 getStringArray 类型守卫 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | ai-pipeline.ts:68,177,309,453,480,499,571,712 | 8 个函数缺少显式返回类型标注 | | P2 | data-access.ts:269 | buildOrderedQuestionsFromStructure 缺返回类型 | ### 4.2 homework 模块 #### v1 修复情况(100% 修复) | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: data-access.ts 直查 exams/classEnrollments/subjects/users 表 | ✅ 已修复 | 改用跨模块 data-access 接口 | | P1: data-access-write.ts 直查 classes/classEnrollments/classSubjectTeachers/exams 表 | ✅ 已修复 | 改用跨模块接口 | | P1: stats-service.ts 直查 classEnrollments/classes/exams/users 表 | ✅ 已修复 | 改用跨模块接口 | | P1: data-access.ts:39 as 断言 | ✅ 已修复 | 改用 isHomeworkQuestionContent 类型守卫 | | P2: data-access-write.ts 循环内串行 await 未用事务 | ✅ 已修复 | 已用 db.transaction 包裹 | | P2: data-access.ts 使用 auth() 而非 getAuthContext() | ✅ 已修复 | 不再使用 auth() | **v2 无新发现问题,模块状态良好。** ### 4.3 questions 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: schema.ts z.any() | ✅ 已修复 | 改为 z.unknown() | | P0: actions.ts 未返回 ActionState | ✅ 已修复 | 包装为 ActionState | | P1: data-access.ts 直查 textbooks 模块表 | ❌ 未修复 | 仍直查 knowledgePoints/chapters/textbooks | | P1: actions.ts import type | ✅ 已修复 | 已改为 import type | | P2: createNestedQuestion 命名不一致 | ❌ 未修复 | 仍为 createNestedQuestion | **v2 无新发现问题。** ### 4.4 grades 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: N+1 查询 | ✅ 已修复 | 改为 inArray 批量查询 + Map 分组 | | P1: 跨模块直查 | ✅ 已修复 | 改用跨模块 data-access 接口 | | P1: 动态 import | ✅ 已修复 | 改为静态 import | | P1: 除零 bug | ✅ 已修复 | 添加 `if (fullScores[i] <= 0) continue` | | P2: 未用 React.cache() | ⚠️ 部分修复 | 4 个函数已用 cache(),3 个仍未用 | | P2: includes O(n) 查找 | ✅ 已修复 | 改用 Set.has() | | P2: 重复 filter | ✅ 已修复 | 改用单次 reduce | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | data-access.ts:57, data-access-analytics.ts:34 | buildScopeClassFilter 缺返回类型 | | P2 | export.ts:129 | 循环内 find O(n) 查找,应改用 Map | ### 4.5 textbooks 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: 无 Zod 验证 | ❌ 未修复 | 仍无 schema.ts,所有 Action 手动校验 | | P0: 14 处 as 断言 | ✅ 已修复 | 已清理所有 as 断言 | | P1: 本地定义 ActionState | ❌ 未修复 | 仍在本地定义 | | P1: import type | ✅ 已修复 | 已改为 import type | | P1: data-access.ts as 断言 | ✅ 已修复 | 改用 isChapterNode 类型守卫 | | P2: 非空断言 | ⚠️ 部分修复 | 原位置已修复,但 314 行仍有 stack.pop()! | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | data-access.ts:19,25 | normalizeOptional、sortChapters 缺返回类型 | ### 4.6 classes 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: actions.ts 直接 DB 操作 | ✅ 已修复 | 已下沉到 data-access | | P0: getTeacherClasses 混入 homework/scheduling | ⚠️ 部分修复 | 架构合规,但职责仍混合 | | P0: data-access-stats.ts 直查 homework/exams | ✅ 已修复 | 改用 homework/data-access-classes | | P0: data-access-students.ts 直查 homework/exams | ✅ 已修复 | 同上 | | P1: 无 schema.ts | ✅ 已修复 | 已创建 schema.ts | | P1: as 断言 | ✅ 已修复 | 原位置已清理 | | P1: 箭头函数缺返回类型 | ⚠️ 部分修复 | 6 个同步箭头函数仍缺 | | P1: data-access-schedule.ts 直查 classSchedule | ❌ 未修复 | 仍直查 scheduling 模块表 | | P2: 不可达代码 | ✅ 已修复 | 已删除 | | P2: 串行查询未并行化 | ✅ 已修复 | 已用 Promise.all | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P1 | actions.ts:47,521,565 | as 断言(v as ClassSubject、weekday as 1\|2\|...\|7) | | P2 | data-access.ts:675 | 非空断言 `!` | | P2 | data-access.ts:371-373 等 | 多处 try-catch 吞错误 | | P2 | data-access.ts | 文件 866 行超 800 行建议上限 | ### 4.7 school 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: actions 层直接 DB 操作 | ✅ 已修复 | DB 操作下沉到 data-access | | P2: try-catch 吞错误 | ❌ 未修复 | 8 个函数仍吞错误 | | P2: logAudit 阻塞响应 | ✅ 已修复 | 已用 after() 异步执行 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P1 | actions.ts:33,60,98,129,171,202,256,289 | 使用 .parse() 而非 .safeParse() | | P1 | data-access.ts:24 | toIso 缺返回类型 | | P2 | data-access.ts:406-469 | 3 个跨模块查询函数未用 cache() | ### 4.8 scheduling 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: actions.ts 直查 users 表 | ✅ 已修复 | 改用 getUserNamesByIds | | P0: 4 个函数缺返回类型 | ✅ 已修复 | 已添加返回类型 | | P1: 非空断言(3 处) | ✅ 已修复 | 改用显式判空 | | P2: auto-scheduler.ts 310 行 | ⚠️ 部分修复 | 311 行,多个函数超 40 行 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | data-access.ts:113-114 | select 中 requesterName 字段冗余 | | P2 | data-access.ts:135-145 | 用户查询应使用 inArray 替代 or(...map(eq)) | ### 4.9 attendance 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: Record 丢失类型安全 | ✅ 已修复 | 改用 Partial | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P1 | data-access.ts:70 | resolveRecorderNames 缺返回类型 | ### 4.10 course-plans 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: 缺 revalidatePath | ⚠️ 部分修复 | delete/toggle 已修复,update 仍缺 | | P1: as 断言 | ✅ 已修复 | 已清理 | | P2: 循环内串行 await | ❌ 未修复 | 仍串行 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | data-access.ts:143-162,166-189,312-323 | 3 处 try-catch 吞错误 | ### 4.11 users 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: updateUserProfile 绕过权限 | ✅ 已修复 | 改用 requirePermission + Zod + ActionState | | P0: 硬编码弱密码 | ✅ 已修复 | 改用 randomBytes 生成 | | P1: actions 层直接 DB 操作 | ✅ 已修复 | 下沉到 data-access | | P1: batchImportUsers 无事务 | ✅ 已修复 | 每个用户创建包裹在 db.transaction | | P2: rolePriority 命名 | ✅ 已修复 | 已移除,改用 resolvePrimaryRole | | P2: normalizeRoleName 重复 | ✅ 已修复 | 改用 shared | | P2: conditions 隐式 any[] | ❌ 未修复 | 仍为 `const conditions = []` | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | import-export.ts:14 | as 断言未加注释 | ### 4.12 messaging 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: 循环依赖 | ✅ 已修复 | 表所有权移交 notifications | | P1: 5 个 Action 用 requireAuth | ✅ 已修复 | 改用 requirePermission | | P1: getRecipients 直查跨模块表 | ❌ 未修复 | 仍 JOIN classEnrollments/classes | | P1: 无 Zod 验证 | ✅ 已修复 | 已用 UpdateNotificationPreferencesSchema | | P2: 非空断言 | ✅ 已修复 | 改用 ?? null | | P2: 缺返回类型 | ✅ 已修复 | 已添加 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | data-access.ts:84 | conds 隐式 any[] | ### 4.13 notifications 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: 反向依赖 messaging | ✅ 已修复 | 表所有权归 notifications | | P0: in-app-channel 动态 import messaging | ✅ 已修复 | 改为静态 import | | P0: 非法 as 断言 | ✅ 已修复 | 新增 mapPayloadTypeToNotificationType | | P1: actions.ts 直查 classes 表 | ✅ 已修复 | 改用 classes data-access 函数 | | P1: 参数无 Zod 验证 | ❌ 未修复 | 仍用手动 if 检查 | | P2: 缺返回类型 | ✅ 已修复 | 已添加 | | P2: external-sdk.d.ts any | ❌ 未修复 | 有 eslint-disable 注释 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | wechat-channel.ts:106 | as 断言未加注释 | ### 4.14 parent 模块 #### v1 修复情况(100% 修复) | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: getChildBasicInfo 直查跨模块表 | ✅ 已修复 | 改用各模块 data-access 函数 | | P2: as 断言 | ✅ 已修复 | 改用 isWeekday 类型守卫 | | P2: 串行查询 | ✅ 已修复 | 改用 Promise.all | **v2 无新发现问题,模块状态良好,是跨模块通信的标杆实现。** ### 4.15 audit 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: 导出函数数据截断 | ✅ 已修复 | 改用分页循环拉取全部数据 | | P2: as 断言 | ✅ 已修复 | 已清理 | | P2: Excel 导出逻辑内联 | ❌ 未修复 | 仍内联在 actions | | P2: conditions 隐式 any[] | ❌ 未修复 | 3 处仍为 `const conditions = []` | ### 4.16 elective 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: data-access-selections.ts 直查 classes 表 | ✅ 已修复 | 改用跨模块接口 | | P1: runLottery 无事务 | ✅ 已修复 | 已用 db.transaction | | P1: 循环内逐条 await | ✅ 已修复 | 改用 inArray 批量更新 | | P1: as 断言 | ✅ 已修复 | 已清理 | | P2: 串行查询未并行化 | ✅ 已修复 | 改用 Promise.all | | P2: 未用 React.cache() | ⚠️ 部分修复 | 大部分已用,getSubjectOptions 仍未用 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P1 | data-access.ts:77-106 | buildCourseSelect 跨模块 join users/subjects/grades | | P1 | data-access.ts:231-242 | 本地 getSubjectOptions 直查 subjects 表且与 school 重复 | | P1 | data-access-operations.ts:97-172 | selectCourse 缺事务包裹 | | P1 | data-access-operations.ts:174-241 | dropCourse 缺事务包裹 | | P2 | data-access-operations.ts:139-148 | FCFS 并发超卖风险 | | P2 | data-access-operations.ts:49 | runLottery 使用 Math.random 不可复现 | | P2 | data-access.ts:46-75, data-access-selections.ts:46-74 | mapCourseRow 重复定义 | ### 4.17 proctoring 模块 #### v1 修复情况(100% 修复) | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P0: actions.ts 直接 DB 操作 | ✅ 已修复 | 下沉到 data-access | | P1: import type | ✅ 已修复 | 已改为 import type | | P1: as 断言 | ✅ 已修复 | 改用类型守卫 | | P1: requireAuth | ✅ 已修复 | 改用 requirePermission | | P1: 直查 exams/examSubmissions 表 | ✅ 已修复 | 改用跨模块函数 | | P1: 多处 as 断言 | ✅ 已修复 | 改用 toExamMode/isSubmissionStatus | | P2: 未调用 revalidatePath | ✅ 已修复 | 已添加 | | P2: 串行查询 | ✅ 已修复 | 改用 Promise.all | | P2: 重复 filter | ✅ 已修复 | 改用单次循环 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | data-access.ts:271-287 | getStudentProctoringStatuses 串行查询未并行化 | ### 4.18 diagnostic 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: 4 个 Action 无 Zod | ✅ 已修复 | 新增 schema.ts,6 个 Action 全用 Zod | | P1: 直查跨模块表 | ✅ 已修复 | 改用跨模块 data-access | | P1: as 断言 | ✅ 已修复 | 改用 isStringArray 类型守卫 | | P2: 循环内 find | ✅ 已修复 | 改用 Map | | P2: 循环内串行 await | ❌ 未修复 | updateMasteryFromSubmission 仍串行 | | P2: 重复 filter | ✅ 已修复 | 改用单次循环 | | P2: 动态 import | ✅ 已修复 | 改为静态 import | | P2: void round2 死代码 | ❌ 未修复 | 仍存在 | | P2: 未用 React.cache() | ✅ 已修复 | 全部用 cache() 包装 | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | data-access.ts:147-159 | getClassMasterySummary 串行查询未并行化 | ### 4.19 dashboard 模块 **v1 无违规问题,v2 仍为标杆模块。** 正确使用 Promise.all 并行获取多模块数据,正确使用 cache(),正确通过各模块 data-access 通信。 ### 4.20 files 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: conditions 隐式 any[] | ❌ 未修复 | 仍为 `const conditions = []` | | P1: or(...)! 非空断言 | ✅ 已修复 | 改用显式判断 | | P2: 循环内串行 await | ⚠️ 部分修复 | 主路径已批量删除,catch 回退仍串行 | | P2: 9 处静默 catch | ❌ 未修复 | 实际 10 处 | | P2: 未用 React.cache() | ✅ 已修复 | 全部用 cache() 包装 | ### 4.21 announcements 模块 #### v1 修复情况 | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: as string 断言 | ✅ 已修复 | 新增 toIso/toIsoRequired 工具函数 | | P2: 冗余 as 断言 | ✅ 已修复 | 已清理 | | P2: catch 吞错误 | ⚠️ 部分修复 | 已加 console.error 但仍吞错误 | | P2: 类型重复定义 | ✅ 已修复 | 已修复 | | P2: requireAuth | ✅ 已修复 | 改用 requirePermission | | P2: 重复 try/catch | ❌ 未修复 | 6 处仍重复 | ### 4.22 settings 模块 #### v1 修复情况(100% 修复) | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P1: 无 data-access.ts | ✅ 已修复 | 新建 data-access.ts | | P1: actions-password.ts 无 data-access | ✅ 已修复 | DB 操作下沉 | | P1: 无 Zod 验证 | ✅ 已修复 | 新增 ChangePasswordSchema | | P2: 类型定义位置 | ✅ 已修复 | 新建 types.ts | | P2: 缺返回类型 | ✅ 已修复 | 已添加 | | P2: 串行查询 | ✅ 已修复 | 改用 Promise.all | | P2: 布尔命名 | ✅ 已修复 | 改为 hasDefault/isNextDefault/shouldMakeDefault | | P2: requireAuth | ✅ 已修复 | 改用 requirePermission | | P2: 串行查询 | ✅ 已修复 | 改用 Promise.all | #### v2 新发现问题 | 严重程度 | 文件 | 问题 | |---------|------|------| | P2 | actions.ts:60-63 | getAiProviderSummaries 返回非 ActionState | ### 4.23 layout 模块 #### v1 修复情况(0% 修复) | v1 问题 | 修复状态 | 说明 | |---------|---------|------| | P2: permission 字段为 string | ❌ 未修复 | 仍为 string | | P2: Role 类型位置 | ❌ 未修复 | 仍在 navigation.ts | --- ## 五、v2 新发现问题清单 ### 5.1 P1 级别新问题(16 个) | 编号 | 模块 | 文件 | 问题 | |------|------|------|------| | N1 | elective | data-access.ts:77-106 | buildCourseSelect 跨模块 join users/subjects/grades | | N2 | elective | data-access.ts:231-242 | 本地 getSubjectOptions 直查 subjects 表且与 school 重复 | | N3 | elective | data-access-operations.ts:97-172 | selectCourse 缺事务包裹 | | N4 | elective | data-access-operations.ts:174-241 | dropCourse 缺事务包裹 | | N5 | classes | actions.ts:47,521,565 | as 断言(v as ClassSubject、weekday as 1\|2\|...\|7) | | N6 | school | actions.ts:33 等 | 使用 .parse() 而非 .safeParse() | | N7 | school | data-access.ts:24 | toIso 缺返回类型 | | N8 | attendance | data-access.ts:70 | resolveRecorderNames 缺返回类型 | | N9 | exams | ai-pipeline.ts | 8 个函数缺返回类型 | | N10 | exams | data-access.ts:269 | buildOrderedQuestionsFromStructure 缺返回类型 | | N11 | grades | data-access.ts:57, data-access-analytics.ts:34 | buildScopeClassFilter 缺返回类型 | | N12 | textbooks | data-access.ts:19,25 | normalizeOptional、sortChapters 缺返回类型 | | N13 | settings | actions.ts:60-63 | getAiProviderSummaries 返回非 ActionState | | N14 | messaging | data-access.ts:84 | conds 隐式 any[] | | N15 | users | import-export.ts:14 | as 断言未加注释 | | N16 | notifications | wechat-channel.ts:106 | as 断言未加注释 | ### 5.2 P2 级别新问题(25 个) | 编号 | 模块 | 文件 | 问题 | |------|------|------|------| | N17 | classes | data-access.ts:675 | 非空断言 `!` | | N18 | classes | data-access.ts:371-373 等 | 多处 try-catch 吞错误 | | N19 | classes | data-access.ts | 文件 866 行超 800 行建议上限 | | N20 | school | data-access.ts:406-469 | 3 个跨模块查询函数未用 cache() | | N21 | scheduling | data-access.ts:113-114 | select 中 requesterName 字段冗余 | | N22 | scheduling | data-access.ts:135-145 | 用户查询应使用 inArray | | N23 | course-plans | data-access.ts:143-162 等 | 3 处 try-catch 吞错误 | | N24 | grades | export.ts:129 | 循环内 find O(n) 查找 | | N25 | proctoring | data-access.ts:271-287 | getStudentProctoringStatuses 串行查询 | | N26 | diagnostic | data-access.ts:147-159 | getClassMasterySummary 串行查询 | | N27 | elective | data-access-operations.ts:139-148 | FCFS 并发超卖风险 | | N28 | elective | data-access-operations.ts:49 | runLottery 使用 Math.random | | N29 | elective | data-access.ts:46-75 等 | mapCourseRow 重复定义 | | N30 | users | import-export.ts:98 | conditions 隐式 any[] | | N31 | audit | data-access.ts:40,96,161 | conditions 隐式 any[] | --- ## 六、架构文档同步提醒 根据项目规则"改码必同步图",以下架构图信息需更新: ### 6.1 需更新的架构文档 | 文档 | 需更新内容 | |------|-----------| | `docs/architecture/004_architecture_impact_map.md` | 1. exams/actions.ts 行数(v1 记录 691,实际 771)
2. exams/ai-pipeline.ts 行数(v1 记录 857,实际 916)
3. settings 导出函数列表(v1 记录 getAiProvidersAction 等,实际为 getAiProviderSummaries/upsertAiProviderAction/testAiProviderAction)
4. P2-11 死代码 void wasPublished 状态(已修复)
5. announcements 依赖 school 模块(仅 components,非后端)
6. elective 依赖关系需补充 classes/school/users
7. messaging↔notifications 循环依赖已解决
8. classes 跨模块直查 homework/exams 已解决 | ### 6.2 需同步的代码变更 本轮修复涉及大量模块结构调整,必须同步更新 004 和 005 架构文档: - **新增模块文件**:classes/schema.ts、settings/data-access.ts、settings/types.ts、diagnostic/schema.ts - **新增跨模块接口**:classes 暴露 getClassGradeIdsByClassIds、getStudentIdsByClassId 等;exams 暴露 getExamIdsByGradeIds、getExamWithQuestionsForHomework 等;users 暴露 getUserNamesByIds、getUserBasicInfo 等;school 暴露 getSubjectOptions、getGradeOptions 等 - **表所有权迁移**:messageNotifications、notificationPreferences 表所有权从 messaging 移交至 notifications - **权限点新增**:USER_PROFILE_UPDATE、PASSWORD_SELF_CHANGE 等 --- ## 七、总体评价与建议 ### 7.1 修复成效 本次 v2 核查显示,项目在 v1 报告后进行了大规模且有成效的修复: 1. **所有 P0 问题已全部修复**(14/14):包括跨模块直写 DB、循环依赖、硬编码弱密码、N+1 查询等高危问题 2. **P1 问题修复率 65%**:剩余 14 个未修复 + 7 个部分修复 3. **4 个模块达到 100% 修复率**:homework、parent、proctoring、settings 4. **架构层面显著改善**: - messaging↔notifications 循环依赖彻底消除 - 跨模块直查 DB 大幅减少(exams、homework、grades、classes、proctoring、diagnostic 等模块已改用 data-access 接口) - parent 模块成为跨模块通信的标杆实现 ### 7.2 仍需改进的领域 1. **textbooks 模块**:P0 问题(无 Zod 验证)仍未修复,是所有模块中唯一未实现输入验证的 Server Action 文件 2. **跨模块直查残留**:exams→school、questions→textbooks、classes→scheduling、messaging→classes 仍存在直查 3. **函数返回类型标注**:多个模块仍存在箭头函数缺返回类型的问题(classes、school、attendance、exams、grades、textbooks) 4. **隐式 any[]**:`const conditions = []` 在 users、messaging、audit、files 等模块普遍存在 5. **错误处理**:try-catch 吞错误在 school、files、classes、course-plans 等模块仍普遍存在 6. **elective 模块**:v2 新发现 selectCourse/dropCourse 缺事务、data-access.ts 跨模块直查等问题 ### 7.3 下一阶段优先修复建议 **第一优先级(P0/P1 核心问题)**: 1. textbooks 模块新建 schema.ts,所有 Action 改用 Zod safeParse 2. textbooks/actions.ts 改用共享 ActionState 类型 3. exams、questions、classes、messaging 模块消除剩余跨模块直查 4. elective selectCourse/dropCourse 加事务包裹 5. school/actions.ts 改用 safeParse 6. 补齐所有函数返回类型标注 **第二优先级(P2 系统性优化)**: 1. 全项目统一修复 `const conditions = []` 隐式 any[](改为 `SQL[]`) 2. 清理 try-catch 吞错误(至少加 console.error 或向上抛出) 3. 补齐 React.cache() 包装 4. 串行查询改用 Promise.all 5. 同步架构文档 **第三优先级(代码质量)**: 1. 抽取重复代码(mapCourseRow、handleActionError、buildExcelSheet 等) 2. 清理死代码(void round2 等) 3. 拆分超长文件(ai-pipeline.ts、classes/data-access.ts) 4. layout 模块类型规范修复