# 管理类模块职责与耦合审查报告 > 审查范围:school / classes / scheduling / attendance / users / audit / course-plans / announcements > 审查日期:2026-06-17 > 审查依据:单一职责原则(SRP)、模块边界清晰度、跨模块耦合度、企业级代码规范(单文件 ≤ 1000 行硬性上限) > 审查方式:只读源码分析,未修改任何代码 --- ## 一、总体评价 | 模块 | 行数(最大文件) | 职责单一性 | 耦合度 | 严重度 | |------|----------------|-----------|--------|--------| | school | 325 | ✅ 良好 | ✅ 低 | 🟢 合格 | | classes | ~~2104~~ → 656 | ✅ 已修复 | ❌ 严重 | 🟡 需改进 | | scheduling | 310(算法)/ 302(actions) | ✅ 算法独立 | ⚠️ 中 | 🟡 需改进 | | attendance | 271 | ✅ 良好 | ⚠️ 中 | 🟢 合格 | | users | 291(import-export) | ❌ 违反 | ❌ 高 | 🟠 较严重 | | audit | 212 | ⚠️ 部分违反 | ✅ 低 | 🟡 需改进 | | course-plans | 320 | ✅ 良好 | ✅ 低 | 🟢 合格 | | announcements | 242 | ⚠️ 部分违反 | ✅ 低 | 🟡 需改进 | **核心结论**: 1. ~~`classes` 模块是全项目耦合最严重的模块,单文件 2104 行远超 1000 行硬性上限,混入了 schedule、homework、grades 三个业务领域的逻辑。~~ ✅ 已修复(2026-06-17 拆分为 5 个文件,均 ≤800 行) 2. `users/import-export.ts` 违反单一职责,同时处理导入、导出、用户创建、班级注册四类逻辑。 3. `scheduling/auto-scheduler.ts` 是算法独立化的**优秀范例**,纯函数、无 DB 访问、可独立测试。 4. `announcements` 和 `audit` 模块的 data-access 层不完整,写操作或导出逻辑泄漏到 actions 层。 --- ## 二、模块审查明细 ### 2.1 school 模块 — 🟢 合格 **文件清单**:actions.ts (325 行) / data-access.ts (186 行) / schema.ts (51 行) / types.ts (42 行) **职责边界**:✅ 清晰。仅负责 schools / academicYears / departments / grades 的 CRUD。 **优点**: - actions.ts 每个 Action 职责单一:权限校验 → 解析 → DB 写入 → 审计日志 → revalidatePath。 - data-access.ts 仅包含只读查询,无跨模块写入。 - 权限校验完整:SCHOOL_MANAGE / GRADE_MANAGE 均接入 requirePermission。 **问题**: - ⚠️ **审计日志不一致**:仅 school 实体的 create/update/delete 调用了 `logAudit`,而 department / academicYear / grade 的 CRUD 均未记录审计日志。 - ⚠️ `getStaffOptions` / `getGrades` 直接查询 users / roles / usersToRoles 表(跨模块读),但属于展示用关联查询,可接受。 **建议**:为 department / academicYear / grade 的 CRUD 补充 `logAudit` 调用,保持审计一致性。 --- ### 2.2 classes 模块 — 🟡 需改进(文件拆分已修复,跨模块耦合仍存在) **文件清单**:actions.ts (765 行) / data-access.ts (656 行) / data-access-stats.ts (604 行) / data-access-schedule.ts (230 行) / data-access-students.ts (280 行) / data-access-admin.ts (441 行) / types.ts (201 行) > ✅ `data-access.ts` 已于 2026-06-17 拆分为 5 个文件,所有文件均 ≤800 行,通过 re-export 保持向后兼容。 #### 2.2.1 职责混乱 — 混入三个外部业务领域 `data-access.ts` 实际承载了四个业务领域的逻辑: | 行范围 | 逻辑 | 应属模块 | |--------|------|---------| | 49-145 | 教师身份解析、班级访问控制 | classes(合理) | | 156-601 | 班级列表/学生/教师查询 | classes(合理) | | 697-735 | 课表查询 `getClassSchedule` | **scheduling** | | 760-998 | `getClassHomeworkInsights` 班级作业洞察(238 行) | **homework** | | 1006-1300 | `getGradeHomeworkInsights` 年级作业洞察(294 行) | **homework** | | 1302-1775 | 班级 CRUD + 邀请码 + 注册 | classes(合理) | | 1838-1968 | 课表项 CRUD `createClassScheduleItem` 等 | **scheduling** | | 1970-2103 | `getStudentsSubjectScores` 学生科目成绩(133 行) | **grades / homework** | **关键问题**: - `getClassHomeworkInsights` 和 `getGradeHomeworkInsights` 合计 **532 行**作业统计逻辑,直接查询 `homeworkAssignments`、`homeworkSubmissions`、`homeworkAssignmentTargets`、`homeworkAssignmentQuestions`、`exams` 五张表,属于 homework 模块的核心业务,不应存在于 classes 模块。 - 课表 CRUD(`createClassScheduleItem` / `updateClassScheduleItem` / `deleteClassScheduleItem`)与 scheduling 模块的 `applyAutoScheduleAction` 写入同一张 `classSchedule` 表,**两个模块对同一张表有写权限**,边界严重模糊。 - `getStudentsSubjectScores` 直接关联 `homeworkSubmissions` + `exams` + `subjects` 计算学生科目分数,属于成绩分析逻辑。 #### 2.2.2 types.ts 跨领域类型污染 `types.ts` 定义了本应属于其他模块的类型: - `ClassHomeworkInsights` / `GradeHomeworkInsights` / `ClassHomeworkAssignmentStats` / `ScoreStats` / `AssignmentSummary` — 应属 homework 模块 - `ClassScheduleItem` / `CreateClassScheduleItemInput` / `UpdateClassScheduleItemInput` / `StudentScheduleItem` — 与 scheduling 模块的 `types.ts` 存在概念重叠 #### 2.2.3 actions.ts 跨模块直接查询 `actions.ts` 多处直接查询 `grades` 表(属于 school 模块)进行权限验证,绕过了 school/data-access: | 行号 | 函数 | 直接查询 | |------|------|---------| | 60-68 | `createTeacherClassAction` | `db.select().from(grades)` 校验 gradeHead 权限 | | 186-194 | `createGradeClassAction` | `db.select().from(grades)` 校验年级管理权 | | 241-249 | `updateGradeClassAction` | `db.select().from(classes)` 绕过自身 data-access | | 251-272 | `updateGradeClassAction` | `db.select().from(grades)` 校验源/目标年级 | | 340-348 | `deleteGradeClassAction` | `db.select().from(grades)` 校验权限 | **问题**:权限校验逻辑散落在 actions 层,既未下沉到 data-access,也未通过 school 模块暴露的查询接口。 #### 2.2.4 actions.ts 职责重复 存在三组近乎重复的 Action 集合: - Teacher 系列:`createTeacherClassAction` / `updateTeacherClassAction` / `deleteTeacherClassAction` - Admin 系列:`createAdminClassAction` / `updateAdminClassAction` / `deleteAdminClassAction` - Grade 系列:`createGradeClassAction` / `updateGradeClassAction` / `deleteGradeClassAction` 三者表单解析、字段校验逻辑高度重复,仅权限上下文不同。 #### 2.2.5 data-access.ts 内调用 auth() `getSessionTeacherId`(行 49-62)在 data-access 层直接调用 `auth()` 获取会话,违反"data-access 不感知请求上下文"的分层原则。会话信息应由 actions 层传入。 #### 2.2.6 组件层边界模糊 `classes/components/` 包含 `schedule-view.tsx`、`schedule-filters.tsx`、`class-detail/class-schedule-widget.tsx` 等课表相关组件,与 `scheduling/components/` 的职责重叠。 **整改建议**(优先级 P0): 1. 将 `getClassHomeworkInsights` / `getGradeHomeworkInsights` / `getStudentsSubjectScores` / `getClassStudentSubjectScoresV2` 迁移至 homework / grades 模块。 2. 将课表 CRUD(`createClassScheduleItem` 等)迁移至 scheduling 模块,统一 `classSchedule` 表的写入口。 3. 将 `data-access.ts` 拆分为 `data-access.ts`(班级 CRUD)+ `data-access-enrollments.ts`(注册/邀请码)+ `data-access-insights.ts`(如暂不迁移则隔离)。 4. 将权限校验中的 `grades` 表查询改为调用 school/data-access 暴露的接口。 5. 将 `getSessionTeacherId` 上移至 actions 层或 shared/lib。 --- ### 2.3 scheduling 模块 — 🟡 需改进(算法层优秀) **文件清单**:actions.ts (302 行) / auto-scheduler.ts (310 行) / data-access.ts (272 行) / schema.ts / types.ts #### 2.3.1 auto-scheduler.ts — ✅ 优秀范例 **这是全项目算法独立化的最佳实践**: - 纯函数:`findOptimalSlot` / `validateSchedule` / `autoSchedule` / `buildDefaultTimeSlots` - 无 `"server-only"` 副作用,无 DB 访问,无 `import { db }` - 仅依赖 types.ts 的类型导入 - **可独立单元测试**:给定输入即可断言输出,无需 mock 数据库 - 算法清晰:贪心 + 约束检查(午餐、每日上限、教师/教室冲突、避免连排) **建议**:以此为模板,指导其他模块的算法抽取(如 homework 的批改评分算法、grades 的统计算法)。 #### 2.3.2 actions.ts — 跨模块直接写入 | 行号 | 函数 | 问题 | |------|------|------| | 110-116 | `autoScheduleAction` | 直接 `db.select().from(users)` 查询教师,绕过 data-access 的 `getTeachersForScheduling` | | 168-180 | `applyAutoScheduleAction` | 直接 `db.transaction` 写入 `classSchedule` 表,**绕过 data-access 且跨模块写 classes 领域的表** | `applyAutoScheduleAction` 的问题尤为突出:它删除并重建 `classSchedule` 表数据,但该写操作既未经过 scheduling/data-access,也未经过 classes/data-access,形成**第三个对 classSchedule 表的写入口**(另两个在 classes/data-access 的 createClassScheduleItem 等)。 #### 2.3.3 data-access.ts — 跨模块读查询 包含 `getAdminClassesForScheduling` / `getTeachersForScheduling` / `getClassroomsForScheduling` / `getClassSubjectsForScheduling` 四个辅助查询,直接访问 `classes` / `classSubjectTeachers` / `subjects` / `users` / `classrooms` 表。 这些是排课场景的只读辅助查询,耦合度可接受,但理想情况下应通过各所属模块的 data-access 暴露接口。 #### 2.3.4 actions.ts 末尾 re-export ```typescript export { getSchedulingRules, getScheduleChanges, ... } from "./data-access" ``` actions 层 re-export data-access 函数是反模式,应让消费方直接从 data-access 导入。 **整改建议**: 1. 将 `applyAutoScheduleAction` 中的 `classSchedule` 写入逻辑下沉到 data-access(或与 classes 协商统一写入口)。 2. 将 `autoScheduleAction` 中的 users 查询改用 `getTeachersForScheduling`。 3. 移除 actions.ts 末尾的 re-export。 --- ### 2.4 attendance 模块 — 🟢 合格(结构典范) **文件清单**:actions.ts (271 行) / data-access.ts (271 行) / data-access-stats.ts (145 行) / schema.ts / types.ts **优点**: - **stats 独立拆分**:`data-access-stats.ts` 专门承载统计逻辑,是 classes 模块应学习的拆分模式。 - actions.ts 每个 Action 职责单一:权限校验 → 解析 → 委托 data-access → revalidate。 - `DataScope` 数据范围控制完整接入,支持 6 种 scope 类型。 - 无写操作泄漏到 actions 层。 **问题**: - ⚠️ `getClassStudentsForAttendance`(data-access.ts 行 206-217)直接查询 `classEnrollments` 表获取班级学生列表,属于 classes 模块数据,应通过 classes/data-access 暴露的接口调用。 - ⚠️ data-access.ts 和 data-access-stats.ts 均直接 JOIN `classes` 表获取班级名称(只读展示,可接受)。 **整改建议**:在 classes/data-access 暴露 `getClassStudentIds(classId)` 接口,供 attendance 调用。 --- ### 2.5 users 模块 — 🟠 较严重 **文件清单**:actions.ts (151 行) / data-access.ts (71 行) / import-export.ts (291 行) #### 2.5.1 import-export.ts — 四重职责混合 该文件同时承载四类互不相关的职责: | 行范围 | 职责 | 应属位置 | |--------|------|---------| | 54-73 | `generateUserImportTemplate` 生成导入模板 | export 模块 | | 78-111 | `parseUserImportData` 解析+校验导入数据 | import 模块 | | 116-207 | `batchImportUsers` 批量导入(含用户创建) | **data-access** | | 212-291 | `exportUsersToExcel` 导出用户列表 | export 模块 | **核心问题**: 1. **导入与导出未分离**:应拆分为 `import.ts` 与 `export.ts`。 2. **用户创建逻辑泄漏**:`batchImportUsers`(行 158-167)直接 `db.insert(users)` + 密码哈希 + `db.insert(usersToRoles)`,这是 data-access 层的职责,不应存在于 import-export 工具文件中。 3. **跨模块写 classEnrollments**:行 174-184,`batchImportUsers` 直接 `db.insert(classEnrollments)` 将学生注册到班级,**这是 classes 模块的写操作**,严重违反模块边界。 4. **跨模块读 classes**:行 130-137,直接查询 `classes` 表根据邀请码查找班级,应通过 classes/data-access 暴露接口。 #### 2.5.2 actions.ts — 绕过 data-access `updateUserProfile`(行 29-51)直接 `db.update(users)` 写入数据库,绕过了 data-access 层。data-access.ts 仅有 `getUserProfile` 一个读函数,写操作缺失。 #### 2.5.3 data-access.ts — 过于单薄 仅 71 行,只包含 `getUserProfile`。用户创建、更新、角色绑定等写操作均未在此层实现。 **整改建议**(优先级 P1): 1. 将 `import-export.ts` 拆分为 `import.ts`(解析+校验)和 `export.ts`(模板生成+列表导出)。 2. 将 `batchImportUsers` 中的用户创建逻辑迁移至 `data-access.ts` 的 `createUser` 函数,import.ts 仅负责编排。 3. 将 classEnrollments 写入改为调用 classes/data-access 的注册接口(如 `enrollStudentByInvitationCode`)。 4. 将 `updateUserProfile` 的 DB 写入下沉到 data-access。 --- ### 2.6 audit 模块 — 🟡 需改进 **文件清单**:actions.ts (212 行) / data-access.ts (260 行) / types.ts **问题**: - ⚠️ **Excel 导出逻辑内联在 actions 层**:`exportAuditLogsAction` / `exportLoginLogsAction` / `exportDataChangeLogsAction` 三个 Action 各自内联了 `exportToExcel` 调用及完整的列定义(表头、宽度、字段映射),每个约 40 行。这是展示/格式化逻辑,应抽取到独立的 `export.ts`。 - ⚠️ 三个导出 Action 的结构高度重复(权限校验 → 查询 → 构造 columns → exportToExcel → 返回 buffer),可抽象为通用导出工厂。 **优点**: - data-access.ts 职责清晰,仅包含日志查询,无跨模块问题。 - 分页逻辑统一(clampPage / clampPageSize)。 **整改建议**:抽取 `export.ts`,封装 `exportAuditLogsToExcel(items)` / `exportLoginLogsToExcel(items)` / `exportDataChangeLogsToExcel(items)`,actions 层仅负责编排。 --- ### 2.7 course-plans 模块 — 🟢 合格 **文件清单**:actions.ts (265 行) / data-access.ts (320 行) / schema.ts / types.ts **优点**: - actions.ts 使用 `handleError` / `revalidatePlanPaths` 辅助函数消除重复,是 actions 层的**良好范例**。 - data-access.ts 职责清晰:课程计划 CRUD + 周计划项 CRUD + 排序。 - 跨模块查询仅为 `classes` / `subjects` / `users` 的 LEFT JOIN 取展示名称(只读,可接受)。 **小问题**: - ⚠️ `getSubjectOptions`(行 310-320)直接查询 `subjects` 表,subjects 无独立模块,暂可接受。 **结论**:该模块结构可作为其他模块的参考模板。 --- ### 2.8 announcements 模块 — 🟡 需改进 **文件清单**:actions.ts (242 行) / data-access.ts (120 行) / schema.ts / types.ts **核心问题 — 写操作泄漏到 actions 层**: data-access.ts 仅包含两个**只读**函数(`getAnnouncements` / `getAnnouncementById`),所有写操作均直接在 actions.ts 中执行 `db.insert` / `db.update` / `db.delete`: | Action | 直接 DB 操作 | 行号 | |--------|-------------|------| | `createAnnouncementAction` | `db.insert(announcements)` | 53-63 | | `updateAnnouncementAction` | `db.update(announcements)` | 118-130 | | `deleteAnnouncementAction` | `db.delete(announcements)` | 154 | | `publishAnnouncementAction` | `db.update(announcements)` | 176-183 | | `archiveAnnouncementAction` | `db.update(announcements)` | 206-212 | 这违反了"data-access 层封装所有 DB 操作"的分层约定,导致: - 写逻辑无法被其他 server 端代码复用 - publishedAt 计算逻辑散落在 actions 中,难以测试 **其他问题**: - ⚠️ 死代码:`updateAnnouncementAction` 行 108 计算 `wasPublished`,行 135 `void wasPublished` 显式丢弃,未实际使用。 - ⚠️ `getAnnouncementsAction` 使用 `requireAuth()` 而非 `requirePermission(ANNOUNCEMENT_READ)`,与其他模块的权限模式不一致。 **整改建议**: 1. 在 data-access.ts 补充 `createAnnouncement` / `updateAnnouncement` / `deleteAnnouncement` / `publishAnnouncement` / `archiveAnnouncement` 写函数。 2. actions 层仅保留权限校验 + 解析 + 委托调用。 3. 清理 `wasPublished` 死代码。 --- ## 三、跨模块直接查询汇总 ### 3.1 跨模块写操作(严重) | 源文件 | 目标表 | 操作 | 应通过 | |--------|--------|------|--------| | classes/data-access.ts | classSchedule | CRUD | scheduling/data-access | | scheduling/actions.ts | classSchedule | delete + insert | scheduling/data-access | | users/import-export.ts | classEnrollments | insert | classes/data-access | | users/import-export.ts | users, usersToRoles | insert | users/data-access | | announcements/actions.ts | announcements | insert/update/delete | announcements/data-access | | users/actions.ts | users | update | users/data-access | > ⚠️ `classSchedule` 表存在 **三个写入口**(classes/data-access、scheduling/actions、scheduling/data-access 间接),是数据完整性高风险点。 ### 3.2 跨模块读操作(需评估) | 源文件 | 目标表 | 用途 | 评估 | |--------|--------|------|------| | classes/data-access.ts | homeworkAssignments, homeworkSubmissions, homeworkAssignmentTargets, homeworkAssignmentQuestions, exams | 作业洞察统计 | ❌ 应迁移至 homework | | classes/data-access.ts | grades, schools | 年级/学校关联 | ⚠️ 应通过 school data-access | | classes/actions.ts | grades | 权限校验 | ⚠️ 应通过 school data-access | | scheduling/data-access.ts | classes, classSubjectTeachers, subjects, users, classrooms | 排课辅助查询 | 🟡 可接受(只读) | | attendance/data-access.ts | classEnrollments | 获取班级学生 | ⚠️ 应通过 classes data-access | | users/import-export.ts | classes | 邀请码查询 | ⚠️ 应通过 classes data-access | | course-plans/data-access.ts | classes, subjects, users | 展示名称 JOIN | 🟢 可接受 | --- ## 四、actions 层多职责问题汇总 | Action | 混入职责 | 应拆分 | |--------|---------|--------| | `users/import-export.ts: batchImportUsers` | 用户创建 + 密码哈希 + 角色绑定 + 班级注册 | 拆为 createUser + enrollStudent | | `classes/actions.ts: updateGradeClassAction` | 班级更新 + 科任教师批量分配 | 拆为 updateClass + setClassSubjectTeachers(已部分拆分但仍在同一 Action 内编排) | | `classes/actions.ts: updateAdminClassAction` | 同上 | 同上 | | `audit/actions.ts: exportAuditLogsAction` | 查询 + Excel 列定义 + 导出 | 拆为 query + exportAuditLogsToExcel | | `audit/actions.ts: exportLoginLogsAction` | 同上 | 同上 | | `audit/actions.ts: exportDataChangeLogsAction` | 同上 | 同上 | | `announcements/actions.ts: createAnnouncementAction` | 解析 + publishedAt 计算 + DB 写入 | DB 写入下沉 data-access | --- ## 五、整改优先级 ### P0 — 立即整改(影响数据完整性 & 严重违反规范) 1. **classes/data-access.ts 拆分**:2104 行远超硬性上限,且混入 homework/scheduling/grades 三个领域。优先迁移 `getClassHomeworkInsights` / `getGradeHomeworkInsights`(532 行)至 homework 模块。 2. **统一 classSchedule 表写入口**:classes 与 scheduling 两个模块对该表有写权限,需协商归属并收敛为单一写入口。 ### P1 — 尽快整改(模块边界违反) 3. **users/import-export.ts 拆分**:分离导入/导出,用户创建逻辑下沉 data-access,classEnrollments 写入改调 classes 接口。 4. **announcements 写操作下沉**:在 data-access 补充写函数,actions 仅编排。 5. **classes/actions.ts 权限校验**:grades 表查询改通过 school/data-access 接口。 ### P2 — 持续优化(代码质量) 6. **audit 导出逻辑抽取**:内联的 Excel 列定义移至独立 export.ts。 7. **school 审计日志补全**:department/academicYear/grade 的 CRUD 补充 logAudit。 8. **attendance 跨模块查询**:`getClassStudentsForAttendance` 改调 classes 接口。 9. **scheduling/actions.ts**:移除 re-export,DB 写入下沉 data-access。 10. **announcements 死代码清理**:移除 `void wasPublished`。 --- ## 六、优秀实践(建议推广) | 实践 | 模块 | 说明 | |------|------|------| | 算法纯函数化 | scheduling/auto-scheduler.ts | 无 DB 依赖,可独立测试,应作为算法抽取模板 | | stats 文件拆分 | attendance/data-access-stats.ts | 统计逻辑独立成文件,classes 应效仿 | | actions 辅助函数 | course-plans/actions.ts | handleError / revalidatePlanPaths 消除重复 | | DataScope 接入 | attendance/actions.ts | 6 种数据范围完整支持 | | 权限统一接入 | school / attendance / course-plans | 全部 Action 使用 requirePermission | --- ## 七、附:文件行数统计 | 文件 | 行数 | 上限 | 状态 | |------|------|------|------| | classes/data-access.ts | 2104 | 1000 | 🔴 超限 2.1x | | classes/actions.ts | 765 | 800(建议) | 🟢 合规 | | classes/types.ts | 201 | 无限制 | 🟢 合规 | | school/actions.ts | 325 | 800(建议) | 🟢 合规 | | school/data-access.ts | 186 | 800(建议) | 🟢 合规 | | scheduling/auto-scheduler.ts | 310 | 无限制 | 🟢 合规 | | scheduling/actions.ts | 302 | 800(建议) | 🟢 合规 | | scheduling/data-access.ts | 272 | 800(建议) | 🟢 合规 | | attendance/actions.ts | 271 | 800(建议) | 🟢 合规 | | attendance/data-access.ts | 271 | 800(建议) | 🟢 合规 | | attendance/data-access-stats.ts | 145 | 800(建议) | 🟢 合规 | | users/import-export.ts | 291 | 800(建议) | 🟢 合规(但职责混合) | | users/actions.ts | 151 | 800(建议) | 🟢 合规 | | users/data-access.ts | 71 | 800(建议) | 🟢 合规 | | audit/actions.ts | 212 | 800(建议) | 🟢 合规 | | audit/data-access.ts | 260 | 800(建议) | 🟢 合规 | | course-plans/actions.ts | 265 | 800(建议) | 🟢 合规 | | course-plans/data-access.ts | 320 | 800(建议) | 🟢 合规 | | announcements/actions.ts | 242 | 800(建议) | 🟢 合规 | | announcements/data-access.ts | 120 | 800(建议) | 🟢 合规 | > 仅 `classes/data-access.ts` 突破硬性上限,需立即拆分。 --- *报告结束。本审查未修改任何源代码。*