Files
NextEdu/docs/architecture/audit/archive/classes-audit-report.md

34 KiB
Raw Blame History

班级classes模块审计报告

审计时间2026-06-25 审计范围:src/modules/classes/ 全部 32 个文件 + src/app/(dashboard)/ 下 11 个相关路由分组 架构图依据:docs/architecture/004_architecture_impact_map.md §2.7、docs/architecture/005_architecture_data.json modules.classes 节点 审计基线项目规则三层架构、权限校验、i18n、TypeScript 严格模式、文件行数、可测试性)

一、现有实现概要

1.1 文件分布

classes 模块位于 src/modules/classes/,共 32 个文件

文件数 关键文件
Server Actions 7 actions.tsbarrel+ actions-admin.ts / actions-grade.ts / actions-teacher.ts / actions-invitations.ts / actions-schedule.ts / actions-shared.ts
Data-access 7 data-access.ts(聚合)+ data-access-admin.ts / data-access-teacher.ts / data-access-students.ts / data-access-stats.ts / data-access-schedule.ts / data-access-invitations.ts
Schema/Types 2 schema.ts13 个 Zod schematypes.ts19 个类型)
Components 21 admin-classes-view.tsx / grade-classes-view.tsx / class-list-table.tsx / class-form-dialog.tsx / class-delete-dialog.tsx / class-list-toolbar.tsx / class-form-utils.ts / class-error-boundary.tsx / class-invitation-manager.tsx / class-skeleton.tsx / my-classes-grid.tsx / schedule-view.tsx / schedule-filters.tsx / students-filters.tsx / students-table.tsx + class-detail/ 下 7 个 widgetheader/overview-stats/quick-actions/schedule-widget/students-widget/assignments-widget/trends-widget+ class-detail/edit-class-dialog.tsx
Hooks 2 use-class-data.tsuse-class-filters.ts

1.2 主要数据流

admin/school/classes        ─┐
management/grade/classes    ─┤  ─▶ actions-admin / actions-grade
                              │     │
teacher/classes/my          ─┤     ▼
teacher/classes/schedule    ─┤  data-access-admin / data-access-teacher
teacher/classes/students    ─┤     │
                              │     ▼  (跨模块通过对方 data-access)
student/learning/courses    ─┤  homework/data-access-classes作业统计
student/schedule            ─┤  scheduling/data-access-class-schedule课表写
parent/children/[id]        ─┘  school/data-access年级管理权限校验

1.3 架构图覆盖完整性评估

维度 004 文档 005 JSON 一致性
模块职责 §2.7 modules.classes.description 一致
依赖关系(出向) shared/auth/school/homework/scheduling dependencyMatrix 5 条边 一致
被依赖关系(入向) ⚠️ 列出 10 个,遗漏 elective/error-book/adaptive-practice 10 条边覆盖完整 不一致
导出函数 17 actions + 33 data-access ⚠️ exports.actions 漏 3 个邀请码 actionexports.dataAccess 漏 6 个工具函数 不一致
数据库表 classes/classSubjectTeachers/classEnrollments/classInvitationCodes ⚠️ classInvitationCodes 字段未详细登记classSchedule 的 usedBy 字段未含 scheduling 部分遗漏
权限点 6 个 CLASS_* 权限 permissions 常量定义完整 一致
路由 11 条路由登记 routes 节点登记 一致
文件清单 33 个文件 ⚠️ modules.classes.files 仅列 21 个,漏 12 个组件文件 不一致

结论:架构图总体覆盖较完整,但存在 7 处需同步更新(详见第五章)。

二、现存问题与原因分析

2.1 三层架构合规性

