Bug fixes (from bugs/ directory): - Fix cross-module DB queries in 9 modules (homework, grades, parent, diagnostic, elective, proctoring, notifications, scheduling, classes) by routing through data-access functions - Fix shared/lib <-> auth circular dependency via new session.ts module - Fix divide-by-zero guard in grades data-access - Fix audit export data truncation (paginated fetch for full datasets) - Fix missing transactions in homework grading and elective lottery - Fix missing revalidatePath in course-plans actions - Fix frontend permission checks using requirePermission instead of requireAuth - Fix dashboard role routing using session.user.roles - Fix student auth pattern (migrate getDemoStudentUser to users module) - Fix ActionState return type handling in components Code quality fixes: - Remove 60+ as type assertions (replace with type guards) - Remove non-null assertions (use optional chaining or explicit checks) - Convert dynamic imports to static imports (grades, diagnostic) - Add React.cache() wrapping for read functions - Parallelize independent queries with Promise.all - Add explicit return types to 30+ arrow functions - Replace any with unknown + type guards - Fix import type for type-only imports - Add Zod validation schemas for classes and diagnostic modules - Extract duplicate code (normalizeRoleName, normalizeBcryptHash, logger IP extraction) - Add console.error to silent catch blocks - Fix permission naming consistency (exam:proctor_read -> exam:proctor:read) Architecture doc sync: - Update 004_architecture_impact_map.md and 005_architecture_data.json - Update management-modules-audit.md for P0-7 cross-module fix Moved deleted proctoring event route to deletes/ folder.
1435 lines
89 KiB
Markdown
1435 lines
89 KiB
Markdown
# 后端模块规范核查报告
|
||
|
||
> 核查日期:2026-06-18
|
||
> 核查范围:`src/modules/` 下所有后端 `.ts` 文件(actions / data-access / schema / types / services 等非 components、非 hooks 文件)
|
||
> 核查依据:
|
||
> - `.trae/rules/project_rules.md` 项目规则
|
||
> - `docs/standards/coding-standards.md` 编码规范
|
||
> - `docs/architecture/004_architecture_impact_map.md` 架构影响地图
|
||
> - Vercel React Best Practices 性能优化规则(react-best-practices 技能)
|
||
>
|
||
> 严重程度定义:
|
||
> - **P0**:严重违规,破坏架构分层 / 安全漏洞 / 数据正确性问题,必须立即修复
|
||
> - **P1**:明确违规,影响可维护性 / 类型安全 / 性能,应尽快修复
|
||
> - **P2**:轻微违规或代码异味,可在迭代中优化
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [一、汇总统计](#一汇总统计)
|
||
- [二、P0 严重问题清单(立即修复)](#二p0-严重问题清单立即修复)
|
||
- [三、按模块详细核查](#三按模块详细核查)
|
||
- [3.1 exams(考试模块)](#31-exams考试模块)
|
||
- [3.2 homework(作业模块)](#32-homework作业模块)
|
||
- [3.3 questions(题库模块)](#33-questions题库模块)
|
||
- [3.4 grades(成绩模块)](#34-grades成绩模块)
|
||
- [3.5 textbooks(教材模块)](#35-textbooks教材模块)
|
||
- [3.6 classes(班级模块)](#36-classes班级模块)
|
||
- [3.7 school(学校模块)](#37-school学校模块)
|
||
- [3.8 scheduling(排课模块)](#38-scheduling排课模块)
|
||
- [3.9 attendance(考勤模块)](#39-attendance考勤模块)
|
||
- [3.10 course-plans(教学计划模块)](#310-course-plans教学计划模块)
|
||
- [3.11 users(用户模块)](#311-users用户模块)
|
||
- [3.12 messaging(消息模块)](#312-messaging消息模块)
|
||
- [3.13 notifications(通知模块)](#313-notifications通知模块)
|
||
- [3.14 parent(家长模块)](#314-parent家长模块)
|
||
- [3.15 audit(审计模块)](#315-audit审计模块)
|
||
- [3.16 elective(选修课模块)](#316-elective选修课模块)
|
||
- [3.17 proctoring(监考模块)](#317-proctoring监考模块)
|
||
- [3.18 diagnostic(诊断模块)](#318-diagnostic诊断模块)
|
||
- [3.19 dashboard(仪表盘模块)](#319-dashboard仪表盘模块)
|
||
- [3.20 files(文件模块)](#320-files文件模块)
|
||
- [3.21 announcements(公告模块)](#321-announcements公告模块)
|
||
- [3.22 settings(设置模块)](#322-settings设置模块)
|
||
- [3.23 layout(布局模块)](#323-layout布局模块)
|
||
- [四、跨模块共性问题](#四跨模块共性问题)
|
||
- [五、性能优化专项(react-best-practices)](#五性能优化专项react-best-practices)
|
||
- [六、优先修复路线图](#六优先修复路线图)
|
||
|
||
---
|
||
|
||
## 一、汇总统计
|
||
|
||
### 1.1 按严重程度
|
||
|
||
| 严重程度 | 数量 | 典型问题 |
|
||
|---------|------|---------|
|
||
| **P0** | 14 | 跨模块直查/直写 DB 表、循环依赖、`z.any()`、N+1 查询、未返回 ActionState、无 Zod 验证、硬编码弱密码 |
|
||
| **P1** | 60+ | `as` 断言滥用、`requireAuth` 替代 `requirePermission`、缺少 `import type`、动态 import、无事务、除零 bug、导出数据截断 |
|
||
| **P2** | 100+ | 非空断言 `!`、未用 `React.cache()`、串行查询未并行化、重复 filter、静默吞异常、缺返回类型标注、文档不同步 |
|
||
|
||
### 1.2 按问题类别
|
||
|
||
| 问题类别 | 涉及文件数 | 关键发现 |
|
||
|---------|-----------|---------|
|
||
| 架构违规 | 25+ | 跨模块直查 DB 是最普遍问题;messaging↔notifications 循环依赖;actions 层直接 DB 操作 |
|
||
| TS 规范 | 30+ | `as` 断言大量存在;`z.any()`;`import type` 未用;非空断言 `!`;隐式 `any[]` |
|
||
| Server Action 规范 | 12+ | `requireAuth` 替代 `requirePermission`;未返回 `ActionState`;无 Zod 验证;缺 `revalidatePath` |
|
||
| 性能 | 20+ | N+1 查询;动态 import;未用 `cache()`;串行 await 循环;重复 filter 遍历 |
|
||
| 安全 | 2 | 硬编码弱密码 "123456";`as` 断言掩盖类型不兼容 |
|
||
| 行数 | 1 | `exams/ai-pipeline.ts` 912 行(超 800 建议,未超 1000 硬上限) |
|
||
|
||
### 1.3 模块问题分布
|
||
|
||
| 模块 | P0 | P1 | P2 | 总体评价 |
|
||
|------|----|----|-----|---------|
|
||
| exams | 3 | 5 | 6 | 跨模块直查/直写严重 |
|
||
| homework | 0 | 8 | 5 | 跨模块直查普遍 |
|
||
| questions | 2 | 2 | 3 | `z.any()` + 未返回 ActionState |
|
||
| grades | 1 | 6 | 7 | N+1 查询 + 跨模块直查 |
|
||
| textbooks | 2 | 2 | 4 | 无 Zod 验证 + `as` 断言 |
|
||
| classes | 3 | 8 | 6 | 耦合最严重,混入多模块逻辑 |
|
||
| school | 1 | 1 | 2 | actions 层直接 DB 操作 |
|
||
| scheduling | 0 | 4 | 3 | 缺返回类型标注 |
|
||
| attendance | 0 | 1 | 0 | 整体规范 |
|
||
| course-plans | 0 | 3 | 2 | 缺 revalidatePath |
|
||
| users | 1 | 3 | 4 | 硬编码弱密码 + 无事务 |
|
||
| messaging | 1 | 6 | 6 | 循环依赖 + requireAuth |
|
||
| notifications | 3 | 3 | 8 | 循环依赖 + as 断言类型不兼容 |
|
||
| parent | 0 | 2 | 2 | 跨模块直查 |
|
||
| audit | 0 | 4 | 6 | 导出数据截断 |
|
||
| elective | 0 | 3 | 5 | 无事务 + 串行更新 |
|
||
| proctoring | 1 | 6 | 3 | actions 层直接 DB 操作 |
|
||
| diagnostic | 0 | 2 | 5 | 跨模块直查 + 性能问题 |
|
||
| dashboard | 0 | 0 | 0 | 标杆模块 |
|
||
| files | 0 | 2 | 3 | 隐式 any[] + 非空断言 |
|
||
| announcements | 0 | 1 | 5 | as 断言 + 错误吞没 |
|
||
| settings | 0 | 3 | 9 | 缺 data-access 层 + 无 Zod |
|
||
| layout | 0 | 0 | 2 | 类型松散 |
|
||
|
||
---
|
||
|
||
## 二、P0 严重问题清单(立即修复)
|
||
|
||
### P0-1:exams 模块直写 questions 表
|
||
|
||
- **文件**:`src/modules/exams/data-access.ts`
|
||
- **行号**:2, 318
|
||
- **问题**:`persistAiGeneratedExamDraft` 直接 `db.insert(questions)` 写入 questions 表(属 questions 模块),破坏模块封装
|
||
- **修复**:改为调用 `questions/data-access.createQuestionWithRelations()` 或在 questions 模块暴露批量插入接口
|
||
|
||
### P0-2:exams 模块直查 classes 表
|
||
|
||
- **文件**:`src/modules/exams/data-access.ts`
|
||
- **行号**:2, 72-80, 156-163, 357-364
|
||
- **问题**:`getExams`/`getExamById`/`getExamsDashboardStats` 直接查询 `classes` 表获取 gradeId
|
||
- **修复**:在 classes 模块暴露 `getGradeIdsByClassIds(classIds)` 接口
|
||
|
||
### P0-3:questions/schema.ts 使用 `z.any()`
|
||
|
||
- **文件**:`src/modules/questions/schema.ts`
|
||
- **行号**:6
|
||
- **问题**:`content: z.any()` 违反"禁止 any"规则
|
||
- **修复**:改为 `z.unknown()`
|
||
|
||
### P0-4:questions/actions.ts 未返回 ActionState
|
||
|
||
- **文件**:`src/modules/questions/actions.ts`
|
||
- **行号**:154, 166-175
|
||
- **问题**:`getQuestionsAction` 和 `getKnowledgePointOptionsAction` 直接返回原始结果,未包装为 `ActionState<T>`,错误时 throw 而非返回失败状态
|
||
- **修复**:改为 `Promise<ActionState<...>>` 返回 `{ success: true, data: result }`
|
||
|
||
### P0-5:textbooks 模块无 Zod 验证 + 大量 `as` 断言
|
||
|
||
- **文件**:`src/modules/textbooks/actions.ts`
|
||
- **行号**:全文件,特别 54-58, 92-98, 154, 219-221, 259-261
|
||
- **问题**:缺少 `schema.ts` 文件,所有 Action 均无 Zod 验证,使用手动 `if (!rawData.title)` 检查;14 处 `formData.get("title") as string` 断言
|
||
- **修复**:新建 `schema.ts`,为每个 Action 定义 Zod schema;改用 `typeof` 守卫或 `getStringValue()` 辅助函数
|
||
|
||
### P0-6:grades 模块 N+1 查询
|
||
|
||
- **文件**:`src/modules/grades/data-access-analytics.ts`
|
||
- **行号**:142-185
|
||
- **问题**:`getClassComparison` 中 `for (const cls of classRows) { ... await db.select(...) }` 循环内串行查询,N+1 问题
|
||
- **修复**:用 `inArray` 一次性查询所有班级的成绩,再在内存分组
|
||
|
||
### P0-7:classes 模块跨模块直接查询 DB 表
|
||
|
||
- **文件**:`src/modules/classes/data-access-stats.ts`、`src/modules/classes/data-access-students.ts`
|
||
- **行号**:data-access-stats.ts: 11-18;data-access-students.ts: 10-14
|
||
- **问题**:直接导入并查询 `homeworkAssignments`、`homeworkSubmissions`、`homeworkAssignmentTargets`、`homeworkAssignmentQuestions`、`exams` 等其他模块的表
|
||
- **修复**:通过 homework 模块的 data-access 暴露的函数获取数据
|
||
|
||
### P0-8:school 模块 actions 层直接操作 DB
|
||
|
||
- **文件**:`src/modules/school/actions.ts`
|
||
- **行号**:7-8, 26-30, 53-59, 73, 96-108, 133-147, 161, 182-186, 211-217, 233, 261-268, 294-303, 317
|
||
- **问题**:actions 层直接导入 `db` 和所有 schema,所有 mutation 直接操作 DB,未下沉到 data-access 层
|
||
- **修复**:在 `data-access.ts` 中新增 `createDepartment`、`updateDepartment`、`deleteDepartment`、`createSchool` 等函数
|
||
|
||
### P0-9:proctoring 模块 actions 层直接查询 DB
|
||
|
||
- **文件**:`src/modules/proctoring/actions.ts`
|
||
- **行号**:11-13, 79-84
|
||
- **问题**:actions.ts(编排层)直接导入 `db`、`examSubmissions` 并执行 DB 查询,违反三层架构
|
||
- **修复**:将 submission 归属校验逻辑移至 `data-access.ts`
|
||
|
||
### P0-10:messaging↔notifications 循环依赖
|
||
|
||
- **文件**:`src/modules/messaging/data-access.ts`、`src/modules/notifications/data-access.ts`、`src/modules/notifications/channels/in-app-channel.ts`
|
||
- **行号**:messaging/data-access.ts: 192-203;notifications/data-access.ts: 20-21;in-app-channel.ts: 50
|
||
- **问题**:messaging 写 `messageNotifications` 表,notifications 反向依赖 messaging,形成循环依赖
|
||
- **修复**:将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块
|
||
|
||
### P0-11:notifications/in-app-channel.ts 非法 as 断言
|
||
|
||
- **文件**:`src/modules/notifications/channels/in-app-channel.ts`
|
||
- **行号**:54
|
||
- **问题**:`payload.type as "message" | "announcement" | "homework" | "grade"` 源类型 `"info"|"warning"|"error"|"success"` 与目标类型不兼容,as 断言非法
|
||
- **修复**:重新设计类型映射函数,或统一 messaging/notifications 的 type 定义
|
||
|
||
### P0-12:users 模块硬编码弱密码
|
||
|
||
- **文件**:`src/modules/users/user-service.ts`
|
||
- **行号**:13
|
||
- **问题**:`const DEFAULT_PASSWORD = "123456"` 硬编码弱密码,批量导入用户使用极弱默认密码
|
||
- **修复**:改为随机生成密码或要求首次登录强制修改密码,至少使用 `crypto.randomBytes` 生成
|
||
|
||
### P0-13:users/actions.ts updateUserProfile 绕过权限校验
|
||
|
||
- **文件**:`src/modules/users/actions.ts`
|
||
- **行号**:29-51
|
||
- **问题**:`updateUserProfile` 使用 `requireAuth()` 而非 `requirePermission()`,且在 actions 层直接执行 `db.update(users)`,返回 `Promise<void>` 而非 `ActionState<T>`
|
||
- **修复**:改为 `requirePermission(Permissions.USER_MANAGE)` 或新增 `USER_PROFILE_UPDATE` 权限点;DB 操作下沉到 data-access;返回 `ActionState<void>`
|
||
|
||
### P0-14:scheduling/data-access.ts 缺返回类型标注
|
||
|
||
- **文件**:`src/modules/scheduling/data-access.ts`
|
||
- **行号**:239, 246, 255, 262
|
||
- **问题**:`getAdminClassesForScheduling()`、`getTeachersForScheduling()`、`getClassroomsForScheduling()`、`getClassSubjectsForScheduling()` 缺少返回类型标注,违反"函数返回值必须显式标注,特别是 Promise<T>"
|
||
- **修复**:添加返回类型 `: Promise<Array<{ id: string; name: string; grade: string }>>` 等
|
||
|
||
---
|
||
|
||
## 三、按模块详细核查
|
||
|
||
### 3.1 exams(考试模块)
|
||
|
||
#### `src/modules/exams/actions.ts`(767 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 270 | TS规范 | P1 | `formData.get("questionsJson") as string \| null` — 使用 `as` 断言,非从 unknown 转换 | 改用 `typeof` 守卫:`const raw = formData.get("questionsJson"); const rawQuestions = typeof raw === "string" ? raw : null` |
|
||
| 349-351 | TS规范 | P1 | 三处 `as string \| null` 断言(`questionsJson`/`aiQuestionsJson`/`structureJson`) | 统一使用 `getStringValue()` 辅助函数 |
|
||
| 4 | TS规范 | P2 | `import { ActionState }` — 类型未使用 `import type` | 改为 `import type { ActionState }` |
|
||
| 564-565 | Server Action规范 | P2 | `JSON.parse(rawQuestions)` 直接 parse 后传入 Zod,parse 异常未捕获 | 用 try-catch 包裹或先 `as unknown` 再 safeParse |
|
||
| 全文件 | 行数 | P2 | 实际 767 行,架构文档记录为 691 行,文档与代码不同步 | 同步更新 004 和 005 架构文档 |
|
||
|
||
**合规项**:所有 10 个 Action 均调用 `requirePermission()` ✓;均使用 Zod 验证 ✓;均返回 `ActionState<T>` ✓;均使用 `revalidatePath` ✓。
|
||
|
||
#### `src/modules/exams/ai-pipeline.ts`(912 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 全文件 | 行数 | P2 | 912 行,超出 800 行建议(架构文档记录 857 行,已不同步)。混合 4 类职责 | 按职责拆分为 `ai-json-parser.ts`、`ai-prompts.ts`、`ai-requests.ts`、`ai-preview-builder.ts` |
|
||
| 464 | TS规范 | P2 | `draft.sections!.forEach` — 非空断言 | 改用 `if (draft.sections) { draft.sections.forEach(...) }` |
|
||
| 657, 665, 666, 671 | TS规范 | P2 | 多处 `aiParsed.sections!.flatMap` / `aiParsed.sections!.map` — 非空断言 | 同上,用条件守卫替代 |
|
||
| 505, 508, 545, 879 | TS规范 | P2 | `as Record<string, unknown>` — 虽然从 unknown 转换允许,但缺少注释说明原因 | 添加注释 `// safe: validated by isRecord` |
|
||
| 557-558 | 代码质量 | P2 | `catch {}` 空捕获块,静默吞掉错误 | 至少记录 `console.warn` |
|
||
| 503-524 | 代码质量 | P2 | `normalizeQuestionCandidate` 嵌套定义在 `parseQuestionDetail` 内部,每次调用都重新创建闭包 | 提取为模块级函数 |
|
||
|
||
**合规项**:使用 `mapWithConcurrency` 实现并发控制 ✓;使用 `Promise.all` ✓;`env.AI_MODEL` 服务端环境变量无 `NEXT_PUBLIC_` 前缀 ✓。
|
||
|
||
#### `src/modules/exams/data-access.ts`(524 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 2, 318 | 架构违规 | **P0** | `persistAiGeneratedExamDraft` 直接 `db.insert(questions)` 写入 questions 表 | 改为调用 `questions/data-access.createQuestionWithRelations()` |
|
||
| 2, 72-80, 156-163, 357-364 | 架构违规 | **P0** | `getExams`/`getExamById`/`getExamsDashboardStats` 直接查询 `classes` 表 | 在 classes 模块暴露 `getGradeIdsByClassIds(classIds)` 接口 |
|
||
| 2, 206-226, 509-524 | 架构违规 | P1 | `resolveSubjectGradeNames`/`getExamSubjects`/`getExamGrades` 直接查询 `subjects`/`grades` 表 | 在 school 模块暴露 `getSubjectOptions()`/`getGradeOptions()` 接口 |
|
||
| 76, 160, 361 | TS规范 | P1 | `teacherGradeIds.map(g => g.gradeId).filter(Boolean) as string[]` — `as` 断言 | 使用类型守卫:`.filter((id): id is string => Boolean(id))` |
|
||
| 108, 172 | TS规范 | P2 | `(exam.status as ExamStatus)` — `as` 断言从 string 转换 | 使用 `z.enum()` 验证或类型守卫函数 |
|
||
| 182 | TS规范 | P2 | `exam.structure as unknown` — 转换到 unknown 允许,但无注释 | 添加注释说明 |
|
||
|
||
**合规项**:使用 `cache()` 包装查询函数 ✓;使用 `Promise.all` 并行查询 ✓;使用 `Map` 做 O(1) 查找 ✓。
|
||
|
||
#### `src/modules/exams/types.ts`(31 行)
|
||
|
||
无违规问题。类型定义规范,接口命名 PascalCase 无 `I` 前缀 ✓。
|
||
|
||
---
|
||
|
||
### 3.2 homework(作业模块)
|
||
|
||
#### `src/modules/homework/actions.ts`(282 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 247 | TS规范 | P1 | `formData.get("answersJson") as string \| null` — `as` 断言 | 改用 `typeof` 守卫 |
|
||
| 54 | TS规范 | P2 | `JSON.parse(targetStudentIdsJson) as unknown` — 转换到 unknown 允许,但无注释 | 添加注释 |
|
||
|
||
**合规项**:所有 5 个 Action 均调用 `requirePermission()` ✓;使用 Zod 验证 ✓;返回 `ActionState` ✓;使用 `revalidatePath` ✓;使用 `Set` 做查找 ✓。
|
||
|
||
#### `src/modules/homework/data-access.ts`(681 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 8-20, 101-105, 162-166, 283-287, 337-340, 518-519 | 架构违规 | P1 | 直接查询 `exams` 表(5 处),属 exams 模块 | 在 exams 模块暴露 `getExamIdsByGradeIds(gradeIds)` 接口 |
|
||
| 8-20, 66-77, 86-95, 149-158, 242-254, 275-281, 347-351 | 架构违规 | P1 | 直接查询 `classEnrollments` 表(多处),属 classes 模块 | 在 classes 模块暴露 `getStudentIdsByClassId(classId)` / `getStudentIdsByClassIds(classIds)` 接口 |
|
||
| 8-20, 515-519 | 架构违规 | P1 | 直接查询 `subjects` 表,属 school 模块 | 通过 school data-access 获取 |
|
||
| 8-20, 480-486 | 架构违规 | P1 | 直接查询 `users`/`roles`/`usersToRoles` 表,属 users 模块 | 在 users 模块暴露 `getUserWithRole(userId, roleName)` 接口 |
|
||
| 475-490 | 架构违规 | P2 | `getDemoStudentUser` 使用 `auth()` 而非 `getAuthContext()`,绕过 auth-guard 标准模式 | 改用 `getAuthContext()` 获取 userId |
|
||
| 39 | TS规范 | P1 | `return v as HomeworkQuestionContent` — 从 `Record<string, unknown>` 断言 | 使用类型守卫或 `z.safeParse()` 验证 |
|
||
| 124, 226, 314, 392, 467, 645 | TS规范 | P2 | 多处 `(status as HomeworkAssignmentStatus)` — `as` 断言 | 使用类型守卫函数或 Zod 验证 |
|
||
| 451-455 | 性能 | P2 | `getHomeworkSubmissionDetails` 获取全部 submissions 仅为计算 prev/next 导航 | 改用 SQL `LAG`/`LEAD` 窗口函数或仅查询相邻 ID |
|
||
|
||
**合规项**:使用 `cache()` ✓;使用 `Map`/`Set` 聚合 ✓;`"server-only"` 导入 ✓。
|
||
|
||
#### `src/modules/homework/data-access-write.ts`(317 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 8-17, 70-76, 125-130 | 架构违规 | P1 | 直接查询 `classes` 表(`getClassTeacherById`) | 在 classes 模块暴露 `getClassTeacherById(classId)` 接口 |
|
||
| 8-17, 132-143 | 架构违规 | P1 | 直接查询 `classEnrollments` 表(`getActiveClassStudentIdsForHomework`) | 在 classes 模块暴露 `getActiveStudentIdsByClassId(classId)` 接口 |
|
||
| 8-17, 107-116 | 架构违规 | P1 | 直接查询 `classSubjectTeachers` 表 | 在 classes 模块暴露 `getTeacherSubjectIdsByClass(classId, teacherId)` 接口 |
|
||
| 8-17, 81-88 | 架构违规 | P1 | 直接查询 `exams` 表(`getExamWithQuestionsForHomework`) | 在 exams 模块暴露 `getExamForHomeworkCreation(examId)` 接口 |
|
||
| 305-311 | 性能 | P2 | `gradeHomeworkAnswers` 中 `for (const ans of answers) { await db.update(...) }` — 循环内串行 await,未使用事务 | 用 `db.transaction` 包裹,或用 `Promise.all` 并行化无依赖更新 |
|
||
|
||
**合规项**:`"server-only"` 导入 ✓;所有函数显式标注返回类型 ✓;无 `as`/`any` ✓。
|
||
|
||
#### `src/modules/homework/schema.ts`(29 行)
|
||
|
||
无违规问题。Zod 验证完整 ✓。
|
||
|
||
#### `src/modules/homework/stats-service.ts`(483 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 8-16, 313-323, 320-323 | 架构违规 | P1 | 直接查询 `classEnrollments` 表 | 通过 classes data-access 获取 |
|
||
| 8-16, 437-441 | 架构违规 | P1 | 直接查询 `classes` 表 | 通过 classes data-access 获取 |
|
||
| 8-16, 425-429, 443-447 | 架构违规 | P1 | 直接查询 `exams` 表 | 通过 exams data-access 获取 |
|
||
| 8-16, 366-369 | 架构违规 | P1 | 直接查询 `users` 表 | 在 users 模块暴露 `getUserNamesByIds(ids)` 接口 |
|
||
| 220 | TS规范 | P2 | `(assignment.status as HomeworkAssignmentStatus)` — `as` 断言 | 使用类型守卫 |
|
||
| 346 | 性能 | P2 | `limit: 5000` 查询班级所有 graded submissions,大班级可能性能问题 | 考虑分批查询或 SQL 聚合 |
|
||
|
||
**合规项**:使用 `cache()` ✓;使用 `Promise.all` 并行查询 ✓;使用 `Map` 聚合 ✓。
|
||
|
||
#### `src/modules/homework/types.ts`(205 行)
|
||
|
||
无违规问题。类型定义规范 ✓。
|
||
|
||
---
|
||
|
||
### 3.3 questions(题库模块)
|
||
|
||
#### `src/modules/questions/actions.ts`(176 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 154 | Server Action规范 | **P0** | `getQuestionsAction` 未返回 `ActionState<T>`,直接返回 `getQuestions(params)` 的原始结果 `{ data, meta }`,错误时 throw | 改为 `Promise<ActionState<...>>` 返回 `{ success: true, data: result }` |
|
||
| 166-175 | Server Action规范 | **P0** | `getKnowledgePointOptionsAction` 未返回 `ActionState<T>`,直接返回 `KnowledgePointOption[]` | 同上,包装为 ActionState |
|
||
| 154 | TS规范 | P1 | `getQuestionsAction` 缺少显式返回类型标注 | 添加 `: Promise<ActionState<...>>` |
|
||
| 7 | TS规范 | P2 | `import { ActionState }` — 类型导入未用 `import type` | 改为 `import type { ActionState }` |
|
||
| 158-163, 170-175 | 代码质量 | P2 | catch 块两个分支都 `throw e`,是死代码 | 简化为单个 `throw e` 或返回失败 ActionState |
|
||
| 20 | 命名规范 | P2 | 函数名 `createNestedQuestion` 与其他模块的 `createXxxAction` 命名模式不一致 | 改为 `createQuestionAction` |
|
||
|
||
**合规项**:所有 Action 调用 `requirePermission()` ✓;create/update/delete 使用 Zod 验证 ✓;写操作使用 `revalidatePath` ✓。
|
||
|
||
#### `src/modules/questions/data-access.ts`(299 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 4, 266-298 | 架构违规 | P1 | `getKnowledgePointOptions` 直接查询 `knowledgePoints`/`chapters`/`textbooks` 表,属 textbooks 模块 | 在 textbooks 模块暴露 `getKnowledgePointOptions()` 接口 |
|
||
| 108 | 代码质量 | P2 | 局部变量名 `knowledgePoints` 与 import 的 `knowledgePoints` 表名冲突 | 重命名为 `kpRows` 或 `kps` |
|
||
| 151-184, 231-242 | 性能 | P2 | `insertQuestionWithRelations` / `deleteQuestionRecursive` 递归内串行 await | 对于删除可改为先收集所有 ID 再批量删除 |
|
||
|
||
**合规项**:使用 `cache()` ✓;`"server-only"` 导入 ✓;无 `as`/`any` ✓;显式返回类型 ✓。
|
||
|
||
#### `src/modules/questions/schema.ts`(18 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 6 | TS规范 | **P0** | `content: z.any()` — 使用 `any`,违反"禁止 any"规则 | 改为 `z.unknown()` |
|
||
|
||
#### `src/modules/questions/types.ts`(34 行)
|
||
|
||
无违规问题。`content: unknown` 正确使用 unknown ✓。
|
||
|
||
---
|
||
|
||
### 3.4 grades(成绩模块)
|
||
|
||
#### `src/modules/grades/actions.ts`(312 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 93 | 代码质量 | P2 | `JSON.parse(recordsJson)` 未用 try-catch 包裹,parse 失败会 500 | 用 try-catch 或先 `as unknown` 再 safeParse |
|
||
|
||
**合规项**:所有 Action 调用 `requirePermission()` ✓;使用 Zod 验证 ✓;返回 `ActionState<T>` ✓;使用 `revalidatePath` ✓;scope 二次校验 ✓。
|
||
|
||
#### `src/modules/grades/actions-analytics.ts`(133 行)
|
||
|
||
**合规项**:所有 Action 调用 `requirePermission()` ✓;返回 `ActionState<T>` ✓;scope 二次校验 ✓。无违规问题。
|
||
|
||
#### `src/modules/grades/data-access.ts`(419 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 6-12, 100, 382-386 | 架构违规 | P1 | 直接查询 `classes` 表 | 通过 classes data-access 获取 |
|
||
| 6-12, 365-368, 408-411 | 架构违规 | P1 | 直接查询 `classEnrollments` 表 | 通过 classes data-access 获取 |
|
||
| 6-12, 102, 276, 280 | 架构违规 | P1 | 直接查询 `subjects` 表 | 通过 school data-access 获取 |
|
||
| 6-12, 100, 109-112, 269, 342 | 架构违规 | P1 | 直接查询 `users` 表 | 通过 users data-access 获取 |
|
||
| 148, 172 | 性能 | P1 | `const { createId } = await import("@paralleldrive/cuid2")` — 函数内动态 import,每次调用都有开销 | 改为文件顶部静态 `import { createId } from "@paralleldrive/cuid2"` |
|
||
| 249 | 代码质量/Bug | P1 | `const ratio = scores[i] / fullScores[i]` — `fullScores[i]` 可能为 0,导致 `Infinity`/`NaN` | 添加 `if (fullScores[i] <= 0) continue` 守卫 |
|
||
| 全文件 | 性能 | P2 | 查询函数未使用 `React.cache()` 包装 | 用 `cache()` 包装查询函数 |
|
||
| 248-252 | 性能 | P2 | `for` 循环内两次独立 `if` 判断遍历同一数组 | 合并为单次循环同时计算 passCount 和 excellentCount |
|
||
|
||
**合规项**:`"server-only"` 导入 ✓;使用 `Map`/`Set` ✓;无 `as`/`any` ✓;显式返回类型 ✓。
|
||
|
||
#### `src/modules/grades/data-access-analytics.ts`(293 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 6-10, 79, 125-128 | 架构违规 | P1 | 直接查询 `classes` 表 | 通过 classes data-access 获取 |
|
||
| 6-10, 80, 211 | 架构违规 | P1 | 直接查询 `subjects` 表 | 通过 school data-access 获取 |
|
||
| 22, 27, 32 | 代码质量 | P2 | `toNumber`/`normalize`/`buildScopeClassFilter` 在 data-access.ts 和 data-access-ranking.ts 中重复定义 | 提取到 `grades/utils.ts` |
|
||
| 142-185 | 性能 | **P0** | `getClassComparison` 中 `for (const cls of classRows) { ... await db.select(...) }` — 循环内串行查询,N+1 问题 | 用 `inArray` 一次性查询所有班级的成绩,再在内存分组 |
|
||
| 133-136 | 性能 | P2 | `classRows.filter((c) => scope.classIds.includes(c.id))` — `includes` 是 O(n) 查找 | 将 `scope.classIds` 转为 `Set` 后用 `.has()` |
|
||
| 180-181, 238-239 | 性能 | P2 | `normalized.filter((s) => s >= 60).length` 和 `normalized.filter((s) => s >= 85).length` — 两次 filter 遍历同一数组 | 合并为单次 `reduce` 同时统计两个计数 |
|
||
| 全文件 | 性能 | P2 | 查询函数未使用 `React.cache()` | 用 `cache()` 包装 |
|
||
|
||
**合规项**:`"server-only"` ✓;无 `as`/`any` ✓;显式返回类型 ✓。
|
||
|
||
#### `src/modules/grades/data-access-ranking.ts`(121 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 6-10, 44-53 | 架构违规 | P1 | 直接查询 `classEnrollments` 表 | 通过 classes data-access 获取 |
|
||
| 6-10, 37-41 | 架构违规 | P1 | 直接查询 `users` 表 | 通过 users data-access 获取 |
|
||
| 17, 22 | 代码质量 | P2 | `toNumber`/`normalize` 重复定义 | 提取到共享 utils |
|
||
| 100, 102 | 性能 | P2 | `sorted.findIndex(...)` 和 `sorted.find(...)` 两次 O(n) 查找同一元素 | 合并为一次遍历 |
|
||
| 全文件 | 性能 | P2 | 查询函数未使用 `React.cache()` | 用 `cache()` 包装 |
|
||
|
||
#### `src/modules/grades/export.ts`(214 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 6-11, 116-120 | 架构违规 | P1 | 直接查询 `classes` 表 | 通过 classes data-access 获取 |
|
||
| 6-11, 124-132 | 架构违规 | P1 | 直接查询 `subjects` 表 | 通过 school data-access 获取 |
|
||
| 6-11, 135-144 | 架构违规 | P1 | 直接查询 `users` 表 | 通过 users data-access 获取 |
|
||
| 154 | TS规范 | P2 | `subjMap.get(r.studentId)!` — 非空断言 | 改用 `const subjMap = scoreMap.get(r.studentId); if (!subjMap) continue;` |
|
||
|
||
#### `src/modules/grades/schema.ts`(52 行)& `types.ts`(176 行)
|
||
|
||
无违规问题。Zod 验证完整,类型定义规范 ✓。
|
||
|
||
---
|
||
|
||
### 3.5 textbooks(教材模块)
|
||
|
||
#### `src/modules/textbooks/actions.ts`(276 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 全文件 | Server Action规范 | **P0** | **缺少 `schema.ts` 文件**,所有 Action 均无 Zod 验证,使用手动 `if (!rawData.title)` 检查 | 新建 `schema.ts`,为每个 Action 定义 Zod schema |
|
||
| 41-45 | 架构违规 | P1 | 本地定义 `export type ActionState`,未使用 `@/shared/types/action-state` 的统一类型 | 删除本地定义,改用 `import type { ActionState } from "@/shared/types/action-state"` |
|
||
| 18 | TS规范 | P1 | `import { CreateTextbookInput, UpdateTextbookInput }` — 类型导入未用 `import type` | 改为 `import type { ... }` |
|
||
| 54-58, 92-98, 154, 219-221, 259-261 | TS规范 | **P0** | 多处 `formData.get("title") as string` 等 `as` 断言(共 14 处) | 改用 `typeof` 守卫或 `getStringValue()` 辅助函数 |
|
||
| 20, 47 | 代码质量 | P2 | `// ... existing code ...` 残留注释 | 删除 |
|
||
| 164 | 代码质量 | P2 | `order: 0 // Default order` 魔法数字 | 提取为常量 `const DEFAULT_CHAPTER_ORDER = 0` |
|
||
|
||
**合规项**:所有 Action 调用 `requirePermission()` ✓;使用 `revalidatePath` ✓。
|
||
|
||
#### `src/modules/textbooks/data-access.ts`(444 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 42 | TS规范 | P2 | `byId.get(pid)!.children.push(ch)` — 非空断言 | 改用 `const parent = byId.get(pid); if (parent) parent.children.push(ch)` |
|
||
| 51 | TS规范 | P1 | `sortRecursive(n.children as Array<Chapter & { children: Chapter[] }>)` — `as` 断言 | 使用类型守卫或在 `Chapter` 类型中明确 `children` 字段 |
|
||
| 71 | TS规范 | P2 | `or(...)!` — 对 `or()` 返回值使用非空断言 | 改用条件判断 |
|
||
| 110, 128 | 代码质量 | P2 | `getTextbookById` 返回 `Textbook \| undefined`,与其他模块返回 `null` 的模式不一致 | 统一返回 `Textbook \| null` |
|
||
| 368, 381 | 代码质量 | P2 | `createKnowledgePoint`/`updateKnowledgePoint` 参数使用内联类型 | 将输入类型移至 `types.ts` |
|
||
|
||
**合规项**:使用 `cache()` ✓;使用 `Promise.all` ✓;`"server-only"` ✓;无 `any` ✓;显式返回类型 ✓。这是架构文档中的"标杆模块",无跨模块 DB 访问 ✓。
|
||
|
||
#### `src/modules/textbooks/types.ts`(83 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 1-3 | 代码质量 | P2 | 残留注释 `// In a real app, we would infer these from the schema...` | 删除过时注释 |
|
||
|
||
---
|
||
|
||
### 3.6 classes(班级模块)
|
||
|
||
#### `src/modules/classes/actions.ts`(765 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 8-9 | 架构违规 | **P0** | actions 层直接导入 `db` 和 `grades, classes` schema 并执行 DB 查询 | 将权限校验查询下沉到 `data-access-admin.ts` |
|
||
| 48-53, 109-111, 140-142, 175-183, 369-377, 518-533, 636-644 | Server Action规范 | P1 | 输入验证使用手动 `typeof` 检查而非 Zod,classes 模块完全没有 schema.ts 文件 | 新建 `schema.ts`,定义 Zod schema |
|
||
| 538 | TS规范 | P1 | `weekdayNum as 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7` — `as` 断言 | 用类型守卫 `isValidWeekday(weekdayNum): weekdayNum is 1\|2\|3\|4\|5\|6\|7` 替换 |
|
||
| 582 | TS规范 | P1 | `weekdayNum as 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| undefined` — 同上 | 同上 |
|
||
| 287, 706 | TS规范 | P2 | `JSON.parse(subjectTeachers) as unknown` 后续用 `as { subject?: unknown }` | 用 Zod schema 解析 |
|
||
|
||
#### `src/modules/classes/data-access.ts`(656 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 26-27, 182-206 | 架构违规 | **P0** | `getTeacherClasses` 中调用 `getClassHomeworkInsights`(homework 逻辑)和 `getClassSchedule`(scheduling 逻辑) | 将 homework insights 聚合逻辑移至 homework 模块 |
|
||
| 47 | TS规范 | P1 | `isDuplicateInvitationCodeError` 箭头函数缺少返回类型标注 | 添加 `: boolean` |
|
||
| 54 | TS规范 | P1 | `generateInvitationCode` 箭头函数缺少返回类型标注 | 添加 `: string` |
|
||
| 87 | TS规范 | P1 | `normalizeSortText` 箭头函数缺少返回类型标注 | 添加 `: string` |
|
||
| 89 | TS规范 | P1 | `parseFirstInt` 箭头函数缺少返回类型标注 | 添加 `: number \| null` |
|
||
| 94 | TS规范 | P1 | `compareGradeLabel` 箭头函数缺少返回类型标注 | 添加 `: number` |
|
||
| 101-104 | TS规范 | P1 | `compareClassLike` 箭头函数缺少返回类型标注 | 添加 `: number` |
|
||
| 240 | TS规范 | P1 | `r.subject as ClassSubject` — `as` 断言 | 用类型守卫 `isClassSubject(r.subject)` 过滤 |
|
||
| 266 | TS规范 | P1 | `r.name as ClassSubject` — 同上 | 同上 |
|
||
| 287 | TS规范 | P2 | `idByName.get(name)!` — 非空断言 | 用 `const id = idByName.get(name); if (!id) return null` 显式处理 |
|
||
| 297-299 | 代码质量 | P1 | `throw new Error("Failed to create class")` 后有 `return id` 不可达代码 | 删除 `return id` |
|
||
| 561 | TS规范 | P1 | `r.name as ClassSubject` — `as` 断言 | 用类型守卫替换 |
|
||
| 567 | TS规范 | P2 | `idByName.get(name)!` — 非空断言 | 显式判空处理 |
|
||
| 120-127 | 性能 | P2 | `getAccessibleClassIdsForTeacher` 中两个独立 DB 查询串行执行 | 用 `Promise.all` 并行化 |
|
||
|
||
#### `src/modules/classes/data-access-admin.ts`(441 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 82-117, 237-239 | 代码质量 | P1 | try-catch 吞掉错误返回空数组或 fallback 查询 | 移除 try-catch 或记录错误日志后 rethrow |
|
||
| 135 | TS规范 | P1 | `r.subject as ClassSubject` — `as` 断言 | 用类型守卫替换 |
|
||
| 259 | TS规范 | P1 | `r.subject as ClassSubject` — 同上 | 同上 |
|
||
| 303 | TS规范 | P1 | `getManagedGrades` 缺少返回类型标注 | 添加 `: Promise<GradeListItem[]>` |
|
||
| 349 | TS规范 | P1 | `r.name as ClassSubject` — `as` 断言 | 用类型守卫替换 |
|
||
| 369 | TS规范 | P2 | `idByName.get(name)!` — 非空断言 | 显式判空 |
|
||
| 380-382 | 代码质量 | P1 | `throw new Error("Failed to create class")` 后有 `return id` 不可达代码 | 删除 `return id` |
|
||
|
||
#### `src/modules/classes/data-access-schedule.ts`(230 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 7-11, 33-48 | 架构违规 | P1 | 直接导入并查询 `classSchedule` 表(该表归属 scheduling 模块) | 将读取也委托给 scheduling 模块的 data-access |
|
||
| 54 | TS规范 | P1 | `r.weekday as StudentScheduleItem["weekday"]` — `as` 断言 | 用类型守卫或 Zod 校验 weekday 范围 |
|
||
| 93 | TS规范 | P1 | `r.weekday as ClassScheduleItem["weekday"]` — 同上 | 同上 |
|
||
|
||
#### `src/modules/classes/data-access-stats.ts`(604 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 11-18 | 架构违规 | **P0** | 直接导入并查询 `homeworkAssignmentQuestions`、`homeworkAssignmentTargets`、`homeworkAssignments`、`homeworkSubmissions`、`exams` 等 homework/exams 模块的 DB 表 | 通过 homework 模块的 data-access 暴露的函数获取数据 |
|
||
| 250 | TS规范 | P1 | `(s.status ?? "started") as string` — `as` 断言 | `s.status` 已是 string 类型,无需断言 |
|
||
| 261 | TS规范 | P1 | `(a.status as string) ?? "draft"` — `as` 断言 | 同上 |
|
||
| 512 | TS规范 | P1 | `(s.status ?? "started") as string` — 同上 | 同上 |
|
||
| 523 | TS规范 | P1 | `(a.status as string) ?? "draft"` — 同上 | 同上 |
|
||
| 130-151, 175-191 | 性能 | P2 | 多个早期返回中重复构造相同的返回对象结构 | 提取 `buildEmptyInsights(classRow, studentCounts)` 工厂函数 |
|
||
|
||
#### `src/modules/classes/data-access-students.ts`(280 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 10-14 | 架构违规 | **P0** | 直接导入并查询 `homeworkAssignmentTargets`、`homeworkAssignments`、`homeworkSubmissions`、`exams` 等 homework/exams 模块的 DB 表 | 通过 homework 模块 data-access 获取数据 |
|
||
| 102 | TS规范 | P2 | `studentScores.get(s.studentId)!` — 非空断言 | 显式判空处理 |
|
||
| 168-204 | 代码质量 | P2 | `getStudentClasses` 中 try-catch fallback 查询使用 `sql\`NULL\`.as(...)` 模式,吞掉错误 | 移除 try-catch |
|
||
|
||
#### `src/modules/classes/types.ts`(201 行)
|
||
|
||
基本符合规范,无违规问题。
|
||
|
||
---
|
||
|
||
### 3.7 school(学校模块)
|
||
|
||
#### `src/modules/school/actions.ts`(325 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 7-8, 26-30, 53-59, 73, 96-108, 133-147, 161, 182-186, 211-217, 233, 261-268, 294-303, 317 | 架构违规 | **P0** | actions 层直接导入 `db` 和所有 schema,所有 mutation 直接操作 DB,完全没有下沉到 data-access 层 | 在 `data-access.ts` 中新增 `createDepartment`、`updateDepartment`、`deleteDepartment`、`createSchool` 等函数 |
|
||
| 188, 219, 235 | 性能 | P2 | `await logAudit(...)` 阻塞响应,审计日志写入是非关键路径 | 使用 Next.js `after()` 函数将 `logAudit` 改为非阻塞执行 |
|
||
|
||
#### `src/modules/school/data-access.ts`(186 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 13, 27, 44, 59, 112, 133 | 代码质量 | P2 | 所有函数都用 try-catch 包裹并返回空数组 `[]` 吞掉错误 | 移除 try-catch 或记录错误日志后 rethrow |
|
||
|
||
#### `src/modules/school/schema.ts`(51 行)& `types.ts`(42 行)
|
||
|
||
符合规范。
|
||
|
||
---
|
||
|
||
### 3.8 scheduling(排课模块)
|
||
|
||
#### `src/modules/scheduling/actions.ts`(300 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 10-11, 112-116 | 架构违规 | **P0** | actions 层直接导入 `db` 和 `users` schema,在 `autoScheduleAction` 中直接查询 `users` 表获取教师信息 | 在 data-access.ts 中新增 `getTeachersByIds(teacherIds: string[])` 函数 |
|
||
| 115 | TS规范 | P1 | `teacherIds[0]!` — 非空断言 | 用条件表达式 `eq(users.id, teacherIds[0] ?? "")` |
|
||
|
||
#### `src/modules/scheduling/auto-scheduler.ts`(310 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 144-145 | TS规范 | P1 | `schedule[i]!`、`schedule[j]!` — 非空断言 | 用局部变量 + 显式判空 |
|
||
| 全文件 | 行数 | P2 | 工具函数规范要求 ≤ 40 行,此文件 310 行 | 拆分为 `auto-scheduler/` 目录下的多个文件 |
|
||
|
||
#### `src/modules/scheduling/data-access.ts`(368 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 239 | TS规范 | **P0** | `getAdminClassesForScheduling()` 缺少返回类型标注 | 添加返回类型 |
|
||
| 246 | TS规范 | **P0** | `getTeachersForScheduling()` 缺少返回类型标注 | 添加返回类型 |
|
||
| 255 | TS规范 | **P0** | `getClassroomsForScheduling()` 缺少返回类型标注 | 添加返回类型 |
|
||
| 262 | TS规范 | **P0** | `getClassSubjectsForScheduling(classId: string)` 缺少返回类型标注 | 添加返回类型 |
|
||
| 113-114 | 代码质量 | P2 | `requesterName: users.name` 与 `originalTeacherName: users.name` 选中同一列,`requesterName` 在 map 中被 `userMap.get()` 覆盖,select 中的字段是死代码 | 删除 select 中的 `requesterName: users.name` |
|
||
| 140 | TS规范 | P1 | `userIds[0]!` — 非空断言 | 显式判空 |
|
||
| 221-222 | TS规范 | P1 | `rows[i]!`、`rows[j]!` — 非空断言 | 用局部变量 + 显式判空 |
|
||
|
||
#### `src/modules/scheduling/schema.ts`(81 行)& `types.ts`(124 行)
|
||
|
||
符合规范。
|
||
|
||
---
|
||
|
||
### 3.9 attendance(考勤模块)
|
||
|
||
#### `src/modules/attendance/actions.ts`(271 行)
|
||
|
||
基本符合规范。所有 action 调用了 `requirePermission`、使用 Zod 验证、返回 `ActionState`、使用 `revalidatePath`。
|
||
|
||
#### `src/modules/attendance/data-access.ts`(271 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 195 | TS规范 | P1 | `const update: Record<string, unknown> = { updatedAt: new Date() }` — 使用宽泛的 `Record<string, unknown>` 丢失类型安全 | 使用 `Partial<typeof attendanceRecords.$inferSelect>` 类型 |
|
||
|
||
#### `src/modules/attendance/data-access-stats.ts`(145 行)& `schema.ts`(43 行)& `types.ts`(103 行)
|
||
|
||
符合规范。
|
||
|
||
---
|
||
|
||
### 3.10 course-plans(教学计划模块)
|
||
|
||
#### `src/modules/course-plans/actions.ts`(265 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 236-246 | Server Action规范 | P1 | `deleteCoursePlanItemAction` 未调用 `revalidatePath` | 添加 `revalidatePlanPaths()` 调用 |
|
||
| 248-264 | Server Action规范 | P1 | `toggleCoursePlanItemCompletedAction` 未调用 `revalidatePath` | 同上 |
|
||
|
||
#### `src/modules/course-plans/data-access.ts`(320 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 149 | TS规范 | P1 | `params.status as CoursePlanStatus` — `as` 断言 | 用类型守卫或 Zod 校验 |
|
||
| 158, 183 | 代码质量 | P2 | `getCoursePlans` 和 `getCoursePlanById` 用 try-catch 返回空数组/null 吞掉错误 | 移除 try-catch 或记录日志后 rethrow |
|
||
| 300-305 | 性能 | P2 | `reorderCoursePlanItems` 中循环内串行 `await db.update()`,N 次 DB 往返 | 用 `db.transaction` 批量更新,或用 `Promise.all` 并行化 |
|
||
|
||
#### `src/modules/course-plans/schema.ts`(148 行)& `types.ts`(60 行)
|
||
|
||
符合规范。
|
||
|
||
---
|
||
|
||
### 3.11 users(用户模块)
|
||
|
||
#### `src/modules/users/actions.ts`(151 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 29-51 | Server Action规范 | **P0** | `updateUserProfile` 使用 `requireAuth()` 而非 `requirePermission()` | 改为 `requirePermission(Permissions.USER_MANAGE)` 或新增 `USER_PROFILE_UPDATE` 权限点 |
|
||
| 29-51 | 架构违规 | P1 | `updateUserProfile` 在 actions 层直接执行 `db.update(users)` | 将写操作下沉到 `data-access.ts` |
|
||
| 29-51 | Server Action规范 | P1 | `updateUserProfile` 返回 `Promise<void>` 而非 `ActionState<T>` | 改为返回 `Promise<ActionState<void>>` |
|
||
| 29 | TS规范 | P2 | `updateUserProfile(data: UpdateUserProfileInput)` 缺少显式返回类型标注 | 标注为 `Promise<ActionState<void>>` |
|
||
| 29-51 | Server Action规范 | P2 | `updateUserProfile` 输入未使用 Zod 验证 | 新增 Zod schema |
|
||
| 76-125 | Server Action规范 | P2 | `importUsersAction` 输入验证使用手动逻辑,未使用 Zod | 将 `parseUserImportData` 改用 Zod 实现 |
|
||
|
||
#### `src/modules/users/class-registration.ts`(27 行)
|
||
|
||
合规,无违规问题。正确通过 `classes/data-access.enrollStudentByInvitationCode` 跨模块通信。
|
||
|
||
#### `src/modules/users/data-access.ts`(152 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 24 | 命名规范 | P2 | `const rolePriority` 常量未使用 UPPER_SNAKE_CASE | 重命名为 `ROLE_PRIORITY` |
|
||
| 26-31 | 架构违规 | P2 | `normalizeRoleName` 与 `shared/lib/role-utils.ts` 的 `normalizeRole` 重复 | 改为 `import { normalizeRole } from "@/shared/lib/role-utils"` 复用 |
|
||
| 26 | TS规范 | P2 | `normalizeRoleName` 缺少显式返回类型标注 | 标注为 `(value: string) => string` |
|
||
| 33 | TS规范 | P2 | `resolvePrimaryRole` 缺少显式返回类型标注 | 标注为 `(roleNames: string[]) => string` |
|
||
|
||
#### `src/modules/users/import-export.ts`(176 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 98 | TS规范 | P2 | `const conditions = []` 隐式推断为 `any[]`,违反"禁止 any"规则 | 标注类型 `const conditions: ReturnType<typeof eq>[] = []` |
|
||
| 100-114 | 性能 | P2 | `exportUsersToExcel` 中 role 查询 → userIds 查询 → users 查询为串行 | 可接受(有依赖关系),但建议合并为子查询减少往返 |
|
||
|
||
#### `src/modules/users/user-service.ts`(99 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 13 | 安全规范 | **P0** | `const DEFAULT_PASSWORD = "123456"` 硬编码弱密码 | 改为随机生成密码或要求首次登录强制修改密码 |
|
||
| 15-19 | 架构违规 | P2 | `normalizeBcryptHash` 与 `shared/lib/bcrypt-utils.ts` 重复 | 改为 `import { normalizeBcryptHash } from "@/shared/lib/bcrypt-utils"` 复用 |
|
||
| 15 | TS规范 | P2 | `normalizeBcryptHash` 缺少显式返回类型标注 | 标注为 `(value: string) => string` |
|
||
| 30-92 | 架构违规 | P1 | `batchImportUsers` 注释标注"批量导入用户(事务)",但实际未使用 `db.transaction` 包裹 | 用 `await db.transaction(async (tx) => { ... })` 包裹 |
|
||
| 50-92 | 性能 | P2 | 循环内串行 `await db.insert`,N 条记录需 N 次 DB 往返 | 可批量插入用户和角色关联 |
|
||
|
||
---
|
||
|
||
### 3.12 messaging(消息模块)
|
||
|
||
#### `src/modules/messaging/actions.ts`(247 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 163 | Server Action规范 | P1 | `getNotificationsAction` 使用 `requireAuth()` 而非 `requirePermission()` | 改为 `requirePermission(Permissions.MESSAGE_READ)` |
|
||
| 177 | Server Action规范 | P1 | `markNotificationAsReadAction` 使用 `requireAuth()` 而非 `requirePermission()` | 同上 |
|
||
| 190 | Server Action规范 | P1 | `markAllNotificationsAsReadAction` 使用 `requireAuth()` 而非 `requirePermission()` | 同上 |
|
||
| 203 | Server Action规范 | P1 | `getNotificationPreferencesAction` 使用 `requireAuth()` 而非 `requirePermission()` | 同上 |
|
||
| 218 | Server Action规范 | P1 | `updateNotificationPreferencesAction` 使用 `requireAuth()` 而非 `requirePermission()` | 同上 |
|
||
| 213-247 | Server Action规范 | P2 | `updateNotificationPreferencesAction` 未使用 Zod 验证 | 新增 Zod schema 校验 8 个布尔字段 |
|
||
| 87, 101, 129 | Server Action规范 | P2 | `markMessageAsReadAction`/`deleteMessageAction`/`getMessageDetailAction` 参数 `messageId` 未使用 Zod 验证 | 新增 `z.string().min(1)` 校验 messageId |
|
||
|
||
#### `src/modules/messaging/data-access.ts`(252 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 12-14 | 架构违规 | P1 | 导入 `classEnrollments, classes`,`getRecipients`(227-251 行)直接 JOIN 跨模块表 | 通过 `classes/data-access` 暴露 `getStudentsByClassIds(classIds)` 接口 |
|
||
| 94 | TS规范 | P2 | `const conds = []` 隐式 `any[]` | 标注 drizzle 条件类型 |
|
||
| 97 | TS规范 | P2 | `or(...)!` 使用非空断言 | 改为显式处理 null |
|
||
| 117 | TS规范 | P2 | `or(...)!` 使用非空断言 | 同上 |
|
||
| 163 | TS规范 | P2 | `or(...)!` 使用非空断言 | 同上 |
|
||
| 63 | TS规范 | P2 | `mapMessage` 缺少显式返回类型标注 | 标注返回类型 |
|
||
| 77 | TS规范 | P2 | `mapNotification` 缺少显式返回类型标注 | 标注返回类型 |
|
||
| 80 | TS规范 | P2 | `r.type as NotificationType` 使用 as 断言 | 使用类型守卫 |
|
||
| 74, 85 | TS规范 | P2 | `toIso(r.createdAt) as string` 使用 as 断言 | 处理 null 情况或修改 toIso 返回类型 |
|
||
| 192-203 | 架构违规 | **P0** | `createNotification` 写 `messageNotifications` 表,但 notifications 模块反向调用此函数,形成循环依赖 | 将 `messageNotifications` 表所有权移交 notifications 模块 |
|
||
|
||
#### `src/modules/messaging/notification-preferences.ts`(166 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 14 | TS规范 | P2 | `toIso` 缺少显式返回类型标注 | 标注为 `(d: Date) => string` |
|
||
| 16 | TS规范 | P2 | `mapRow` 缺少显式返回类型标注 | 标注返回类型 |
|
||
|
||
#### `src/modules/messaging/schema.ts`(18 行)& `types.ts`(108 行)
|
||
|
||
合规,无违规问题。
|
||
|
||
---
|
||
|
||
### 3.13 notifications(通知模块)
|
||
|
||
#### `src/modules/notifications/actions.ts`(119 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 14-17 | 架构违规 | P1 | actions 层直接导入 `db`、`classEnrollments`、`classes` | 将 DB 查询下沉到 `data-access.ts` |
|
||
| 83-96 | 架构违规 | P1 | `sendClassNotificationAction` 直接查询 `classes` 和 `classEnrollments` 表 | 通过 `classes/data-access` 暴露查询接口 |
|
||
| 30-52 | Server Action规范 | P2 | `sendNotificationAction` 参数 `payload` 未使用 Zod 验证 | 新增 `NotificationPayloadSchema` |
|
||
| 62-119 | Server Action规范 | P2 | `sendClassNotificationAction` 参数 `classId`/`payload` 未使用 Zod 验证 | 新增 Zod schema 校验 |
|
||
|
||
#### `src/modules/notifications/data-access.ts`(86 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 20 | 架构违规 | **P0** | `import { getNotificationPreferences } from "@/modules/messaging/notification-preferences"` 反向依赖 messaging 模块 | 将 `notificationPreferences` 表所有权移交 notifications 模块 |
|
||
| 21 | 架构违规 | **P0** | `import type { NotificationPreferences } from "@/modules/messaging/types"` 反向依赖 messaging 模块 | 类型定义应迁移到 notifications 模块 |
|
||
| 71-77 | 架构违规 | P2 | `logNotificationSend` 仅 `console.info`,无 DB 持久化 | 新增 `notification_logs` 表并写入发送结果 |
|
||
|
||
#### `src/modules/notifications/dispatcher.ts`(152 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 44 | TS规范 | P2 | `getSenders` 缺少显式返回类型标注 | 标注为 `() => SenderRegistry` |
|
||
| 59 | TS规范 | P2 | `selectChannels` 缺少显式返回类型标注 | 标注返回类型 |
|
||
|
||
#### `src/modules/notifications/external-sdk.d.ts`(47 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 12, 20, 25, 33, 42, 43 | TS规范 | P2 | 多处使用 `any`(`const _default: any`、`config: any`、`options: any`) | 安装实际 SDK 后用其自带类型覆盖;或使用 `unknown` |
|
||
|
||
#### `src/modules/notifications/index.ts`(38 行)& `types.ts`(70 行)& `channels/types.ts`(38 行)
|
||
|
||
合规,无违规问题。
|
||
|
||
#### `src/modules/notifications/channels/email-channel.ts`(183 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 29 | TS规范 | P2 | `getEmailConfig` 缺少显式返回类型标注 | 标注返回类型 |
|
||
| 45 | TS规范 | P2 | `getTypeColor` 缺少显式返回类型标注 | 标注为 `(type: NotificationPayload["type"]) => string` |
|
||
| 60 | TS规范 | P2 | `buildHtmlContent` 缺少显式返回类型标注 | 标注为 `(payload: NotificationPayload) => string` |
|
||
| 79 | TS规范 | P2 | `escapeHtml` 缺少显式返回类型标注 | 标注为 `(text: string) => string` |
|
||
|
||
#### `src/modules/notifications/channels/in-app-channel.ts`(88 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 50 | 架构违规 | **P0** | `await import("@/modules/messaging/data-access")` 动态 import messaging 模块,运行时形成循环 | 将 `messageNotifications` 表所有权移交 notifications |
|
||
| 54 | TS规范 | P1 | `payload.type as "message" \| "announcement" \| "homework" \| "grade"` 使用 as 断言,且源类型与目标类型不兼容,as 断言非法 | 重新设计类型映射函数 |
|
||
|
||
#### `src/modules/notifications/channels/sms-channel.ts`(236 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 30 | TS规范 | P2 | `getSmsConfig` 缺少显式返回类型标注 | 标注返回类型 |
|
||
| 32 | TS规范 | P2 | `(process.env.SMS_PROVIDER ?? "mock") as "aliyun" \| "tencent" \| "mock"` 使用 as 断言 | 使用类型守卫或 Zod 校验环境变量 |
|
||
| 44 | TS规范 | P2 | `buildTemplateParams` 缺少显式返回类型标注 | 标注返回类型 |
|
||
|
||
#### `src/modules/notifications/channels/wechat-channel.ts`(208 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 40 | TS规范 | P2 | `getWechatConfig` 缺少显式返回类型标注 | 标注返回类型 |
|
||
| 99 | TS规范 | P2 | `buildTemplateData` 缺少显式返回类型标注 | 标注返回类型 |
|
||
|
||
---
|
||
|
||
### 3.14 parent(家长模块)
|
||
|
||
#### `src/modules/parent/data-access.ts`(234 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 59-105 | 架构违规 | P1 | `getChildBasicInfo` 直接查询 `users`、`grades`、`classEnrollments`、`classes` 表 | 通过 `users/data-access.getUserProfile`、`classes/data-access` 等接口查询 |
|
||
| 58 | TS规范 | P2 | `getChildBasicInfo` 缺少显式返回类型标注 | 标注返回类型 |
|
||
| 30 | TS规范 | P2 | `(day === 0 ? 7 : day) as 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7` 使用 as 断言 | 使用类型守卫 `function isWeekday(n: number): n is 1\|2\|3\|4\|5\|6\|7` |
|
||
| 58-117 | 性能 | P2 | `getChildBasicInfo` 串行查询 student → grade → enrollment → class | 使用 JOIN 合并为单次查询,或用 Promise.all 并行化 |
|
||
|
||
#### `src/modules/parent/types.ts`(57 行)
|
||
|
||
合规,无违规问题。
|
||
|
||
---
|
||
|
||
### 3.15 audit(审计模块)
|
||
|
||
#### `src/modules/audit/actions.ts`(212 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 6, 70-100, 120-146, 166-192 | 架构违规 | P2 | Excel 导出逻辑内联在 actions 层 | 新建 `audit/export.ts`,将 Excel 构建逻辑迁移 |
|
||
| 63-205 | 架构违规 | P2 | 三个导出 Action 结构高度重复 | 抽取通用 `buildExcelSheet(name, columns, rows)` 辅助函数 |
|
||
| 207-212 | 架构违规 | P2 | `formatDateForFile` 工具函数定义在 actions.ts 中 | 迁移到 `shared/lib/utils.ts` 或 `audit/utils.ts` |
|
||
| 24-61 | Server Action规范 | P2 | `getDataChangeLogsAction` 参数 `params` 未使用 Zod 验证 | 新增 `DataChangeLogQueryParamsSchema` |
|
||
| 63-205 | Server Action规范 | P2 | 三个导出 Action 参数未使用 Zod 验证 | 新增对应 Zod schema |
|
||
|
||
#### `src/modules/audit/data-access.ts`(260 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 18 | TS规范 | P2 | `toIso` 缺少显式返回类型标注 | 标注为 `(d: Date) => string` |
|
||
| 23 | TS规范 | P2 | `clampPageSize` 缺少显式返回类型标注 | 标注为 `(size?: number) => number` |
|
||
| 28 | TS规范 | P2 | `clampPage` 缺少显式返回类型标注 | 标注为 `(page?: number) => number` |
|
||
| 40, 95, 158 | TS规范 | P2 | `const conditions = []` 隐式 `any[]` | 标注 drizzle 条件类型 |
|
||
| 75 | TS规范 | P2 | `r.status as "success" \| "failure"` 使用 as 断言 | 使用类型守卫 |
|
||
| 122 | TS规范 | P2 | `r.action as "signin" \| "signout" \| "signup"` 使用 as 断言 | 使用类型守卫 |
|
||
| 123 | TS规范 | P2 | `r.status as "success" \| "failure"` 使用 as 断言 | 使用类型守卫 |
|
||
| 186 | TS规范 | P2 | `r.action as "create" \| "update" \| "delete"` 使用 as 断言 | 使用类型守卫 |
|
||
| 50-86, 104-137, 168-202 | 安全/质量 | P2 | `getAuditLogs`/`getLoginLogs`/`getDataChangeLogs` 使用 try-catch 吞错误返回空数组 | 至少 `console.error` 记录错误 |
|
||
| 235-240 | 逻辑错误 | P1 | `getAuditLogsForExport` 注释标注 "no pagination cap",但实际调用 `getAuditLogs({ page: 1, pageSize: 100 })`,最多只返回 100 条,导出数据不完整 | 实现真正的无分页查询,或循环分页拉取全部数据 |
|
||
| 245-250 | 逻辑错误 | P1 | `getLoginLogsForExport` 同上问题,最多 100 条 | 同上 |
|
||
| 255-260 | 逻辑错误 | P1 | `getDataChangeLogsForExport` 同上问题,最多 100 条 | 同上 |
|
||
|
||
#### `src/modules/audit/types.ts`(91 行)
|
||
|
||
合规,无违规问题。
|
||
|
||
---
|
||
|
||
### 3.16 elective(选修课模块)
|
||
|
||
#### `src/modules/elective/data-access.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 117 | TS规范 | P1 | `params.status as ElectiveCourseStatus` 使用 `as` 断言 | 直接使用 `params.status`,无需断言 |
|
||
| 78 | TS规范 | P2 | `buildCourseSelect = () =>` 箭头函数缺少显式返回类型标注 | 添加返回类型标注 |
|
||
| 136-138, 150-152 | 代码质量 | P2 | `catch { return [] / null }` 静默吞掉异常 | 至少 `console.error` 记录错误 |
|
||
|
||
#### `src/modules/elective/data-access-selections.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 102 | TS规范 | P1 | `r.status as CourseSelectionStatus` 使用 `as` 断言 | 应通过类型守卫或直接赋值 |
|
||
| 114 | TS规范 | P1 | `r.courseStatus as ElectiveCourseStatus \| null` 同上 | 同上 |
|
||
| 59, 117 | TS规范 | P2 | `buildCourseSelect` 和 `selectionDetailSelect` 箭头函数缺少显式返回类型 | 添加返回类型标注 |
|
||
| 7-14 | 架构违规 | P1 | 直接导入并查询 `classes`、`classEnrollments` 表 | 通过 `@/modules/classes/data-access` 暴露的函数获取 |
|
||
| 全文件 | 性能 | P2 | 均未用 `React.cache()` 包裹 | 对读函数用 `cache()` 包裹 |
|
||
|
||
#### `src/modules/elective/data-access-operations.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 18-34 | 性能 | P2 | `runLottery` 中先查 course 再查 selections,两次独立查询串行 `await` | 用 `Promise.all` 并行 |
|
||
| 46-71 | 性能 | P1 | `for` 循环内逐条 `await db.update()`,N 名学生产生 N 次 DB 往返 | 拆分为两组 ID,用 `inArray` 执行 2 次批量 UPDATE |
|
||
| 46-76 | 安全/数据完整性 | P1 | `runLottery` 多步更新未包裹事务 | 使用 `db.transaction()` 包裹整个摇号流程 |
|
||
| 86-112 | 性能 | P2 | `selectCourse` 中查 course 和查 existing selection 两次独立查询串行 | 用 `Promise.all` 并行 |
|
||
| 158-182 | 性能 | P2 | `dropCourse` 中查 existing 和查 course 串行 | 并行化独立查询 |
|
||
|
||
#### `src/modules/elective/schema.ts`(20-24 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 20-24 | TS规范 | P2 | `emptyToNull` 和 `optionalStringToNull` 箭头函数缺少显式返回类型 | 添加返回类型 |
|
||
|
||
#### `src/modules/elective/types.ts`
|
||
|
||
未发现违规问题。
|
||
|
||
#### `src/modules/elective/actions.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 278-294 | Server Action规范 | P2 | `getStudentSelectionsAction(studentId: string)` 直接接收参数,未经 Zod 校验 | 对 `studentId` 用 Zod 校验 |
|
||
|
||
---
|
||
|
||
### 3.17 proctoring(监考模块)
|
||
|
||
#### `src/modules/proctoring/actions.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 11-13, 79-84 | 架构违规 | **P0** | actions.ts(编排层)直接导入 `db`、`examSubmissions` 并执行 DB 查询 | 将 submission 归属校验逻辑移至 `data-access.ts` |
|
||
| 3 | TS规范 | P1 | `import { ActionState }` 应为 `import type { ActionState }` | 改为 `import type { ActionState }` |
|
||
| 39 | TS规范 | P1 | `as z.ZodType<ProctoringEventType>` 使用 `as` 断言 | 直接使用 `z.enum([...])` 推导 |
|
||
| 63 | Server Action规范 | P1 | `recordProctoringEventAction` 使用 `requireAuth()` 而非 `requirePermission()` | 使用 `requirePermission(Permissions.EXAM_SUBMIT)` |
|
||
| 全文件 | Server Action规范 | P2 | `recordProctoringEventAction` 未调用 `revalidatePath` | 添加 `revalidatePath` 刷新监考面板相关路径 |
|
||
|
||
#### `src/modules/proctoring/data-access.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 127 | TS规范 | P1 | `row.event.eventType as ProctoringEventType` 使用 `as` 断言 | drizzle enum 列类型已是字面量联合,应直接匹配 |
|
||
| 152 | TS规范 | P1 | `row.eventType as ProctoringEventType` 同上 | 同上 |
|
||
| 208 | TS规范 | P1 | `stat.eventType as ProctoringEventType` 同上 | 同上 |
|
||
| 290 | TS规范 | P1 | `row.eventType as ProctoringEventType` 同上 | 同上 |
|
||
| 313 | TS规范 | P1 | `(row.submission.status ?? null) as StudentProctoringStatus["submissionStatus"]` 使用 `as` 断言 | 用类型守卫函数校验 status 值 |
|
||
| 378 | TS规范 | P1 | `row.event.eventType as ProctoringEventType` 同上 | 同上 |
|
||
| 5-9 | 架构违规 | P1 | 直接导入 `exams`、`examSubmissions` 表 | 通过 `@/modules/exams/data-access` 暴露的函数获取 |
|
||
| 188-193 | 性能 | P2 | `getExamProctoringSummary` 中对 `submissions` 数组调用两次 `.filter()` | 用单次 `for` 循环同时统计两个计数 |
|
||
| 178-223 | 性能 | P2 | `getExamProctoringSummary` 中 submissions 查询、eventStats 查询、studentEventCounts 查询三者相互独立,却串行 `await` | 用 `Promise.all` 并行执行 |
|
||
|
||
#### `src/modules/proctoring/types.ts`
|
||
|
||
未发现违规问题。
|
||
|
||
---
|
||
|
||
### 3.18 diagnostic(诊断模块)
|
||
|
||
#### `src/modules/diagnostic/actions.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 26-33, 54-61, 82-85, 105-108 | Server Action规范 | P1 | `generateStudentReportAction`、`generateClassReportAction`、`publishReportAction`、`deleteReportAction` 均使用手动 `typeof` + `length` 校验,未使用 Zod schema | 新建 `schema.ts`,定义 Zod schema |
|
||
| 121-133, 136-148 | Server Action规范 | P2 | `getDiagnosticReportsAction(params)` 和 `getDiagnosticReportByIdAction(id)` 直接接收参数,未经 Zod 校验 | 对入参用 Zod 校验 |
|
||
| 123, 138 | TS规范 | P2 | `Promise<ActionState<Awaited<ReturnType<typeof getDiagnosticReports>>>>` 类型表达式过于复杂 | 在 types.ts 中定义具名返回类型并导入 |
|
||
|
||
#### `src/modules/diagnostic/data-access.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 9, 13 | 架构违规 | P1 | 直接导入 `examSubmissions`、`submissionAnswers`、`questionsToKnowledgePoints` 表 | 通过对应模块的 data-access 函数获取数据 |
|
||
| 116 | 性能 | P2 | `answers.find((a) => a.questionId === link.questionId)` 在 `for` 循环内调用,O(n×m) 复杂度 | 先用 `new Map(answers.map(a => [a.questionId, a]))` 构建 Map |
|
||
| 125-146 | 性能 | P2 | `for` 循环内逐条 `await db.insert().onDuplicateKeyUpdate()`,N 个知识点产生 N 次 DB 往返 | 用 `Promise.all` 并行化,或构建批量 upsert |
|
||
| 80-81 | 性能 | P2 | `allMastery.filter(m => m.masteryLevel >= 80)` 和 `allMastery.filter(m => m.masteryLevel < 60)` 两次遍历同一数组 | 合并为单次循环 |
|
||
| 全文件 | 性能 | P2 | 所有读函数均未用 `React.cache()` 包裹 | 用 `cache()` 包裹读函数 |
|
||
|
||
#### `src/modules/diagnostic/data-access-reports.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 29-31 | TS规范 | P1 | `(r.strengths as string[] \| null)`、`(r.weaknesses as string[] \| null)`、`(r.recommendations as string[] \| null)` 三处 `as` 断言 | 编写类型守卫 `isStringArray(v: unknown): v is string[]` |
|
||
| 59, 103 | 性能/规范 | P2 | `const { createId } = await import("@paralleldrive/cuid2")` 在函数内部动态导入 | 改为顶层静态导入 |
|
||
| 201-202 | 代码质量 | P2 | `void round2` — 为抑制未使用警告而保留无用函数 | 直接删除 `round2` 函数定义及 `void round2` |
|
||
| 172-180 | 性能 | P2 | `getDiagnosticReportById` 中先查 report,再单独查 generator 名称,两次串行查询 | 将 generator 查询合并到主查询的 JOIN 中 |
|
||
| 全文件 | 性能 | P2 | `getDiagnosticReports`、`getDiagnosticReportById` 未用 `React.cache()` 包裹 | 用 `cache()` 包裹 |
|
||
| 107 | 代码质量 | P2 | `studentId: generatedBy` 班级报告将生成者 ID 存入 studentId 字段 | 考虑将 studentId 改为可空,或使用单独的 classId 字段 |
|
||
|
||
#### `src/modules/diagnostic/types.ts`
|
||
|
||
未发现违规问题。
|
||
|
||
---
|
||
|
||
### 3.19 dashboard(仪表盘模块)
|
||
|
||
#### `src/modules/dashboard/data-access.ts` & `types.ts`
|
||
|
||
**无违规问题**。正确使用 `Promise.all` 并行获取多模块数据,正确使用 `cache()`,正确通过各模块 data-access 通信。是除 textbooks 外的另一个标杆模块。
|
||
|
||
---
|
||
|
||
### 3.20 files(文件模块)
|
||
|
||
#### `src/modules/files/data-access.ts`
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 190 | TS规范 | P1 | `const conditions = []` 缺少类型标注,TypeScript 推断为 `never[]` 或 `any[]` | 改为 `const conditions: SQL[] = []` |
|
||
| 204 | TS规范 | P1 | `or(...)!` 使用非空断言 | 显式处理 undefined |
|
||
| 160-170 | 性能 | P2 | `deleteFileAttachments` 回退逻辑中 `for` 循环内逐条 `await db.delete()` | 用 `Promise.allSettled` 并行删除 |
|
||
| 53-55, 70-72, 95-97, 114-116, 131-133, 143-145, 219-221, 248-250, 264-266 | 代码质量 | P2 | 大量 `catch { return null / [] / false }` 静默吞掉异常,共 9 处 | 至少 `console.error` 记录错误信息 |
|
||
| 全文件 | 性能 | P2 | 所有读函数均未用 `React.cache()` 包裹 | 用 `cache()` 包裹读函数 |
|
||
|
||
#### `src/modules/files/types.ts`
|
||
|
||
未发现违规问题。
|
||
|
||
---
|
||
|
||
### 3.21 announcements(公告模块)
|
||
|
||
#### `src/modules/announcements/data-access.ts`(186 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 46-47 | TS规范 | P1 | `toIso(row.createdAt) as string` — `as` 断言掩盖潜在 null | 让 `toIso` 对非空入参返回 `string`,拆分为两个函数 |
|
||
| 59, 62 | TS规范 | P2 | `params.status as AnnouncementStatus` — 冗余 `as` 断言 | 直接使用收窄后的变量 |
|
||
| 88-90, 118-120 | 性能/可靠性 | P2 | `catch {}` 静默吞掉所有异常 | 至少记录错误日志 |
|
||
| 25-26 | 命名/DRY | P2 | `mapRow` 参数中内联定义类型,与 types.ts 重复 | 导入并复用 types.ts 中的类型 |
|
||
|
||
#### `src/modules/announcements/actions.ts`(231 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 217-231 | Server Action规范 | P2 | `getAnnouncementsAction` 使用 `requireAuth()` 而非 `requirePermission(Permissions.ANNOUNCEMENT_READ)` | 改为 `requirePermission(Permissions.ANNOUNCEMENT_READ)` |
|
||
| 73-79, 137-143, 159-165, 185-191, 208-214, 224-230 | 性能/可维护性 | P2 | 6 个 Action 的 try/catch 错误处理块完全相同 | 提取共享错误处理函数 `handleActionError(e)` |
|
||
|
||
#### `src/modules/announcements/schema.ts`(45 行)& `types.ts`(50 行)
|
||
|
||
符合规范。
|
||
|
||
---
|
||
|
||
### 3.22 settings(设置模块)
|
||
|
||
#### `src/modules/settings/actions.ts`(205 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 8, 64-76, 95-100, 104-113, 123-139, 154-169 | 架构违规 | P1 | 模块无 `data-access.ts` 文件,actions.ts 直接 `import { db }` 并执行 DB 查询 | 新建 `data-access.ts`,将所有 DB 操作下沉 |
|
||
| 62-77 | Server Action规范 | P2 | `getAiProviderSummaries` 返回 `Promise<AiProviderSummary[]>` 而非 `ActionState<T>` | 移至 data-access.ts 或包装为 ActionState |
|
||
| 38-46 | 架构违规 | P2 | `AiProviderSummary` 类型定义在 actions.ts 中 | 新建 `types.ts`,将类型移入 |
|
||
| 48-51 | TS规范 | P2 | `ensureUser` 是 async 箭头函数,未显式标注返回类型 | 添加 `: Promise<{ id: string }>` |
|
||
| 53-60 | TS规范 | P2 | `normalizeBaseUrl` 未显式标注返回类型 | 添加 `: string \| null` |
|
||
| 95-113 | 性能 | P2 | `upsertAiProviderAction` 的更新分支中,`count()` 查询与 `select` 查询是两个独立查询,串行执行 | 用 `Promise.all` 并行化 |
|
||
| 120, 152 | 命名规范 | P2 | `nextIsDefault`、`makeDefault` 布尔变量前缀不规范 | 改为 `isNextDefault`、`shouldMakeDefault` |
|
||
|
||
#### `src/modules/settings/actions-password.ts`(113 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 7, 58-62, 78-81, 83-87, 89-105 | 架构违规 | P1 | actions-password.ts 直接 `import { db }` 并执行 DB 操作,无 data-access.ts | 将 DB 操作下沉至 `data-access.ts` |
|
||
| 39-51 | Server Action规范 | P1 | 输入通过 `String(formData.get(...))` 手动读取,未使用 Zod schema 验证 | 定义 Zod schema 并 safeParse |
|
||
| 30 | Server Action规范 | P2 | `changePasswordAction` 使用 `requireAuth()` 而非 `requirePermission()` | 新增 `PASSWORD_SELF_CHANGE` 权限点并赋给所有角色 |
|
||
| 58-87 | 性能 | P2 | 查询 user 和查询 passwordSecurity 是两个独立表的查询,当前串行执行 | 重构为 `Promise.all` 并行获取 |
|
||
| 14-18 | TS规范 | P2 | `normalizeBcryptHash` 未显式标注返回类型 | 添加 `: string` |
|
||
|
||
---
|
||
|
||
### 3.23 layout(布局模块)
|
||
|
||
#### `src/modules/layout/config/navigation.ts`(317 行)
|
||
|
||
| 行号范围 | 问题类别 | 严重程度 | 问题描述 | 改进建议 |
|
||
|---------|---------|---------|---------|---------|
|
||
| 30-31 | TS规范 | P2 | `NavItem.permission` 和子项的 `permission` 均为 `string` 类型,但项目已有 `Permission` 类型 | 导入 Permission 类型并使用 |
|
||
| 34 | 架构违规 | P2 | `export type Role = "admin" \| "teacher" \| "student" \| "parent"` 定义在 config 文件中 | 考虑将 Role 类型提升至 `@/shared/types` |
|
||
|
||
---
|
||
|
||
## 四、跨模块共性问题
|
||
|
||
### 4.1 架构违规(P0/P1,最高优先级)
|
||
|
||
#### 4.1.1 跨模块直接查询 DB 表(最普遍问题)
|
||
|
||
| 调用方模块 | 被查模块表 | 严重程度 |
|
||
|-----------|-----------|---------|
|
||
| exams | questions(直写)、classes、subjects、grades | P0/P1 |
|
||
| homework | exams(5 处)、classes、classEnrollments、subjects、users | P1 |
|
||
| questions | knowledgePoints、chapters、textbooks | P1 |
|
||
| grades | classes、classEnrollments、subjects、users | P1 |
|
||
| classes | homeworkAssignments、homeworkSubmissions、exams、classSchedule | P0 |
|
||
| school | (actions 层直接 DB 操作) | P0 |
|
||
| scheduling | users(actions 层) | P0 |
|
||
| messaging | classEnrollments、classes | P1 |
|
||
| notifications | classes、classEnrollments、messaging(循环) | P0/P1 |
|
||
| parent | users、grades、classEnrollments、classes | P1 |
|
||
| proctoring | exams、examSubmissions | P0/P1 |
|
||
| diagnostic | examSubmissions、submissionAnswers、questionsToKnowledgePoints | P1 |
|
||
| elective | classes、classEnrollments | P1 |
|
||
|
||
**统一修复方案**:在各模块 data-access 暴露跨模块查询接口,消除直查。
|
||
|
||
#### 4.1.2 messaging↔notifications 循环依赖
|
||
|
||
- `messaging/data-access.ts` 写 `messageNotifications` 表
|
||
- `notifications/data-access.ts` 反向 import messaging 的 `notification-preferences` 和 `types`
|
||
- `notifications/channels/in-app-channel.ts` 动态 import messaging 的 `data-access`
|
||
|
||
**修复方案**:将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块。
|
||
|
||
#### 4.1.3 actions 层直接操作 DB
|
||
|
||
| 模块 | 文件 | 问题 |
|
||
|------|------|------|
|
||
| school | actions.ts | 所有 mutation 直接操作 DB |
|
||
| scheduling | actions.ts | 直接查询 users 表 |
|
||
| proctoring | actions.ts | 直接查询 examSubmissions 表 |
|
||
| classes | actions.ts | 直接查询 grades、classes 表做权限校验 |
|
||
| settings | actions.ts、actions-password.ts | 无 data-access.ts,直接 DB 操作 |
|
||
| users | actions.ts | updateUserProfile 直接 db.update |
|
||
|
||
**统一修复方案**:DB 操作必须下沉到 data-access.ts。
|
||
|
||
### 4.2 TypeScript 规范违规(P1)
|
||
|
||
#### 4.2.1 `as` 断言滥用(非从 unknown 转换)
|
||
|
||
| 模块 | 文件 | 行号 |
|
||
|------|------|------|
|
||
| exams | data-access.ts | 76, 160, 361, 108, 172 |
|
||
| exams | actions.ts | 270, 349-351 |
|
||
| homework | data-access.ts | 39, 124, 226, 314, 392, 467, 645 |
|
||
| homework | stats-service.ts | 220 |
|
||
| textbooks | actions.ts | 54-58, 92-98, 154, 219-221, 259-261(14 处) |
|
||
| textbooks | data-access.ts | 51 |
|
||
| classes | data-access.ts | 240, 266, 561 |
|
||
| classes | data-access-admin.ts | 135, 259, 349 |
|
||
| classes | data-access-schedule.ts | 54, 93 |
|
||
| classes | data-access-stats.ts | 250, 261, 512, 523 |
|
||
| classes | actions.ts | 538, 582 |
|
||
| course-plans | data-access.ts | 149 |
|
||
| messaging | data-access.ts | 80, 74, 85 |
|
||
| notifications | channels/in-app-channel.ts | 54(非法) |
|
||
| notifications | channels/sms-channel.ts | 32 |
|
||
| audit | data-access.ts | 75, 122, 123, 186 |
|
||
| elective | data-access.ts | 117 |
|
||
| elective | data-access-selections.ts | 102, 114 |
|
||
| proctoring | data-access.ts | 127, 152, 208, 290, 313, 378 |
|
||
| proctoring | actions.ts | 39 |
|
||
| diagnostic | data-access-reports.ts | 29-31 |
|
||
| parent | data-access.ts | 30 |
|
||
| announcements | data-access.ts | 46-47, 59, 62 |
|
||
| attendance | data-access.ts | 195(Record<string, unknown>) |
|
||
|
||
**统一修复方案**:用类型守卫函数或 Zod 校验替换 `as` 断言。
|
||
|
||
#### 4.2.2 非空断言 `!`
|
||
|
||
| 模块 | 文件 | 行号 |
|
||
|------|------|------|
|
||
| exams | ai-pipeline.ts | 464, 657, 665, 666, 671 |
|
||
| homework | export.ts | 154 |
|
||
| textbooks | data-access.ts | 42, 71 |
|
||
| classes | data-access.ts | 287, 567 |
|
||
| classes | data-access-admin.ts | 369 |
|
||
| classes | data-access-students.ts | 102 |
|
||
| scheduling | data-access.ts | 140, 221, 222 |
|
||
| scheduling | actions.ts | 115 |
|
||
| scheduling | auto-scheduler.ts | 144, 145 |
|
||
| messaging | data-access.ts | 97, 117, 163 |
|
||
| files | data-access.ts | 204 |
|
||
|
||
**统一修复方案**:用显式判空处理替换非空断言。
|
||
|
||
#### 4.2.3 函数返回值未显式标注
|
||
|
||
| 模块 | 文件 | 函数 |
|
||
|------|------|------|
|
||
| classes | data-access.ts | isDuplicateInvitationCodeError, generateInvitationCode, normalizeSortText, parseFirstInt, compareGradeLabel, compareClassLike |
|
||
| classes | data-access-admin.ts | getManagedGrades |
|
||
| scheduling | data-access.ts | getAdminClassesForScheduling, getTeachersForScheduling, getClassroomsForScheduling, getClassSubjectsForScheduling |
|
||
| users | data-access.ts | normalizeRoleName, resolvePrimaryRole |
|
||
| users | user-service.ts | normalizeBcryptHash |
|
||
| messaging | data-access.ts | mapMessage, mapNotification |
|
||
| messaging | notification-preferences.ts | toIso, mapRow |
|
||
| notifications | dispatcher.ts | getSenders, selectChannels |
|
||
| notifications | channels/email-channel.ts | getEmailConfig, getTypeColor, buildHtmlContent, escapeHtml |
|
||
| notifications | channels/sms-channel.ts | getSmsConfig, buildTemplateParams |
|
||
| notifications | channels/wechat-channel.ts | getWechatConfig, buildTemplateData |
|
||
| audit | data-access.ts | toIso, clampPageSize, clampPage |
|
||
| parent | data-access.ts | getChildBasicInfo |
|
||
| announcements | — | — |
|
||
| settings | actions.ts | ensureUser, normalizeBaseUrl |
|
||
| settings | actions-password.ts | normalizeBcryptHash |
|
||
| elective | data-access.ts | buildCourseSelect |
|
||
| elective | data-access-selections.ts | buildCourseSelect, selectionDetailSelect |
|
||
| elective | schema.ts | emptyToNull, optionalStringToNull |
|
||
| questions | actions.ts | getQuestionsAction |
|
||
|
||
**统一修复方案**:所有函数(特别是箭头函数)必须显式标注返回类型。
|
||
|
||
#### 4.2.4 `import type` 未使用
|
||
|
||
| 模块 | 文件 | 行号 |
|
||
|------|------|------|
|
||
| exams | actions.ts | 4 |
|
||
| questions | actions.ts | 7 |
|
||
| textbooks | actions.ts | 18 |
|
||
| proctoring | actions.ts | 3 |
|
||
|
||
**统一修复方案**:仅用于类型的导入必须使用 `import type`。
|
||
|
||
### 4.3 Server Action 规范违规
|
||
|
||
#### 4.3.1 `requireAuth()` 替代 `requirePermission()`
|
||
|
||
| 模块 | 文件 | Action |
|
||
|------|------|--------|
|
||
| users | actions.ts | updateUserProfile |
|
||
| messaging | actions.ts | getNotificationsAction, markNotificationAsReadAction, markAllNotificationsAsReadAction, getNotificationPreferencesAction, updateNotificationPreferencesAction |
|
||
| proctoring | actions.ts | recordProctoringEventAction |
|
||
| announcements | actions.ts | getAnnouncementsAction |
|
||
| settings | actions-password.ts | changePasswordAction |
|
||
|
||
**统一修复方案**:改为 `requirePermission(Permissions.XXX)`。
|
||
|
||
#### 4.3.2 缺少 Zod 验证
|
||
|
||
| 模块 | 文件 | 问题 |
|
||
|------|------|------|
|
||
| textbooks | actions.ts | 完全无 schema.ts,所有 Action 无 Zod 验证 |
|
||
| classes | actions.ts | 完全无 schema.ts,使用手动 typeof 检查 |
|
||
| diagnostic | actions.ts | 4 个 Action 使用手动 typeof 校验 |
|
||
| settings | actions-password.ts | 输入手动读取,无 Zod |
|
||
| messaging | actions.ts | 多个 Action 参数未 Zod 校验 |
|
||
| notifications | actions.ts | 多个 Action 参数未 Zod 校验 |
|
||
| audit | actions.ts | 多个 Action 参数未 Zod 校验 |
|
||
| users | actions.ts | updateUserProfile 无 Zod |
|
||
| elective | actions.ts | getStudentSelectionsAction 无 Zod |
|
||
|
||
**统一修复方案**:新建/补充 schema.ts,所有 Action 输入用 Zod 验证。
|
||
|
||
#### 4.3.3 缺少 `revalidatePath`
|
||
|
||
| 模块 | 文件 | Action |
|
||
|------|------|--------|
|
||
| course-plans | actions.ts | deleteCoursePlanItemAction, toggleCoursePlanItemCompletedAction |
|
||
| proctoring | actions.ts | recordProctoringEventAction |
|
||
|
||
#### 4.3.4 未返回 `ActionState<T>`
|
||
|
||
| 模块 | 文件 | Action |
|
||
|------|------|--------|
|
||
| questions | actions.ts | getQuestionsAction, getKnowledgePointOptionsAction |
|
||
| users | actions.ts | updateUserProfile(返回 Promise<void>) |
|
||
| settings | actions.ts | getAiProviderSummaries(返回原始数组) |
|
||
|
||
### 4.4 重复代码
|
||
|
||
| 重复内容 | 出现位置 | 修复方案 |
|
||
|---------|---------|---------|
|
||
| `normalizeRoleName` | users/data-access.ts、shared/lib/role-utils.ts | 复用 shared |
|
||
| `normalizeBcryptHash` | users/user-service.ts、settings/actions-password.ts、shared/lib/bcrypt-utils.ts | 复用 shared |
|
||
| `toNumber`/`normalize`/`buildScopeClassFilter` | grades/data-access.ts、data-access-analytics.ts、data-access-ranking.ts | 提取到 grades/utils.ts |
|
||
| `serializeDate` | scheduling/data-access.ts、attendance/data-access.ts、attendance/data-access-stats.ts | 提取到 shared |
|
||
| `toIso` | announcements/data-access.ts、messaging/data-access.ts、messaging/notification-preferences.ts、audit/data-access.ts | 提取到 shared |
|
||
| try/catch 错误处理块 | announcements/actions.ts(6 处) | 提取 `handleActionError(e)` |
|
||
| Excel 导出逻辑 | audit/actions.ts(3 处) | 抽取 `buildExcelSheet` |
|
||
|
||
### 4.5 错误吞没(静默 catch)
|
||
|
||
| 模块 | 文件 | 处数 |
|
||
|------|------|------|
|
||
| school | data-access.ts | 6 |
|
||
| classes | data-access-admin.ts、data-access-students.ts | 多处 |
|
||
| course-plans | data-access.ts | 2 |
|
||
| announcements | data-access.ts | 2 |
|
||
| files | data-access.ts | 9 |
|
||
| audit | data-access.ts | 3 |
|
||
| elective | data-access.ts | 2 |
|
||
|
||
**统一修复方案**:至少 `console.error` 记录错误,或向上抛出由调用方处理。
|
||
|
||
### 4.6 不可达代码
|
||
|
||
| 模块 | 文件 | 行号 |
|
||
|------|------|------|
|
||
| classes | data-access.ts | 297-299(throw 后 return id) |
|
||
| classes | data-access-admin.ts | 380-382(throw 后 return id) |
|
||
| questions | actions.ts | 158-163, 170-175(catch 块两分支都 throw e) |
|
||
|
||
---
|
||
|
||
## 五、性能优化专项(react-best-practices)
|
||
|
||
依据 Vercel React Best Practices 规则集,识别以下性能优化机会:
|
||
|
||
### 5.1 CRITICAL:消除瀑布流(async-*)
|
||
|
||
#### 5.1.1 `async-parallel`:独立操作用 Promise.all()
|
||
|
||
| 模块 | 文件 | 行号 | 问题 | 修复 |
|
||
|------|------|------|------|------|
|
||
| classes | data-access.ts | 120-127 | `getAccessibleClassIdsForTeacher` 两个独立 DB 查询串行 | `Promise.all` |
|
||
| settings | actions.ts | 95-113 | `upsertAiProviderAction` count 与 select 串行 | `Promise.all` |
|
||
| settings | actions-password.ts | 58-87 | user 与 passwordSecurity 查询串行 | `Promise.all` |
|
||
| elective | data-access-operations.ts | 18-34 | `runLottery` course 与 selections 串行 | `Promise.all` |
|
||
| elective | data-access-operations.ts | 86-112 | `selectCourse` course 与 existing 串行 | `Promise.all` |
|
||
| elective | data-access-operations.ts | 158-182 | `dropCourse` existing 与 course 串行 | `Promise.all` |
|
||
| proctoring | data-access.ts | 178-223 | `getExamProctoringSummary` 三个独立查询串行 | `Promise.all` |
|
||
| diagnostic | data-access-reports.ts | 172-180 | `getDiagnosticReportById` report 与 generator 串行 | 合并 JOIN 或 `Promise.all` |
|
||
| parent | data-access.ts | 58-117 | `getChildBasicInfo` 4 个串行查询 | JOIN 或 `Promise.all` |
|
||
|
||
#### 5.1.2 `async-defer-await`:将 await 移到实际使用的分支
|
||
|
||
无明显违规,多数文件已正确处理。
|
||
|
||
### 5.2 HIGH:服务端性能(server-*)
|
||
|
||
#### 5.2.1 `server-cache-react`:用 React.cache() 做请求内去重
|
||
|
||
| 模块 | 文件 | 缺失 cache() 的函数 |
|
||
|------|------|---------------------|
|
||
| grades | data-access.ts | getGradeRecords, getGradeRecordById, getClassGradeStats |
|
||
| grades | data-access-analytics.ts | getClassComparison, getGradeTrends |
|
||
| grades | data-access-ranking.ts | getClassRanking, getStudentRanking |
|
||
| elective | data-access-selections.ts | getCourseSelections, getStudentSelections, getStudentGradeId, getAvailableCoursesForStudent |
|
||
| diagnostic | data-access.ts | getStudentMastery, getStudentMasterySummary, getClassMasterySummary, getKnowledgePointStats |
|
||
| diagnostic | data-access-reports.ts | getDiagnosticReports, getDiagnosticReportById |
|
||
| files | data-access.ts | getFileAttachment, getFileAttachmentsByTarget, getFileAttachmentsByUploader, getAllFileAttachments, getFileAttachmentsWithFilters, getFileStats, getFileAttachmentsByIds |
|
||
| proctoring | data-access.ts | 多个查询函数 |
|
||
|
||
**标杆**:exams、homework、questions、textbooks、dashboard、announcements 已正确使用 `cache()` ✓。
|
||
|
||
#### 5.2.2 `server-after-nonblocking`:用 after() 做非阻塞操作
|
||
|
||
| 模块 | 文件 | 行号 | 问题 |
|
||
|------|------|------|------|
|
||
| school | actions.ts | 188, 219, 235 | `await logAudit(...)` 阻塞响应 |
|
||
|
||
**修复**:使用 Next.js `after()` 将审计日志改为非阻塞。
|
||
|
||
#### 5.2.3 `server-serialization`:最小化传给客户端组件的数据
|
||
|
||
需结合前端组件审查,本次后端核查未深入。
|
||
|
||
### 5.3 MEDIUM:JavaScript 性能(js-*)
|
||
|
||
#### 5.3.1 `js-set-map-lookups`:用 Set/Map 做 O(1) 查找
|
||
|
||
| 模块 | 文件 | 行号 | 问题 |
|
||
|------|------|------|------|
|
||
| grades | data-access-analytics.ts | 133-136 | `scope.classIds.includes(c.id)` — O(n) 查找 |
|
||
| diagnostic | data-access.ts | 116 | `answers.find(...)` 在 for 循环内 — O(n×m) |
|
||
|
||
**修复**:将数组转为 Set/Map 后用 `.has()` / `.get()`。
|
||
|
||
#### 5.3.2 `js-combine-iterations`:合并多次 filter/map 为一次循环
|
||
|
||
| 模块 | 文件 | 行号 | 问题 |
|
||
|------|------|------|------|
|
||
| grades | data-access.ts | 248-252 | 两次 if 判断遍历同一数组 |
|
||
| grades | data-access-analytics.ts | 180-181, 238-239 | 两次 filter 遍历同一数组 |
|
||
| grades | data-access-ranking.ts | 100, 102 | findIndex 和 find 查找同一元素 |
|
||
| diagnostic | data-access.ts | 80-81 | 两次 filter 遍历同一数组 |
|
||
| proctoring | data-access.ts | 188-193 | 两次 filter 统计 submissions |
|
||
|
||
#### 5.3.3 `js-early-exit`:函数早返回
|
||
|
||
多数文件已正确处理。
|
||
|
||
#### 5.3.4 动态 import 改静态 import
|
||
|
||
| 模块 | 文件 | 行号 | 问题 |
|
||
|------|------|------|------|
|
||
| grades | data-access.ts | 148, 172 | `await import("@paralleldrive/cuid2")` 函数内动态导入 |
|
||
| diagnostic | data-access-reports.ts | 59, 103 | 同上 |
|
||
|
||
**修复**:改为文件顶部静态 `import { createId } from "@paralleldrive/cuid2"`。
|
||
|
||
### 5.4 性能问题汇总(按影响排序)
|
||
|
||
| 优先级 | 问题 | 影响范围 |
|
||
|--------|------|---------|
|
||
| **CRITICAL** | grades N+1 查询(getClassComparison) | 大班级成绩对比性能差 |
|
||
| **HIGH** | 循环内串行 await db.update/insert(homework/data-access-write.ts、elective/data-access-operations.ts、diagnostic/data-access.ts、course-plans/data-access.ts) | 批量操作慢 |
|
||
| **HIGH** | 大量读函数未用 React.cache()(grades、elective、diagnostic、files、proctoring) | 请求内重复查询 |
|
||
| **MEDIUM** | 独立查询未并行化(9 处) | 响应时间累加 |
|
||
| **MEDIUM** | 动态 import(3 处) | 每次调用有开销 |
|
||
| **LOW** | 重复 filter 遍历(5 处) | 微小性能损失 |
|
||
| **LOW** | O(n) 查找代替 O(1)(2 处) | 微小性能损失 |
|
||
|
||
---
|
||
|
||
## 六、优先修复路线图
|
||
|
||
### 第一阶段:P0 严重问题(立即修复)
|
||
|
||
1. **exams/data-access.ts**:`persistAiGeneratedExamDraft` 改为调用 questions data-access
|
||
2. **exams/data-access.ts**:`getExams`/`getExamById` 改为通过 classes data-access 获取 gradeId
|
||
3. **questions/schema.ts**:`z.any()` → `z.unknown()`
|
||
4. **questions/actions.ts**:`getQuestionsAction` 和 `getKnowledgePointOptionsAction` 包装为 ActionState
|
||
5. **textbooks/actions.ts**:新建 schema.ts,添加 Zod 验证;清理 14 处 `as` 断言
|
||
6. **grades/data-access-analytics.ts**:`getClassComparison` N+1 查询重构为 `inArray` 批量查询
|
||
7. **classes/data-access-stats.ts、data-access-students.ts**:移除对 homework/exams 表的直接查询
|
||
8. **school/actions.ts**:所有 DB 操作下沉到 data-access.ts
|
||
9. **proctoring/actions.ts**:DB 查询下沉到 data-access.ts
|
||
10. **messaging↔notifications**:将 `messageNotifications` 和 `notificationPreferences` 表所有权移交 notifications 模块
|
||
11. **notifications/channels/in-app-channel.ts:54**:修复非法 as 断言
|
||
12. **users/user-service.ts:13**:移除硬编码弱密码 "123456"
|
||
13. **users/actions.ts**:`updateUserProfile` 整改(权限+下沉+返回类型)
|
||
14. **scheduling/data-access.ts**:4 个函数补充返回类型标注
|
||
|
||
### 第二阶段:P1 重要问题(尽快修复)
|
||
|
||
1. **跨模块直查 DB**:在各模块 data-access 暴露跨模块查询接口,消除直查(涉及 exams、homework、questions、grades、classes、messaging、notifications、parent、proctoring、diagnostic、elective)
|
||
2. **`as` 断言清理**:全局替换为类型守卫(涉及 20+ 文件,60+ 处)
|
||
3. **`requireAuth` → `requirePermission`**:users、messaging、proctoring、announcements、settings 共 8 处
|
||
4. **`import type` 修正**:exams、questions、textbooks、proctoring 共 4 处
|
||
5. **动态 import 改静态**:grades、diagnostic 共 3 处
|
||
6. **grades/data-access.ts:249**:除零 bug 守卫
|
||
7. **audit/data-access.ts**:导出函数数据截断问题(3 处)
|
||
8. **elective/data-access-operations.ts**:`runLottery` 加事务包裹 + 批量更新
|
||
9. **diagnostic/actions.ts**:新建 schema.ts,用 Zod 替代手动校验
|
||
10. **classes 模块**:新建 schema.ts,用 Zod 替换 actions.ts 中的手动校验
|
||
11. **course-plans/actions.ts**:补齐 revalidatePath 调用(2 处)
|
||
12. **homework/data-access-write.ts:305-311**:`gradeHomeworkAnswers` 加事务包裹
|
||
13. **users/user-service.ts**:`batchImportUsers` 加事务包裹
|
||
14. **不可达代码清理**:classes/data-access.ts:297-299、classes/data-access-admin.ts:380-382
|
||
|
||
### 第三阶段:P2 优化(迭代修复)
|
||
|
||
1. **React.cache() 包装**:grades、elective、diagnostic、files、proctoring 读函数添加 cache()
|
||
2. **Promise.all 并行化**:9 处独立查询串行执行
|
||
3. **`after()` 非阻塞审计日志**:school/actions.ts(3 处)
|
||
4. **错误吞没清理**:school、classes、course-plans、announcements、files、audit、elective 共 30+ 处静默 catch
|
||
5. **非空断言 `!` 清理**:20+ 处替换为显式判空
|
||
6. **函数返回类型补齐**:30+ 个箭头函数
|
||
7. **重复代码提取**:normalizeRoleName、normalizeBcryptHash、toIso、serializeDate、toNumber/normalize、handleActionError、buildExcelSheet
|
||
8. **合并 filter 遍历**:5 处
|
||
9. **Set/Map 查找优化**:2 处
|
||
10. **架构文档同步**:exams/actions.ts 行数、settings 导出函数列表、P2-11 状态、announcements 依赖关系
|
||
11. **exams/ai-pipeline.ts 拆分**:912 行按职责拆分为 4 个文件
|
||
12. **layout/navigation.ts**:permission 字段改用 Permission 类型
|
||
13. **notifications/external-sdk.d.ts**:安装实际 SDK 后用其自带类型覆盖 any
|
||
|
||
---
|
||
|
||
## 附录:核查方法说明
|
||
|
||
### 核查流程
|
||
|
||
1. **架构图优先**:先阅读 `docs/architecture/004_architecture_impact_map.md`,理解模块依赖关系和已知问题
|
||
2. **并行核查**:启动 5 个并行搜索代理,分别核查核心教学、教学管理、用户沟通、扩展功能、其他模块
|
||
3. **逐文件审查**:每个代理读取所有后端 `.ts` 文件,对照项目规范逐条核查
|
||
4. **性能规则应用**:应用 Vercel React Best Practices 的 65 条规则,识别性能优化机会
|
||
5. **结果汇总**:合并 5 个代理的核查结果,按严重程度和模块分类整理
|
||
|
||
### 核查依据
|
||
|
||
- `.trae/rules/project_rules.md`:项目规则(架构分层、TS规范、命名、组件、Server Action、Tailwind、安全、提交)
|
||
- `docs/standards/coding-standards.md`:编码规范(详细规范)
|
||
- `docs/architecture/004_architecture_impact_map.md`:架构影响地图(模块清单、依赖关系、已知问题)
|
||
- `docs/architecture/005_architecture_data.json`:AI 友好格式的结构化数据
|
||
- Vercel React Best Practices:65 条性能优化规则(8 个类别)
|
||
|
||
### 未核查范围
|
||
|
||
- `src/modules/*/components/` 下的前端组件文件
|
||
- `src/modules/*/hooks/` 下的 Hook 文件
|
||
- `src/shared/` 下的基础设施文件
|
||
- `src/app/` 下的路由层文件
|
||
- 测试文件(`*.test.ts`)
|
||
|
||
如需核查上述范围,请另行指派任务。
|