Files
NextEdu/docs/architecture/audit/01_decoupling_roadmap.md

15 KiB
Raw Blame History

架构解耦路线图

创建日期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/ 不得 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              # 班级核心 CRUD656 行)
├── data-access-stats.ts        # 班级统计查询604 行)
├── data-access-schedule.ts     # 班级课表查询230 行)
├── data-access-students.ts     # 学生相关查询280 行)
└── data-access-admin.ts        # 管理员班级管理441 行)

迁移步骤

  1. 创建 3 个新文件,按职责迁移对应函数 已创建 4 个新文件
  2. data-access.ts 中 re-export 以保持向后兼容 已完成
  3. 逐步更新调用方 import 路径
  4. 最终移除 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目标 ≤800 行)
├── ranking-service.ts          # 排名计算业务逻辑
└── stats-service.ts            # 作业统计业务逻辑

迁移步骤

  1. 抽取 calculateClassRankingsranking-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
  • 无法通过模块单元测试覆盖

解耦方案

// 为每个模块添加 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 实现的通知渠道系统完全失效
  • 用户无法通过偏好设置控制通知渠道

解耦方案

// 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 替换 createNotificationdispatchNotification
  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
  • classesscheduling/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 中导出查询函数(如 getClassByIdgetUserById
  • 违规模块改为调用对方 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 对项目可维护性的提升

  • 新人上手更快:清晰的分层和模块边界
  • 技术债可控:已知问题有明确修复计划
  • 演进路径清晰:解耦后可独立演进各模块