Files
NextEdu/docs/architecture/audit/school-grade-class-audit-report.md
SpecialX 10c668f36a feat(school,classes): 学校/年级/班级模块审计修复 — 权限校验 + i18n + 架构图同步
- 新增审计报告 docs/architecture/audit/school-grade-class-audit-report.md

- 修复 P0-4: teacher/classes 4 个页面补充 requirePermission 权限校验

- 修复 P0-5: 新增 school.json i18n 文件(zh-CN/en)并接入 schools-view 组件

- 同步架构图 004:补充 grade-management 死模块记录与 teacher/classes 权限修复说明
2026-06-22 16:44:02 +08:00

319 lines
21 KiB
Markdown
Raw Permalink 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`(学校/学年/部门/年级 CRUD、`grade-management`(年级管理重构模块)、`classes`(班级管理)
> 审查日期2026-06-22
> 审查依据项目规则三层架构、权限校验、i18n、TypeScript 严格模式、单文件行数限制、K12 行业优秀实践
> 审查方式:只读源码分析 + 架构图比对,未修改任何代码
---
## 一、现有实现概要
### 1.1 模块文件分布
| 模块 | 核心文件 | 行数(约) | 职责 |
|------|---------|-----------|------|
| `school` | `actions.ts` / `data-access.ts` / `schema.ts` / `types.ts` + 4 个组件 | 349 / 504 / 51 / 96 | 学校/学年/部门/年级的 CRUD |
| `grade-management` | `actions.ts` / `data-access.ts` / `data-access-insights.ts` / `schema.ts` / `types.ts` + 11 组件 + 4 hooks + 4 services + 2 widgets + 1 config | 213 / 238 / 75 / — / 149 | 年级管理(重构版,含洞察) |
| `classes` | `actions.ts` / `data-access.ts` / `data-access-{admin,stats,schedule,students,invitations}.ts` / `schema.ts` / `types.ts` + 14 组件 | 974 / 548 / 406 / 513 / 194 / 253 / — / 152 / 183 | 班级 CRUD + 学生/教师管理 + 邀请码 + 课表 + 作业洞察 |
### 1.2 页面分布(共 13 个 page.tsx
| 路由分组 | 页面 | 权限校验 | i18n | 调用方式 |
|---------|------|---------|------|---------|
| `admin/school/*` | schools / grades / classes / departments / academic-year | ✅ `requirePermission(SCHOOL_MANAGE)` | ❌ 中文硬编码 | 直接调用 data-access |
| `management/grade/*` | classes / insights | ✅ `requirePermission(GRADE_MANAGE/GRADE_RECORD_READ)` | ❌ 英文硬编码 | 直接调用 classes/school data-access |
| `teacher/classes/*` | my / my/[id] / schedule / students | ❌ **无任何校验** | ❌ 英文硬编码 | 直接调用 data-access |
### 1.3 架构图记录情况
`docs/architecture/004_architecture_impact_map.md` 中:
- ✅ 已记录 `school` 模块§2.8)和 `classes` 模块§2.7)的职责、依赖关系、跨模块通信方式
- ✅ 已记录 `classes` 模块的文件拆分5 个 data-access 子文件)和 P0-7 修复homework 跨模块封装)
-**未记录 `grade-management` 模块** — 该模块拥有完整的 services/hooks/widgets/config 架构,但架构图中完全缺失
-**未记录 `grade-management` 模块未被任何页面使用的事实** — 这是重大架构偏差
### 1.4 数据流概要
```
admin/school/grades 页面
└─→ school/data-access.getGrades() / getSchools() / getStaffOptions()
└─→ school/components/grades-view.tsx客户端组件
└─→ school/actions.ts → createGradeAction / updateGradeAction / deleteGradeAction
management/grade/classes 页面
└─→ classes/data-access.getGradeManagedClasses() / getTeacherOptions()
└─→ school/data-access.getGradesForStaff()
└─→ classes/components/grade-classes-view.tsx
└─→ classes/actions.ts → createGradeClassAction / updateGradeClassAction / ...
teacher/classes/* 页面
└─→ classes/data-access.getTeacherClasses() / getClassStudents() / getClassSchedule()
└─→ classes/components/*my-classes-grid / students-table / schedule-view
└─→ classes/actions.ts → createTeacherClassAction / ...
grade-management 模块(⚠️ 完全未被使用)
└─→ services/grade-service.ts接口定义
└─→ services/admin-grade-service.ts / teacher-grade-service.ts实现
└─→ widgets/grade-management-widget.tsx主面板
└─→ ⚠️ 无任何页面导入此模块
```
---
## 二、现存问题与原因分析
### 2.1 架构层面
#### P0-1`grade-management` 模块完全未被使用(死模块)
- **位置**`src/modules/grade-management/` 全模块
- **问题**该模块拥有完整的理想架构Service 接口 + Context 依赖注入 + 角色配置 + Error Boundary + Skeleton + i18n + hooks 分离),但 **13 个相关页面中无任何一个导入该模块**`management/grade/*` 页面实际依赖 `classes``school` 模块的 data-access。
- **违反规则**:架构图优先规则(图未覆盖则先补图)、模块标准结构(该模块存在但未接入)
- **原因**:推测为未完成的重构 — 已建立目标架构但未将页面迁移过来
- **后果**:维护两套年级管理逻辑(`school` 模块的 grade CRUD + `grade-management` 模块的 grade CRUD职责重叠、产生混淆理想架构模式无法落地发挥价值
#### P0-2年级 CRUD 逻辑重复定义
- **位置**
- `src/modules/school/actions.ts` L268-349`createGradeAction` / `updateGradeAction` / `deleteGradeAction`
- `src/modules/grade-management/actions.ts` L37-203同名函数 `createGradeAction` / `updateGradeAction` / `deleteGradeAction`
- `src/modules/school/data-access.ts` L256-285`createGrade` / `updateGrade` / `deleteGrade`
- `src/modules/grade-management/data-access.ts` L137-171同名函数 `createGrade` / `updateGrade` / `deleteGrade`
- **问题**:两套模块各自定义了完全相同的年级 CRUD 逻辑,`admin/school/grades` 页面使用 `school` 模块版本,`grade-management` 模块版本无人调用
- **违反规则**DRY 原则、模块标准结构(职责应归属单一模块)
- **后果**修改年级逻辑需同步两处极易遗漏两套实现的审计日志策略不一致school 模块 grade CRUD 无 `logAudit`grade-management 模块有)
#### P0-3`classes/actions.ts` 接近行数硬上限
- **位置**`src/modules/classes/actions.ts`974 行)
- **问题**:文件已达 974 行,接近 1000 行硬性上限。包含 3 组近乎重复的 CRUD ActionTeacher 系列 / Admin 系列 / Grade 系列)+ 邀请码 Action + 课表 Action
- **违反规则**单文件行数限制Server Actions 建议 ≤ 800 行,硬性上限 1000 行)
- **后果**:再增加任何功能即超限;文件过大降低可读性和可维护性
### 2.2 权限层面
#### P0-4`teacher/classes/*` 4 个页面完全缺少权限校验
- **位置**
- `src/app/(dashboard)/teacher/classes/my/page.tsx` — 无 `requirePermission()`
- `src/app/(dashboard)/teacher/classes/my/[id]/page.tsx` — 无 `requirePermission()`
- `src/app/(dashboard)/teacher/classes/schedule/page.tsx` — 无 `requirePermission()`
- `src/app/(dashboard)/teacher/classes/students/page.tsx` — 无 `requirePermission()`
- **问题**:这 4 个业务页面直接调用 data-access 获取数据,依赖路由中间件隐式保障身份,无显式权限校验
- **违反规则**Server Action 规范(每个 Action 必须调用 `requirePermission()`);安全性规范(所有敏感数据查询必须在 data-access 层结合当前用户权限过滤)
- **后果**若路由中间件配置错误或被绕过教师可访问任意班级数据data-access 层的 `getTeacherClasses()` 未接收 userId 参数做范围过滤
#### P1-1`classes/actions.ts` 中存在 `ctx.roles.includes("xxx")` 硬编码
- **位置**`src/modules/classes/actions.ts` L81、L420、L422、L428、L446、L451
- **问题**Server Action 中使用 `ctx.roles.includes("admin")` / `ctx.roles.includes("teacher")` / `ctx.roles.includes("student")` 进行角色判断
- **违反规则**:前端组件禁止 `role === "xxx"` 硬编码(虽此处在 Server Action 而非前端组件,但精神一致 — 应使用权限点而非角色名)
- **后果**:新增角色(如 grade_head需修改所有硬编码处角色与权限耦合不符合权限点驱动设计
### 2.3 国际化层面
#### P0-5全部 13 个页面均未使用 i18n
- **位置**:所有 13 个 page.tsx 及其引用的组件
- **问题**
- `admin/school/*` 页面使用**中文硬编码**(如 "学校管理"、"年级管理"、"班级管理"
- `management/grade/*``teacher/classes/*` 页面使用**英文硬编码**(如 "Class Management"、"Grade Insights"
- `school/components/*` 全部使用英文硬编码(如 "New school"、"All schools"、"Edit"、"Delete"
- `classes/components/*` 混用中英文
- **违反规则**:所有用户可见文本必须适配 i18n使用 next-intl提取翻译键
- **后果**无法支持多语言中英文混用严重影响一致性和专业度i18n 资源文件(`grade.json``classes.json`)已存在但未被使用
#### P1-2`school` 模块无 i18n 资源文件
- **位置**`src/shared/i18n/messages/{zh-CN,en}/` 目录
- **问题**:存在 `grade.json``classes.json`,但**不存在 `school.json`**。school 模块的学校/学年/部门管理文本无翻译键可用
- **违反规则**i18n 就绪规范
- **后果**:即使想为 school 模块补充 i18n也缺少翻译文件基础设施
### 2.4 组件质量层面
#### P1-3`school/components/*` 缺少 Error Boundary 和 Skeleton
- **位置**`src/modules/school/components/schools-view.tsx` / `grades-view.tsx` / `departments-view.tsx` / `academic-year-view.tsx`
- **问题**4 个组件均为 `"use client"` 客户端组件,无 Error Boundary 包裹、无加载骨架屏、无 Suspense 处理。对比 `grade-management` 模块已有 `grade-error-boundary.tsx` / `grade-skeleton.tsx` / `grade-states.tsx`(但未被使用)
- **违反规则**:错误与边界处理(每个独立数据区块必须用 React Error Boundary 包裹;异步数据使用 React Suspense + 骨架屏)
- **后果**:数据加载失败时整页崩溃无降级;加载过程无反馈
#### P1-4`classes/types.ts` 跨领域类型污染
- **位置**`src/modules/classes/types.ts`
- **问题**:定义了本应属于其他模块的类型:
- `ClassHomeworkInsights` / `GradeHomeworkInsights` / `ClassHomeworkAssignmentStats` / `ScoreStats` / `AssignmentSummary` — 应属 homework 模块
- `ClassScheduleItem` / `StudentScheduleItem` — 与 scheduling 模块概念重叠
- **违反规则**:模块标准结构(类型应归属对应模块)
- **后果**classes 模块承担了 homework/scheduling 的类型定义职责,耦合度高
#### P1-5`school/components/*` 未使用组合模式
- **位置**`src/modules/school/components/schools-view.tsx`
- **问题**`SchoolsClient` 组件内部硬编码了 Table + Dialog + AlertDialog 的完整结构,无法通过 slots/render props 定制。对比 `grade-management` 模块的 `GradeManagementWidget` 通过组合 `GradeListTable` + `GradeListToolbar` + `GradeFormDialog` + `GradeDeleteDialog` 实现灵活性
- **违反规则**:组合优先(所有 UI 通过组件组合实现灵活性)
- **后果**:无法复用表格/对话框子部件;新增角色差异需复制整个组件
### 2.5 数据安全层面
#### P1-6data-access 层部分查询未结合用户权限过滤
- **位置**
- `src/modules/classes/data-access.ts``getTeacherClasses()` 未接收 userId 参数
- `src/modules/school/data-access.ts``getGrades()` / `getSchools()` 返回全量数据,无权限过滤
- **问题**data-access 函数为全局查询,不结合当前用户身份做数据范围过滤,完全依赖 actions 层或页面层校验
- **违反规则**:安全性规范(所有敏感数据查询必须在 data-access 层结合当前用户权限过滤)
- **后果**:若上层遗漏校验(如 P0-4 中 teacher/classes 页面),数据越权访问风险
### 2.6 可测试性层面
#### P2-1`school` 和 `classes` 模块逻辑与 UI 耦合,难以单测
- **位置**`school/components/*` / `classes/components/*`
- **问题**:组件内部直接调用 actions、管理状态、处理错误未将数据获取/计算/格式化逻辑抽取为独立 hooks 或纯函数。对比 `grade-management` 模块已抽取 `use-grade-data` / `use-grade-filters` / `use-grade-form` / `use-grade-insights` 四个 hooks
- **违反规则**:可测试性(数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks与 UI 分离)
- **后果**:无法对筛选逻辑、表单校验逻辑进行独立单测
---
## 三、行业差距对比
### 3.1 与优秀 K12 产品的差距
| 功能/交互 | 行业优秀实践Google Classroom / 钉钉教育 / 智学网) | 当前状态 | 影响 |
|----------|------------------------------------------------------|---------|------|
| **学校切换** | 顶部全局学校切换器,切换后所有页面数据联动 | 仅 admin 跨校可见,无全局切换器 | 多校区场景下教师/学生无法快速切换视角 |
| **年级→班级树形导航** | 左侧树形结构(学校→年级→班级),支持展开/折叠/搜索 | 扁平列表,无层级导航 | 班级数量多时查找效率低 |
| **班级详情仪表盘** | 一页聚合:基本信息 + 学生名单 + 课表 + 作业 + 成绩趋势 | `teacher/classes/my/[id]` 已有 class-detail 子组件,但 admin/grade 视角无详情页 | admin/年级组长无法下钻查看班级详情 |
| **批量操作** | 批量导入学生、批量分配教师、批量升级班级 | 仅支持单条 CRUD + 邮箱注册 | 开学季配置效率低 |
| **空状态引导** | 空状态带引导按钮和说明文案 | schools-view 有 EmptyState其他组件不一致 | 新用户不知道下一步该做什么 |
| **加载骨架屏** | 数据加载时显示骨架屏保持布局稳定 | school/classes 组件无骨架屏grade-management 有但未使用) | 加载过程布局跳动,体验差 |
| **邀请码加入** | 二维码 + 链接 + 6 位码三种方式 | 仅 6 位码v3 已支持有效期/次数) | 家长端操作门槛略高 |
| **年级升级** | 学年末一键升级(三年级→四年级),保留历史档案 | 无此功能 | 每年需手动重建班级 |
| **数据权限隔离** | 教师仅看到自己班级年级组长看到年级所有班级admin 看到全部 | teacher/classes 页面无权限校验P0-4data-access 无范围过滤P1-6 | 存在越权风险 |
### 3.2 多角色体验差距
| 角色 | 优秀实践 | 当前状态 |
|------|---------|---------|
| **admin** | 统一管理面板,学校/年级/班级三级联动,支持批量配置 | 分散在 4 个独立页面,无联动 |
| **teacher** | 我的班级 + 可加入班级 + 邀请码管理一站式 | 有基本功能,但无权限校验、无 i18n |
| **parent** | 查看孩子所在班级信息、任课教师、班级通知 | 无专属页面(依赖 dashboard 间接展示) |
| **student** | 查看我的班级、同学名单、课表 | 有基本功能,但无权限校验、无 i18n |
---
## 四、改进优先级建议
### P0紧急 — 安全与架构正确性)
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P0-1 | `grade-management` 模块完全未被使用 | **决策**:要么将 `admin/school/grades` 页面迁移到使用 `grade-management` 模块的 Widget + Service 模式,要么删除该死模块。**推荐迁移**,因为该模块实现了用户要求的全部原则(解耦/组合/i18n/复用/边界/可测试/可扩展) |
| P0-2 | 年级 CRUD 逻辑重复 | 统一到 `grade-management` 模块,`school` 模块仅保留学校/学年/部门 CRUD删除 school 模块中的 grade CRUD |
| P0-3 | `classes/actions.ts` 974 行接近上限 | 按职责拆分为 `actions-teacher.ts` / `actions-admin.ts` / `actions-grade.ts` / `actions-invitations.ts` / `actions-schedule.ts` |
| P0-4 | `teacher/classes/*` 4 页面无权限校验 | 每个页面添加 `requirePermission(Permissions.CLASS_READ)` 或对应权限点 |
| P0-5 | 全部 13 页面无 i18n | 提取翻译键,使用 `getTranslations`(服务端组件)或 `useTranslations`(客户端组件)。补充 `school.json` 翻译文件 |
### P1重要 — 代码质量与可维护性)
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P1-1 | `classes/actions.ts` 角色硬编码 | 将 `ctx.roles.includes("admin")` 改为 `ctx.hasPermission(Permissions.xxx)``ctx.roles` 中的权限点判断 |
| P1-2 | `school` 模块无 i18n 文件 | 新建 `src/shared/i18n/messages/{zh-CN,en}/school.json` |
| P1-3 | `school/components/*` 缺少 Error Boundary/Skeleton | 参照 `grade-management` 模块的 `grade-error-boundary.tsx` / `grade-skeleton.tsx` 模式补充 |
| P1-4 | `classes/types.ts` 跨领域类型污染 | 将 `ClassHomeworkInsights` 等类型迁移至 homework 模块classes 模块通过 import type 引用 |
| P1-5 | `school/components/*` 未使用组合模式 | 将 `SchoolsClient` 拆分为 `SchoolListTable` + `SchoolFormDialog` + `SchoolDeleteDialog` + `SchoolListToolbar` |
| P1-6 | data-access 层未结合权限过滤 | `getTeacherClasses(userId)` 接收 userId 参数,在查询中过滤 |
### P2优化 — 体验与扩展性)
| 编号 | 问题 | 改进方向 |
|------|------|---------|
| P2-1 | 逻辑与 UI 耦合,难以单测 | 参照 `grade-management` 模块抽取 `use-school-data` / `use-class-data` 等 hooks |
| P2-2 | 缺少年级→班级树形导航 | 新增 `OrgTreeNav` 组件,学校→年级→班级三级树 |
| P2-3 | 缺少年级升级功能 | 新增 `promoteGradeAction`,学年末批量升级 |
| P2-4 | 缺少批量操作 | 批量导入学生、批量分配教师 |
| P2-5 | `school` 模块审计日志不一致 | 为 department/academicYear/grade 的 CRUD 补充 `logAudit` |
### 重构方案设计要点(参照用户强制原则)
1. **完全解耦**:以 `grade-management` 模块的 `GradeService` 接口 + `GradeServiceProvider` Context 注入为范本,为 school 和 classes 模块建立对应的 `SchoolService` / `ClassService` 接口
2. **组合优先**:参照 `GradeManagementWidget` 的组合方式Toolbar + Table + FormDialog + DeleteDialog所有模块的 Widget 通过组合子组件实现
3. **国际化就绪**:翻译文件结构示例
```json
// school.json
{
"schools": { "title": "学校管理", "list": { "title": "学校列表", "empty": "暂无学校" }, "form": { ... } },
"grades": { "title": "年级管理", ... },
"departments": { "title": "部门管理", ... },
"academicYear": { "title": "学年管理", ... }
}
```
4. **最大化复用**:抽取 `OrgCrudWidget<T>` 泛型组件(列表+工具栏+表单+删除school/grade/department 共用
5. **错误与边界**:每个 Widget 用 Error Boundary 包裹,异步数据用 Suspense + 骨架屏
6. **可测试性**:数据获取/筛选/校验逻辑全部抽取为 hooks
7. **可扩展性**:参照 `GRADE_ROLE_CONFIG`,为 school/classes 建立角色配置驱动设计
8. **企业级补充**a11y语义化标签 + ARIA、性能RSC 获取初始数据、安全data-access 层权限过滤)、监控(`GradeAnalyticsTracker` 模式扩展到 school/classes
---
## 五、架构图同步说明
本次审计发现架构图存在以下遗漏和不一致,需同步更新:
### 5.1 需补充的节点
| 文档 | 需补充内容 |
|------|-----------|
| `004_architecture_impact_map.md` | 新增 `## 2.X grade-management年级管理模块` 章节,记录其 services/hooks/widgets/config 架构,并标注"⚠️ 该模块当前未被任何 app 页面使用" |
| `004_architecture_impact_map.md` | 在 `## 2.8 school` 章节补充说明school 模块包含 grade CRUD 但与 grade-management 模块职责重叠 |
| `004_architecture_impact_map.md` | 在路由表中补充 `teacher/classes/*` 4 个页面缺少 `requirePermission` 的标注 |
| `005_architecture_data.json` | `modules` 节点新增 `grade-management` 模块及其 exports/dependencies |
| `005_architecture_data.json` | `dependencyMatrix` 新增 grade-management → classes通过 data-access、grade-management → school 的依赖关系 |
| `005_architecture_data.json` | `routes` 节点补充 teacher/classes/* 的权限缺失标注 |
### 5.2 需修改的节点
| 文档 | 需修改内容 |
|------|-----------|
| `004_architecture_impact_map.md` §2.7 classes | 更新 `actions.ts` 行数676 → 974标注接近硬上限 |
| `004_architecture_impact_map.md` §2.8 school | 更新 `data-access.ts` 行数186 → 504补充新增的跨模块查询函数`getGradeNameById` / `getSubjectNameById` / `isGradeHead` / `isGradeManager` / `findGradeIdByHeadAndName` |
### 5.3 i18n 翻译文件结构示例
```
src/shared/i18n/messages/
├─ zh-CN/
│ ├─ school.json ← 新增(学校/学年/部门管理翻译键)
│ ├─ grade.json ← 已存在年级管理翻译键grade-management 模块用)
│ └─ classes.json ← 已存在(班级管理翻译键,需扩充)
└─ en/
├─ school.json ← 新增
├─ grade.json ← 已存在
└─ classes.json ← 已存在
```
---
## 附录:审计检查清单
| 检查项 | school | grade-management | classes |
|--------|:------:|:---------------:|:-------:|
| 三层架构划分合理 | ✅ | ✅ | ⚠️ actions.ts 过大 |
| 文件大小符合规范 | ✅ | ✅ | ❌ actions.ts 974 行 |
| 无跨模块直接依赖 | ✅ | ✅ | ✅ |
| Server Action 权限校验 | ✅ | ✅ | ⚠️ 角色硬编码 |
| 前端无 role 硬编码 | ✅ | ✅ | ✅ |
| i18n 适配 | ❌ | ✅(组件层) | ❌ |
| 错误处理/边界 | ❌ | ✅ | ❌ |
| 骨架屏/空状态 | ⚠️ 部分 | ✅ | ⚠️ 部分 |
| 逻辑与 UI 分离 | ❌ | ✅ | ⚠️ 部分 |
| 组合模式 | ❌ | ✅ | ⚠️ 部分 |
| 配置驱动 | ❌ | ✅ | ❌ |
| 被页面实际使用 | ✅ | ❌ **死模块** | ✅ |
| 审计日志完整 | ⚠️ 不一致 | ✅ | ✅ |
| 监控埋点接口 | ❌ | ✅(预留) | ❌ |