Files
NextEdu/docs/architecture/audit/01_decoupling_roadmap.md
SpecialX ee517f2b33 docs: 新增架构解耦路线图文档
- 新增 docs/architecture/audit/01_decoupling_roadmap.md
  - 解耦原则: 单一职责 / 模块封装 / 分层单向依赖
  - 过耦合问题清单: 6 项 P0 + 6 项 P1 + 2 项 P2
  - 每项含问题/影响/解耦方案/迁移步骤
  - 三阶段执行优先级与验收标准
- 更新 docs/README.md 索引加入解耦路线图
- 更新 work_log 记录本次工作
2026-06-17 21:56:44 +08:00

455 lines
14 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
> 依据:`docs/architecture/audit/` 下 4 份审查报告
> 目标:消除过耦合,使每个模块/函数遵守单一职责原则,让架构文档一次阅读即可理解项目
> 关联文档:
> - [004 架构影响地图](../004_architecture_impact_map.md)
> - [005 架构数据 JSON](../005_architecture_data.json)
> - [审查汇总报告](./00_summary.md)
---
## 一、解耦原则
### 1.1 单一职责原则SRP
- **模块**:一个模块只负责一个业务域(如 `exams` 只管考试,不管作业)
- **文件**一个文件只承担一类职责data-access 只做数据存取,不做业务计算)
- **函数**:一个函数只做一件事(要么查询,要么写入,要么计算,不混合)
### 1.2 模块封装原则
- 模块对外只暴露 `actions.ts`(编排)和必要的 `data-access.ts` 查询函数
- **禁止跨模块直接查询 DB 表**,必须通过对方模块的 data-access 函数
- 模块间类型导入允许,但 DB 访问必须走 data-access
### 1.3 分层单向依赖原则
```
app/ ──▶ modules/ ──▶ shared/
禁止反向依赖
```
- `shared/` 不得 import `@/auth``@/proxy` 或任何 `modules/*`
- `modules/` 不得 import `app/*`
- `app/` 不得直接 import `shared/db`(必须通过 modules 的 data-access
---
## 二、过耦合问题清单
### P0 严重问题(必须立即修复)
#### P0-1 `classes/data-access.ts` 2104 行,超硬上限 2.1 倍
**问题**
- 文件行数 2104远超 1000 行硬上限
- 混入 homework 相关逻辑getHomeworkStats 等)
- 混入 scheduling 相关逻辑getClassSchedule 等)
- 混入 grades 相关逻辑getClassGradeSummary 等)
**影响**
- 单文件改动影响多业务域,回归风险高
- 阅读者无法快速定位班级相关数据访问
**解耦方案**
```
src/modules/classes/
├── data-access.ts # 班级核心 CRUD目标 ≤500 行)
├── data-access-stats.ts # 班级统计查询getHomeworkStats 等)
├── data-access-schedule.ts # 班级课表查询getClassSchedule 等)
└── data-access-grades.ts # 班级成绩汇总getClassGradeSummary 等)
```
**迁移步骤**
1. 创建 3 个新文件,按职责迁移对应函数
2.`data-access.ts` 中 re-export 以保持向后兼容
3. 逐步更新调用方 import 路径
4. 最终移除 re-export强制使用新路径
---
#### P0-2 `homework/data-access.ts` 1038 行,混入排名计算
**问题**
- 文件行数 1038超 1000 行硬上限
- 混入排名计算业务逻辑calculateClassRankings 等)
- data-access 层不应包含业务计算
**影响**
- 排名算法变更需要修改 data-access 文件
- data-access 职责不清,难以测试
**解耦方案**
```
src/modules/homework/
├── data-access.ts # 作业 CRUD目标 ≤800 行)
├── ranking-service.ts # 排名计算业务逻辑
└── stats-service.ts # 作业统计业务逻辑
```
**迁移步骤**
1. 抽取 `calculateClassRankings``ranking-service.ts`
2. 抽取统计相关函数到 `stats-service.ts`
3. data-access 只保留 CRUD + 简单查询
---
#### P0-3 `shared/lib` ↔ `@/auth` 循环依赖
**问题**
```
shared/lib/audit-logger.ts ──┐
shared/lib/change-logger.ts ──┼──▶ import { auth } from "@/auth"
shared/lib/auth-guard.ts ──┘
src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
──▶ import { ... } from "@/shared/lib/login-logger"
──▶ import { ... } from "@/shared/lib/password-policy"
──▶ import { ... } from "@/shared/lib/rate-limit"
```
**影响**
- shared 层无法独立测试/复用
- 架构上基础设施不应反向依赖应用层
- 模块加载顺序不确定,潜在运行时错误
**解耦方案**
-`auth()` 调用改为依赖注入logger 函数接收 `session` 参数,由调用方传入
- 或抽取 `shared/lib/session.ts` 提供 `getCurrentSession()`,由 `auth.ts` 委托调用
**迁移步骤**
1. 创建 `shared/lib/session.ts`,封装 session 获取逻辑
2. 修改 3 个 logger 文件,改为接收 session 参数
3. 修改所有调用方,传入 session
4. 验证 `shared/lib` 不再 import `@/auth`
---
#### P0-4 `dashboard/data-access.ts` 直查 11 张跨模块表
**问题**
`getAdminDashboardData` 直查 sessions/users/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions/usersToRoles/roles
**影响**
- 严重违反模块封装
- dashboard 与 11 张表强耦合,任何表结构变更都需修改 dashboard
- 无法通过模块单元测试覆盖
**解耦方案**
```typescript
// 为每个模块添加 dashboard 聚合查询函数
// src/modules/exams/data-access.ts
export async function getExamsDashboardStats(): Promise<ExamStats> { ... }
// src/modules/homework/data-access.ts
export async function getHomeworkDashboardStats(): Promise<HomeworkStats> { ... }
// src/modules/dashboard/data-access.ts
export async function getAdminDashboardData() {
const [examStats, homeworkStats, ...] = await Promise.all([
getExamsDashboardStats(),
getHomeworkDashboardStats(),
...
]);
return { exams: examStats, homework: homeworkStats, ... };
}
```
**迁移步骤**
1. 在各模块 data-access 添加 `get[Module]DashboardStats()` 函数
2. dashboard 改为并行调用各模块的 stats 函数
3. 移除 dashboard 中的直接 DB 查询
---
#### P0-5 `messaging` 绕过 `notifications` 直接写通知
**问题**
`messaging/actions.ts` 第 66-72 行直接调用 `createNotification`,导致:
- 用户通知偏好失效
- 多渠道通知SMS/微信/邮件)无效
- notifications 模块形同虚设
**影响**
- P2 实现的通知渠道系统完全失效
- 用户无法通过偏好设置控制通知渠道
**解耦方案**
```typescript
// src/modules/messaging/actions.ts
import { dispatchNotification } from "@/modules/notifications/dispatcher";
// 替换 createNotification 调用
await dispatchNotification({
userId: recipientId,
type: "message",
title: "...",
content: "...",
channels: ["in-app"], // 由 dispatcher 根据用户偏好决定实际渠道
});
```
**迁移步骤**
1. messaging/actions.ts 替换 `createNotification``dispatchNotification`
2. notifications/dispatcher.ts 确保支持 message 类型
3. 测试用户通知偏好是否生效
---
#### P0-6 `classSchedule` 表三处写入口
**问题**
- `classes/data-access.ts` 写入
- `scheduling/actions.ts` 直接 transaction 写入
- `scheduling/data-access.ts` 写入
**影响**
- 数据完整性高风险
- 三处写入逻辑可能不一致
- 难以添加全局校验(如冲突检测)
**解耦方案**
- 统一写入口到 `scheduling/data-access.ts`
- `classes``scheduling/actions` 调用 `scheduling/data-access` 的函数
-`scheduling/data-access` 添加冲突检测等全局校验
**迁移步骤**
1.`scheduling/data-access.ts` 添加 `upsertClassSchedule()` 函数
2. `classes/data-access.ts` 移除 classSchedule 写入,改为调用 scheduling
3. `scheduling/actions.ts` 移除直接 transaction改为调用 data-access
4. 添加冲突检测逻辑
---
### P1 较严重问题(短期修复)
#### P1-1 跨模块直接 DB 查询普遍存在
| 被访问表 | 访问次数 | 应归属模块 | 主要违规者 |
|---------|---------|-----------|-----------|
| `classes` | 8+ | classes | exams, homework, grades, dashboard |
| `classEnrollments` | 6+ | classes | homework, grades, attendance |
| `users` | 6+ | users | 多个模块 |
| `subjects` | 6+ | school | exams, homework, questions |
| `exams` | 5+ | exams | homework, grades, dashboard |
**解耦方案**
- 每个模块在 data-access 中导出查询函数(如 `getClassById``getUserById`
- 违规模块改为调用对方 data-access 函数
- 建立 ESLint 规则禁止跨模块 import `shared/db/schema` 中的非本模块表
**迁移步骤**
1. 为高频被访问的模块classes/users/subjects补全查询函数
2. 逐个模块替换直接 DB 查询
3. 添加 ESLint 自定义规则(可选,但推荐)
---
#### P1-2 actions 层混入数据访问逻辑
**问题**
exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert/update/delete`
**影响**
- actions 层职责不清
- 数据访问逻辑分散,难以统一优化(如缓存)
**解耦方案**
- 所有 DB 操作下沉到 data-access 层
- actions 只做:权限校验 + 调用 data-access + revalidatePath
**迁移步骤**
1. 识别 actions.ts 中的直接 DB 操作
2. 迁移到对应 data-access.ts
3. actions 改为调用 data-access 函数
---
#### P1-3 `auth.ts` 混合 5 类职责
**问题**
293 行,混合:
1. NextAuth 配置
2. 密码安全 DB 操作
3. 角色规范化
4. bcrypt 哈希规范化
5. IP 解析
**解耦方案**
```
src/
├── auth.ts # 仅 NextAuth 配置(目标 ≤150 行)
└── shared/lib/
├── password-security-service.ts # 密码安全 DB 操作
├── role-utils.ts # 角色规范化
├── bcrypt-utils.ts # bcrypt 哈希规范化
└── http-utils.ts # IP 解析(与 logger 共用)
```
---
#### P1-4 `users/import-export.ts` 四重职责
**问题**
导入解析 + 导出 + 用户创建(含密码哈希) + 班级注册(跨模块写 classEnrollments
**解耦方案**
```
src/modules/users/
├── import-export.ts # 仅文件解析与生成
├── user-service.ts # 用户创建(含密码哈希)
└── class-registration.ts # 班级注册(调用 classes/data-access
```
---
#### P1-5 `proctoring/exam-mode-config.tsx` 死代码
**问题**
组件已创建但未集成到考试表单DB schema 有 examMode 字段但表单不收集
**解耦方案**
- 集成到考试创建/编辑表单
- 或删除组件和 DB 字段(如果业务上不需要)
---
#### P1-6 `notifications` 反向依赖 `messaging`
**问题**
notifications 模块 import messaging 的类型,形成反向依赖
**解耦方案**
- 将共享类型抽取到 `shared/types/notifications.ts`
- notifications 和 messaging 都从 shared/types 导入
---
### P2 中期优化
#### P2-1 `schema.ts` 按业务域分节
**问题**
1111 行54 张表混合
**解耦方案**
```
src/shared/db/schema/
├── index.ts # 聚合导出
├── auth.ts # 用户、角色、会话
├── academic.ts # 学校、年级、科目、教材、章节、知识点
├── classes.ts # 班级、选课、排课、考勤
├── exam.ts # 考试、题目、提交
├── homework.ts # 作业、提交、答案
├── grades.ts # 成绩
├── ai.ts # AI 提供商、调用记录
├── audit.ts # 审计日志、变更日志
└── notifications.ts # 通知、偏好
```
---
#### P2-2 `ai.ts` 拆分
**问题**
218 行,混合 5 类职责
**解耦方案**
```
src/shared/lib/ai/
├── index.ts # 聚合导出
├── payload-parser.ts # 请求负载解析
├── api-key-crypto.ts # API Key 加密/解密
├── provider-config.ts # Provider 配置查询
├── client.ts # AI 客户端创建与调用
└── errors.ts # 错误格式化
```
---
## 三、解耦执行优先级
### 第一阶段P0 修复(建议 1-2 周)
| 优先级 | 任务 | 预估影响范围 | 风险 |
|--------|------|-------------|------|
| 1 | P0-3 修复循环依赖 | shared/lib + auth.ts | 低 |
| 2 | P0-5 messaging 改用 dispatcher | messaging + notifications | 低 |
| 3 | P0-6 统一 classSchedule 写入口 | classes + scheduling | 中 |
| 4 | P0-2 拆分 homework/data-access | homework | 中 |
| 5 | P0-4 dashboard 改用模块 data-access | dashboard + 11 个模块 | 高 |
| 6 | P0-1 拆分 classes/data-access | classes + 多个调用方 | 高 |
### 第二阶段P1 修复(建议 2-4 周)
| 优先级 | 任务 | 预估影响范围 | 风险 |
|--------|------|-------------|------|
| 7 | P1-2 actions 层下沉 DB 操作 | 4 个模块 | 中 |
| 8 | P1-1 跨模块 DB 查询改为 data-access | 全项目 | 高 |
| 9 | P1-3 拆分 auth.ts | auth + shared/lib | 中 |
| 10 | P1-4 拆分 users/import-export | users | 低 |
| 11 | P1-6 修复 notifications 反向依赖 | notifications + messaging | 低 |
| 12 | P1-5 集成或删除 proctoring 死代码 | proctoring + exams | 低 |
### 第三阶段P2 优化(建议 4-8 周)
| 优先级 | 任务 | 预估影响范围 | 风险 |
|--------|------|-------------|------|
| 13 | P2-1 schema.ts 按业务域拆分 | shared/db + 全项目 | 高(需全面回归) |
| 14 | P2-2 ai.ts 拆分 | shared/lib/ai | 中 |
---
## 四、解耦验收标准
### 4.1 文件行数
- [ ] 所有文件 ≤ 1000 行(硬上限)
- [ ] React 组件 ≤ 500 行(复杂表单/表格 ≤ 800 行)
- [ ] Server Actions / Data Access ≤ 800 行
### 4.2 模块封装
- [ ] 无跨模块直接 DB 查询(通过 ESLint 规则验证)
- [ ] 无循环依赖(通过 `madge` 工具验证)
- [ ] shared/ 不 import @/auth 或 modules/
### 4.3 职责单一
- [ ] actions.ts 只做编排(权限 + 调用 data-access + revalidate
- [ ] data-access.ts 只做数据存取(无业务计算)
- [ ] 业务计算逻辑在 *-service.ts 文件中
### 4.4 架构文档可读性
- [ ] 阅读 004 文档后能说出每个模块的职责
- [ ] 阅读 004 文档后能说出模块间的依赖关系
- [ ] 阅读 004 文档后能说出核心业务的数据流向
- [ ] 阅读 004 文档后能说出关键 API 的调用链路
---
## 五、解耦后的预期效果
### 5.1 对开发效率的提升
- **定位代码更快**:知道模块职责后,直接到对应模块找代码
- **修改影响更小**:单一职责的函数修改不会影响其他业务域
- **测试更简单**:模块封装后可独立单元测试
### 5.2 对架构文档的提升
- **一次阅读即可理解**:模块职责清晰,依赖关系明确
- **无需查看源码**004 文档提供足够的架构信息
- **问题定位明确**:已知问题清单帮助快速定位技术债
### 5.3 对项目可维护性的提升
- **新人上手更快**:清晰的分层和模块边界
- **技术债可控**:已知问题有明确修复计划
- **演进路径清晰**:解耦后可独立演进各模块