# 后端模块规范核查报告 > 核查日期:2026-06-18 > 核查范围:`src/modules/` 下所有后端 `.ts` 文件(actions / data-access / schema / types / services 等非 components、非 hooks 文件) > 核查依据: > - `.trae/rules/project_rules.md` 项目规则 > - `docs/standards/coding-standards.md` 编码规范 > - `docs/architecture/004_architecture_impact_map.md` 架构影响地图 > - Vercel React Best Practices 性能优化规则(react-best-practices 技能) > > 严重程度定义: > - **P0**:严重违规,破坏架构分层 / 安全漏洞 / 数据正确性问题,必须立即修复 > - **P1**:明确违规,影响可维护性 / 类型安全 / 性能,应尽快修复 > - **P2**:轻微违规或代码异味,可在迭代中优化 --- ## 目录 - [一、汇总统计](#一汇总统计) - [二、P0 严重问题清单(立即修复)](#二p0-严重问题清单立即修复) - [三、按模块详细核查](#三按模块详细核查) - [3.1 exams(考试模块)](#31-exams考试模块) - [3.2 homework(作业模块)](#32-homework作业模块) - [3.3 questions(题库模块)](#33-questions题库模块) - [3.4 grades(成绩模块)](#34-grades成绩模块) - [3.5 textbooks(教材模块)](#35-textbooks教材模块) - [3.6 classes(班级模块)](#36-classes班级模块) - [3.7 school(学校模块)](#37-school学校模块) - [3.8 scheduling(排课模块)](#38-scheduling排课模块) - [3.9 attendance(考勤模块)](#39-attendance考勤模块) - [3.10 course-plans(教学计划模块)](#310-course-plans教学计划模块) - [3.11 users(用户模块)](#311-users用户模块) - [3.12 messaging(消息模块)](#312-messaging消息模块) - [3.13 notifications(通知模块)](#313-notifications通知模块) - [3.14 parent(家长模块)](#314-parent家长模块) - [3.15 audit(审计模块)](#315-audit审计模块) - [3.16 elective(选修课模块)](#316-elective选修课模块) - [3.17 proctoring(监考模块)](#317-proctoring监考模块) - [3.18 diagnostic(诊断模块)](#318-diagnostic诊断模块) - [3.19 dashboard(仪表盘模块)](#319-dashboard仪表盘模块) - [3.20 files(文件模块)](#320-files文件模块) - [3.21 announcements(公告模块)](#321-announcements公告模块) - [3.22 settings(设置模块)](#322-settings设置模块) - [3.23 layout(布局模块)](#323-layout布局模块) - [四、跨模块共性问题](#四跨模块共性问题) - [五、性能优化专项(react-best-practices)](#五性能优化专项react-best-practices) - [六、优先修复路线图](#六优先修复路线图) --- ## 一、汇总统计 ### 1.1 按严重程度 | 严重程度 | 数量 | 典型问题 | |---------|------|---------| | **P0** | 14 | 跨模块直查/直写 DB 表、循环依赖、`z.any()`、N+1 查询、未返回 ActionState、无 Zod 验证、硬编码弱密码 | | **P1** | 60+ | `as` 断言滥用、`requireAuth` 替代 `requirePermission`、缺少 `import type`、动态 import、无事务、除零 bug、导出数据截断 | | **P2** | 100+ | 非空断言 `!`、未用 `React.cache()`、串行查询未并行化、重复 filter、静默吞异常、缺返回类型标注、文档不同步 | ### 1.2 按问题类别 | 问题类别 | 涉及文件数 | 关键发现 | |---------|-----------|---------| | 架构违规 | 25+ | 跨模块直查 DB 是最普遍问题;messaging↔notifications 循环依赖;actions 层直接 DB 操作 | | TS 规范 | 30+ | `as` 断言大量存在;`z.any()`;`import type` 未用;非空断言 `!`;隐式 `any[]` | | Server Action 规范 | 12+ | `requireAuth` 替代 `requirePermission`;未返回 `ActionState`;无 Zod 验证;缺 `revalidatePath` | | 性能 | 20+ | N+1 查询;动态 import;未用 `cache()`;串行 await 循环;重复 filter 遍历 | | 安全 | 2 | 硬编码弱密码 "123456";`as` 断言掩盖类型不兼容 | | 行数 | 1 | `exams/ai-pipeline.ts` 912 行(超 800 建议,未超 1000 硬上限) | ### 1.3 模块问题分布 | 模块 | P0 | P1 | P2 | 总体评价 | |------|----|----|-----|---------| | exams | 3 | 5 | 6 | 跨模块直查/直写严重 | | homework | 0 | 8 | 5 | 跨模块直查普遍 | | questions | 2 | 2 | 3 | `z.any()` + 未返回 ActionState | | grades | 1 | 6 | 7 | N+1 查询 + 跨模块直查 | | textbooks | 2 | 2 | 4 | 无 Zod 验证 + `as` 断言 | | classes | 3 | 8 | 6 | 耦合最严重,混入多模块逻辑 | | school | 1 | 1 | 2 | actions 层直接 DB 操作 | | scheduling | 0 | 4 | 3 | 缺返回类型标注 | | attendance | 0 | 1 | 0 | 整体规范 | | course-plans | 0 | 3 | 2 | 缺 revalidatePath | | users | 1 | 3 | 4 | 硬编码弱密码 + 无事务 | | messaging | 1 | 6 | 6 | 循环依赖 + requireAuth | | notifications | 3 | 3 | 8 | 循环依赖 + as 断言类型不兼容 | | parent | 0 | 2 | 2 | 跨模块直查 | | audit | 0 | 4 | 6 | 导出数据截断 | | elective | 0 | 3 | 5 | 无事务 + 串行更新 | | proctoring | 1 | 6 | 3 | actions 层直接 DB 操作 | | diagnostic | 0 | 2 | 5 | 跨模块直查 + 性能问题 | | dashboard | 0 | 0 | 0 | 标杆模块 | | files | 0 | 2 | 3 | 隐式 any[] + 非空断言 | | announcements | 0 | 1 | 5 | as 断言 + 错误吞没 | | settings | 0 | 3 | 9 | 缺 data-access 层 + 无 Zod | | layout | 0 | 0 | 2 | 类型松散 | --- ## 二、P0 严重问题清单(立即修复) ### P0-1:exams 模块直写 questions 表 - **文件**:`src/modules/exams/data-access.ts` - **行号**:2, 318 - **问题**:`persistAiGeneratedExamDraft` 直接 `db.insert(questions)` 写入 questions 表(属 questions 模块),破坏模块封装 - **修复**:改为调用 `questions/data-access.createQuestionWithRelations()` 或在 questions 模块暴露批量插入接口 ### P0-2:exams 模块直查 classes 表 - **文件**:`src/modules/exams/data-access.ts` - **行号**:2, 72-80, 156-163, 357-364 - **问题**:`getExams`/`getExamById`/`getExamsDashboardStats` 直接查询 `classes` 表获取 gradeId - **修复**:在 classes 模块暴露 `getGradeIdsByClassIds(classIds)` 接口 ### P0-3:questions/schema.ts 使用 `z.any()` - **文件**:`src/modules/questions/schema.ts` - **行号**:6 - **问题**:`content: z.any()` 违反"禁止 any"规则 - **修复**:改为 `z.unknown()` ### P0-4:questions/actions.ts 未返回 ActionState - **文件**:`src/modules/questions/actions.ts` - **行号**:154, 166-175 - **问题**:`getQuestionsAction` 和 `getKnowledgePointOptionsAction` 直接返回原始结果,未包装为 `ActionState`,错误时 throw 而非返回失败状态 - **修复**:改为 `Promise>` 返回 `{ success: true, data: result }` ### P0-5:textbooks 模块无 Zod 验证 + 大量 `as` 断言 - **文件**:`src/modules/textbooks/actions.ts` - **行号**:全文件,特别 54-58, 92-98, 154, 219-221, 259-261 - **问题**:缺少 `schema.ts` 文件,所有 Action 均无 Zod 验证,使用手动 `if (!rawData.title)` 检查;14 处 `formData.get("title") as string` 断言 - **修复**:新建 `schema.ts`,为每个 Action 定义 Zod schema;改用 `typeof` 守卫或 `getStringValue()` 辅助函数 ### P0-6:grades 模块 N+1 查询 - **文件**:`src/modules/grades/data-access-analytics.ts` - **行号**:142-185 - **问题**:`getClassComparison` 中 `for (const cls of classRows) { ... await db.select(...) }` 循环内串行查询,N+1 问题 - **修复**:用 `inArray` 一次性查询所有班级的成绩,再在内存分组 ### P0-7:classes 模块跨模块直接查询 DB 表 - **文件**:`src/modules/classes/data-access-stats.ts`、`src/modules/classes/data-access-students.ts` - **行号**:data-access-stats.ts: 11-18;data-access-students.ts: 10-14 - **问题**:直接导入并查询 `homeworkAssignments`、`homeworkSubmissions`、`homeworkAssignmentTargets`、`homeworkAssignmentQuestions`、`exams` 等其他模块的表 - **修复**:通过 homework 模块的 data-access 暴露的函数获取数据 ### P0-8:school 模块 actions 层直接操作 DB - **文件**:`src/modules/school/actions.ts` - **行号**:7-8, 26-30, 53-59, 73, 96-108, 133-147, 161, 182-186, 211-217, 233, 261-268, 294-303, 317 - **问题**:actions 层直接导入 `db` 和所有 schema,所有 mutation 直接操作 DB,未下沉到 data-access 层 - **修复**:在 `data-access.ts` 中新增 `createDepartment`、`updateDepartment`、`deleteDepartment`、`createSchool` 等函数 ### P0-9:proctoring 模块 actions 层直接查询 DB - **文件**:`src/modules/proctoring/actions.ts` - **行号**:11-13, 79-84 - **问题**:actions.ts(编排层)直接导入 `db`、`examSubmissions` 并执行 DB 查询,违反三层架构 - **修复**:将 submission 归属校验逻辑移至 `data-access.ts` ### P0-10:messaging↔notifications 循环依赖 - **文件**:`src/modules/messaging/data-access.ts`、`src/modules/notifications/data-access.ts`、`src/modules/notifications/channels/in-app-channel.ts` - **行号**:messaging/data-access.ts: 192-203;notifications/data-access.ts: 20-21;in-app-channel.ts: 50 - **问题**:messaging 写 `messageNotifications` 表,notifications 反向依赖 messaging,形成循环依赖 - **修复**:将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块 ### P0-11:notifications/in-app-channel.ts 非法 as 断言 - **文件**:`src/modules/notifications/channels/in-app-channel.ts` - **行号**:54 - **问题**:`payload.type as "message" | "announcement" | "homework" | "grade"` 源类型 `"info"|"warning"|"error"|"success"` 与目标类型不兼容,as 断言非法 - **修复**:重新设计类型映射函数,或统一 messaging/notifications 的 type 定义 ### P0-12:users 模块硬编码弱密码 - **文件**:`src/modules/users/user-service.ts` - **行号**:13 - **问题**:`const DEFAULT_PASSWORD = "123456"` 硬编码弱密码,批量导入用户使用极弱默认密码 - **修复**:改为随机生成密码或要求首次登录强制修改密码,至少使用 `crypto.randomBytes` 生成 ### P0-13:users/actions.ts updateUserProfile 绕过权限校验 - **文件**:`src/modules/users/actions.ts` - **行号**:29-51 - **问题**:`updateUserProfile` 使用 `requireAuth()` 而非 `requirePermission()`,且在 actions 层直接执行 `db.update(users)`,返回 `Promise` 而非 `ActionState` - **修复**:改为 `requirePermission(Permissions.USER_MANAGE)` 或新增 `USER_PROFILE_UPDATE` 权限点;DB 操作下沉到 data-access;返回 `ActionState` ### P0-14:scheduling/data-access.ts 缺返回类型标注 - **文件**:`src/modules/scheduling/data-access.ts` - **行号**:239, 246, 255, 262 - **问题**:`getAdminClassesForScheduling()`、`getTeachersForScheduling()`、`getClassroomsForScheduling()`、`getClassSubjectsForScheduling()` 缺少返回类型标注,违反"函数返回值必须显式标注,特别是 Promise" - **修复**:添加返回类型 `: Promise>` 等 --- ## 三、按模块详细核查 ### 3.1 exams(考试模块) #### `src/modules/exams/actions.ts`(767 行) | 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 | |---------|---------|---------|---------|---------| | 270 | TS规范 | P1 | `formData.get("questionsJson") as string \| null` — 使用 `as` 断言,非从 unknown 转换 | 改用 `typeof` 守卫:`const raw = formData.get("questionsJson"); const rawQuestions = typeof raw === "string" ? raw : null` | | 349-351 | TS规范 | P1 | 三处 `as string \| null` 断言(`questionsJson`/`aiQuestionsJson`/`structureJson`) | 统一使用 `getStringValue()` 辅助函数 | | 4 | TS规范 | P2 | `import { ActionState }` — 类型未使用 `import type` | 改为 `import type { ActionState }` | | 564-565 | Server Action规范 | P2 | `JSON.parse(rawQuestions)` 直接 parse 后传入 Zod,parse 异常未捕获 | 用 try-catch 包裹或先 `as unknown` 再 safeParse | | 全文件 | 行数 | P2 | 实际 767 行,架构文档记录为 691 行,文档与代码不同步 | 同步更新 004 和 005 架构文档 | **合规项**:所有 10 个 Action 均调用 `requirePermission()` ✓;均使用 Zod 验证 ✓;均返回 `ActionState` ✓;均使用 `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` — 虽然从 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` 断言 | 使用类型守卫或 `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`,直接返回 `getQuestions(params)` 的原始结果 `{ data, meta }`,错误时 throw | 改为 `Promise>` 返回 `{ success: true, data: result }` | | 166-175 | Server Action规范 | **P0** | `getKnowledgePointOptionsAction` 未返回 `ActionState`,直接返回 `KnowledgePointOption[]` | 同上,包装为 ActionState | | 154 | TS规范 | P1 | `getQuestionsAction` 缺少显式返回类型标注 | 添加 `: Promise>` | | 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` ✓;使用 `revalidatePath` ✓;scope 二次校验 ✓。 #### `src/modules/grades/actions-analytics.ts`(133 行) **合规项**:所有 Action 调用 `requirePermission()` ✓;返回 `ActionState` ✓;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)` — `as` 断言 | 使用类型守卫或在 `Chapter` 类型中明确 `children` 字段 | | 71 | TS规范 | P2 | `or(...)!` — 对 `or()` 返回值使用非空断言 | 改用条件判断 | | 110, 128 | 代码质量 | P2 | `getTextbookById` 返回 `Textbook \| undefined`,与其他模块返回 `null` 的模式不一致 | 统一返回 `Textbook \| null` | | 368, 381 | 代码质量 | P2 | `createKnowledgePoint`/`updateKnowledgePoint` 参数使用内联类型 | 将输入类型移至 `types.ts` | **合规项**:使用 `cache()` ✓;使用 `Promise.all` ✓;`"server-only"` ✓;无 `any` ✓;显式返回类型 ✓。这是架构文档中的"标杆模块",无跨模块 DB 访问 ✓。 #### `src/modules/textbooks/types.ts`(83 行) | 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 | |---------|---------|---------|---------|---------| | 1-3 | 代码质量 | P2 | 残留注释 `// In a real app, we would infer these from the schema...` | 删除过时注释 | --- ### 3.6 classes(班级模块) #### `src/modules/classes/actions.ts`(765 行) | 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 | |---------|---------|---------|---------|---------| | 8-9 | 架构违规 | **P0** | actions 层直接导入 `db` 和 `grades, classes` schema 并执行 DB 查询 | 将权限校验查询下沉到 `data-access-admin.ts` | | 48-53, 109-111, 140-142, 175-183, 369-377, 518-533, 636-644 | Server Action规范 | P1 | 输入验证使用手动 `typeof` 检查而非 Zod,classes 模块完全没有 schema.ts 文件 | 新建 `schema.ts`,定义 Zod schema | | 538 | TS规范 | P1 | `weekdayNum as 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7` — `as` 断言 | 用类型守卫 `isValidWeekday(weekdayNum): weekdayNum is 1\|2\|3\|4\|5\|6\|7` 替换 | | 582 | TS规范 | P1 | `weekdayNum as 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| undefined` — 同上 | 同上 | | 287, 706 | TS规范 | P2 | `JSON.parse(subjectTeachers) as unknown` 后续用 `as { subject?: unknown }` | 用 Zod schema 解析 | #### `src/modules/classes/data-access.ts`(656 行) | 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 | |---------|---------|---------|---------|---------| | 26-27, 182-206 | 架构违规 | **P0** | `getTeacherClasses` 中调用 `getClassHomeworkInsights`(homework 逻辑)和 `getClassSchedule`(scheduling 逻辑) | 将 homework insights 聚合逻辑移至 homework 模块 | | 47 | TS规范 | P1 | `isDuplicateInvitationCodeError` 箭头函数缺少返回类型标注 | 添加 `: boolean` | | 54 | TS规范 | P1 | `generateInvitationCode` 箭头函数缺少返回类型标注 | 添加 `: string` | | 87 | TS规范 | P1 | `normalizeSortText` 箭头函数缺少返回类型标注 | 添加 `: string` | | 89 | TS规范 | P1 | `parseFirstInt` 箭头函数缺少返回类型标注 | 添加 `: number \| null` | | 94 | TS规范 | P1 | `compareGradeLabel` 箭头函数缺少返回类型标注 | 添加 `: number` | | 101-104 | TS规范 | P1 | `compareClassLike` 箭头函数缺少返回类型标注 | 添加 `: number` | | 240 | TS规范 | P1 | `r.subject as ClassSubject` — `as` 断言 | 用类型守卫 `isClassSubject(r.subject)` 过滤 | | 266 | TS规范 | P1 | `r.name as ClassSubject` — 同上 | 同上 | | 287 | TS规范 | P2 | `idByName.get(name)!` — 非空断言 | 用 `const id = idByName.get(name); if (!id) return null` 显式处理 | | 297-299 | 代码质量 | P1 | `throw new Error("Failed to create class")` 后有 `return id` 不可达代码 | 删除 `return id` | | 561 | TS规范 | P1 | `r.name as ClassSubject` — `as` 断言 | 用类型守卫替换 | | 567 | TS规范 | P2 | `idByName.get(name)!` — 非空断言 | 显式判空处理 | | 120-127 | 性能 | P2 | `getAccessibleClassIdsForTeacher` 中两个独立 DB 查询串行执行 | 用 `Promise.all` 并行化 | #### `src/modules/classes/data-access-admin.ts`(441 行) | 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 | |---------|---------|---------|---------|---------| | 82-117, 237-239 | 代码质量 | P1 | try-catch 吞掉错误返回空数组或 fallback 查询 | 移除 try-catch 或记录错误日志后 rethrow | | 135 | TS规范 | P1 | `r.subject as ClassSubject` — `as` 断言 | 用类型守卫替换 | | 259 | TS规范 | P1 | `r.subject as ClassSubject` — 同上 | 同上 | | 303 | TS规范 | P1 | `getManagedGrades` 缺少返回类型标注 | 添加 `: Promise` | | 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 = { updatedAt: new Date() }` — 使用宽泛的 `Record` 丢失类型安全 | 使用 `Partial` 类型 | #### `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` 而非 `ActionState` | 改为返回 `Promise>` | | 29 | TS规范 | P2 | `updateUserProfile(data: UpdateUserProfileInput)` 缺少显式返回类型标注 | 标注为 `Promise>` | | 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[] = []` | | 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` 使用 `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>>>` 类型表达式过于复杂 | 在 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` 而非 `ActionState` | 移至 data-access.ts 或包装为 ActionState | | 38-46 | 架构违规 | P2 | `AiProviderSummary` 类型定义在 actions.ts 中 | 新建 `types.ts`,将类型移入 | | 48-51 | TS规范 | P2 | `ensureUser` 是 async 箭头函数,未显式标注返回类型 | 添加 `: Promise<{ id: string }>` | | 53-60 | TS规范 | P2 | `normalizeBaseUrl` 未显式标注返回类型 | 添加 `: string \| null` | | 95-113 | 性能 | P2 | `upsertAiProviderAction` 的更新分支中,`count()` 查询与 `select` 查询是两个独立查询,串行执行 | 用 `Promise.all` 并行化 | | 120, 152 | 命名规范 | P2 | `nextIsDefault`、`makeDefault` 布尔变量前缀不规范 | 改为 `isNextDefault`、`shouldMakeDefault` | #### `src/modules/settings/actions-password.ts`(113 行) | 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 | |---------|---------|---------|---------|---------| | 7, 58-62, 78-81, 83-87, 89-105 | 架构违规 | P1 | actions-password.ts 直接 `import { db }` 并执行 DB 操作,无 data-access.ts | 将 DB 操作下沉至 `data-access.ts` | | 39-51 | Server Action规范 | P1 | 输入通过 `String(formData.get(...))` 手动读取,未使用 Zod schema 验证 | 定义 Zod schema 并 safeParse | | 30 | Server Action规范 | P2 | `changePasswordAction` 使用 `requireAuth()` 而非 `requirePermission()` | 新增 `PASSWORD_SELF_CHANGE` 权限点并赋给所有角色 | | 58-87 | 性能 | P2 | 查询 user 和查询 passwordSecurity 是两个独立表的查询,当前串行执行 | 重构为 `Promise.all` 并行获取 | | 14-18 | TS规范 | P2 | `normalizeBcryptHash` 未显式标注返回类型 | 添加 `: string` | --- ### 3.23 layout(布局模块) #### `src/modules/layout/config/navigation.ts`(317 行) | 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 | |---------|---------|---------|---------|---------| | 30-31 | TS规范 | P2 | `NavItem.permission` 和子项的 `permission` 均为 `string` 类型,但项目已有 `Permission` 类型 | 导入 Permission 类型并使用 | | 34 | 架构违规 | P2 | `export type Role = "admin" \| "teacher" \| "student" \| "parent"` 定义在 config 文件中 | 考虑将 Role 类型提升至 `@/shared/types` | --- ## 四、跨模块共性问题 ### 4.1 架构违规(P0/P1,最高优先级) #### 4.1.1 跨模块直接查询 DB 表(最普遍问题) | 调用方模块 | 被查模块表 | 严重程度 | |-----------|-----------|---------| | exams | questions(直写)、classes、subjects、grades | P0/P1 | | homework | exams(5 处)、classes、classEnrollments、subjects、users | P1 | | questions | knowledgePoints、chapters、textbooks | P1 | | grades | classes、classEnrollments、subjects、users | P1 | | classes | homeworkAssignments、homeworkSubmissions、exams、classSchedule | P0 | | school | (actions 层直接 DB 操作) | P0 | | scheduling | users(actions 层) | P0 | | messaging | classEnrollments、classes | P1 | | notifications | classes、classEnrollments、messaging(循环) | P0/P1 | | parent | users、grades、classEnrollments、classes | P1 | | proctoring | exams、examSubmissions | P0/P1 | | diagnostic | examSubmissions、submissionAnswers、questionsToKnowledgePoints | P1 | | elective | classes、classEnrollments | P1 | **统一修复方案**:在各模块 data-access 暴露跨模块查询接口,消除直查。 #### 4.1.2 messaging↔notifications 循环依赖 - `messaging/data-access.ts` 写 `messageNotifications` 表 - `notifications/data-access.ts` 反向 import messaging 的 `notification-preferences` 和 `types` - `notifications/channels/in-app-channel.ts` 动态 import messaging 的 `data-access` **修复方案**:将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块。 #### 4.1.3 actions 层直接操作 DB | 模块 | 文件 | 问题 | |------|------|------| | school | actions.ts | 所有 mutation 直接操作 DB | | scheduling | actions.ts | 直接查询 users 表 | | proctoring | actions.ts | 直接查询 examSubmissions 表 | | classes | actions.ts | 直接查询 grades、classes 表做权限校验 | | settings | actions.ts、actions-password.ts | 无 data-access.ts,直接 DB 操作 | | users | actions.ts | updateUserProfile 直接 db.update | **统一修复方案**:DB 操作必须下沉到 data-access.ts。 ### 4.2 TypeScript 规范违规(P1) #### 4.2.1 `as` 断言滥用(非从 unknown 转换) | 模块 | 文件 | 行号 | |------|------|------| | exams | data-access.ts | 76, 160, 361, 108, 172 | | exams | actions.ts | 270, 349-351 | | homework | data-access.ts | 39, 124, 226, 314, 392, 467, 645 | | homework | stats-service.ts | 220 | | textbooks | actions.ts | 54-58, 92-98, 154, 219-221, 259-261(14 处) | | textbooks | data-access.ts | 51 | | classes | data-access.ts | 240, 266, 561 | | classes | data-access-admin.ts | 135, 259, 349 | | classes | data-access-schedule.ts | 54, 93 | | classes | data-access-stats.ts | 250, 261, 512, 523 | | classes | actions.ts | 538, 582 | | course-plans | data-access.ts | 149 | | messaging | data-access.ts | 80, 74, 85 | | notifications | channels/in-app-channel.ts | 54(非法) | | notifications | channels/sms-channel.ts | 32 | | audit | data-access.ts | 75, 122, 123, 186 | | elective | data-access.ts | 117 | | elective | data-access-selections.ts | 102, 114 | | proctoring | data-access.ts | 127, 152, 208, 290, 313, 378 | | proctoring | actions.ts | 39 | | diagnostic | data-access-reports.ts | 29-31 | | parent | data-access.ts | 30 | | announcements | data-access.ts | 46-47, 59, 62 | | attendance | data-access.ts | 195(Record) | **统一修复方案**:用类型守卫函数或 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` | 模块 | 文件 | Action | |------|------|--------| | questions | actions.ts | getQuestionsAction, getKnowledgePointOptionsAction | | users | actions.ts | updateUserProfile(返回 Promise) | | settings | actions.ts | getAiProviderSummaries(返回原始数组) | ### 4.4 重复代码 | 重复内容 | 出现位置 | 修复方案 | |---------|---------|---------| | `normalizeRoleName` | users/data-access.ts、shared/lib/role-utils.ts | 复用 shared | | `normalizeBcryptHash` | users/user-service.ts、settings/actions-password.ts、shared/lib/bcrypt-utils.ts | 复用 shared | | `toNumber`/`normalize`/`buildScopeClassFilter` | grades/data-access.ts、data-access-analytics.ts、data-access-ranking.ts | 提取到 grades/utils.ts | | `serializeDate` | scheduling/data-access.ts、attendance/data-access.ts、attendance/data-access-stats.ts | 提取到 shared | | `toIso` | announcements/data-access.ts、messaging/data-access.ts、messaging/notification-preferences.ts、audit/data-access.ts | 提取到 shared | | try/catch 错误处理块 | announcements/actions.ts(6 处) | 提取 `handleActionError(e)` | | Excel 导出逻辑 | audit/actions.ts(3 处) | 抽取 `buildExcelSheet` | ### 4.5 错误吞没(静默 catch) | 模块 | 文件 | 处数 | |------|------|------| | school | data-access.ts | 6 | | classes | data-access-admin.ts、data-access-students.ts | 多处 | | course-plans | data-access.ts | 2 | | announcements | data-access.ts | 2 | | files | data-access.ts | 9 | | audit | data-access.ts | 3 | | elective | data-access.ts | 2 | **统一修复方案**:至少 `console.error` 记录错误,或向上抛出由调用方处理。 ### 4.6 不可达代码 | 模块 | 文件 | 行号 | |------|------|------| | classes | data-access.ts | 297-299(throw 后 return id) | | classes | data-access-admin.ts | 380-382(throw 后 return id) | | questions | actions.ts | 158-163, 170-175(catch 块两分支都 throw e) | --- ## 五、性能优化专项(react-best-practices) 依据 Vercel React Best Practices 规则集,识别以下性能优化机会: ### 5.1 CRITICAL:消除瀑布流(async-*) #### 5.1.1 `async-parallel`:独立操作用 Promise.all() | 模块 | 文件 | 行号 | 问题 | 修复 | |------|------|------|------|------| | classes | data-access.ts | 120-127 | `getAccessibleClassIdsForTeacher` 两个独立 DB 查询串行 | `Promise.all` | | settings | actions.ts | 95-113 | `upsertAiProviderAction` count 与 select 串行 | `Promise.all` | | settings | actions-password.ts | 58-87 | user 与 passwordSecurity 查询串行 | `Promise.all` | | elective | data-access-operations.ts | 18-34 | `runLottery` course 与 selections 串行 | `Promise.all` | | elective | data-access-operations.ts | 86-112 | `selectCourse` course 与 existing 串行 | `Promise.all` | | elective | data-access-operations.ts | 158-182 | `dropCourse` existing 与 course 串行 | `Promise.all` | | proctoring | data-access.ts | 178-223 | `getExamProctoringSummary` 三个独立查询串行 | `Promise.all` | | diagnostic | data-access-reports.ts | 172-180 | `getDiagnosticReportById` report 与 generator 串行 | 合并 JOIN 或 `Promise.all` | | parent | data-access.ts | 58-117 | `getChildBasicInfo` 4 个串行查询 | JOIN 或 `Promise.all` | #### 5.1.2 `async-defer-await`:将 await 移到实际使用的分支 无明显违规,多数文件已正确处理。 ### 5.2 HIGH:服务端性能(server-*) #### 5.2.1 `server-cache-react`:用 React.cache() 做请求内去重 | 模块 | 文件 | 缺失 cache() 的函数 | |------|------|---------------------| | grades | data-access.ts | getGradeRecords, getGradeRecordById, getClassGradeStats | | grades | data-access-analytics.ts | getClassComparison, getGradeTrends | | grades | data-access-ranking.ts | getClassRanking, getStudentRanking | | elective | data-access-selections.ts | getCourseSelections, getStudentSelections, getStudentGradeId, getAvailableCoursesForStudent | | diagnostic | data-access.ts | getStudentMastery, getStudentMasterySummary, getClassMasterySummary, getKnowledgePointStats | | diagnostic | data-access-reports.ts | getDiagnosticReports, getDiagnosticReportById | | files | data-access.ts | getFileAttachment, getFileAttachmentsByTarget, getFileAttachmentsByUploader, getAllFileAttachments, getFileAttachmentsWithFilters, getFileStats, getFileAttachmentsByIds | | proctoring | data-access.ts | 多个查询函数 | **标杆**:exams、homework、questions、textbooks、dashboard、announcements 已正确使用 `cache()` ✓。 #### 5.2.2 `server-after-nonblocking`:用 after() 做非阻塞操作 | 模块 | 文件 | 行号 | 问题 | |------|------|------|------| | school | actions.ts | 188, 219, 235 | `await logAudit(...)` 阻塞响应 | **修复**:使用 Next.js `after()` 将审计日志改为非阻塞。 #### 5.2.3 `server-serialization`:最小化传给客户端组件的数据 需结合前端组件审查,本次后端核查未深入。 ### 5.3 MEDIUM:JavaScript 性能(js-*) #### 5.3.1 `js-set-map-lookups`:用 Set/Map 做 O(1) 查找 | 模块 | 文件 | 行号 | 问题 | |------|------|------|------| | grades | data-access-analytics.ts | 133-136 | `scope.classIds.includes(c.id)` — O(n) 查找 | | diagnostic | data-access.ts | 116 | `answers.find(...)` 在 for 循环内 — O(n×m) | **修复**:将数组转为 Set/Map 后用 `.has()` / `.get()`。 #### 5.3.2 `js-combine-iterations`:合并多次 filter/map 为一次循环 | 模块 | 文件 | 行号 | 问题 | |------|------|------|------| | grades | data-access.ts | 248-252 | 两次 if 判断遍历同一数组 | | grades | data-access-analytics.ts | 180-181, 238-239 | 两次 filter 遍历同一数组 | | grades | data-access-ranking.ts | 100, 102 | findIndex 和 find 查找同一元素 | | diagnostic | data-access.ts | 80-81 | 两次 filter 遍历同一数组 | | proctoring | data-access.ts | 188-193 | 两次 filter 统计 submissions | #### 5.3.3 `js-early-exit`:函数早返回 多数文件已正确处理。 #### 5.3.4 动态 import 改静态 import | 模块 | 文件 | 行号 | 问题 | |------|------|------|------| | grades | data-access.ts | 148, 172 | `await import("@paralleldrive/cuid2")` 函数内动态导入 | | diagnostic | data-access-reports.ts | 59, 103 | 同上 | **修复**:改为文件顶部静态 `import { createId } from "@paralleldrive/cuid2"`。 ### 5.4 性能问题汇总(按影响排序) | 优先级 | 问题 | 影响范围 | |--------|------|---------| | **CRITICAL** | grades N+1 查询(getClassComparison) | 大班级成绩对比性能差 | | **HIGH** | 循环内串行 await db.update/insert(homework/data-access-write.ts、elective/data-access-operations.ts、diagnostic/data-access.ts、course-plans/data-access.ts) | 批量操作慢 | | **HIGH** | 大量读函数未用 React.cache()(grades、elective、diagnostic、files、proctoring) | 请求内重复查询 | | **MEDIUM** | 独立查询未并行化(9 处) | 响应时间累加 | | **MEDIUM** | 动态 import(3 处) | 每次调用有开销 | | **LOW** | 重复 filter 遍历(5 处) | 微小性能损失 | | **LOW** | O(n) 查找代替 O(1)(2 处) | 微小性能损失 | --- ## 六、优先修复路线图 ### 第一阶段:P0 严重问题(立即修复) 1. **exams/data-access.ts**:`persistAiGeneratedExamDraft` 改为调用 questions data-access 2. **exams/data-access.ts**:`getExams`/`getExamById` 改为通过 classes data-access 获取 gradeId 3. **questions/schema.ts**:`z.any()` → `z.unknown()` 4. **questions/actions.ts**:`getQuestionsAction` 和 `getKnowledgePointOptionsAction` 包装为 ActionState 5. **textbooks/actions.ts**:新建 schema.ts,添加 Zod 验证;清理 14 处 `as` 断言 6. **grades/data-access-analytics.ts**:`getClassComparison` N+1 查询重构为 `inArray` 批量查询 7. **classes/data-access-stats.ts、data-access-students.ts**:移除对 homework/exams 表的直接查询 8. **school/actions.ts**:所有 DB 操作下沉到 data-access.ts 9. **proctoring/actions.ts**:DB 查询下沉到 data-access.ts 10. **messaging↔notifications**:将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块 11. **notifications/channels/in-app-channel.ts:54**:修复非法 as 断言 12. **users/user-service.ts:13**:移除硬编码弱密码 "123456" 13. **users/actions.ts**:`updateUserProfile` 整改(权限+下沉+返回类型) 14. **scheduling/data-access.ts**:4 个函数补充返回类型标注 ### 第二阶段:P1 重要问题(尽快修复) 1. **跨模块直查 DB**:在各模块 data-access 暴露跨模块查询接口,消除直查(涉及 exams、homework、questions、grades、classes、messaging、notifications、parent、proctoring、diagnostic、elective) 2. **`as` 断言清理**:全局替换为类型守卫(涉及 20+ 文件,60+ 处) 3. **`requireAuth` → `requirePermission`**:users、messaging、proctoring、announcements、settings 共 8 处 4. **`import type` 修正**:exams、questions、textbooks、proctoring 共 4 处 5. **动态 import 改静态**:grades、diagnostic 共 3 处 6. **grades/data-access.ts:249**:除零 bug 守卫 7. **audit/data-access.ts**:导出函数数据截断问题(3 处) 8. **elective/data-access-operations.ts**:`runLottery` 加事务包裹 + 批量更新 9. **diagnostic/actions.ts**:新建 schema.ts,用 Zod 替代手动校验 10. **classes 模块**:新建 schema.ts,用 Zod 替换 actions.ts 中的手动校验 11. **course-plans/actions.ts**:补齐 revalidatePath 调用(2 处) 12. **homework/data-access-write.ts:305-311**:`gradeHomeworkAnswers` 加事务包裹 13. **users/user-service.ts**:`batchImportUsers` 加事务包裹 14. **不可达代码清理**:classes/data-access.ts:297-299、classes/data-access-admin.ts:380-382 ### 第三阶段:P2 优化(迭代修复) 1. **React.cache() 包装**:grades、elective、diagnostic、files、proctoring 读函数添加 cache() 2. **Promise.all 并行化**:9 处独立查询串行执行 3. **`after()` 非阻塞审计日志**:school/actions.ts(3 处) 4. **错误吞没清理**:school、classes、course-plans、announcements、files、audit、elective 共 30+ 处静默 catch 5. **非空断言 `!` 清理**:20+ 处替换为显式判空 6. **函数返回类型补齐**:30+ 个箭头函数 7. **重复代码提取**:normalizeRoleName、normalizeBcryptHash、toIso、serializeDate、toNumber/normalize、handleActionError、buildExcelSheet 8. **合并 filter 遍历**:5 处 9. **Set/Map 查找优化**:2 处 10. **架构文档同步**:exams/actions.ts 行数、settings 导出函数列表、P2-11 状态、announcements 依赖关系 11. **exams/ai-pipeline.ts 拆分**:912 行按职责拆分为 4 个文件 12. **layout/navigation.ts**:permission 字段改用 Permission 类型 13. **notifications/external-sdk.d.ts**:安装实际 SDK 后用其自带类型覆盖 any --- ## 附录:核查方法说明 ### 核查流程 1. **架构图优先**:先阅读 `docs/architecture/004_architecture_impact_map.md`,理解模块依赖关系和已知问题 2. **并行核查**:启动 5 个并行搜索代理,分别核查核心教学、教学管理、用户沟通、扩展功能、其他模块 3. **逐文件审查**:每个代理读取所有后端 `.ts` 文件,对照项目规范逐条核查 4. **性能规则应用**:应用 Vercel React Best Practices 的 65 条规则,识别性能优化机会 5. **结果汇总**:合并 5 个代理的核查结果,按严重程度和模块分类整理 ### 核查依据 - `.trae/rules/project_rules.md`:项目规则(架构分层、TS规范、命名、组件、Server Action、Tailwind、安全、提交) - `docs/standards/coding-standards.md`:编码规范(详细规范) - `docs/architecture/004_architecture_impact_map.md`:架构影响地图(模块清单、依赖关系、已知问题) - `docs/architecture/005_architecture_data.json`:AI 友好格式的结构化数据 - Vercel React Best Practices:65 条性能优化规则(8 个类别) ### 未核查范围 - `src/modules/*/components/` 下的前端组件文件 - `src/modules/*/hooks/` 下的 Hook 文件 - `src/shared/` 下的基础设施文件 - `src/app/` 下的路由层文件 - 测试文件(`*.test.ts`) 如需核查上述范围,请另行指派任务。