Files
NextEdu/docs/architecture/audit/core-business-audit.md
SpecialX f8dfd1dddd docs: 全项目架构审查与文档体系重写
- 全项目逐文件审查: 4 份审计报告(shared/core-business/management/new-modules)
- 重写 004 架构影响地图: 图优先 + 模块依赖图 + 数据流 + 调用链 + 问题分级
- 更新 005 结构化数据: 新增 architectureOverview/moduleDependencyGraph/knownIssues/dbTables 节点
- 更新 006 功能清单: 143 项功能标注实现状态, P0 覆盖率 80%->92%
- 更新 007 差距审计: v2->v3, P0 完成 69%->84%, 新增架构技术债章节
- 更新 001 项目概览: 6 角色/54 权限/26 模块/54 表
- 新增 docs/README.md 文档索引
- 归档 11 份过时文档(002x2/003/designx8) 标注
- 更新 work_log
2026-06-17 21:51:32 +08:00

418 lines
21 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.
# 核心业务模块职责与耦合审查报告
> 审计日期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 混入排名业务逻辑 |
| actions 层职责 | ⚠️ 部分违规 | exams/homework/questions 的 actions 直接访问 DBtextbooks/grades 的 actions 设计良好 |
| 组件耦合 | ✅ 基本合规 | 组件层跨模块依赖均为类型导入或 UI 组合,无直接 data-access 调用 |
| 跨模块依赖 | ⚠️ 存在风险 | 无循环依赖,但 exams→questions、homework→exams、grades→classes 的直接 DB 访问破坏封装 |
| 文件行数 | ❌ 存在违规 | homework/data-access.ts 1038 行(超 1000 硬上限exams/ai-pipeline.ts 912 行、exams/actions.ts 832 行(超 800 建议值) |
### 关键风险项
1. **homework/data-access.ts 超过 1000 行硬上限**1038 行)—— 必须拆分
2. **5 个模块均存在跨模块直接 DB 查询** —— 违反模块封装原则
3. **exams/homework/questions 的 actions 层混入数据访问逻辑** —— 应只做编排
4. **homework/data-access.ts 混入排名计算业务逻辑** —— data-access 应只负责数据存取
---
## 二、模块依赖关系图
```
┌──────────────┐
│ 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 引用 examsourceExamId合理但直接查询 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 → questionsdata-access与 questions → textbooksactions与 textbooks → questions组件形成链式依赖但因 textbooks→questions 仅为组件层导入,不构成 data-access 层的循环依赖
---
## 三、各模块审查明细
### 3.1 exams 模块
#### 文件行数
| 文件 | 行数 | 限制 | 状态 |
|------|------|------|------|
| actions.ts | 832 | ≤800 | ⚠️ 超限 |
| ai-pipeline.ts | 912 | ≤800 | ⚠️ 超限 |
| data-access.ts | 339 | ≤800 | ✅ |
| types.ts | 31 | 无限制 | ✅ |
| hooks/use-exam-preview.ts | 295 | ≤500 | ✅ |
#### 模块职责边界
- **职责**:考试全生命周期管理(创建/编辑/预览/发布/删除/复制)+ AI 辅助出题
- **问题**`getSubjectsAction`/`getGradesAction` 属于 school 模块职责,被放在 exams/actions.ts 中
#### data-access 层问题
| 函数 | 问题 | 严重程度 |
|------|------|---------|
| `getExams` | 直接查询 `classes` 表获取教师 gradeIds第 71-78 行) | 高 |
| `getExamById` | 直接查询 `classes` 表获取教师 gradeIds第 155-159 行) | 高 |
| `persistAiGeneratedExamDraft` | 直接 insert 到 `questions` 表(第 317-326 行) | 高 |
#### actions 层问题
| 函数 | 问题 | 严重程度 |
|------|------|---------|
| `updateExamAction` | 直接 `db.query.exams.findFirst` 做归属校验 + `db.delete`/`db.insert`/`db.update` 操作 examQuestions | 高 |
| `deleteExamAction` | 直接 `db.query.exams.findFirst` 做归属校验 + `db.delete` | 高 |
| `duplicateExamAction` | 直接 `db.query.exams.findFirst` + `db.transaction` 内联 insert exams/examQuestions | 高 |
| `getExamPreviewAction` | 直接 `db.query.exams.findFirst` 查询考试预览数据 | 高 |
| `getSubjectsAction` | 直接查询 `subjects` 表 —— 跨模块 DB 访问 | 高 |
| `getGradesAction` | 直接查询 `grades` 表 —— 跨模块 DB 访问 | 高 |
#### 组件耦合
| 组件 | 问题 | 严重程度 |
|------|------|---------|
| `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 问题
- 912 行,超过 800 行建议值
- 混合了 Zod schema、AI prompt、JSON 解析修复、题目详情解析、并发控制等多种职责
- 建议拆分为:`ai-schema.ts`Zod schema`ai-prompts.ts`prompt 常量)、`ai-parser.ts`JSON 解析修复)、`ai-pipeline.ts`(核心生成逻辑)
---
### 3.2 homework 模块
#### 文件行数
| 文件 | 行数 | 限制 | 状态 |
|------|------|------|------|
| data-access.ts | **1038** | ≤1000 硬上限 | ❌ **必须拆分** |
| actions.ts | 387 | ≤800 | ✅ |
| schema.ts | 29 | 无限制 | ✅ |
| types.ts | 186 | 无限制 | ✅ |
#### 模块职责边界
- **职责**:作业全生命周期(创建/发布/作答/批改/分析)
- **问题**`getStudentDashboardGrades` 包含班级排名计算逻辑150+ 行),属于 dashboard 或 grades 模块职责
#### data-access 层问题(严重)
| 函数 | 问题 | 严重程度 |
|------|------|---------|
| `getStudentDashboardGrades` | 150+ 行排名计算业务逻辑混入 data-access | 高 |
| `getHomeworkAssignmentAnalytics` | 145+ 行错误率/错误答案统计业务逻辑混入 data-access | 高 |
| `getHomeworkAssignments` | 直接查询 `exams` 表(第 167-171 行) | 高 |
| `getHomeworkAssignmentReviewList` | 直接查询 `exams` 表(第 227-233 行) | 高 |
| `getHomeworkSubmissions` | 直接查询 `exams` 表(第 349-359 行) | 高 |
| `getHomeworkAssignmentById` | 直接查询 `exams` 表(第 403-407 行) | 高 |
| `getStudentHomeworkAssignments` | 直接 join `exams`/`subjects` 表(第 730-731 行) | 高 |
| `getDemoStudentUser` | 直接查询 `users`/`roles`/`usersToRoles` 表 + 使用 `auth()` 而非 auth-guard | 高 |
| `getStudentDashboardGrades` | 直接查询 `classEnrollments`/`users` 表 | 高 |
#### actions 层问题
| 函数 | 问题 | 严重程度 |
|------|------|---------|
| `createHomeworkAssignmentAction` | **157 行**,混合数据访问 + 业务逻辑 + 权限校验 | 高 |
| 同上 | 直接查询 `classes`/`classSubjectTeachers`/`exams`/`classEnrollments` 表 | 高 |
| 同上 | 内联 `db.transaction` insert homeworkAssignments/homeworkAssignmentQuestions/homeworkAssignmentTargets | 高 |
| `startHomeworkSubmissionAction` | 直接 `db.query` + `db.insert` | 高 |
| `saveHomeworkAnswerAction` | 直接 `db.query` + `db.transaction` | 高 |
| `submitHomeworkAction` | 直接 `db.query` + `db.update` | 高 |
| `gradeHomeworkSubmissionAction` | 直接 `db.update` 循环更新 homeworkAnswers | 高 |
#### 拆分建议
`data-access.ts`1038 行)建议拆分为:
- `data-access.ts`:基础 CRUDgetHomeworkAssignments, getHomeworkAssignmentById, getHomeworkSubmissions
- `data-access-student.ts`学生视角查询getStudentHomeworkAssignments, getStudentHomeworkTakeData, getStudentDashboardGrades
- `data-access-analytics.ts`分析统计getHomeworkAssignmentAnalytics, getTeacherGradeTrends
- `data-access-grading.ts`批改相关getHomeworkSubmissionDetails, getHomeworkAssignmentReviewList
---
### 3.3 questions 模块
#### 文件行数
| 文件 | 行数 | 限制 | 状态 |
|------|------|------|------|
| actions.ts | 294 | ≤800 | ✅ |
| data-access.ts | 129 | ≤800 | ✅ |
| schema.ts | 18 | 无限制 | ✅ |
| types.ts | 34 | 无限制 | ✅ |
#### 模块职责边界
- **职责**:题库管理(题目 CRUD、知识点关联、题型支持
- **问题**`getKnowledgePointOptionsAction` 查询 textbooks 模块的表,属于 textbooks 模块职责
#### data-access 层问题
- ✅ 仅访问 `questions``questionsToKnowledgePoints` 表,无跨模块 DB 访问
-**缺失写操作函数**`insertQuestionWithRelations``deleteQuestionRecursive` 等 data-access 函数被错误地放在 actions.ts 中
#### actions 层问题
| 函数 | 问题 | 严重程度 |
|------|------|---------|
| `createNestedQuestion` | 内联 `db.transaction` + 调用 `insertQuestionWithRelations`data-access 函数错放在 actions | 高 |
| `updateQuestionAction` | 内联 `db.transaction` 做 update/delete/insert | 高 |
| `deleteQuestionAction` | 内联 `db.transaction` + 调用 `deleteQuestionRecursive`data-access 函数错放在 actions | 高 |
| `getKnowledgePointOptionsAction` | 直接查询 `knowledgePoints`/`chapters`/`textbooks` 表 —— 跨模块 DB 访问 | 高 |
| `getQuestionsAction` | ✅ 正确委托给 data-access.getQuestions | ✅ |
#### 组件耦合
- ✅ 无跨模块依赖
---
### 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
1. **拆分 homework/data-access.ts**1038 行 → 4 个文件)
- 按职责拆分为 data-access.ts / data-access-student.ts / data-access-analytics.ts / data-access-grading.ts
2. **消除跨模块直接 DB 访问**
- 在 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`
3. **将 exams/actions.ts 中的 DB 操作下沉到 data-access**
- `updateExamAction``deleteExamAction``duplicateExamAction``getExamPreviewAction` 的 DB 操作移至 data-access
-`getSubjectsAction`/`getGradesAction` 移至 school 模块或改为调用 school/data-access
4. **将 homework/actions.ts 中的 DB 操作下沉到 data-access**
- `createHomeworkAssignmentAction`157 行拆分为data-access 函数 + action 编排
- 其他 action 的 DB 操作全部移至 data-access
5. **将 questions/actions.ts 中的 DB 操作下沉到 data-access**
- `insertQuestionWithRelations``deleteQuestionRecursive` 移至 data-access
- `getKnowledgePointOptionsAction` 改为调用 textbooks/data-access
### 5.2 中优先级P1
6. **拆分 exams/ai-pipeline.ts**912 行 → 4 个文件)
- ai-schema.tsZod schema、ai-prompts.tsprompt 常量、ai-parser.tsJSON 解析修复、ai-pipeline.ts核心生成逻辑
7. **将 homework/data-access.ts 中的业务逻辑提取到独立服务层**
- `getStudentDashboardGrades` 的排名计算逻辑提取到 `services/ranking-service.ts`
- `getHomeworkAssignmentAnalytics` 的错误率统计逻辑提取到 `services/analytics-service.ts`
8. **将 grades/data-access.ts 中的统计计算逻辑提取到独立服务层**
- `getClassGradeStats` 的统计计算提取到 `services/stats-service.ts`
- `getGradeDistribution` 的分桶逻辑提取到 `services/distribution-service.ts`
9. **减少组件 props 数量**
- `ExamAssembly`10 props`ExamPreviewQuestionEditor`10 props应考虑使用 Context 或组合模式减少 props
### 5.3 低优先级P2
10. **统一 auth 调用方式**
- `homework/data-access.ts``getDemoStudentUser` 使用 `auth()` 而非 `auth-guard.getAuthContext()`,应统一
11. **补全 questions/data-access.ts 的写操作**
- 当前 data-access 仅有 `getQuestions`,所有写操作错放在 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 数量和跨模块依赖)