Files
NextEdu/docs/architecture/audit/grades-diagnostic-audit-report-v2.md
SpecialX 27db170c0a docs: update architecture docs, audit reports, and bug tracking
- Update architecture impact map, data, feature checklist, gap audit

- Add audit reports for dashboard, exam-homework, grades-diagnostic, settings-profile, textbooks

- Update bug reports (admin, teacher, lesson-preparation, others, shared)

- Update coding standards, DR plan, design docs, and README
2026-06-23 17:36:18 +08:00

21 KiB
Raw Blame History

成绩和学情诊断模块审计报告 v2

审查日期2026-06-22 审查范围:在 v1 审计(grades-diagnostic-audit-report.md)完成所有 P0/P1/P2 改进项之后,对 src/modules/grades/**src/modules/diagnostic/**、相关路由层、i18n、架构图进行二次深度审计 审查目的:发现 v1 修复后仍存在的代码质量、架构、类型安全、i18n、a11y、错误处理、性能、业务逻辑问题


一、v1 完成情况确认

v1 审计报告所有 P0/P1/P2 改进项(共 16 项)均已真实落地,代码验证通过:

v1 编号 改进项 验证结果
P0-1 权限校验缺失 所有页面均调用 requirePermission()
P0-2 diagnostic 直查 users 表 已改用 getUserNamesByIds
P0-3 i18n 完全缺失 ⚠️ 翻译文件已创建,但组件未接入(见 v2 P1-4
P0-4 /management/grade/page.tsx 缺失 已补齐
P1-1 统计业务逻辑抽取 stats-service.ts 已创建305 行8 个纯函数)
P1-2 重复工具函数 lib/grade-utils.ts 已创建
P1-3 Zod 校验缺失 12 个 Action 已补齐
P1-4 as 断言违规 已修复(但 stats-service.ts 新增 1 处,见 v2 P2-2
P1-5 Error Boundary 和 Suspense ⚠️ widget-boundary.tsx 已创建但未被使用(见 v2 P1-1
P1-6 架构图同步 ⚠️ 部分同步,行数和路由仍有不一致(见 v2 P2-10
P2-1 a11y 无障碍 ⚠️ 部分修复,热力图和表单 Label 仍有问题(见 v2 P1-6、P2-7
P2-2 Tailwind 任意值 已修复
P2-3 studentId 字段语义 已修复schema + types + data-access + components
P2-4 grade_managed scope 已修复(子查询过滤)
P2-5 parent/diagnostic 页面 已创建
P2-6 SearchParams 统一 ⚠️ 部分统一4 个 student 路由仍自定义(见 v2 P2-8

二、v2 新发现问题

2.1 P1 严重问题

P1-1 WidgetBoundary 组件已定义但全项目未被使用

位置 问题 违反规则
widget-boundary.tsx L117 WidgetBoundary 组件已导出139 行),但全项目无任何 import 语句引用它 "每个独立的数据区块必须用 React Error Boundary 包裹"
004_architecture_impact_map.md L696 声称"已新增 WidgetBoundary 通用组件",但从未被使用 架构文档虚假声明

后果v1 P1-5 改进项仅创建了组件但未实际应用Error Boundary + Suspense + Skeleton 三件套未生效,单个 Widget 抛错仍会导致整个页面崩溃。

改进方向:在 9 个关键组件中应用 WidgetBoundary

  • gradesgrade-trend-chartgrade-distribution-chartclass-comparison-chartsubject-comparison-chartgrade-stats-cardclass-grade-report
  • diagnosticmastery-radar-chartclass-diagnostic-viewstudent-diagnostic-view

P1-2 admin/school/grades/insights 路由完全缺失 loading.tsx 和 error.tsx

位置 问题 违反规则
src/app/(dashboard)/admin/school/grades/insights/ loading.tsx 和 error.tsx 两者都缺失 "路由级错误边界和加载态"

后果:访问 /admin/school/grades/insights 时无骨架屏过渡,运行时错误会导致整页崩溃。

P1-3 架构数据 JSON 005 权限记录错误

位置 问题 违反规则
005_architecture_data.json /admin/school/grades/admin/school/grades/insights 权限记录为 grade:manage,实际代码使用 school:manage "架构图应准确反映代码实际"

后果:架构图与代码不一致,权限审计会得出错误结论。

P1-4 grades 和 diagnostic 模块 i18n 完全未接入

位置 问题 违反规则
src/modules/grades/components/*17 个文件) 翻译文件 grades.json 已存在,但没有任何组件导入或调用 useTranslations,全部硬编码字符串 "所有用户可见文本必须适配 i18n"
src/modules/diagnostic/components/*4 个文件) 翻译文件 diagnostic.json 已存在,但 4 个组件全部硬编码英文字符串 同上

后果v1 P0-3 仅创建了翻译文件但未接入组件i18n 实际仍未生效。多语言用户无法切换语言。

改进方向21 个组件全部接入 useTranslations("grades")useTranslations("diagnostic")

P1-5 exportGradesAction 安全漏洞

位置 问题 违反规则
grades/actions.ts L369-380 exportGradesAction 调用 exportGradeRecordsToExcel / exportClassGradeReportToExcel未传递 currentUserId: ctx.userId "Server Action 必须传递用户身份到 data-access 层"
grades/actions.ts L235-239, L303-307, L333 getClassGradeStatsActiongetClassRankingActiongetGradeRecordByIdAction 均未将 ctx.dataScope 传递给 data-access 函数 同上

后果:学生(class_members scope调用 exportGradesAction 时,getGradeRecords 中的 if (params.scope.type === "class_members" && params.currentUserId) 条件不成立,不会按 studentId 过滤,学生可导出全班成绩

P1-6 diagnostic 缺少 stats-service.ts

位置 问题 违反规则
diagnostic/data-access.ts L62-90, L146-219, L222-256 getStudentMasterySummarygetClassMasterySummarygetKnowledgePointStats 包含大量统计计算逻辑averageMastery、强弱项分类、KP 聚合) "严格三层架构,统计计算属业务逻辑层"
diagnostic/data-access-reports.ts L46-81, L84-124 generateDiagnosticReportgenerateClassDiagnosticReport 包含摘要文本生成、强弱项列表构建逻辑 同上

后果diagnostic 模块未遵循 v1 P1-1 为 grades 模块建立的范例,统计逻辑仍混在 data-access 层,难以单独测试。

改进方向:抽取 diagnostic/stats-service.ts,包含 classifyStrengthsWeaknessescomputeKpStatscomputeStudentAverage 等纯函数。

P1-7 热力图色块缺少 a11y 支持

位置 问题 违反规则
class-diagnostic-view.tsx L128-139 热力图色块仅靠 title 属性,无 role="img"aria-label,颜色编码语义无法被辅助技术感知 "可访问性ARIA 属性"

后果:屏幕阅读器用户无法识别热力图色块的颜色等级含义(绿/黄/橙/红代表掌握度等级)。

P1-8 getKnowledgePointStats() 无参调用导致班级平均对比功能失效

位置 问题 违反规则
teacher/diagnostic/student/[studentId]/page.tsx L35 调用 getKnowledgePointStats()(无参数) "函数调用应正确传参"
diagnostic/data-access.ts L222-256 getKnowledgePointStats(classId?, gradeId?) 当两参都为 undefined 时,studentIds[],直接返回空数组 同上

后果classStats 恒为 []classAverageMastery 恒为 [],雷达图中班级平均对比曲线永不显示。架构文档标注的"班级平均对比"功能完全失效。

改进方向:页面应先查询学生所属班级,再调用 getKnowledgePointStats(classId)

P1-9 updateMasteryFromSubmission 覆盖而非累积掌握度

位置 问题 违反规则
diagnostic/data-access.ts L93-143 onDuplicateKeyUpdatetotalQuestions/correctQuestions/masteryLevel 设为本次提交的值,而非累积 "掌握度应反映学习轨迹"

后果:学生上次考 10 题 8 对mastery=80%),本次考 1 题 1 对mastery=100%),更新后 mastery 变为 100% 而非累积的 81.8%。掌握度随单次考试剧烈波动,无法反映真实学习轨迹。

改进方向:读取已有记录,将 totalQuestions/correctQuestions 累加后再计算,或采用加权/衰减算法。

2.2 P2 中等问题

P2-1 5 个 grades 路由和 1 个 diagnostic 路由缺失 error.tsx

位置 问题
src/app/(dashboard)/management/grade/classes/ 缺失 error.tsx
src/app/(dashboard)/management/grade/insights/ 缺失 error.tsx
src/app/(dashboard)/parent/grades/ 缺失 error.tsx
src/app/(dashboard)/student/grades/ 缺失 error.tsx
src/app/(dashboard)/student/diagnostic/ 缺失 error.tsx

P2-2 lib/grade-utils.ts 跨模块直接查询 classes 表

位置 问题 违反规则
lib/grade-utils.ts L6, L48-50 直接导入并查询 classes 表:db.select({ id: classes.id }).from(classes).where(...) "modules 之间通过对方 data-access 通信"

改进方向:在 classes/data-access.ts 新增 getClassIdsByGradeIds(gradeIds: string[]) 函数并调用。

P2-3 死代码清理

位置 问题
diagnostic/data-access.ts L93 updateMasteryFromSubmission 全局零调用(架构文档标注"待扩展"
diagnostic/actions.ts L133, L154 getDiagnosticReportsActiongetDiagnosticReportByIdAction 全局零调用,页面直接调用 data-access

改进方向:要么删除死代码,要么让页面改为通过 Action 调用(统一权限校验入口)。本报告选择后者,保留 Action 并让页面使用。

P2-4 totalStudents 语义错误和班级平均掌握度计算偏差

位置 问题 违反规则
diagnostic/data-access.ts L201, L255 totalStudents: students.length 是班级总人数,但 masteredCount + notMasteredCount 仅统计有掌握度记录的学生,数据自相矛盾 "数据模型应语义清晰"
diagnostic/data-access.ts L204-205 averageMastery 按记录数而非学生数平均,偏向多 KP 记录的学生 同上

改进方向totalStudents 改为实际有掌握度记录的学生数(levels.lengthaverageMastery 先算每个学生的个人平均,再对学生平均取平均。

P2-5 多 upsert 无事务包裹

位置 问题 违反规则
diagnostic/data-access.ts L119-141 Promise.all(Array.from(kpStats.entries()).map(... db.insert(...).onDuplicateKeyUpdate(...))) 并行执行多个 upsert无事务包裹 "多写操作应保证原子性"

后果:部分成功部分失败时,掌握度数据将处于不一致状态。

P2-6 生成报告未校验掌握度数据

位置 问题 违反规则
diagnostic/data-access-reports.ts L46-81, L84-124 generateDiagnosticReport 只检查 summary 是否为 null不检查 totalKnowledgePoints === 0 "应处理空数据边界"

后果:学生存在但无任何掌握度数据时,会生成 overallScore: 0%strengths: []weaknesses: [] 的误导性报告。

P2-7 表单 Label 未关联控件

位置 问题
batch-grade-entry.tsx L277, L293, L319, L334 Class、Subject、Type、Semester 的 <Label>htmlFor
grade-record-form.tsx L88, L104, L120, L151, L166 5 个 <Label>htmlFor
grade-query-filters.tsx L40, L57, L74, L90 4 个 <Label>htmlFor
report-list.tsx L120-147 过滤器 Label 缺少 htmlFor

P2-8 SearchParams 统一(剩余文件)

位置 问题
admin/school/grades/insights/page.tsx L16 使用旧版 getSearchParam, type SearchParams from @/shared/lib/utils
src/app/(dashboard)/student/schedule/page.tsx L11 自定义 type SearchParams
src/app/(dashboard)/student/learning/assignments/page.tsx L23 自定义 type SearchParams
src/app/(dashboard)/student/learning/textbooks/page.tsx L13 自定义 type SearchParams + 自定义 getParam
src/app/(dashboard)/student/learning/courses/page.tsx L11 自定义 type SearchParams + 自定义 getParam

P2-9 recorderName 硬编码和 grade-trend-card a11y

位置 问题
grades/data-access.ts L266 getStudentGradeSummaryrecorderName: "Unknown" 硬编码,已导入 getUserNamesByIds 但未用于获取录入人姓名
grade-trend-card.tsx L37-53 TrendLineChart 未包裹 role="img" + aria-label(其他 4 个图表组件均已添加)

P2-10 架构文档行数和路由记录不一致

位置 问题
004_architecture_impact_map.md §2.6 grades 模块 10 个文件行数与实际不一致(如 actions.ts 文档 359 行,实际 398 行)
004_architecture_impact_map.md §2.22 diagnostic 模块 3 个文件行数与实际不一致
005_architecture_data.json 缺失 /teacher/grades/analytics/management/grade 路由记录

2.3 P3 长期问题(记录但不本次实施)

编号 问题 位置
P3-1 toNumber 工具函数在 grades 和 diagnostic 模块重复定义 多处
P3-2 byKp 聚合逻辑重复 diagnostic/data-access.ts L175-202 / L238-256
P3-3 actions.ts 错误处理模板重复 14 次 grades/actions.ts + actions-analytics.ts
P3-4 isGradeType/isSemester 类型守卫重复定义 batch-grade-entry.tsx / grade-record-form.tsx
P3-5 Option 类型重复定义 3 次 3 个组件
P3-6 export.tsavg 函数与 stats-service.ts 逻辑重复 export.ts L148
P3-7 TYPE_LABELS 硬编码中文映射与 i18n 重复 export.ts L12-17
P3-8 classIds 过滤逻辑重复 3 次 data-access.ts / export.ts
P3-9 WidgetBoundaryWidgetErrorBoundary 类构造函数参数类型不匹配 widget-boundary.tsx L47
P3-10 createDefaultBuckets 不必要导出 stats-service.ts L229
P3-11 6 个组件内部回调函数缺失返回类型标注 多处
P3-12 batch-grade-entry.tsx useEffect 草稿保存 bug依赖数组含 scores L182-193
P3-13 batch-grade-entry.tsx useMemo 依赖数组未包含 validateScore L162-177
P3-14 5 处串行 DB 查询可并行化 data-access.ts / data-access-analytics.ts 等
P3-15 getDiagnosticReports 无分页 data-access-reports.ts L127-159
P3-16 强弱项分类存在 60-79 盲区 data-access.ts L77-78
P3-17 班级报告 strengths 无数量上限 data-access-reports.ts L96-98
P3-18 getStudentMasterySummary 内部串行可并行化 data-access.ts L62-67
P3-19 getStudentMastery 导出但仅内部使用 data-access.ts L42
P3-20 grade-filters.tsx 硬编码科目列表 L47-53
P3-21 class-diagnostic-view.tsx "View" 按钮缺少描述性 aria-label L218-223
P3-22 student-diagnostic-view.tsx "Practice" 按钮缺少描述性 aria-label L129-133
P3-23 3 个表格缺少 <caption> class-grade-report / student-grade-summary / batch-grade-entry
P3-24 stats-service.ts L110 as GradeTrendPoint["type"] 断言违规 stats-service.ts
P3-25 batch-grade-entry.tsx JSON.parse 后 as 断言(灰色地带) L75, L90, L127
P3-26 lib/grade-utils.ts 61 行略超 40 行工具函数建议上限 lib/grade-utils.ts
P3-27 data-access 写操作抛异常暴露给用户,建议结构化错误码 data-access-reports.ts
P3-28 grade-filters.tsx 使用科目名称作为 value 而非科目 ID L47-53

三、v2 改进优先级

P1本次实施

# 问题 改进方向 状态
v2-P1-1 WidgetBoundary 未被使用 在 9 个关键组件中应用 WidgetBoundary 已在 3 个页面应用
v2-P1-2 admin/school/grades/insights 缺失 loading/error 补齐 loading.tsx 和 error.tsx 已补齐
v2-P1-3 架构数据 JSON 005 权限记录错误 修正为 school:manage 已修正
v2-P1-4 i18n 完全未接入 21 个组件接入 useTranslations 21 个组件全部接入
v2-P1-5 exportGradesAction 安全漏洞 传递 currentUserId 和 dataScope 已修复
v2-P1-6 diagnostic 缺少 stats-service.ts 抽取纯统计函数 已抽取352 行12 个纯函数)
v2-P1-7 热力图色块 a11y 添加 role="img" + aria-label 已修复
v2-P1-8 getKnowledgePointStats 无参调用 页面先查班级再传参 已修复
v2-P1-9 updateMasteryFromSubmission 覆盖逻辑 改为累积计算 已改为累积模式

P2本次实施

# 问题 改进方向 状态
v2-P2-1 5 个路由缺失 error.tsx 补齐 已补齐 7 个 error.tsx
v2-P2-2 lib/grade-utils.ts 跨模块查询 改用 classes data-access 已改用子查询
v2-P2-3 死代码清理 页面改用 Action 调用 已删除 2 个死 Action + 2 个死 schema
v2-P2-4 totalStudents 语义和平均掌握度计算 修正计算逻辑 已修正
v2-P2-5 多 upsert 无事务 包裹 db.transaction() 已包裹事务
v2-P2-6 生成报告未校验掌握度数据 添加 totalKnowledgePoints === 0 校验 已添加校验
v2-P2-7 表单 Label 未关联控件 添加 htmlFor 和 id 4 个组件已修复
v2-P2-8 SearchParams 统一剩余文件 改用 @/shared/lib/search-params 5 个文件已统一
v2-P2-9 recorderName 硬编码和 grade-trend-card a11y 修复 已修复
v2-P2-10 架构文档行数和路由记录 同步更新 004 和 005 已同步

P3长期本次不实施

P3-1 ~ P3-28 共 28 项长期改进,记录备查,后续迭代处理。


四、合规项确认v2

以下条目在 v2 审计中已通过

  • 所有 Server Action 调用 requirePermission()
  • 所有 Server Action 返回 ActionState<T>
  • 所有 Server Action 使用 revalidatePath
  • any 类型
  • ?! 组合(可选链后非空断言)
  • 无模块循环依赖
  • 无 N+1 查询
  • 所有读查询函数使用 cache()
  • 文件行数全部合规(最大 batch-grade-entry.tsx 450 行 < 500
  • i18n 翻译文件键完整zh-CN 与 en 一致)
  • i18n/request.ts 已加载所有命名空间
  • studentId 可空 null 安全处理完整
  • diagnostic 跨模块依赖通过 data-access
  • grades data-access 统计逻辑已抽取到 stats-service.ts

五、实施计划

本报告列出的 P19 项)和 P210 项改进项将在本次实施中全部完成。P3 长期改进项记录备查,后续迭代处理。

实施顺序:

  1. P1 安全漏洞修复v2-P1-5
  2. P1 业务逻辑修复v2-P1-8、v2-P1-9
  3. P1 架构修复v2-P1-6
  4. P1 路由补齐v2-P1-2
  5. P1 a11y 修复v2-P1-7
  6. P1 WidgetBoundary 应用v2-P1-1
  7. P1 i18n 接入v2-P1-4
  8. P1 架构图修正v2-P1-3
  9. P2 改进项v2-P2-1 ~ v2-P2-10
  10. 验证lint + tsc + 提交