22 KiB
核心业务模块职责与耦合审查报告
审计日期:2026-06-17 审计范围:
src/modules/exams、src/modules/homework、src/modules/questions、src/modules/textbooks、src/modules/grades审计目标:识别职责不单一和过耦合问题,重点关注跨模块直接数据库查询(违反模块封装) 审计依据:项目规则(.trae/rules/project_rules.md)、架构影响地图(004/005)
一、总体结论
| 维度 | 状态 | 说明 |
|---|---|---|
| 模块职责边界 | ⚠️ 部分违规 | homework 混入考试/班级逻辑;grades 混入班级/用户逻辑 |
| data-access 层职责 | ❌ 普遍违规 | 5 个模块均存在跨模块直接 DB 查询;homework/data-access.ts 混入排名业务逻辑(已拆分 stats-service.ts) |
| actions 层职责 | ✅ 已修复 | exams/homework/questions/announcements 的 actions DB 操作已下沉到 data-access(P1-2);textbooks/grades 的 actions 设计良好 |
| 组件耦合 | ✅ 基本合规 | 组件层跨模块依赖均为类型导入或 UI 组合,无直接 data-access 调用 |
| 跨模块依赖 | ⚠️ 存在风险 | 无循环依赖,但 exams→questions、homework→exams、grades→classes 的直接 DB 访问破坏封装 |
| 文件行数 | ✅ 已修复 | homework/data-access.ts 已拆分(598 行 + stats-service.ts 425 行 + data-access-write.ts 285 行);exams/ai-pipeline.ts 857 行(超 800 建议值,P1 待拆分) |
关键风险项
homework/data-access.ts 超过 1000 行硬上限(1038 行)—— 必须拆分✅ 已拆分- 5 个模块均存在跨模块直接 DB 查询 —— 违反模块封装原则(P1-1 待修复)
exams/homework/questions 的 actions 层混入数据访问逻辑 —— 应只做编排✅ 已修复(P1-2)homework/data-access.ts 混入排名计算业务逻辑 —— data-access 应只负责数据存取✅ 已修复(拆分到 stats-service.ts)
二、模块依赖关系图
┌──────────────┐
│ textbooks │ ← 被引用方(知识点/章节)
└──────┬───────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌────────────┐
│ questions │ │ exams │ │ homework │
└─────┬──────┘ └────┬─────┘ └─────┬──────┘
│ │ │
│ ┌──────────┘ │
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌──────────┐
│ grades │ │ classes │
└────────────────┘ └──────────┘
依赖关系明细
| 依赖方向 | 类型 | 合理性 | 问题 |
|---|---|---|---|
| exams → questions | data-access + 类型 + action | ⚠️ 部分合理 | 类型导入合理;但 persistAiGeneratedExamDraft 直接 insert 到 questions 表,应通过 questions/data-access |
| exams → classes | data-access | ❌ 不合理 | getExams/getExamById 直接查询 classes 表获取教师 gradeIds,应通过 classes/data-access |
| exams → school | actions | ❌ 不合理 | getSubjectsAction/getGradesAction 直接查询 subjects/grades 表,应通过 school/data-access |
| homework → exams | data-access + 组件 | ⚠️ 部分合理 | 业务上 homework 引用 exam(sourceExamId)合理;但直接查询 exams 表应改为调用 exams/data-access |
| homework → classes | data-access + actions | ❌ 不合理 | 直接查询 classes/classEnrollments/classSubjectTeachers 表,应通过 classes/data-access |
| homework → questions | data-access | ✅ 合理 | 通过 Drizzle 关系查询 homeworkAssignmentQuestions.question,未直接访问 questions 表 |
| grades → exams | 无 | ✅ 合理 | grades 仅通过 examId 外键引用,不直接查询 exams 表 |
| grades → homework | 无 | ✅ 合理 | grades 仅通过 type 枚举值 "homework" 引用,不直接查询 homework 表 |
| grades → classes | data-access | ❌ 不合理 | 多个 data-access 文件直接查询 classes/classEnrollments 表 |
| grades → users/subjects | data-access | ❌ 不合理 | 直接查询 users/subjects 表获取关联名称,应通过对应模块 data-access |
| questions → textbooks | actions | ❌ 不合理 | getKnowledgePointOptionsAction 直接查询 knowledgePoints/chapters/textbooks 表 |
| textbooks → questions | 组件 | ✅ 合理 | knowledge-point-dialogs.tsx 导入 CreateQuestionDialog 组件,属于 UI 组合 |
循环依赖分析
- 无直接循环依赖:模块间的 data-access 依赖是单向的
- 潜在风险:exams → questions(data-access)与 questions → textbooks(actions)与 textbooks → questions(组件)形成链式依赖,但因 textbooks→questions 仅为组件层导入,不构成 data-access 层的循环依赖
三、各模块审查明细
3.1 exams 模块
文件行数
| 文件 | 行数 | 限制 | 状态 |
|---|---|---|---|
| actions.ts | 691 | ≤800 | ✅(P1-2 后从 832 降至 691) |
| ai-pipeline.ts | 857 | ≤800 | ⚠️ 超限(P1 待拆分) |
| data-access.ts | 471 | ≤800 | ✅(P1-2 后从 339 扩展到 471) |
| types.ts | 31 | 无限制 | ✅ |
| hooks/use-exam-preview.ts | 295 | ≤500 | ✅ |
模块职责边界
- 职责:考试全生命周期管理(创建/编辑/预览/发布/删除/复制)+ AI 辅助出题
- 问题:
getSubjectsAction/getGradesAction属于 school 模块职责,被放在 exams/actions.ts 中(P1-1 待修复)
data-access 层问题
| 函数 | 问题 | 严重程度 |
|---|---|---|
getExams |
直接查询 classes 表获取教师 gradeIds |
高(P1-1 待修复) |
getExamById |
直接查询 classes 表获取教师 gradeIds |
高(P1-1 待修复) |
persistAiGeneratedExamDraft |
直接 insert 到 questions 表 |
高(P1-1 待修复) |
actions 层问题 ✅ 已修复(P1-2)
exams/actions.ts 中的 DB 操作已下沉到 data-access
已完成修复(2026-06-17,commit 84d6636):
- 新增 7 个 data-access 函数(updateExam / deleteExam / duplicateExam / getExamPreview 等)
- actions.ts 从 832 行降至 691 行
- data-access.ts 从 339 行扩展到 471 行
- actions 层不再有直接
db.insert/update/delete
组件耦合
| 组件 | 问题 | 严重程度 |
|---|---|---|
exam-assembly.tsx |
调用 getQuestionsAction(questions 模块的 action) |
中 |
| 8 个组件 | 导入 Question 类型自 questions/types |
低(类型导入合理) |
ExamAssembly |
10 个 props(examId, title, subject, grade, difficulty, totalScore, durationMin, initialSelected, initialStructure, questionOptions) | 中 |
ExamPreviewQuestionEditor |
10 个 props | 中 |
ai-pipeline.ts 问题
- 857 行,超过 800 行建议值(原 912 行,已部分优化)
- 混合了 Zod schema、AI prompt、JSON 解析修复、题目详情解析、并发控制等多种职责
- 建议拆分为:
ai-schema.ts(Zod schema)、ai-prompts.ts(prompt 常量)、ai-parser.ts(JSON 解析修复)、ai-pipeline.ts(核心生成逻辑)(P1 待处理)
3.2 homework 模块
文件行数
| 文件 | 行数 | 限制 | 状态 |
|---|---|---|---|
| data-access.ts | 598 | ≤1000 硬上限 | ✅(P0-2 后从 1038 降至 598) |
| data-access-write.ts | 285 | ≤800 | ✅(P1-2 新增,10 个写函数) |
| stats-service.ts | 425 | ≤800 | ✅(P0-2 新增,统计业务逻辑) |
| actions.ts | 239 | ≤800 | ✅(P1-2 后从 387 降至 239) |
| schema.ts | 29 | 无限制 | ✅ |
| types.ts | 186 | 无限制 | ✅ |
模块职责边界
- 职责:作业全生命周期(创建/发布/作答/批改/分析)
- 问题:
getStudentDashboardGrades包含班级排名计算逻辑(已迁移到 stats-service.ts)
data-access 层问题(部分修复)
| 函数 | 问题 | 严重程度 |
|---|---|---|
getStudentDashboardGrades |
✅ 已修复 | |
getHomeworkAssignmentAnalytics |
✅ 已修复 | |
getHomeworkAssignments |
直接查询 exams 表 |
高(P1-1 待修复) |
getHomeworkAssignmentReviewList |
直接查询 exams 表 |
高(P1-1 待修复) |
getHomeworkSubmissions |
直接查询 exams 表 |
高(P1-1 待修复) |
getHomeworkAssignmentById |
直接查询 exams 表 |
高(P1-1 待修复) |
getStudentHomeworkAssignments |
直接 join exams/subjects 表 |
高(P1-1 待修复) |
getDemoStudentUser |
直接查询 users/roles/usersToRoles 表 + 使用 auth() 而非 auth-guard |
高(P1-1 待修复) |
getStudentDashboardGrades |
直接查询 classEnrollments/users 表 |
高(P1-1 待修复) |
actions 层问题 ✅ 已修复(P1-2)
homework/actions.ts 中的 DB 操作已下沉到 data-access
已完成修复(2026-06-17,commit 84d6636):
- 新建 data-access-write.ts(285 行,10 个写函数)
- actions.ts 从 387 行降至 239 行
createHomeworkAssignmentAction等 Action 的 DB 操作全部下沉到 data-access-write.ts- actions 层不再有直接
db.insert/update/delete
拆分结果 ✅ 已完成
data-access.ts(原 1038 行)已拆分为:
data-access.ts(598 行):基础 CRUD + 查询data-access-write.ts(285 行):写操作(10 个写函数)stats-service.ts(425 行):统计业务逻辑(排名计算、错误率统计等)
3.3 questions 模块
文件行数
| 文件 | 行数 | 限制 | 状态 |
|---|---|---|---|
| actions.ts | 149 | ≤800 | ✅(P1-2 后从 294 降至 149) |
| data-access.ts | 260 | ≤800 | ✅(P1-2 后从 129 扩展到 260) |
| schema.ts | 18 | 无限制 | ✅ |
| types.ts | 34 | 无限制 | ✅ |
模块职责边界
- 职责:题库管理(题目 CRUD、知识点关联、题型支持)
- 问题:
getKnowledgePointOptionsAction查询 textbooks 模块的表,属于 textbooks 模块职责(P1-1 待修复)
data-access 层问题 ✅ 已修复(P1-2)
- ✅ 仅访问
questions和questionsToKnowledgePoints表,无跨模块 DB 访问 - ✅ 写操作函数已补全:
insertQuestionWithRelations、deleteQuestionRecursive等 data-access 函数已从 actions.ts 迁移到 data-access.ts(data-access.ts 从 129 行扩展到 260 行)
actions 层问题 ✅ 已修复(P1-2)
questions/actions.ts 中的 DB 操作已下沉到 data-access
已完成修复(2026-06-17,commit 84d6636):
- 新增 4 个 data-access 函数(insertQuestionWithRelations / deleteQuestionRecursive 等)
- actions.ts 从 294 行降至 149 行
createNestedQuestion/updateQuestionAction/deleteQuestionAction的 DB 操作全部下沉- actions 层不再有直接
db.transaction
剩余问题:
getKnowledgePointOptionsAction仍直接查询knowledgePoints/chapters/textbooks表 —— 跨模块 DB 访问(P1-1 待修复)
组件耦合
- ✅ 无跨模块依赖
3.4 textbooks 模块(标杆模块)
文件行数
| 文件 | 行数 | 限制 | 状态 |
|---|---|---|---|
| actions.ts | 276 | ≤800 | ✅ |
| data-access.ts | 428 | ≤800 | ✅ |
| types.ts | 79 | 无限制 | ✅ |
| hooks/use-knowledge-point-actions.ts | 121 | ≤500 | ✅ |
| hooks/use-text-selection.ts | - | ≤500 | ✅ |
模块职责边界
- 职责:教材与知识体系管理(教材/章节树形结构、知识点 CRUD、Markdown 内容编辑、知识图谱)
- ✅ 职责单一,无越界
data-access 层评价
- ✅ 仅访问
textbooks、chapters、knowledgePoints表 - ✅ 无跨模块 DB 访问
- ✅ 无业务逻辑混入
actions 层评价
- ✅ 标杆实现:所有 action 均遵循"权限校验 → 调用 data-access → revalidatePath → 返回"模式
- ✅ 无直接 DB 访问
- ✅ 无业务逻辑混入
组件耦合
knowledge-point-dialogs.tsx导入CreateQuestionDialog自 questions 模块 —— ✅ 合理的 UI 组合
hooks 评价
useKnowledgePointActions有 7 个参数(textbookId, selectedChapterId, selectedChapterTextbookId, highlightedKpId, setHighlightedKpId, onKpCreated)—— ✅ 在 8 个限制内
3.5 grades 模块
文件行数
| 文件 | 行数 | 限制 | 状态 |
|---|---|---|---|
| actions.ts | 312 | ≤800 | ✅ |
| actions-analytics.ts | 133 | ≤800 | ✅ |
| data-access.ts | 419 | ≤800 | ✅ |
| data-access-analytics.ts | 293 | ≤800 | ✅ |
| data-access-ranking.ts | 121 | ≤800 | ✅ |
| export.ts | 214 | ≤800 | ✅ |
| schema.ts | 52 | 无限制 | ✅ |
| types.ts | - | 无限制 | ✅ |
模块职责边界
- 职责:成绩分析(录入/查询/统计/导出/趋势对比分析)
- ✅ 职责单一,未混入考试/作业逻辑
- ✅ 通过
examId外键引用考试,通过type枚举引用作业类型,未直接依赖 exams/homework 模块的 data-access
data-access 层问题
| 文件 | 函数 | 问题 | 严重程度 |
|---|---|---|---|
| data-access.ts | getGradeRecords |
直接 join classes/subjects/users 表 |
高 |
| data-access.ts | getStudentGradeSummary |
直接 join classes/subjects/users 表 |
高 |
| data-access.ts | getClassRanking |
直接 join users 表 |
高 |
| data-access.ts | getClassStudentsForEntry |
直接查询 classEnrollments/users 表 —— 应在 classes 模块 |
高 |
| data-access.ts | getClassGradeStatsWithMeta |
直接查询 classes/classEnrollments 表 |
高 |
| data-access.ts | getClassGradeStats |
统计计算业务逻辑(average/median/stdDev/passRate/excellentRate)混入 data-access | 中 |
| data-access-analytics.ts | getGradeTrend |
直接 join classes/subjects 表 |
高 |
| data-access-analytics.ts | getClassComparison |
直接查询 classes 表 + 统计计算业务逻辑 |
高 |
| data-access-analytics.ts | getSubjectComparison |
直接 join subjects 表 + 统计计算业务逻辑 |
高 |
| data-access-analytics.ts | getGradeDistribution |
分桶统计业务逻辑混入 data-access | 中 |
| data-access-ranking.ts | getRankingTrend |
直接查询 classEnrollments/users 表 + 排名计算业务逻辑 |
高 |
| export.ts | exportClassGradeReportToExcel |
直接查询 classes/subjects/users 表 + 排名计算业务逻辑 |
高 |
actions 层评价
- ✅ 标杆实现:
actions.ts和actions-analytics.ts均遵循"权限校验 → 调用 data-access → 返回"模式 - ✅ 无直接 DB 访问
- ✅ 无业务逻辑混入
组件耦合
- ✅ 无跨模块依赖
四、跨模块直接 DB 访问汇总
以下为违反模块封装原则的直接数据库查询,应改为通过对方模块的 data-access 函数调用。
4.1 按来源模块分类
| 来源模块 | 文件 | 被访问的表 | 应调用的模块 |
|---|---|---|---|
| exams | data-access.ts | classes |
classes/data-access |
| exams | data-access.ts | questions(insert) |
questions/data-access |
| exams | actions.ts | subjects, grades |
school/data-access |
| homework | actions.ts | classes, classSubjectTeachers, exams, classEnrollments |
classes/data-access, exams/data-access |
| homework | data-access.ts | exams, classEnrollments, subjects, users, roles, usersToRoles |
exams/data-access, classes/data-access, school/data-access |
| questions | actions.ts | knowledgePoints, chapters, textbooks |
textbooks/data-access |
| grades | data-access.ts | classes, classEnrollments, subjects, users |
classes/data-access, school/data-access |
| grades | data-access-analytics.ts | classes, subjects |
classes/data-access, school/data-access |
| grades | data-access-ranking.ts | classEnrollments, users |
classes/data-access |
| grades | export.ts | classes, subjects, users |
classes/data-access, school/data-access |
4.2 按被访问表分类(频次)
| 被访问表 | 访问次数 | 应归属模块 |
|---|---|---|
classes |
8+ | classes |
classEnrollments |
6+ | classes |
users |
6+ | users |
subjects |
6+ | school |
exams |
5+ | exams |
grades(年级表) |
1 | school |
classSubjectTeachers |
1 | classes |
knowledgePoints |
1 | textbooks |
chapters |
1 | textbooks |
textbooks |
1 | textbooks |
roles, usersToRoles |
1 | users |
questions(insert) |
1 | questions |
五、改进建议
5.1 高优先级(P0)
-
拆分 homework/data-access.ts(1038 行 → 4 个文件)✅ 已完成按职责拆分为 data-access.ts / data-access-student.ts / data-access-analytics.ts / data-access-grading.ts- 实际拆分为 data-access.ts(598 行)+ data-access-write.ts(285 行)+ stats-service.ts(425 行)
-
消除跨模块直接 DB 访问(P1-1 待修复)
- 在 classes/data-access 暴露
getClassGradeIdsByClassIds、getClassStudentsByClassId、getActiveClassStudents等函数 - 在 exams/data-access 暴露
getExamForHomeworkCreation(含 questions 关联) - 在 school/data-access 暴露
getSubjectOptions、getGradeOptions - 在 users/data-access 暴露
getUserNameByIds、getStudentInfo - 在 textbooks/data-access 暴露
getKnowledgePointOptions - 在 questions/data-access 暴露
insertQuestionWithRelations、deleteQuestionRecursive
- 在 classes/data-access 暴露
-
将 exams/actions.ts 中的 DB 操作下沉到 data-access✅ 已完成(P1-2)updateExamAction、deleteExamAction、duplicateExamAction、getExamPreviewAction的 DB 操作移至 data-access将(P1-1 待修复)getSubjectsAction/getGradesAction移至 school 模块或改为调用 school/data-access
-
将 homework/actions.ts 中的 DB 操作下沉到 data-access✅ 已完成(P1-2)createHomeworkAssignmentAction(157 行)拆分为:data-access 函数 + action 编排其他 action 的 DB 操作全部移至 data-access
-
将 questions/actions.ts 中的 DB 操作下沉到 data-access✅ 已完成(P1-2)insertQuestionWithRelations、deleteQuestionRecursive移至 data-access(P1-1 待修复)getKnowledgePointOptionsAction改为调用 textbooks/data-access
5.2 中优先级(P1)
-
拆分 exams/ai-pipeline.ts(857 行 → 4 个文件)
- ai-schema.ts(Zod schema)、ai-prompts.ts(prompt 常量)、ai-parser.ts(JSON 解析修复)、ai-pipeline.ts(核心生成逻辑)
-
将 homework/data-access.ts 中的业务逻辑提取到独立服务层✅ 已完成→ 实际提取到getStudentDashboardGrades的排名计算逻辑提取到services/ranking-service.tsstats-service.ts→ 实际提取到getHomeworkAssignmentAnalytics的错误率统计逻辑提取到services/analytics-service.tsstats-service.ts
-
将 grades/data-access.ts 中的统计计算逻辑提取到独立服务层(P1 待处理)
getClassGradeStats的统计计算提取到services/stats-service.tsgetGradeDistribution的分桶逻辑提取到services/distribution-service.ts
-
减少组件 props 数量(P2 待处理)
ExamAssembly(10 props)和ExamPreviewQuestionEditor(10 props)应考虑使用 Context 或组合模式减少 props
5.3 低优先级(P2)
-
统一 auth 调用方式(P2 待处理)
homework/data-access.ts的getDemoStudentUser使用auth()而非auth-guard.getAuthContext(),应统一
-
补全 questions/data-access.ts 的写操作✅ 已完成(P1-2)当前 data-access 仅有→ 写操作已下沉到 data-accessgetQuestions,所有写操作错放在 actions.ts
六、标杆模块推荐
| 模块 | 推荐参考点 |
|---|---|
| textbooks | actions 层编排模式(权限校验 → 调用 data-access → revalidatePath) |
| textbooks | data-access 层职责单一(仅访问本模块表,无业务逻辑) |
| grades | actions 层拆分(actions.ts + actions-analytics.ts 按职责分文件) |
| grades | data-access 层拆分(data-access.ts + data-access-analytics.ts + data-access-ranking.ts) |
| grades | 跨模块解耦(通过外键引用 exams/homework,不直接访问其表) |
七、审查方法说明
- 审查范围:5 个核心业务模块的 actions/data-access/schema/types/components/hooks 全量文件
- 审查工具:源码全量阅读 + Grep 跨模块依赖扫描 + PowerShell 行数统计
- 审查依据:项目规则中"Server Action 必须使用 requirePermission()"、"单文件行数规范"、"模块职责单一"等规则
- 未覆盖项:未运行 lint/typecheck(本次为只读审查,不修改代码);未审查组件内部实现细节(仅审查 props 数量和跨模块依赖)