Files
NextEdu/docs/architecture/audit/management-modules-audit.md

394 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 管理类模块职责与耦合审查报告
> 审查范围school / classes / scheduling / attendance / users / audit / course-plans / announcements
> 审查日期2026-06-17
> 审查依据单一职责原则SRP、模块边界清晰度、跨模块耦合度、企业级代码规范单文件 ≤ 1000 行硬性上限)
> 审查方式:只读源码分析,未修改任何代码
---
## 一、总体评价
| 模块 | 行数(最大文件) | 职责单一性 | 耦合度 | 严重度 |
|------|----------------|-----------|--------|--------|
| school | 325 | ✅ 良好 | ✅ 低 | 🟢 合格 |
| classes | ~~2104~~ → 656 | ✅ 已修复 | ❌ 严重 | 🟡 需改进 |
| scheduling | 310算法/ 302actions | ✅ 算法独立 | ⚠️ 中 | 🟡 需改进 |
| attendance | 271 | ✅ 良好 | ⚠️ 中 | 🟢 合格 |
| users | 291import-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-accessclassEnrollments 写入改调 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-exportDB 写入下沉 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` 突破硬性上限,需立即拆分。
---
*报告结束。本审查未修改任何源代码。*