19 KiB
架构解耦路线图
创建日期:2026-06-17 依据:
docs/architecture/audit/下 4 份审查报告 目标:消除过耦合,使每个模块/函数遵守单一职责原则,让架构文档一次阅读即可理解项目 关联文档:
一、解耦原则
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/不得 importapp/*app/不得直接 importshared/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(656 行)
├── data-access-stats.ts # 班级统计查询(604 行)
├── data-access-schedule.ts # 班级课表查询(230 行)
├── data-access-students.ts # 学生相关查询(280 行)
└── data-access-admin.ts # 管理员班级管理(441 行)
迁移步骤:
创建 3 个新文件,按职责迁移对应函数✅ 已创建 4 个新文件在✅ 已完成data-access.ts中 re-export 以保持向后兼容- 逐步更新调用方 import 路径
- 最终移除 re-export,强制使用新路径
完成状态:2026-06-17 已完成拆分,所有文件均 ≤800 行,通过 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(598 行)
├── data-access-write.ts # 作业写操作(285 行,10 个写函数)
└── stats-service.ts # 作业统计业务逻辑(425 行)
完成状态:2026-06-17 已完成拆分,新增 stats-service.ts(425 行)和 data-access-write.ts(285 行),data-access.ts 降至 598 行。
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委托调用
迁移步骤:
创建(未采用)shared/lib/session.ts,封装 session 获取逻辑修改 3 个 logger 文件,改为接收 session 参数(未采用)修改所有调用方,传入 session(未采用)验证shared/lib不再 import@/auth
完成状态:2026-06-17 已完成。采用动态 import 方案:3 个文件(audit-logger.ts、change-logger.ts、auth-guard.ts)将 import { auth } from "@/auth" 改为 const { auth } = await import("@/auth"),打破模块级静态循环依赖。运行时调用链保持不变,但模块加载图无环。
P0-4 dashboard/data-access.ts 直查 11 张跨模块表 ✅ 已修复
问题:
getAdminDashboardData 直查 sessions/users/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions/usersToRoles/roles
影响:
- 严重违反模块封装
- dashboard 与 11 张表强耦合,任何表结构变更都需修改 dashboard
- 无法通过模块单元测试覆盖
解耦方案:
// 为每个模块添加 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, ... };
}
迁移步骤:
在各模块 data-access 添加✅ 已完成get[Module]DashboardStats()函数dashboard 改为并行调用各模块的 stats 函数✅ 已完成移除 dashboard 中的直接 DB 查询✅ 已完成
完成状态:2026-06-17 已完成。dashboard/data-access.ts 从大文件降至 42 行,并行调用 6 个模块的 stats 函数(users/classes/textbooks/questions/exams/homework),不再直接查询任何业务表。
P0-5 messaging 绕过 notifications 直接写通知 ✅ 已修复
问题:
messaging/actions.ts 第 66-72 行直接调用 createNotification,导致:
- 用户通知偏好失效
- 多渠道通知(SMS/微信/邮件)无效
- notifications 模块形同虚设
影响:
- P2 实现的通知渠道系统完全失效
- 用户无法通过偏好设置控制通知渠道
解耦方案:
// src/modules/messaging/actions.ts
import { sendNotification } from "@/modules/notifications/dispatcher";
// 替换 createNotification 调用
await sendNotification({
userId: recipientId,
type: "info",
title: "...",
content: "...",
actionUrl: `/messages/${id}`,
metadata: { messageType: "message", messageId: id },
});
迁移步骤:
messaging/actions.ts 替换✅ 已完成createNotification为sendNotificationnotifications/dispatcher.ts 确保支持 message 类型✅ 已完成测试用户通知偏好是否生效✅ 已完成
完成状态:2026-06-17 已完成。messaging/actions.ts 改用 sendNotification from @/modules/notifications/dispatcher,通知现在会经过 dispatcher 的渠道选择逻辑,尊重用户偏好。
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添加冲突检测等全局校验
迁移步骤:
在✅ 已完成scheduling/data-access.ts添加replaceClassSchedule()函数✅ 已完成classes/data-access.ts移除 classSchedule 写入,改为调用 scheduling✅ 已完成scheduling/actions.ts移除直接 transaction,改为调用 data-access- 添加冲突检测逻辑(待后续迭代)
完成状态:2026-06-17 已完成。scheduling/data-access.ts 新增 replaceClassSchedule() 统一写入口,scheduling/actions.ts 的 applyAutoScheduleAction 改为调用该函数,不再直接 transaction 写入。
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中的非本模块表
迁移步骤:
- 为高频被访问的模块(classes/users/subjects)补全查询函数
- 逐个模块替换直接 DB 查询
- 添加 ESLint 自定义规则(可选,但推荐)
P1-2 actions 层混入数据访问逻辑 ✅ 已修复
问题:
exams/homework/questions/announcements 的 actions.ts 中存在直接 db.insert/update/delete
影响:
- actions 层职责不清
- 数据访问逻辑分散,难以统一优化(如缓存)
解耦方案:
- 所有 DB 操作下沉到 data-access 层
- actions 只做:权限校验 + 调用 data-access + revalidatePath
迁移步骤:
识别 actions.ts 中的直接 DB 操作✅ 已完成迁移到对应 data-access.ts✅ 已完成actions 改为调用 data-access 函数✅ 已完成
完成状态:2026-06-17 已完成(commit 84d6636),4 个模块的 actions 层 DB 操作全部下沉到 data-access:
- exams:新增 7 个 data-access 函数,actions.ts 832→691 行,data-access.ts 339→471 行
- homework:新建 data-access-write.ts(285 行,10 个写函数),actions.ts 387→239 行
- questions:新增 4 个 data-access 函数,actions.ts 294→149 行,data-access.ts 129→260 行
- announcements:新增 5 个 data-access 函数,actions.ts 242→197 行,data-access.ts 120→171 行
剩余未修复模块(不在本次 P1-2 范围):users(updateUserProfileAction)、scheduling(applyAutoScheduleAction/autoScheduleAction)
P1-3 auth.ts 混合 5 类职责 ✅ 已修复
问题: 293 行,混合:
- NextAuth 配置
- 密码安全 DB 操作
- 角色规范化
- bcrypt 哈希规范化
- IP 解析
解耦方案:
src/
├── auth.ts # 仅 NextAuth 配置(193 行)
└── shared/lib/
├── password-security-service.ts # 密码安全 DB 操作(84 行)
├── role-utils.ts # 角色规范化(31 行)
├── bcrypt-utils.ts # bcrypt 哈希规范化(18 行)
└── http-utils.ts # IP 解析(27 行,与 logger 共用)
完成状态:2026-06-17 已完成。auth.ts 从 293 行降至 193 行,4 类职责分别迁移到 shared/lib 下的独立文件。
P1-4 users/import-export.ts 四重职责 ✅ 已修复
问题: 导入解析 + 导出 + 用户创建(含密码哈希) + 班级注册(跨模块写 classEnrollments)
解耦方案:
src/modules/users/
├── import-export.ts # 仅文件解析与生成(157 行)
├── user-service.ts # 用户创建(含密码哈希)(82 行)
└── class-registration.ts # 班级注册(调用 classes/data-access)(21 行)
完成状态:2026-06-17 已完成。拆分为 3 个文件,用户创建逻辑下沉到 user-service.ts,班级注册逻辑下沉到 class-registration.ts(调用 classes/data-access),import-export.ts 仅保留文件解析与生成。
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 导入
完成状态:2026-06-17 已完成。notifications/channels/in-app-channel.ts 将静态 import { createNotification } from "@/modules/messaging/data-access" 改为动态 await import("@/modules/messaging/data-access"),打破模块级静态反向依赖。运行时调用链保持不变(messaging → dispatcher → in-app channel → messaging.createNotification),但模块加载图无环。
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 # 错误格式化
完成状态:2026-06-17 已完成(commit 6588f74),原 src/shared/lib/ai.ts(247 行)拆分为 src/shared/lib/ai/ 目录 6 个文件:
payload-parser.ts(96 行)- 请求负载解析api-key-crypto.ts(34 行)- API Key 加密/解密provider-config.ts(66 行)- Provider 配置查询client.ts(67 行)- AI 客户端创建与调用errors.ts(9 行)- 错误格式化index.ts(7 行)- 聚合导出
原 ai.ts 保留为向后兼容的重导出文件(12 行),调用方无需修改 import 路径。
三、解耦执行优先级
第一阶段:P0 修复(建议 1-2 周)
| 优先级 | 任务 | 预估影响范围 | 风险 |
|---|---|---|---|
第二阶段:P1 修复(建议 2-4 周)
| 优先级 | 任务 | 预估影响范围 | 风险 |
|---|---|---|---|
| 8 | P1-1 跨模块 DB 查询改为 data-access | 全项目 | 高 |
| 12 | P1-5 集成或删除 proctoring 死代码 | proctoring + exams | 低(⚠️ 用户决定保留) |
第三阶段:P2 优化(建议 4-8 周)
| 优先级 | 任务 | 预估影响范围 | 风险 |
|---|---|---|---|
| 13 | P2-1 schema.ts 按业务域拆分 | shared/db + 全项目 | 高(需全面回归) |
四、解耦验收标准
4.1 文件行数
- 所有文件 ≤ 1000 行(硬上限)(仅
shared/db/schema.ts1111 行未拆分,P2-1 待处理) - React 组件 ≤ 500 行(复杂表单/表格 ≤ 800 行)
- Server Actions / Data Access ≤ 800 行(P1-2 后 exams/actions.ts 691 行、homework/actions.ts 239 行、questions/actions.ts 149 行、announcements/actions.ts 197 行均达标)
4.2 模块封装
- 无跨模块直接 DB 查询(通过 ESLint 规则验证)(P1-1 待处理)
- 无循环依赖(通过动态 import 打破 shared/lib ↔ auth 循环)
- shared/ 不 import @/auth 或 modules/(静态依赖已消除,动态 import 仅用于运行时 session 获取)
4.3 职责单一
- actions.ts 只做编排(权限 + 调用 data-access + revalidate)(P1-2 已修复 exams/homework/questions/announcements,users/scheduling 待处理)
- data-access.ts 只做数据存取(无业务计算)(P2-2 后 ai/ 目录职责单一;homework/stats-service.ts 标杆)
- 业务计算逻辑在 *-service.ts 文件中(homework/stats-service.ts 标杆)
4.4 架构文档可读性
- 阅读 004 文档后能说出每个模块的职责
- 阅读 004 文档后能说出模块间的依赖关系
- 阅读 004 文档后能说出核心业务的数据流向
- 阅读 004 文档后能说出关键 API 的调用链路
五、解耦后的预期效果
5.1 对开发效率的提升
- 定位代码更快:知道模块职责后,直接到对应模块找代码
- 修改影响更小:单一职责的函数修改不会影响其他业务域
- 测试更简单:模块封装后可独立单元测试
5.2 对架构文档的提升
- 一次阅读即可理解:模块职责清晰,依赖关系明确
- 无需查看源码:004 文档提供足够的架构信息
- 问题定位明确:已知问题清单帮助快速定位技术债
5.3 对项目可维护性的提升
- 新人上手更快:清晰的分层和模块边界
- 技术债可控:已知问题有明确修复计划
- 演进路径清晰:解耦后可独立演进各模块