36 KiB
Next_Edu 架构设计文档
版本: V3 (2026-07-07,从"影响地图"重构为"架构设计文档") 目标: 系统性展示项目架构设计、技术栈选型、跨模块协作机制、架构约束与决策 数据来源:
arch.db(npm run arch:scan生成) + 人工整理 配套文档: 各模块 README.md(模块级架构图与流程图) · coding-standards.md · known-issues.md
目录
- 项目概览
- 技术栈选型
- 分层架构
- 模块依赖全景图
- 认证与权限架构
- 数据访问与缓存架构
- 状态管理架构(5 层模型)
- 跨模块协作机制
- 核心业务流程
- 设计令牌体系
- API 与实时通信架构
- 架构约束与设计原则
- 架构决策记录
- 附录
1. 项目概览
Next_Edu 是一套 K12 智慧教务系统,采用单 Next.js 应用 + 严格模块化架构,服务四类角色(管理员/教师/学生/家长)的全流程教学活动。
1.1 系统边界
graph TB
subgraph Users["用户"]
Admin[管理员]
Teacher[教师]
Student[学生]
Parent[家长]
end
subgraph NextEdu["Next_Edu 系统"]
Web[Web 应用<br/>Next.js 16 App Router]
API[REST API<br/>17 个路由]
SSE[SSE 实时流<br/>AI 对话 + 通知]
end
subgraph External["外部依赖"]
MySQL[(MySQL 8.0<br/>54 张表)]
Redis[Redis<br/>可选,限流/缓存]
AI[AI Provider<br/>OpenAI/Anthropic/...]
Storage[文件存储<br/>本地/云]
SMS[SMS/Email/WeChat]
end
Admin --> Web
Teacher --> Web
Student --> Web
Parent --> Web
Web --> MySQL
Web --> Redis
Web --> AI
Web --> Storage
Web --> SMS
API --> MySQL
SSE --> AI
SSE --> MySQL
1.2 规模指标(arch.db)
| 指标 | 数值 |
|---|---|
| 模块数 | 35 个业务模块 + shared 基础设施层 |
| 源文件数 | 1030 |
| 符号数 | 5913 |
| 函数调用数 | 36845 |
| 数据库表 | 54 |
| API 路由 | 17 |
| 权限点 | 61 |
| 系统角色 | 6 (admin/teacher/student/parent/grade_head/teaching_head) |
2. 技术栈选型
2.1 核心框架
| 类别 | 技术 | 版本 | 选型理由 |
|---|---|---|---|
| 全栈框架 | Next.js | 16 (App Router) | RSC + Server Actions + Edge middleware(proxy.ts) |
| 语言 | TypeScript | 5.x | strict 模式,禁止 any/as |
| 数据库 | MySQL | 8.0 | FULLTEXT 索引 + JSON 字段 + 事务 |
| ORM | Drizzle | latest | 类型安全 + 轻量 + 迁移可控 |
| 认证 | NextAuth.js | 4.x | JWT + Credentials Provider + 2FA |
| 运行时 | Node.js | 22 | 原生 fetch / Web Crypto |
2.2 状态与数据获取(arch.db 技术标签统计)
graph LR
subgraph State["状态管理 5 层模型"]
L1[L1 URL<br/>nuqs<br/>16 处]
L2[L2 Server<br/>TanStack Query<br/>18 处]
L3[L3 Client Business<br/>Zustand slice<br/>22 处]
L4[L4 Global UI<br/>Zustand ui-store<br/>+ ModalRoot]
L5[L5 Form<br/>react-hook-form<br/>32 处<br/>+ zodResolver]
end
subgraph Optimistic["乐观更新"]
Opt[useOptimistic<br/>13 处<br/>消息星标/成绩编辑/考勤提交]
end
L1 --> L2 --> L3 --> L4 --> L5
L3 -.-> Opt
2.3 UI 与可视化
| 类别 | 技术 | 用途规模 |
|---|---|---|
| 样式 | Tailwind CSS v4 | 双层令牌(Primitive + Semantic) + @theme inline |
| 组件库 | shadcn/ui | 基于 Radix UI,自托管可定制 |
| 富文本 | Tiptap | 68 处,备课 + 试卷富文本编辑 |
| 图表 | recharts | 101 处,12 个图表类型 |
| 力导向图 | 自定义 force-graph | 知识图谱(textbooks 模块) |
| 图标 | lucide-react | 统一图标库 |
2.4 服务端基础设施(arch.db 技术标签统计)
| 技术 | 用途规模 | 说明 |
|---|---|---|
| Drizzle ORM | 1540 处 | 所有 DB 访问 |
| cacheFn | 1279 处 | 封装 React cache() + 自定义缓存层,替代 unstable_cache |
| Server Action | 454 处 | 编排层:权限校验 + data-access 调用 + revalidate |
3. 分层架构
项目采用严格三层架构,依赖方向必须单向: app → modules → shared。
graph TB
subgraph AppLayer["app/ 路由层"]
direction LR
Auth["(auth)/<br/>login/register/privacy/terms"]
Dash["(dashboard)/<br/>admin/teacher/student/parent/management"]
APIRoutes["api/<br/>17 个 REST 路由<br/>auth/ai/upload/files/search/..."]
end
subgraph ModulesLayer["modules/ 业务模块层 (35 个模块)"]
direction LR
Core["核心教学<br/>exams·homework·questions<br/>·textbooks·grades<br/>·lesson-preparation"]
Mgmt["教学管理<br/>classes·school·scheduling<br/>·attendance·course-plans<br/>·standards"]
User["用户沟通<br/>users·messaging·notifications<br/>·parent·audit·rbac<br/>·auth·onboarding"]
Ext["扩展功能<br/>ai·dashboard·diagnostic<br/>·adaptive-practice·error-book<br/>·elective·proctoring<br/>·leave-requests<br/>·invitation-codes"]
Other["其他<br/>files·settings·layout<br/>·student·search<br/>·announcements"]
end
subgraph SharedLayer["shared/ 基础设施层"]
direction LR
DB["db/<br/>schema.ts (54 表)<br/>relations.ts<br/>index.ts"]
Lib["lib/<br/>auth-guard·permissions<br/>·audit-logger·change-logger<br/>·ai·rate-limit·excel<br/>·file-storage·session<br/>·permission-bitmap<br/>·route-permissions"]
Hooks["hooks/<br/>use-permission<br/>·use-aria-live"]
Components["components/<br/>ui/(shadcn)·a11y/<br/>·global-search<br/>·schedule·charts"]
Types["types/<br/>permissions·action-state<br/>·errors"]
end
subgraph Root["根模块 (应用层)"]
AuthCfg["src/auth.ts<br/>NextAuth 配置"]
Proxy["src/proxy.ts<br/>Edge 中间件"]
Env["src/env.mjs<br/>环境变量校验"]
end
AppLayer -->|"调用 Server Actions<br/>+ data-access"| ModulesLayer
ModulesLayer -->|"依赖"| SharedLayer
AppLayer -->|"直接使用"| SharedLayer
Root -.->|"配置"| AppLayer
Root -.->|"被 shared/lib/session.ts<br/>dynamic import"| SharedLayer
style AppLayer fill:#e0f2fe
style ModulesLayer fill:#fef3c7
style SharedLayer fill:#dcfce7
style Root fill:#fce7f3
3.1 分层规则
| 层级 | 允许的依赖 | 禁止的依赖 |
|---|---|---|
app/ |
modules/* 的 Server Actions + data-access;shared/* |
直接访问 DB |
modules/* |
shared/*;其他 modules/* 的 data-access |
直接查询他模块 DB 表;反向依赖 shared |
shared/* |
自身内部 | 反向依赖 @/auth/@/proxy/modules/*(用 dynamic import 打破循环) |
3.2 模块标准结构
graph LR
subgraph Module["src/modules/[module]/"]
Actions["actions.ts<br/>Server Actions 编排层<br/>(权限+调用+revalidate)"]
DA["data-access.ts<br/>DB CRUD<br/>(可拆 data-access-*.ts)"]
Schema["schema.ts<br/>Zod 验证 (可选)"]
Types["types.ts<br/>类型定义"]
Components["components/<br/>模块专属组件"]
Hooks["hooks/<br/>模块专属 Hook (可选)"]
Lib["lib/<br/>纯函数/类型守卫 (可选)"]
end
subgraph Flow["调用流向"]
F1[Client Component] -->|"use server"| Actions
F2[Server Component] -->|"直接调用"| DA
Actions -->|"编排"| DA
Actions -->|"权限校验"| RequirePerm[requirePermission]
DA -->|"DB CRUD"| DB[(MySQL)]
end
Components --> Actions
Hooks --> Actions
Components --> Hooks
4. 模块依赖全景图
完整依赖查询:
npm run arch:query -- sql "SELECT sm.name as source, tm.name as target, md.dep_type FROM module_deps md JOIN modules sm ON md.source_module_id=sm.id JOIN modules tm ON md.target_module_id=tm.id WHERE md.dep_type='data-access-call' AND sm.name<>'shared' AND tm.name<>'shared'"
4.1 核心依赖关系(mermaid)
graph TB
subgraph Foundation["基础层(被广泛依赖)"]
Shared[shared]
Users[users]
Classes[classes]
School[school]
end
subgraph Content["内容层"]
Textbooks[textbooks<br/>教材/知识点]
Questions[questions<br/>题库]
Standards[standards<br/>课程标准]
end
subgraph Core["核心教学层"]
Exams[exams<br/>试卷]
Homework[homework<br/>作业]
Grades[grades<br/>成绩]
LessonPrep[lesson-preparation<br/>备课]
end
subgraph Analytics["分析诊断层"]
Diagnostic[diagnostic<br/>学情诊断]
ErrorBook[error-book<br/>错题本]
Adaptive[adaptive-practice<br/>自适应练习]
end
subgraph Aggregation["聚合层"]
Dashboard[dashboard<br/>仪表盘]
Parent[parent<br/>家长端]
end
subgraph Communication["沟通层"]
Messaging[messaging]
Notifications[notifications]
Announcements[announcements]
end
subgraph Management["管理层"]
Attendance[attendance]
Scheduling[scheduling]
CoursePlans[course-plans]
Elective[elective]
LeaveRequests[leave-requests]
Rbac[rbac]
Audit[audit]
Onboarding[onboarding]
Auth[auth]
Settings[settings]
end
subgraph Extension["扩展层"]
AI[ai]
Files[files]
Proctoring[proctoring]
Search[search]
Layout[layout]
Student[student]
InvitationCodes[invitation-codes]
end
%% 内容层依赖基础层
Textbooks --> Classes
Textbooks --> School
Textbooks --> Users
Questions --> Textbooks
%% 核心教学层依赖内容层 + 基础层
Exams --> Questions
Exams --> Classes
Exams --> School
Homework --> Classes
Homework --> School
Homework --> Users
Grades --> Classes
Grades --> School
Grades --> Users
LessonPrep --> Textbooks
LessonPrep --> Questions
LessonPrep --> Exams
LessonPrep --> Homework
LessonPrep --> Classes
LessonPrep --> Standards
%% 分析层依赖核心层
Diagnostic --> Questions
Diagnostic --> Classes
Diagnostic --> Users
ErrorBook --> Questions
ErrorBook --> Classes
Adaptive --> Classes
Adaptive --> Users
%% 聚合层依赖一切
Dashboard --> Classes
Dashboard --> Homework
Dashboard --> Questions
Dashboard --> Textbooks
Dashboard --> Users
Dashboard --> Parent
Parent --> Classes
Parent --> Homework
Parent --> Grades
Parent --> School
Parent --> Users
%% 核心层依赖分析/通知层(闭环)
Homework --> Diagnostic
Homework --> ErrorBook
Homework --> Notifications
Grades --> Diagnostic
Grades --> ErrorBook
Grades --> Notifications
Grades --> Attendance
Grades --> Parent
%% 沟通层
Messaging --> Classes
Messaging --> Users
Messaging --> Files
Messaging --> Parent
Announcements --> Classes
Announcements --> School
Announcements --> Users
%% 管理层
Attendance --> Classes
Attendance --> School
Attendance --> Users
Attendance --> Grades
Attendance --> Parent
Scheduling --> Classes
Scheduling --> School
Scheduling --> Users
CoursePlans --> Classes
CoursePlans --> School
CoursePlans --> Users
Elective --> Classes
Elective --> School
Elective --> Users
LeaveRequests --> Classes
LeaveRequests --> Attendance
LeaveRequests --> Users
LeaveRequests --> Parent
Rbac --> Classes
Rbac --> Parent
Rbac --> School
Settings --> Classes
Settings --> Files
Settings --> Homework
Settings --> Onboarding
Settings --> Users
Auth --> Classes
Onboarding --> Classes
%% 扩展层
AI --> Textbooks
Proctoring --> Users
Search --> Shared
4.2 依赖深度统计
| 层级 | 模块 | 特征 |
|---|---|---|
| L0 基础层 | shared · users · classes · school | 被几乎所有模块依赖,自身不依赖业务模块 |
| L1 内容层 | textbooks · questions · standards | 教学内容基础,被核心层依赖 |
| L2 核心教学层 | exams · homework · grades · lesson-preparation | 业务核心,依赖内容层 + 基础层 |
| L3 分析诊断层 | diagnostic · error-book · adaptive-practice | 依赖核心层数据,产出分析洞察 |
| L4 聚合层 | dashboard · parent | 依赖一切,为前端提供聚合视图 |
| 横切层 | messaging · notifications · audit · rbac · attendance · scheduling · ai · files | 被核心层调用,提供横切功能 |
4.3 循环依赖处理
graph LR
subgraph Problem["潜在循环(已解决)"]
SharedLib["shared/lib/*<br/>{audit-logger, change-logger,<br/>auth-guard, login-logger}"]
Session["shared/lib/session.ts<br/>单一入口"]
AuthCfg["src/auth.ts<br/>NextAuth 配置"]
end
SharedLib -.->|"需要 session"| Session
Session -.->|"dynamic import<br/>打破静态循环"| AuthCfg
AuthCfg -.->|"正常依赖"| SharedLib
style Session fill:#fef3c7
解决方式: shared/lib/* 不再静态 import @/auth,统一通过 session.ts 单一入口;session.ts 内部使用 dynamic import("@/auth") 打破模块级静态循环,运行时调用链保持不变,模块加载图无环。
5. 认证与权限架构
5.1 认证流程
sequenceDiagram
participant U as 用户
participant P as proxy.ts<br/>(Edge 中间件)
participant Auth as src/auth.ts<br/>(NextAuth)
participant DB as MySQL
participant SA as Server Action
participant Ctx as AuthContext
U->>P: 访问任意路由
P->>P: getToken() 读 JWT
alt 未认证
P-->>U: 重定向 /login
else 已认证未引导
P-->>U: 重定向 /onboarding
else 已认证已引导
P->>P: hasPermissionInBitmap(token.permissions, routePerm)
alt 权限不足
P-->>U: 重定向 /forbidden
else 权限通过
P-->>U: 注入 x-request-id,放行
end
end
U->>Auth: 登录(credentials)
Auth->>DB: 验证密码 + 2FA
Auth->>DB: 查 roles + permissions
Auth->>Auth: JWT callback<br/>resolvePermissions(roleNames)<br/>存入 token
Auth-->>U: Session Cookie
U->>SA: 调用 Server Action
SA->>Ctx: requirePermission(PERM)
Ctx->>Ctx: getSession() 读 session
Ctx->>Ctx: resolveDataScope(userId, roles)
Ctx->>DB: 查 classSubjectTeachers/<br/>grades/parentStudentRelations
Ctx-->>SA: AuthContext{userId, roles,<br/>permissions, dataScope}
SA->>SA: ctx.permissions.includes(PERM)?
alt 权限不足
SA-->>U: PermissionDeniedError
else 权限通过
SA->>DB: 执行业务(带 dataScope 过滤)
end
5.2 权限系统架构
graph TB
subgraph Definition["权限定义层"]
Perms["Permissions 常量<br/>shared/types/permissions.ts<br/>61 个权限点<br/>格式: resource:action"]
Roles["BUILTIN_ROLES<br/>6 个系统角色<br/>admin/teacher/student/<br/>parent/grade_head/teaching_head"]
RolePerm["ROLE_PERMISSIONS_SEED<br/>角色→权限种子映射"]
end
subgraph Runtime["运行时解析层"]
Bitmap["permission-bitmap.ts<br/>位图压缩(62 权限→8 字节)"]
Catalog["permission-catalog.ts<br/>权限元数据目录"]
Resolver["rbac/lib/<br/>data-scope-resolver.ts<br/>配置驱动 DATA_SCOPE_RULES"]
end
subgraph Enforcement["权限执行层(3 道防线)"]
Edge["1. proxy.ts<br/>路由级 bitmap 检查<br/>(Edge Runtime)"]
Server["2. requirePermission()<br/>Server Action 级断言"]
Client["3. usePermission()<br/>.hasPermission()<br/>客户端条件渲染"]
end
subgraph Scope["数据范围过滤"]
DataScope["DataScope 类型<br/>all / grade_managed /<br/>class_taught / class_members /<br/>children / owned"]
Filter["行级过滤<br/>传递到 data-access"]
end
subgraph UI["UI 层"]
Nav["layout/config/navigation.ts<br/>NavItem.permission 过滤侧边栏"]
I18n["i18n/rbac.json<br/>权限显示名"]
end
Definition --> Runtime
Runtime --> Enforcement
Enforcement --> Scope
Scope --> Filter
Definition --> UI
Runtime --> UI
5.3 权限点全量分布
| 资源 | 权限点 | 数量 |
|---|---|---|
| exam | create/read/update/delete/duplicate/publish/ai_generate/submit | 8 |
| homework | create/grade/submit | 3 |
| question | create/read/update/delete | 4 |
| textbook | create/read/update/delete | 4 |
| class | create/read/update/delete/enroll/schedule | 6 |
| school | manage/grade_manage/user_manage | 3 |
| user | read/update/delete/invite | 4 |
| 其他 | attendance/messaging/notification/audit/rbac/... | 33 |
| 合计 | 61 |
6. 数据访问与缓存架构
6.1 数据访问分层
graph TB
subgraph Client["客户端"]
CC[Client Component]
end
subgraph Server["服务端"]
SC[Server Component]
SA[Server Action<br/>编排层]
DA[data-access.ts<br/>DB CRUD]
Cache[cacheFn<br/>请求级去重]
end
subgraph Infra["基础设施"]
Drizzle[Drizzle ORM<br/>1540 处]
MySQL[(MySQL 8.0<br/>54 张表)]
ReactCache[React cache<br/>原生]
end
CC -->|"use server"| SA
SC -->|"直接调用"| DA
SA -->|"权限+编排"| DA
DA -->|"包装"| Cache
Cache -->|"封装"| ReactCache
DA -->|"查询"| Drizzle
Drizzle --> MySQL
6.2 缓存策略
| 层级 | 技术 | 用途规模 | 适用场景 |
|---|---|---|---|
| 请求级去重 | cacheFn (封装 React cache()) |
1279 处 | 同一请求内多次调用同一 data-access 函数 |
| 跨请求缓存 | 不使用 unstable_cache |
- | 权限数据易变,风险高于收益 |
| 客户端缓存 | TanStack Query | 18 处 | 服务端动态数据,自动重试/乐观更新 |
| URL 状态 | nuqs | 16 处 | 可分享/可刷新的筛选/分页状态 |
6.3 data-access 拆分模式
graph LR
subgraph Small["小模块(单文件)"]
Single["data-access.ts<br/>(< 800 行)"]
end
subgraph Medium["中模块(按职责拆分)"]
DA1["data-access.ts<br/>核心 CRUD"]
DA2["data-access-stats.ts<br/>统计查询"]
DA3["data-access-graph.ts<br/>图谱查询"]
end
subgraph Large["大模块(按领域拆分)"]
Barrel["index.ts<br/>barrel 导出"]
D1["data-access-read.ts"]
D2["data-access-write.ts"]
D3["data-access-classes.ts"]
D4["data-access-versions.ts"]
D5["data-access-..."]
end
Small --> Medium --> Large
拆分信号: 单文件超过 800 行建议拆分,超过 1000 行硬性上限必须拆分。
7. 状态管理架构(5 层模型)
graph TB
subgraph L1["L1 URL 状态"]
NUQS["nuqs<br/>16 处<br/>可分享/可刷新"]
end
subgraph L2["L2 Server 状态"]
TQ["TanStack Query<br/>18 处<br/>缓存/重试/乐观更新"]
end
subgraph L3["L3 Client Business 状态"]
Zustand["Zustand slice<br/>22 处<br/>模块业务状态"]
end
subgraph L4["L4 Global UI 状态"]
UIStore["Zustand ui-store<br/>全局 UI 状态"]
Modal["ModalRoot<br/>命令式 registry"]
end
subgraph L5["L5 Form 状态"]
RHF["react-hook-form<br/>32 处"]
Zod["zodResolver<br/>Schema 验证"]
end
subgraph Optimistic["乐观更新层"]
Opt["useOptimistic<br/>13 处<br/>+ useTransition"]
end
L1 --> L2 --> L3 --> L4 --> L5
L3 -.->|"配合"| Optimistic
L2 -.->|"配合"| Optimistic
7.1 状态层选用规则
| 场景 | 选用层 | 理由 |
|---|---|---|
| 筛选/分页/排序(可分享) | L1 nuqs | URL 即状态,刷新可恢复 |
| 服务端数据(列表/详情) | L2 TanStack Query | 自动缓存/重试/失效 |
| 复杂客户端业务(编辑器/画布) | L3 Zustand slice | 模块隔离,可拆分 |
| 全局弹窗/抽屉/主题 | L4 Zustand ui-store | 跨模块共享 |
| 表单(含验证) | L5 react-hook-form | 性能 + 验证一体化 |
| 即时反馈(星标/提交) | useOptimistic | React 19 原生,自动回滚 |
7.2 乐观更新适用场景
| 场景 | 是否乐观更新 | 理由 |
|---|---|---|
| 消息星标 | ✓ | 纯 UI 状态,失败可回滚 |
| 成绩编辑 | ✓ | 用户期望即时反馈 |
| 考勤提交 | ✓ | form action 模式,useFormStatus 依赖 promise |
| 消息撤回 | ✗ | 需服务端校验 2 分钟窗口 |
| 创建/删除 | ✗ | 需服务端返回 ID/校验依赖 |
8. 跨模块协作机制
8.1 跨模块调用规则
graph LR
subgraph ModuleA["模块 A"]
ActionA[actions.ts]
DAA[data-access.ts]
end
subgraph ModuleB["模块 B"]
DAB[data-access.ts]
DBB[(DB 表)]
end
ActionA -->|"✓ 允许:调用 B 的 data-access"| DAB
DAA -->|"✓ 允许:调用 B 的 data-access"| DAB
DAA -.->|"✗ 禁止:直查 B 的 DB 表"| DBB
DAB --> DBB
8.2 聚合层模式(dashboard/parent)
sequenceDiagram
participant Page as 聚合页<br/>(dashboard/parent)
participant DA as 聚合 data-access
participant M1 as 模块1 data-access
participant M2 as 模块2 data-access
participant M3 as 模块3 data-access
participant DB as MySQL
Page->>DA: getDashboardData(userId)
DA->>DA: Promise.allSettled([</br>getM1Data(userId),</br>getM2Data(userId),</br>getM3Data(userId)</br>])
par 并行查询
DA->>M1: getM1Data
M1->>DB: SELECT ...
M1-->>DA: M1Result
and
DA->>M2: getM2Data
M2->>DB: SELECT ...
M2-->>DA: M2Result
and
DA->>M3: getM3Data
M3->>DB: SELECT ...
M3-->>DA: M3Result
end
DA->>DA: 容错处理(allSettled)
DA-->>Page: { m1, m2, m3 }
关键约束:
- 聚合层必须用
Promise.allSettled而非Promise.all,避免单模块失败导致整页白屏 - 聚合 data-access 不直查业务表,只调用各模块 data-access
- 聚合页优先用 Server Component,除非需要
useMemo等客户端 Hook
8.3 事件驱动闭环(批改后联动)
flowchart TB
A[教师批改作业] --> B[gradeHomeworkSubmission]
B --> C{批改完成?}
C -->|是| D[Promise.allSettled 并行闭环]
D --> E1[error-book<br/>采集错题]
D --> E2[diagnostic<br/>更新知识点掌握度]
D --> E3[notifications<br/>通知学生]
D --> E4[grades<br/>更新成绩统计]
E1 --> F[闭环完成]
E2 --> F
E3 --> F
E4 --> F
9. 核心业务流程
9.1 考试全生命周期(跨 4 模块)
sequenceDiagram
autonumber
participant T as 教师
participant S as 学生
participant Exams as exams 模块
participant HW as homework 模块
participant Grades as grades 模块
participant Diag as diagnostic 模块
participant DB as MySQL
rect rgb(240, 249, 255)
Note over T,Exams: 阶段 1: 教师创建考试
T->>Exams: createExamAction
Exams->>Exams: requirePermission(EXAM_CREATE)
Exams->>DB: INSERT exams + examQuestions
Exams->>T: ActionState{examId}
end
rect rgb(254, 249, 195)
Note over T,HW: 阶段 2: 作业化下发
T->>HW: createHomeworkAssignmentAction(sourceExamId)
HW->>HW: requirePermission(HOMEWORK_CREATE)
HW->>DB: INSERT homeworkAssignments(sourceExamId)
end
rect rgb(240, 253, 244)
Note over S,HW: 阶段 3: 学生作答
S->>HW: startHomeworkSubmissionAction
HW->>DB: INSERT homeworkSubmissions
S->>HW: saveHomeworkAnswerAction (多次)
HW->>DB: INSERT homeworkAnswers
S->>HW: submitHomeworkAction
HW->>DB: UPDATE status=submitted
end
rect rgb(253, 235, 214)
Note over T,HW: 阶段 4: 教师批改
T->>HW: gradeHomeworkSubmissionAction
HW->>HW: requirePermission(HOMEWORK_GRADE)
HW->>DB: UPDATE homeworkAnswers(score, isCorrect)
HW->>HW: Promise.allSettled 并行闭环
par 闭环
HW->>Diag: updateMasteryFromSubmission
Diag->>DB: UPDATE knowledgePointMastery
and
HW->>Grades: 更新成绩统计
Grades->>DB: UPDATE/INSERT gradeRecords
end
end
9.2 备课→发布作业流程
flowchart LR
A[教师创建教案] --> B[lesson-preparation<br/>Tiptap + 节点树编辑]
B --> C{选择发布}
C --> D[publish-service<br/>处理教案]
D --> E{发布类型}
E -->|作业| F[homework<br/>createHomeworkAssignment]
E -->|仅教案| G[存档供查阅]
F --> H[学生收到作业通知]
9.3 AI 辅助流程(SSE 流式)
sequenceDiagram
participant C as Client
participant API as /api/ai/chat/stream
participant AIService as ai/createAiService
participant Provider as AI Provider
participant DB as MySQL
C->>API: POST messages[]
API->>API: 鉴权 + 限流
API->>AIService: createAiChatCompletion(stream)
AIService->>Provider: stream chat completion
loop 流式响应
Provider-->>AIService: chunk
AIService-->>API: chunk
API-->>C: SSE event: data chunk
end
Provider-->>AIService: done
AIService->>DB: 记录 usage + cost
API-->>C: SSE event: [DONE]
10. 设计令牌体系
项目采用双层令牌架构(Primitive + Semantic),Tailwind v4 通过 @theme inline 暴露为业务可用的类。
10.1 令牌分层架构
graph TB
subgraph L1["Layer 1: Primitive (原始)"]
Prim["primitive.css<br/>原始色板(zinc/stone/indigo)<br/>字号/间距/阴影/字体家族<br/>业务代码不直接引用"]
end
subgraph L2["Layer 2: Semantic (语义)"]
Light["semantic-light.css<br/>:root 语义令牌"]
Dark["semantic-dark.css<br/>.dark 语义令牌"]
end
subgraph L3["Layer 3: 模块命名空间"]
LP["lesson-preparation.css<br/>--lp-* 令牌<br/>(明暗双份)"]
end
subgraph L4["Layer 4: Tailwind 暴露"]
Theme["tailwind-theme.css<br/>@theme inline<br/>暴露为 bg-*/text-*/font-*"]
end
subgraph Business["业务代码"]
Code[TSX/TS<br/>唯一引用入口]
end
Prim --> Light
Prim --> Dark
Light --> Theme
Dark --> Theme
LP --> Theme
Theme --> Code
10.2 令牌分类
| 分类 | 命名 | Tailwind 类 |
|---|---|---|
| shadcn 标准 | --background/--foreground/--primary |
bg-background/text-foreground/bg-primary |
| 扩展层级 | --background-elevated/--text-secondary/--border-strong |
bg-background-elevated/text-text-secondary |
| chart | --chart-1~5 |
bg-chart-1 |
| sidebar | --sidebar-* |
bg-sidebar |
| 模块命名空间 | --lp-paper/--lp-inline-node-text |
bg-lp-paper/text-lp-inline-node-text |
| 字体家族 | --font-family-sans/serif/mono |
font-sans/font-serif/font-mono |
| 字号阶梯 | --font-size-1~9 |
text-size-1~text-size-9 |
| 间距阶梯 | --space-0~18 |
w-[length:var(--space-N)] |
| 阴影阶梯 | --shadow-1~6 |
shadow-1~shadow-6 |
10.3 强制约束
- ESLint
no-restricted-syntax: 禁止#hex字面量 - 自定义规则
design-tokens/no-hardcoded-fonts: 禁止'Inter'/'Fraunces'/'JetBrains Mono'字面量 - 白名单:
primitive.css(令牌定义)、email-channel.ts(邮件 HTML)、manifest.ts(PWA) - 任意值
w-[Npx]需// eslint-disable-next-line no-restricted-syntax -- <reason>注释豁免
11. API 与实时通信架构
11.1 API 路由清单(17 个)
graph LR
subgraph Auth["认证 (2)"]
A1[/api/auth/[...nextauth]/]
A2[/api/onboarding/status,<br/>/api/onboarding/complete]
end
subgraph AI["AI (2)"]
AI1[/api/ai/chat<br/>同步]
AI2[/api/ai/chat/stream<br/>SSE 流式]
end
subgraph Files["文件 (3)"]
F1[/api/upload]
F2[/api/files/[id]]
F3[/api/files/batch-delete]
F4[/api/export]
end
subgraph Other["其他 (5)"]
O1[/api/search]
O2[/api/import]
O3[/api/notifications/stream<br/>SSE]
O4[/api/proctoring/event]
O5[/api/cron/audit-cleanup]
end
subgraph Monitor["监控 (2)"]
M1[/api/client-error]
M2[/api/web-vitals]
end
11.2 响应信封规范
所有 API 统一采用 { success, message?, errorCode?, data? } 信封,与 ActionState<T> 对齐:
graph LR
subgraph ErrorMap["错误→状态码映射"]
E1[PermissionDeniedError] -->|403| S1[Forbidden]
E2[NotFoundError] -->|404| S2[Not Found]
E3[ValidationError] -->|400| S3[Bad Request]
E4[BusinessError] -->|400| S3
E5[其他] -->|500| S4[Internal Server Error]
end
subgraph Utils["工具函数"]
HOF[withApiErrorHandler<br/>HOF 消除重复 try/catch]
FromAction[apiFromAction<br/>errorCode 驱动状态码]
end
11.3 SSE 实时通信
| 场景 | 端点 | 头 |
|---|---|---|
| AI 对话流 | /api/ai/chat/stream |
Content-Type: text/event-streamCache-Control: no-cache, no-transformConnection: keep-alive |
| 通知推送 | /api/notifications/stream |
同上 |
| 错误格式 | - | { type: "error", message } |
| 结束信号 | - | [DONE] |
12. 架构约束与设计原则
12.1 硬性约束(违反即违规)
| 约束 | 检测方式 | 修复方式 |
|---|---|---|
| 严格三层架构,依赖单向 | npm run arch:query -- violations |
调整 import 路径 |
| modules 间不直查对方 DB 表 | arch:query violations | 改调对方 data-access |
| shared 不反向依赖 auth/proxy/modules/* | arch:query violations | 用 dynamic import 打破循环 |
Server Action 必须 requirePermission() |
arch:query violations | 补充权限校验 |
禁止 any/as(除 unknown→类型守卫) |
npx tsc --noEmit + ESLint |
用类型守卫替代 |
| 禁止硬编码颜色/字体/字号 | ESLint no-restricted-syntax |
用设计令牌 |
| 单文件 ≤ 1000 行 | arch:query violations | 按职责拆分 |
禁止 dangerouslySetInnerHTML(未清洗) |
Code review | 先 DOMPurify 清洗 |
12.2 性能原则
| 原则 | 实践 |
|---|---|
| 批量 SQL 优于循环 SQL | INSERT db.insert().values([...]);UPDATE sql.join + CASE WHEN |
| 递归删除批量收集 | 收集后代 ID + inArray 单次删除,禁止递归逐个 |
| 动态导入重 UI 库 | xxx-inner.tsx + xxx.tsx lazy wrapper(tiptap/@xyflow/react/AI SDK) |
| 请求级去重 | cacheFn 包装 data-access 函数 |
| 细粒度 Zustand selector | 单字段 selector 优于 useShallow |
| React 19 useOptimistic | 替代手动 isPending 状态 |
| FULLTEXT 索引 | 大表 MATCH AGAINST BOOLEAN MODE,小表 LIKE |
| 内存 filter 改 SQL | inArray(field, ids) 下推 |
| 统计走 SQL 聚合 | COUNT(*)/SUM(CASE WHEN) 替代拉全表循环 |
| chart props 模块级常量 | 12 个 recharts 图表提取 CHART_MARGIN |
12.3 安全原则
| 原则 | 实现 |
|---|---|
| JWT/session 存储 | httpOnly + Secure + SameSite=Strict Cookie |
| 环境变量校验 | @t3-oss/env-nextjs + Zod(src/env.mjs) |
| 服务端变量 | 不加 NEXT_PUBLIC_ 前缀 |
| 权限 3 道防线 | proxy.ts(路由) → requirePermission(Action) → usePermission(客户端) |
| 数据范围过滤 | DataScope 行级过滤(all/grade_managed/class_taught/children/owned) |
| parent 双重校验 | parentId + studentId 防跨家庭信息泄露 |
| 破坏性操作独立权限 | AUDIT_LOG_PURGE ≠ AUDIT_LOG_READ |
| 密码策略 | validatePassword() + isAccountLocked() + rateLimit() |
| 2FA | TOTP(自研) + 备用码 |
13. 架构决策记录
13.1 关键决策
| 决策 | 选项 | 选择 | 理由 |
|---|---|---|---|
| 全栈框架 | Next.js vs Remix vs 自研 | Next.js 16 | RSC + Server Actions + Edge middleware + 生态 |
| ORM | Drizzle vs Prisma vs TypeORM | Drizzle | 类型安全 + 轻量 + 迁移可控 + 无运行时开销 |
| 状态管理 | Redux vs Zustand vs Jotai | Zustand(5 层模型) | 模块化 slice + 细粒度 selector + 无样板 |
| 缓存策略 | unstable_cache vs cacheFn | cacheFn(请求级) | 权限数据易变,跨请求缓存风险高于收益 |
| 富文本 | Tiptap vs Slate vs Lexical | Tiptap | 扩展生态 + SSR 友好 + 节点树支持 |
| 知识图谱 | ReactFlow vs dagre vs 自定义 | 自定义 force-graph + graph-layout | K12 前置依赖需力导向 + 层级布局,通用库不满足 |
| 认证 | NextAuth vs Lucia vs 自研 | NextAuth 4 | JWT + Credentials + 2FA + 生态 |
| 中间件 | middleware.ts vs proxy.ts | proxy.ts (Next.js 16) | Edge Runtime + 重命名规范 |
| 模块通信 | 事件总线 vs 直接调用 vs data-access | data-access 调用 | 显式依赖 + 类型安全 + 易追踪 |
| 聚合层数据获取 | Promise.all vs Promise.allSettled | allSettled | 单模块失败不导致整页白屏 |
| 乐观更新 | 手动 isPending vs useOptimistic | useOptimistic (React 19) | 原生 + 自动回滚 + useTransition 配合 |
13.2 架构演进历程
| 阶段 | 时间 | 变更 |
|---|---|---|
| 初始架构 | 2026-06 | Next.js 16 + 三层架构 + 35 模块 |
| V2 状态重构 | 2026-07-05 | 5 层状态模型 + notify 替换 + ModalRoot 命令式 registry |
| V2 API 规范化 | 2026-07-04 | 响应信封 + 错误→状态码 + SSE 工具 + withApiErrorHandler |
| V2 设计令牌 | 2026-07-04 | 双层令牌(Primitive + Semantic) + ESLint 强制约束 |
| V2 权限重构 | 2026-07-07 | as 断言违规修复 + 11 个类型守卫 + BLOCK_DATA_GUARDS registry |
| V3 文档体系 | 2026-07-07 | arch.db 扫描器 + 004 瘦身 + 35 模块 README + known-issues 索引化 |
14. 附录
14.1 关键参数影响链
userId
graph LR
A[auth.ts<br/>JWT callback] -->|"查询 users 表"| B[token.userId]
B -->|"session.user.id"| C[Server/Client Components]
B -->|"getAuthContext().userId"| D[Server Actions]
D -->|"creatorId"| E[exams/homework 表写入]
D -->|"teacherId"| F[getTeacherClasses<br/>getGradeManagedClasses]
D -->|"resolveDataScope"| G[DataScope 计算]
permission
graph LR
A[Permissions 常量<br/>61 个权限点] --> B[ROLE_PERMISSIONS_SEED<br/>角色→权限种子]
B --> C[permission-bitmap.ts<br/>位图压缩]
C --> D[auth.ts JWT callback<br/>resolvePermissions 合并多角色]
D --> E[proxy.ts<br/>路由级 bitmap 检查]
D --> F[requirePermission<br/>Server Action 断言]
D --> G[usePermission<br/>.hasPermission 客户端]
D --> H[NavItem.permission<br/>侧边栏过滤]
DataScope
| 类型 | 适用角色 | 数据范围 |
|---|---|---|
all |
admin | 全部数据 |
grade_managed |
grade_head | 管辖年级的数据 |
class_taught |
teacher | 所教班级的数据 |
class_members |
student | 所在班级的数据 |
children |
parent | 关联孩子的数据 |
owned |
所有角色 | 自己创建的数据 |
14.2 arch.db 查询入口
# 全量扫描(修改源码后须重新执行)
npm run arch:scan
# 自定义 SQL 查询
npm run arch:query -- sql "<SQL>"
# 查询模块依赖
npm run arch:query -- module <module-name>
# 查询符号引用链
npm run arch:query -- ref <symbol-name>
# 查询技术使用
npm run arch:query -- tech <tag-name>
# 架构违规检测
npm run arch:query -- violations
14.3 相关文档
| 文档 | 用途 |
|---|---|
| 各模块 README.md | 模块级架构图与流程图(35 个) |
| coding-standards.md | 编码规范 |
| known-issues.md | 已知问题速查(场景→技术映射) |
| roadmap/README.md | 长远规划入口 |
| roadmap/tech-debt.md | 技术债清单 |
| roadmap/decoupling.md | 解耦路线图 |
| audit/README.md | 架构审查报告入口 |
| audit/archive/ | 历史审查报告归档(含 004 V1/V2、005 JSON) |