问题 A1data-access.ts 与拆分文件 3 对同名函数重复定义(🔴 P0

  • 位置
  • 现状data-access.ts:383-386 同时 export * from "./data-access-*" 与本地 export constES 模块语义下本地定义优先,子文件同名导出对聚合入口而言是死代码。
  • 违反规则架构分层规则「data-access 拆分应避免职责重叠」+ DRY 原则。
  • 后果:两份实现目前逻辑一致,但任何一方修改都不会自动同步。若消费者直接 import { getStudentScopeData } from "@/modules/classes/data-access-students",会得到另一份实现,是高风险维护陷阱。

问题 A2组件使用绝对路径导入本模块 actions🟢 P2

  • 位置class-invitation-manager.tsx:30-32
  • 现状import { createClassInvitationCodeAction } from "@/modules/classes/actions",而 my-classes-grid.tsxadmin-classes-view.tsx 等同模块其他组件使用 "../actions" 相对路径。
  • 违反规则:编码规范一致性。
  • 后果:模块迁移/重命名成本上升。

2.2 权限校验

问题 B1listClassInvitationCodesAction 无班级归属校验(🔴 P0 越权漏洞)

  • 位置actions-invitations.ts:312-351
  • 现状
    export async function listClassInvitationCodesAction(classId: string) {
      await requirePermission(Permissions.CLASS_ENROLL)
      // ❌ 任何拥有 CLASS_ENROLL 权限的用户都可以列出任意 classId 的所有邀请码
      const codes = await listClassInvitationCodes(classId)
    }
    
  • 违反规则安全规范「data-access 查询是否结合当前用户权限过滤(防越权)」+ Server Action 规范。
  • 后果:教师 A 可通过传入教师 B 的 classId 枚举其邀请码(含 code 字符串),可能引发越权获取加入凭证,破坏邀请码体系的安全性。

问题 B23 个 schedule action 无班级归属校验(🔴 P0 越权漏洞)

  • 位置actions-schedule.ts:21-59create61-101update103-122
  • 现状3 个 action 仅调用 requirePermission(CLASS_SCHEDULE) 后直接调用 scheduling 模块 data-access未校验当前用户对 classId 的归属。
  • 违反规则:安全规范越权防护。
  • 后果:教师 A 可为教师 B 的班级添加/修改/删除课表项,破坏排课数据完整性。

问题 B3教师 update/delete/enroll action 未在 actions 层做归属校验(🟡 P1

  • 位置actions-teacher.ts:79-122update125-146deleteactions-invitations.ts:21-49353-377383-440
  • 现状actions 层仅 requirePermission,依赖 data-access 内部 getTeacherIdForMutations() + eq(classes.teacherId, teacherId) 校验。
  • 违反规则Server Action 规范「权限校验应在 actions 层完成」。
  • 后果:管理员(拥有 CLASS_UPDATE 权限)调用时,因 data-access 内部强制按 teacherId 过滤而失败,错误信息为 "Teacher not found" 不友好;如未来重构 data-access 暴露 admin 路径,归属校验会被跳过。

问题 B4data-access 中 7 处硬编码角色名字符串(🟡 P1

问题 B5student 三个页面未调用 requirePermission 显式声明权限点(🟡 P1

2.3 国际化i18n

问题 C1class-detail/ 子组件普遍硬编码英文(🟡 P1

问题 C2schedule-view / schedule-filters / students-filters 硬编码英文(🟡 P1

问题 C3所有 actions 返回的 message 为英文硬编码(🟡 P1

  • 位置:全部 7 个 actions 文件
  • 示例actions-admin.ts:39 "Class name, grade and teacher are required" / actions-admin.ts:59 "Class created successfully" / actions-invitations.ts:432 `Imported ${imported} students, ${failed} failed`
  • 现状:组件层 toast.success(res.message) 直接显示后端字符串。
  • 违反规则i18n 强制要求。
  • 后果:中文用户看到全英文错误提示,体验差。

问题 C430+ 个 error.tsx 使用硬编码中文文案(🟡 P1

  • 位置src/app/(dashboard)/ 下 30 处 error.tsxadmin/school/classes/error.tsxmanagement/grade/classes/error.tsx 等)
  • 示例admin/school/classes/error.tsx:12-16 title="页面加载失败" / description="抱歉,页面加载时发生了意外错误。请稍后重试。"
  • 违反规则i18n 强制要求。
  • 后果英文用户在错误页仍看到中文破坏语言一致性30+ 处重复字符串维护成本高。

问题 C5DEFAULT_CLASS_SUBJECTS 与 excludeSubjects 业务常量硬编码中文(🟡 P1

  • 位置
    • types.ts:46 export const DEFAULT_CLASS_SUBJECTS = ["语文", "数学", "英语", "美术", "体育", "科学", "社会", "音乐"] as const
    • data-access-students.ts:41 const excludeSubjects = ["体育", "音乐", "美术"]
  • 违反规则DRY两份科目清单+ i18n业务规则硬编码字符串
  • 后果:与 subjects 表查询重复;不同学校配置无法适配;excludeSubjects 未在架构图记录。

问题 C6getSubjectColor 用英文匹配中文科目(🔴 P0 功能 bug

  • 位置schedule-view.tsx:186-195
  • 现状
    const getSubjectColor = (subject: string) => {
      const s = subject.toLowerCase()
      if (s.includes('math')) return 'bg-blue-500/10 ...'
      if (s.includes('physics') || s.includes('science')) return '...'
      if (s.includes('english') || s.includes('lit')) return '...'
    
  • 问题DEFAULT_CLASS_SUBJECTS 是中文("数学"、"英语"等),但 getSubjectColor 用英文 'math'/'english' 匹配,所有中文科目都会落到 default 分支,颜色视觉分组完全失效。
  • 违反规则i18n + 功能正确性。
  • 后果:课表颜色视觉分组完全失效,不仅是 i18n 问题。

2.4 错误处理

问题 D1catch 块未记录错误,仅显示 toast🟡 P1

问题 D2data-access 内 catch 静默返回空数组(🟡 P1

  • 位置
  • 问题getTeacherClasses 失败时返回空数组,会让教师看到"无班级"假象,区分不出"数据库错误"与"无数据"。
  • 违反规则:错误处理最佳实践。
  • 后果:教师班级列表假性空数据,难以排查。

问题 D3actions 层错误处理两套风格混用(🟡 P1

问题 D4teacher/classes/my/[id] 缺 loading.tsx 和 error.tsx🟡 P1

  • 位置src/app/(dashboard)/teacher/classes/my/[id]/
  • 现状:页面内部 4 个 Promise.all 并行数据获取,但完全没有 loading.tsx 和 error.tsx。
  • 违反规则:项目规则「学生路由必须包含 loading.tsx 和 error.tsx」+ 企业级规范。
  • 后果:无骨架屏感知性能差;任一 data-access 抛错冒泡至上层;notFound() 触发时展示默认 404 与站点风格不一致。

问题 D510 个页面缺 error.tsx🟡 P1

  • 位置teacher/classes/my、teacher/classes/schedule、teacher/classes/students、student/learning/courses、student/learning/courses/[classId]、student/schedule 等
  • 违反规则:企业级规范要求主要路由必须有 error.tsx。
  • 后果:错误上下文丢失,错误冒泡至上层。

2.5 类型安全

问题 E1formData.get 强转 string 应使用类型守卫(🟢 P2

  • 位置actions-admin.ts:93actions-grade.ts:98
  • 现状formData.get("subjectTeachers") as string | null
  • 违反规则:禁止 as 断言(除类型收窄外)。
  • 后果:若前端意外提交 File 对象(如通过 FormData.append 上传文件),parseSubjectTeachers 会因 typeof raw !== "string" 返回 null 而静默丢弃数据。

问题 E2data-access-invitations.ts 中两处 string → union 强转(🟢 P2

  • 位置data-access-invitations.ts:274363
  • 现状record.status as ValidationResult["reason"] / status: row.status as InvitationCodeStatus
  • 违反规则:禁止 as 断言。
  • 后果DB 中出现意外值(如 "pending")不会触发类型错误,运行时返回错误 reason。
  • 位置class-trends-widget.tsx:129
  • 现状Array.from(new Set(assignments.map(a => a.subject).filter(Boolean))) as string[]
  • 违反规则:禁止 as 断言。
  • 后果filter(Boolean) 在 TypeScript 中不会收窄类型,跳过空值检查。

问题 E414 个组件事件处理函数缺 Promise 返回类型(🟢 P2

  • 位置my-classes-grid.tsx4 处、schedule-filters.tsx、schedule-view.tsx4 处、students-filters.tsx、students-table.tsx、class-invitation-manager.tsx3 处、edit-class-dialog.tsx
  • 违反规则:函数返回值必须显式标注,特别是 Promise<T>
  • 后果:若将来函数内部 return 一个值(如返回 boolean 表示是否成功),调用方无类型提示。

2.6 文件大小

问题 F1schedule-view.tsx 527 行超出组件 500 行建议(🟡 P1

  • 位置schedule-view.tsx
  • 现状:同时承担"周历视图渲染 + 创建对话框 + 编辑对话框 + 删除确认对话框"4 个职责。
  • 违反规则React 组件 ≤ 500 行。
  • 后果:可读性差、修改易引入回归。

问题 F2my-classes-grid.tsx 内含 210 行巨型组件 ClassTicket🟢 P2

  • 位置my-classes-grid.tsx:208-417
  • 现状:单一组件管理 4 段视觉职责(票据左侧信息+邀请码+趋势图+周历嵌入式渲染)。
  • 违反规则:组件规范。
  • 后果:调试困难。

2.7 组件复用性

问题 G13 个 view 的 CRUD handler 几乎重复(🟢 P2

  • 位置admin-classes-view.tsx:41-95、grade-classes-view.tsx:31-85、schedule-view.tsx:100-158
  • 现状:相同的 try/catch + toast + router.refresh 模式重复 9 次(每个 view 3 个 handler
  • 违反规则DRY。
  • 后果:错误处理改进(如 D1 加 console.error需修改 9 处。

2.8 可测试性

问题 H1纯函数内嵌组件未导出🟢 P2

2.9 i18n metadata 缺失

问题 I18 个 teacher/student 页面缺 generateMetadata🟢 P2

  • 位置teacher/classes/my、teacher/classes/my/[id]、teacher/classes/schedule、teacher/classes/students、student/learning/courses、student/learning/courses/[classId]、student/schedule、parent/children/[studentId]
  • 违反规则:页面级 metadata.title 应走 i18n。
  • 后果浏览器标签栏、社交分享卡片等场景文案不本地化SEO 友好度差。

三、行业差距对比

参考 Google Classroom、钉钉教育、智学网、ClassDojo、PowerSchool 等 K12 班级管理产品的主流设计模式,对比当前实现差距:

3.1 班级列表与详情

维度 优秀实践 当前实现 差距
班级卡片信息密度 Google Classroom 卡片含教师头像、学生数、最近活动时间、未读作业数 my-classes-grid.tsx 含邀请码、提交率趋势、周历嵌入,信息密度高但未提供"最近活动"时间线 缺少"班级最近动态"feed
班级封面图 Google Classroom / ClassDojo 支持自定义班级主题图 仅色彩区分 视觉识别度弱
班级详情布局 智学网采用"Tab 切换(学生/作业/成绩/课表/设置)+ 顶部 sticky header" class-detail/ 采用 widget 网格布局 widget 平铺在班级数多时滚动疲劳;可考虑 Tab 化
班级归档 Google Classroom 支持"归档班级",归档后只读但保留数据 无归档功能 学年结束后历史班级污染列表
班级复制 Google Classroom 支持复制班级(含学生/科目配置) 新学年建班成本高

3.2 学生管理

维度 优秀实践 当前实现 差距
学生加入方式 邀请码 + 邮件 + 批量导入 + 班级链接 已实现邀请码 + 邮件 + 批量导入 缺少"班级加入链接"(点击即加入)
学生列表筛选 智学网支持按科目成绩、出勤率、活跃度多维筛选 students-filters.tsx 仅按班级 + 状态 缺少按学业表现筛选
学生卡片信息 Google Classroom 显示学生头像、最近提交、整体进度 students-table.tsx 显示头像+科目成绩 缺少"最近提交/整体进度"时间维度
学生迁移 钉钉教育支持"批量迁班"(学年升级时) 学年升级时手动逐个调整
学生邀请码状态可视化 ClassDojo 显示邀请码扫描情况(已加入/待加入) class-invitation-manager.tsx 显示邀请码列表 缺少"已扫码未加入"中间状态

3.3 课表

维度 优秀实践 当前实现 差距
课表视图 智学网周课表 + 日课表 + 月课表三视图切换 schedule-view.tsx 仅周课表 缺少日/月视图
课表冲突检测 PowerSchool 在添加时自动检测教室/教师/时段冲突 依赖 scheduling 模块外部检测 当前 actions-schedule 未触发检测B2
课表颜色编码 钉钉教育按科目自动配色 getSubjectColor 中文失效C6 功能 bug必须修复
课表导出/打印 智学网支持导出 PDF/Excel、打印 教师打印课表需求未满足
课表提醒 Google Classroom 课前 5 分钟推送提醒 缺少课表提醒集成

3.4 多角色协作

维度 优秀实践 当前实现 差距
家长视角班级信息 ClassDojo 家长看到班级公告、教师动态、孩子表现 parent 仅看到孩子课表 + 班级概览 缺少"班级动态 feed"
学生视角班级首页 Google Classroom 学生首页是"待办作业流" student/learning/courses 是班级列表 缺少"班级作业待办流"聚合视图
跨班级协作 钉钉教育支持"年级主任一键查看所有班级对比" management/grade 已有 insights 已实现,对比图较完善
班级消息 Google Classroom 班级内消息流 messaging 模块支持 class_members scope 已通过 messaging 模块实现

3.5 数据洞察

维度 优秀实践 当前实现 差距
班级健康度评分 PowerSchool 综合出勤+成绩+参与度给出班级健康分 class-trends-widget.tsx 仅展示提交率/平均分 缺少综合健康度评分
早期预警 ClassDojo 识别"低参与度学生"自动预警 缺少学生风险预警
班级对比 智学网支持同年级班级多维度对比 management/grade/insights 已有
趋势同比环比 PowerSchool 提供周/月/学期同比 class-trends-widget.tsx 仅"Latest"指标 缺少时间维度对比

3.6 可访问性与性能

维度 优秀实践 当前实现 差距
键盘导航 WCAG 2.1 AA 要求所有交互可键盘操作 班级列表/课表键盘可访问但无 focus-visible 样式优化 a11y 待加强
流式渲染 React 18+ Suspense 流式渲染 全部 RSC 同步获取,无 Suspense 边界 班级详情 4 个并行查询可流式
骨架屏精确度 骨架屏应反映实际布局 class-skeleton.tsx 5 个 skeleton 布局匹配 良好

四、改进优先级建议

P0 — 安全/功能阻断(立即修复)

# 问题 改进方向
P0-1 A1data-access.ts 与拆分文件 3 对同名函数重复定义 删除 data-access.ts 中的本地定义,统一从拆分文件导出(保持 barrel 入口兼容)
P0-2 B1listClassInvitationCodesAction 无归属校验 在 actions 层调用 verifyTeacherOwnsClass(classId, ctx.userId)admin scope 跳过)
P0-3 B23 个 schedule action 无归属校验 同 P0-2调用 verifyTeacherOwnsClass
P0-4 C6getSubjectColor 中文科目不匹配 改用科目 ID 或类型守卫匹配;纯函数抽出到 schedule-utils.ts 并补单测
P0-5 B3教师 update/delete/enroll action 未在 actions 层做归属校验 actions 层显式判断 hasAdminScope(ctx) 否则校验 classes.teacherId === ctx.userId

P1 — 重要合规性(本期实施)

# 问题 改进方向
P1-1 C1class-detail/ 8 子组件硬编码英文 全部接入 useTranslations("classes.detail.*")
P1-2 C2schedule-view / schedule-filters / students-filters 硬编码英文 接入 i18n
P1-3 C3所有 actions message 英文硬编码 在 actions 层使用 getTranslations() 或返回错误码由组件层翻译
P1-4 C430+ error.tsx 硬编码中文 抽取 shared/components/ErrorState + i18n key common.error.boundary.*
P1-5 C5DEFAULT_CLASS_SUBJECTS 与 excludeSubjects 硬编码 改为从 subjects 表查询;统一单一来源
P1-6 B4data-access 中 7 处 roles.name 硬编码 抽出 ROLE_TEACHER 常量
P1-7 B5student 三个页面未调用 requirePermission requirePermission(Permissions.HOMEWORK_SUBMIT) 或新增 STUDENT_READ 权限
P1-8 D110 处 catch 块未记录错误 改为 catch (error) { console.error("[classes] xxx:", error); toast.error(...) }
P1-9 D2getTeacherClasses 静默返回空数组 区分"DB 错误"与"无数据"DB 错误抛出
P1-10 D3actions 层错误处理两套风格 统一为 handleActionError
P1-11 D4teacher/classes/my/[id] 缺 loading.tsx 和 error.tsx 新增
P1-12 D510 个页面缺 error.tsx 补齐
P1-13 F1schedule-view.tsx 527 行超限 抽出 3 个对话框子组件

P2 — 工程优化(中长期)

# 问题 改进方向
P2-1 E1/E2/E35 处 as 断言 改用类型守卫
P2-2 E414 个事件处理函数缺 Promise 补返回类型
P2-3 G13 个 view CRUD handler 重复 useClassFormHandlers hook
P2-4 H1纯函数内嵌组件 抽到 class-stats-utils.ts / schedule-utils.ts 并补单测
P2-5 F2my-classes-grid ClassTicket 210 行 拆分为 ClassTicketHeader / ClassTicketInvitation / ClassTicketTrend
P2-6 A2class-invitation-manager 绝对路径 改为相对路径
P2-7 I18 个页面缺 generateMetadata 补齐
P2-8 行业差距:班级归档/复制 中长期产品规划
P2-9 行业差距:班级健康度评分/早期预警 中长期产品规划
P2-10 行业差距:课表多视图/导出/提醒 中长期产品规划

重构设计原则(强制满足)

为达成"完全解耦 + 组合优先 + 国际化就绪 + 最大化复用 + 错误边界 + 可测试 + 可扩展 + 企业级补充"八项原则,本次实施遵循:

  1. 完全解耦classes 模块内部组件不直接 import 其他业务模块exams/grades/homework/scheduling的 actions/data-access跨模块数据通过本模块 data-access 暴露的接口调用(已基本达成,仅需修复 P0-2/P0-3 的越权问题)。
  2. 组合优先:错误处理抽 useClassFormHandlers hook纯逻辑抽 class-stats-utils.tsschedule-utils.ts;详情 widget 通过配置驱动(ClassDetailWidgetConfig)。
  3. 国际化就绪:所有修复项必须使用 i18n key新增翻译键写入 messages/{locale}/classes.jsondetail.* / schedule.* / students.* 命名空间。
  4. 最大化复用admin/grade/teacher/student 共用 ClassListTable / ClassFormDialog / ClassDeleteDialog / useClassData / useClassFilters / useClassFormHandlers
  5. 错误与边界:每个详情 widget 独立用 ClassErrorBoundary 包裹RSC 数据获取用 React Suspense + 骨架屏;空数据/无权限/网络异常状态明确处理。
  6. 可测试性:纯函数导出至 *.ts 文件,便于 vitest 单测;接口类型显式标注。
  7. 可扩展性:角色差异通过 hasAdminScope / hasTeacherScope 抽象,新增角色只改 actions-shared.ts。
  8. 企业级补充:补 a11yfocus-visible、ARIA、性能Suspense 流式、安全actions 层归属校验)、监控(埋点接口预留 trackClassEvent 工具函数)。

五、架构图同步说明

本次审计发现架构图存在以下遗漏或不一致,需在实施修复时同步更新:

# 位置 问题 修复动作
1 005 modules.classes.files 仅列 21 个文件,漏 schema.ts/types.ts/12 个组件文件 补全至 33 个文件
2 005 modules.classes.exports.actions createClassInvitationCodeAction / revokeClassInvitationCodesAction / listClassInvitationCodesAction 3 个 v3 邀请码 action 补全至 20 个
3 005 modules.classes.exports.dataAccess compareClassLike / isDuplicateInvitationCodeError / generateUniqueInvitationCode / getAccessibleClassIdsForTeacher / getSessionTeacherId / getTeacherSubjectIdsForClass 6 个工具函数 补全
4 005 dbTables classInvitationCodes 表字段未详细登记 补全字段定义
5 005 dbTables classScheduleusedBy 字段未含 scheduling 补充 ["classes", "scheduling"]
6 005 knownIssues.P0-1 仍记录原始问题状态,未标注「已修复」 更新 status 字段
7 004 §2.7「被依赖」 遗漏 elective / error-book / adaptive-practice 三个模块 补全至 13 个
8 005 dependencyMatrix exams → classeshomework → classes 边未明确登记 补全边定义
9 004 §2.7 已知问题 P0-2/P0-3/P0-4/P0-5 等本次新增修复 同步新增修复记录
10 004 §2.7 文件清单 修复后行数变化(如 schedule-view.tsx 拆分) 更新行数