# 核心业务模块职责与耦合审查报告

> 审计日期：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 待拆分） |

### 关键风险项

1. ~~**homework/data-access.ts 超过 1000 行硬上限**（1038 行）—— 必须拆分~~ ✅ 已拆分
2. **5 个模块均存在跨模块直接 DB 查询** —— 违反模块封装原则（P1-1 待修复）
3. ~~**exams/homework/questions 的 actions 层混入数据访问逻辑** —— 应只做编排~~ ✅ 已修复（P1-2）
4. ~~**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`~~ | ~~150+ 行排名计算业务逻辑混入 data-access~~ ✅ 已迁移到 stats-service.ts | ✅ 已修复 |
| ~~`getHomeworkAssignmentAnalytics`~~ | ~~145+ 行错误率/错误答案统计业务逻辑混入 data-access~~ ✅ 已迁移到 stats-service.ts | ✅ 已修复 |
| `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）

1. ~~**拆分 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 行）

2. **消除跨模块直接 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`

3. ~~**将 exams/actions.ts 中的 DB 操作下沉到 data-access**~~ ✅ 已完成（P1-2）
   - ~~`updateExamAction`、`deleteExamAction`、`duplicateExamAction`、`getExamPreviewAction` 的 DB 操作移至 data-access~~
   - ~~将 `getSubjectsAction`/`getGradesAction` 移至 school 模块或改为调用 school/data-access~~（P1-1 待修复）

4. ~~**将 homework/actions.ts 中的 DB 操作下沉到 data-access**~~ ✅ 已完成（P1-2）
   - ~~`createHomeworkAssignmentAction`（157 行）拆分为：data-access 函数 + action 编排~~
   - ~~其他 action 的 DB 操作全部移至 data-access~~

5. ~~**将 questions/actions.ts 中的 DB 操作下沉到 data-access**~~ ✅ 已完成（P1-2）
   - ~~`insertQuestionWithRelations`、`deleteQuestionRecursive` 移至 data-access~~
   - ~~`getKnowledgePointOptionsAction` 改为调用 textbooks/data-access~~（P1-1 待修复）

### 5.2 中优先级（P1）

6. **拆分 exams/ai-pipeline.ts**（857 行 → 4 个文件）
   - ai-schema.ts（Zod schema）、ai-prompts.ts（prompt 常量）、ai-parser.ts（JSON 解析修复）、ai-pipeline.ts（核心生成逻辑）

7. ~~**将 homework/data-access.ts 中的业务逻辑提取到独立服务层**~~ ✅ 已完成
   - ~~`getStudentDashboardGrades` 的排名计算逻辑提取到 `services/ranking-service.ts`~~ → 实际提取到 `stats-service.ts`
   - ~~`getHomeworkAssignmentAnalytics` 的错误率统计逻辑提取到 `services/analytics-service.ts`~~ → 实际提取到 `stats-service.ts`

8. **将 grades/data-access.ts 中的统计计算逻辑提取到独立服务层**（P1 待处理）
   - `getClassGradeStats` 的统计计算提取到 `services/stats-service.ts`
   - `getGradeDistribution` 的分桶逻辑提取到 `services/distribution-service.ts`

9. **减少组件 props 数量**（P2 待处理）
   - `ExamAssembly`（10 props）和 `ExamPreviewQuestionEditor`（10 props）应考虑使用 Context 或组合模式减少 props

### 5.3 低优先级（P2）

10. **统一 auth 调用方式**（P2 待处理）
    - `homework/data-access.ts` 的 `getDemoStudentUser` 使用 `auth()` 而非 `auth-guard.getAuthContext()`，应统一

11. ~~**补全 questions/data-access.ts 的写操作**~~ ✅ 已完成（P1-2）
    - ~~当前 data-access 仅有 `getQuestions`，所有写操作错放在 actions.ts~~ → 写操作已下沉到 data-access

---

## 六、标杆模块推荐

| 模块 | 推荐参考点 |
|------|-----------|
| **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 数量和跨模块依赖）
