coord.md 新增 ARB-019/020/021 三章仲裁章节,修正 ARB-001。 - coord.md: 新增 ARB-019/020/021(student/parent/admin-portal 24 项) - coord.md: 修正 ARB-001(admin P2 预留/schema 文件名/classes 数据源) - 004 §4: 依赖图加 PBFF→DataAna+Msg - 004 §7.2: push-gateway→Redis 软失败标注 - 004 §11.4: 错误码前缀矩阵(11 服务+i18n key) - 004 §11.5: ActionState 信封规范(降级模式方案 B) - matrix §1: 依赖矩阵加 PBFF 边 - matrix §2: 移除 api-gateway 为 iam gRPC 消费方 - matrix §4: admin-portal→teacher-bff - matrix §5: 移除 /sse+鉴权头统一 - matrix §6: 错误码表补 i18n key 列 - 15 个 issue.md: 仲裁结论回写 - push-gateway_contract: 移除 /sse+鉴权头改 X-Internal-Token - packages/contracts: 新建包 ADMIN_* 权限点常量 AI: coord
41 KiB
架构影响地图(微服务版)
版本:1.0 日期:2026-07-07 状态:基线发布 适用范围:Edu 微服务架构(DDD + EDA + CQRS) 关联文档:
目录
1. 项目概述
1.1a 技术分层视角(系统边界)
本图展示部署分层结构(自上而下:用户 → 微前端 → 网关 → BFF → 业务服务 → 总线 → 数据)。 用户层按"使用场景域"标注,BFF 层按场景域分(不是按角色分)。业务领域视角见 1.1b。
graph TB
subgraph Users["用户层(场景域用户)"]
Teacher["教学场景域用户<br/>(教师 / 教导主任 / 教研组长 共用)"]
Student["学习场景域用户<br/>(学生)"]
Parent["家长场景域用户<br/>(家长)"]
Admin["管理场景域用户<br/>(系统管理员 / 校管理员)"]
end
subgraph MFE["微前端层(Module Federation)"]
TeacherPortal["teacher-portal<br/>教学场景域前端"]
StudentPortal["student-portal<br/>学习场景域前端"]
ParentPortal["parent-portal<br/>家长场景域前端"]
AdminPortal["admin-portal<br/>管理场景域前端"]
end
subgraph Gateway["网关层(Go)"]
APIGateway[api-gateway<br/>Gin + JWT + 限流]
PushGateway[push-gateway<br/>WebSocket/SSE]
end
subgraph BFF["BFF 聚合层(NestJS)<br/>按使用场景域分 BFF(不是按角色分)"]
TeacherBFF["teacher-bff<br/>教学场景域聚合"]
StudentBFF["student-bff<br/>学习场景域聚合"]
ParentBFF["parent-bff<br/>家长场景域聚合"]
end
subgraph Services["业务微服务(NestJS + FastAPI)"]
IAM[iam<br/>身份认证]
CoreEdu[core-edu<br/>教学核心]
Content[content<br/>内容资源]
DataAna[data-ana<br/>数据分析 Python]
Msg[msg<br/>消息通知]
AI[ai<br/>AI 网关 Python]
end
subgraph Bus["事件总线"]
Kafka[(Kafka)]
Debezium[Debezium CDC]
end
subgraph Data["数据层"]
MySQL[(MySQL<br/>每服务独占)]
Redis[(Redis<br/>缓存/会话)]
ClickHouse[(ClickHouse<br/>读模型宽表)]
Neo4j[(Neo4j<br/>知识图谱)]
ES[(Elasticsearch<br/>题库检索)]
end
Teacher --> TeacherPortal
Student --> StudentPortal
Parent --> ParentPortal
Admin --> AdminPortal
TeacherPortal --> APIGateway
StudentPortal --> APIGateway
ParentPortal --> APIGateway
AdminPortal --> APIGateway
TeacherPortal -.推送.-> PushGateway
StudentPortal -.推送.-> PushGateway
APIGateway --> TeacherBFF
APIGateway --> StudentBFF
APIGateway --> ParentBFF
TeacherBFF --> IAM
TeacherBFF --> CoreEdu
TeacherBFF --> Content
TeacherBFF --> DataAna
StudentBFF --> IAM
StudentBFF --> CoreEdu
ParentBFF --> IAM
ParentBFF --> CoreEdu
CoreEdu <--> Kafka
Content <--> Kafka
DataAna <--> Kafka
Msg <--> Kafka
IAM <--> Kafka
MySQL --> Debezium
Debezium --> Kafka
IAM --> MySQL
CoreEdu --> MySQL
Content --> Neo4j
DataAna --> ClickHouse
Msg --> MySQL
AI --> ES
IAM --> Redis
CoreEdu --> Redis
1.1b 业务领域视角
本图按 DDD 限界上下文展示 6 个业务领域及其依赖关系。同一服务可横跨多个领域(如 core-edu 同时承载"教学组织"与"教学核心")。 技术分层视角见 1.1a。
graph TB
subgraph D1["D1 身份认证领域"]
IAM[iam 服务]
IAM_M[users / roles / permissions<br/>refresh_tokens / sessions]
end
subgraph D2["D2 教学组织领域"]
ORG[core-edu 服务<br/>classes 模块]
ORG_M[classes / subjects / enrollment]
end
subgraph D3["D3 教学核心领域"]
TEACH[core-edu 服务<br/>exams/homework/grades]
TEACH_M[exams / homework / grades<br/>courses / lessons / schedule / attendance]
end
subgraph D4["D4 内容资源领域"]
CONTENT[content 服务]
CONTENT_M[textbooks / knowledge-points<br/>questions / grading / search]
end
subgraph D5["D5 沟通通知领域"]
MSG[msg 服务]
MSG_M[messaging / notifications / announcements]
end
subgraph D6["D6 智能洞察领域"]
DATA[data-ana 服务]
AI[ai 服务]
DATA_M[analytics / dashboard / diagnostic]
AI_M[AI 备课 / 出题 / 分析 / 搜索]
end
IAM --> ORG
IAM --> TEACH
IAM --> CONTENT
IAM --> MSG
ORG --> TEACH
TEACH --> CONTENT
TEACH --> MSG
CONTENT --> DATA
TEACH --> DATA
双图并存说明:
- 1.1a 技术分层:描述部署、流量路径、网络边界,关注"如何部署与调用"
- 1.1b 业务领域:描述 DDD 限界上下文、聚合根、领域依赖,关注"业务边界与归属"
- 两图互补,分别服务于运维/SRE 与产品/架构视角
1.2 服务清单
| 类别 | 服务名 | 语言/框架 | 限界上下文 | 业务领域 | 阶段 |
|---|---|---|---|---|---|
| 基础设施 | api-gateway | Go (Gin) | 网关 | — | P1 |
| 基础设施 | push-gateway | Go (Gin) | 推送 | — | P5 |
| BFF | teacher-bff | TS (NestJS) | 教师聚合 | 教学场景域 | P2 |
| BFF | student-bff | TS (NestJS) | 学生聚合 | 学习场景域 | P3 |
| BFF | parent-bff | TS (NestJS) | 家长聚合 | 家长场景域 | P4 |
| 业务 | iam | TS (NestJS) | 身份认证 | D1 身份认证 | P2 |
| 业务 | core-edu | TS (NestJS) | 教学核心 | D2 教学组织 + D3 教学核心 | P3 |
| 业务 | content | TS (NestJS) | 内容资源 | D4 内容资源 | P4 |
| 业务 | data-ana | Python (FastAPI) | 数据分析 | D6 智能洞察 | P4 |
| 业务 | msg | TS (NestJS) | 消息通知 | D5 沟通通知 | P5 |
| 业务 | ai | Python (FastAPI) | AI 网关 | D6 智能洞察 | P5 |
| 微前端 | teacher-portal | TS (Next.js) | 教师端 | 教学场景域 | P2 |
| 微前端 | student-portal | TS (Next.js) | 学生端 | 学习场景域 | P3 |
| 微前端 | parent-portal | TS (Next.js) | 家长端 | 家长场景域 | P4 |
| 微前端 | admin-portal | TS (Next.js) | 管理端 | 管理场景域 | P6 |
| 共享包 | shared-proto | TS | protobuf 契约 | — | P1 |
| 共享包 | shared-ts | TS | TS 共享工具 | — | P1 |
| 共享包 | shared-go | Go | Go 共享工具 | — | P1 |
| 共享包 | shared-py | Python | Python 共享工具 | — | P4 |
1.3 CICD → Edu 模块映射
| CICD 模块(旧) | Edu 服务(新) | 迁移阶段 |
|---|---|---|
| auth + users + rbac | iam | P2 |
| classes + subjects + enrollment | core-edu | P3 |
| courses + lessons + schedule + attendance | core-edu | P3 |
| assignments + grades + exams | core-edu | P3 |
| textbooks + knowledge-points | content | P4 |
| questions + grading | content | P4 |
| messaging + notifications | msg | P5 |
| analytics + dashboard + diagnostic | data-ana | P4 |
| ai + lesson-preparation | ai | P5 |
| search | content (ES) | P4 |
2. 技术栈
2.1 多语言技术栈矩阵
| 层级 | 语言 | 框架 | 用途 |
|---|---|---|---|
| 网关层 | Go 1.22+ | Gin | API Gateway、Push Gateway |
| 业务服务 | TypeScript 5.5+ | NestJS 10 | IAM、CoreEdu、Content、Msg |
| 分析/AI | Python 3.12+ | FastAPI | DataAna、AI 网关 |
| BFF | TypeScript 5.5+ | NestJS 10 + GraphQL | 教师/学生/家长聚合 |
| 微前端 | TypeScript 5.5+ | Next.js 15 + Module Federation | 4 端门户 |
| 契约 | protobuf | buf | 跨语言契约定义 |
2.2 多存储矩阵
| 存储 | 用途 | 使用服务 |
|---|---|---|
| MySQL 8 | 写模型主库(每服务独占) | IAM、CoreEdu、Content、Msg |
| Redis 7 | 缓存、会话、限流计数 | 全部服务 |
| ClickHouse | 读模型宽表、分析聚合 | DataAna、CoreEdu(读模型) |
| Neo4j | 知识图谱、前置依赖 | Content |
| Elasticsearch | 题库全文检索 | Content、AI |
2.3 基础设施矩阵
| 组件 | 用途 |
|---|---|
| Kafka | 事件总线,领域事件异步通信 |
| Debezium | CDC,MySQL Binlog → Kafka 实时同步 |
| Temporal | 工作流编排(考试生命周期、AI 编排) |
| OpenTelemetry | 分布式追踪 |
| Loki | 日志聚合 |
| Tempo | 分布式 Trace 存储 |
| Prometheus | 指标采集 |
| Grafana | 可观测性可视化 |
| Vault | 密钥管理(P6) |
3. 分层架构
3.1 六层架构
graph TB
subgraph L1["L1 客户端层"]
Browser[浏览器]
Mobile[移动端]
end
subgraph L2["L2 微前端层"]
MFE[Module Federation<br/>4 端门户]
end
subgraph L3["L3 网关层"]
GW[API Gateway Go<br/>路由+鉴权+限流+熔断]
Push[Push Gateway Go<br/>WebSocket/SSE]
end
subgraph L4["L4 BFF 聚合层"]
BFF[BFF NestJS<br/>聚合+裁剪+协议转换]
end
subgraph L5["L5 业务微服务层"]
SVC[6 业务服务<br/>DDD+CQRS]
end
subgraph L6["L6 数据与总线层"]
DB[(MySQL/CH/Neo4j/ES)]
KAFKA[(Kafka)]
TEMPORAL[Temporal]
end
Browser --> MFE
Mobile --> MFE
MFE --> GW
MFE -.推送.-> Push
GW --> BFF
BFF --> SVC
SVC --> DB
SVC <--> KAFKA
SVC --> TEMPORAL
3.2 依赖方向
L1 客户端 → L2 微前端 → L3 网关 → L4 BFF → L5 业务服务 → L6 数据/总线
严格规则:
- L3 网关层只做路由、鉴权、限流、熔断,不写业务逻辑
- L4 BFF 层只做聚合、裁剪、协议转换,不持有业务状态
- L5 业务服务之间通过 gRPC(同步)或 Kafka 事件(异步)通信,不直接访问对方数据库
- L6 数据层每个微服务独占自身数据库,禁止跨库联表
4. 服务依赖图
graph TB
subgraph Gateway["网关层"]
APIGW[api-gateway]
PushGW[push-gateway]
end
subgraph BFF["BFF 层"]
TBFF[teacher-bff]
SBFF[student-bff]
PBFF[parent-bff]
end
subgraph Services["业务服务"]
IAM[iam]
CoreEdu[core-edu]
Content[content]
DataAna[data-ana]
Msg[msg]
AI[ai]
end
subgraph Shared["共享包"]
Proto[shared-proto]
SharedTS[shared-ts]
end
APIGW --> TBFF
APIGW --> SBFF
APIGW --> PBFF
PushGW --> Msg
TBFF --> IAM
TBFF --> CoreEdu
TBFF --> Content
TBFF --> DataAna
TBFF --> AI
SBFF --> IAM
SBFF --> CoreEdu
SBFF --> Content
SBFF --> DataAna
PBFF --> IAM
PBFF --> CoreEdu
PBFF --> DataAna
PBFF --> Msg
CoreEdu -.事件.-> Content
CoreEdu -.事件.-> DataAna
CoreEdu -.事件.-> Msg
Content -.事件.-> DataAna
IAM -.事件.-> CoreEdu
IAM -.事件.-> Msg
AI -.gRPC.-> Content
AI -.gRPC.-> DataAna
IAM --> Proto
CoreEdu --> Proto
Content --> Proto
DataAna --> Proto
Msg --> Proto
AI --> Proto
IAM --> SharedTS
CoreEdu --> SharedTS
Content --> SharedTS
Msg --> SharedTS
4.1 服务间通信矩阵
| 调用方 → 被调用方 | 协议 | 场景 |
|---|---|---|
| api-gateway → BFF | gRPC | 请求路由 |
| BFF → 业务服务 | gRPC | 同步查询聚合 |
| CoreEdu → Content | Kafka 事件 | 教学内容变更通知 |
| CoreEdu → DataAna | Kafka 事件 | 学情数据投递 |
| CoreEdu → Msg | Kafka 事件 | 通知触发 |
| IAM → CoreEdu | Kafka 事件 | 用户变更同步 |
| AI → Content | gRPC | 题库查询 |
| AI → DataAna | gRPC | 学情数据查询 |
| push-gateway → Msg | gRPC | 推送通道建立 |
5. 认证与权限
5.1 JWT RS256 认证流程
sequenceDiagram
participant U as 用户
participant GW as API Gateway
participant IAM as IAM 服务
participant SVC as 业务服务
rect rgb(240, 248, 255)
Note over U,IAM: 阶段 1: 登录签发
U->>GW: POST /auth/login {username, password}
GW->>IAM: gRPC Login(username, password)
IAM->>IAM: 校验密码 + 查询权限
IAM->>IAM: 生成 JWT(RS256 私钥签发)
IAM-->>GW: {accessToken, refreshToken, userInfo}
GW-->>U: 200 {accessToken, refreshToken}
end
rect rgb(240, 255, 240)
Note over U,SVC: 阶段 2: 请求鉴权
U->>GW: GET /api/classes + Authorization: Bearer <token>
GW->>GW: RS256 公钥校验签名
GW->>GW: 校验 exp/iss/aud
GW->>GW: 提取 userId/role/dataScope
GW->>SVC: gRPC 请求 + 透传 userId/role/dataScope metadata
SVC->>SVC: 权限校验(requirePermission)
SVC-->>GW: 响应数据
GW-->>U: 200 响应
end
5.2 三层角色模型
| 层级 | 来源 | 示例 | 优先级 |
|---|---|---|---|
| 系统角色 | 系统预设 | admin、teacher、student、parent | 最高 |
| 组织角色 | 学校/班级分配 | 年级组长、班主任、学科组长 | 中 |
| 临时角色 | 临时授权 | 代课教师、临时代理 | 最低 |
规则:权限取三层角色权限的并集,拒绝权限取交集(任一层拒绝则拒绝)。
5.3 DataScope 6 级数据范围
| 级别 | 名称 | 数据范围 | 典型角色 |
|---|---|---|---|
| L0 | SELF | 仅本人数据 | 学生、家长 |
| L1 | CLASS | 本班数据 | 班主任、学生 |
| L2 | GRADE | 本年级数据 | 年级组长 |
| L3 | SCHOOL | 本校数据 | 校管理员 |
| L4 | DISTRICT | 本区数据 | 区教研员 |
| L5 | ALL | 全部数据 | 系统管理员 |
实现:业务服务在 Repository 层根据 dataScope 级别动态注入 WHERE 条件。
5.4 视口四层模型
视口(Viewport)是用户在特定场景域下的可见范围。视口既可独立配置(RoleViewport 表), 也可由权限推导(permission → viewport 默认映射)。新角色只需配置权限集,视口自动推导; 需要差异化时再显式配置视口。
| 层级 | 含义 | 配置载体 | 示例 |
|---|---|---|---|
| L1 导航 | 侧边栏菜单项 | navigation_config 表 | 教导主任看到"全校成绩分析"菜单 |
| L2 路由 | 可访问路由 | route_permission 表 + Gateway 校验 | 教导主任可访问 /admin/grade-analysis |
| L3 组件 | 页面内组件可见性 | usePermission().hasPermission() | 教导主任看到"导出全校报表"按钮 |
| L4 数据 | 数据行级过滤 | DataScope 枚举 | 教导主任 DataScope=grade_managed(管辖年级) |
场景域 BFF 复用策略:
按"使用场景域"分 BFF,而非按角色分。新角色复用现有 BFF,通过视口差异化。
| BFF | 场景域 | 复用角色 |
|---|---|---|
| Teacher BFF | 教学场景 | 教师、教导主任、教研组长 |
| Student BFF | 学习场景 | 学生 |
| Parent BFF | 家长场景 | 家长 |
| Admin BFF | 管理场景 | 系统管理员、校管理员 |
实现:教导主任归入 Teacher BFF + 额外管理视口(L1 导航增加管理菜单项,L4 数据范围扩大到年级)。
iam 服务职责:
- 认证:登录/登出/JWT/2FA
- RBAC:角色/权限/角色-权限映射 CRUD
- 视口配置:导航/路由/组件级视口配置 CRUD
- DataScope:数据范围解析(all/grade_managed/class_taught/children/owned + 自定义)
- 权限解析 API:getEffectivePermissions(userId) → {permissions, viewports, dataScope}
6. 数据访问与缓存
6.1 CQRS 读写分离
graph LR
subgraph Write["写路径"]
Cmd[Command 命令] --> App[Application Service]
App --> Domain[Domain 领域模型]
Domain --> Repo[Repository 写模型]
Repo --> mysql_w[(MySQL 主库)]
App --> Outbox[(Outbox 表<br/>同事务)]
end
subgraph Sync["同步链路"]
Outbox --> Relay[Relay Worker]
Relay --> kafka_sync[(Kafka)]
kafka_sync --> Proj[Projection]
Proj --> ch_sync[(ClickHouse 宽表)]
Proj --> redis_sync[(Redis 缓存)]
Proj --> es_sync[(ES 索引)]
end
subgraph Read["读路径"]
Query[Query 查询] --> ReadModel[Read Model]
ReadModel --> ch_read[(ClickHouse 宽表)]
ReadModel --> redis_read[(Redis 缓存)]
ReadModel --> es_read[(ES 索引)]
end
6.2 BFF 混合读策略
flowchart TD
Q[BFF 收到查询请求] --> C1{Redis 命中?}
C1 -- 是 --> R1[返回缓存]
C1 -- 否 --> C2{需要聚合多服务?}
C2 -- 否 --> C3[直接 gRPC 调用单一服务]
C3 --> C4[写入 Redis]
C4 --> R2[返回]
C2 -- 是 --> C5[并行 gRPC 调用多服务]
C5 --> C6[内存聚合裁剪]
C6 --> C7[写入 Redis 5-30s 短缓存]
C7 --> R3[返回聚合结果]
6.3 缓存策略矩阵
| 数据类型 | 存储 | TTL | 失效策略 |
|---|---|---|---|
| 用户会话 | Redis | 30 分钟 | 滑动过期 |
| 权限列表 | Redis | 5 分钟 | 事件驱动失效 |
| 班级/年级列表 | Redis | 5 分钟 | 事件驱动失效 |
| 教学资源详情 | Redis | 30 秒 | 短 TTL |
| BFF 聚合结果 | Redis | 5-30 秒 | 短 TTL |
| 学情宽表 | ClickHouse | 实时 | CDC 同步 |
| 题库检索 | ES | 实时 | CDC 同步 |
7. 事件驱动架构
7.1 Outbox + Kafka + CDC 全链路
graph LR
subgraph Service["业务服务"]
Cmd[Command 处理]
Domain[Domain 聚合]
Repo[Repository]
Outbox[(Outbox 表)]
end
subgraph MySQL["MySQL 主库"]
BizTable[(业务表)]
OutboxTable[(outbox 表)]
end
subgraph Relay["Relay Worker"]
Poll[轮询 outbox<br/>每 100ms]
Publish[发布到 Kafka]
Mark[标记 processed]
end
subgraph Bus["事件总线"]
kafka_bus[(Kafka topic)]
end
subgraph Consumers["消费者"]
Proj[Projection<br/>更新读模型]
OtherSvc[其他服务<br/>业务订阅]
end
Cmd --> Domain
Domain --> Repo
Repo --> BizTable
Repo --> OutboxTable
OutboxTable --> Poll
Poll --> Publish
Publish --> kafka_bus
kafka_bus --> Proj
kafka_bus --> OtherSvc
Proj --> read_stores[(ClickHouse / Redis / ES)]
7.2 事件 Topic 分类
| Topic 模式 | 示例 | 生产者 | 消费者 |
|---|---|---|---|
edu.identity.user.created |
用户创建 | IAM | CoreEdu、Msg |
edu.identity.user.updated |
用户更新 | IAM | CoreEdu、Msg |
edu.org.class.created |
班级创建 | CoreEdu | DataAna |
edu.teaching.assignment.submitted |
作业提交 | CoreEdu | DataAna、Msg |
edu.teaching.exam.published |
考试发布 | CoreEdu | Msg |
edu.teaching.grade.recorded |
成绩录入 | CoreEdu | DataAna、Msg |
edu.content.question.published |
题目发布 | Content | AI、ES |
edu.insight.mastery.updated |
掌握度更新 | DataAna | CoreEdu、Msg |
必需依赖软失败标注(ARB-015 §17.6,ISSUE-058 覆盖 ISSUE-055):
- push-gateway → Redis:软失败(Redis 故障仅告警 +
degraded: true+ /readyz 返 200,不返 503、不触发 Pod 重启)- 理由:Redis 故障时单实例仍能服务本地连接(仅跨实例广播失效);重启会丢失本地连接表,加剧雪崩风险
- ISSUE-055 必需依赖列表应排除 push-gateway → Redis
7.3 核心领域事件
| 事件 | 触发场景 | 消费者动作 |
|---|---|---|
| UserRegistered | 新用户注册 | CoreEdu 初始化默认班级关联;Msg 发送欢迎通知 |
| ExamPublished | 考试发布 | Msg 推送考试通知给学生;DataAna 创建考试分析骨架 |
| HomeworkSubmitted | 学生提交作业 | DataAna 记录提交行为;Msg 通知教师 |
| HomeworkGraded | 教师批改完成 | DataAna 更新掌握度;Msg 通知学生 |
| MasteryUpdated | 掌握度计算完成 | CoreEdu 推荐个性化练习;Msg 触发预警 |
| NotificationRequested | 通知请求 | Msg 投递通知到多渠道 |
8. 跨服务协作
8.1 同步 vs 异步决策
flowchart TD
Req[跨服务协作需求] --> C1{需要强一致性?}
C1 -- 是 --> C2{调用方需要立即结果?}
C2 -- 是 --> Sync[同步 gRPC 调用]
C2 -- 否 --> Saga[Saga 编排<br/>Temporal]
C1 -- 否 --> C3{需要事件最终一致?}
C3 -- 是 --> Async[异步 Kafka 事件]
C3 -- 否 --> C4{仅查询读取?}
C4 -- 是 --> Read[读模型冗余]
C4 -- 否 --> Sync
8.2 BFF 同步聚合
sequenceDiagram
participant U as 教师端
participant BFF as teacher-bff
participant IAM as iam
participant CoreEdu as core-edu
participant DataAna as data-ana
U->>BFF: 查询班级仪表盘
BFF->>BFF: 解析 token 获取 userId
par 并行查询
BFF->>IAM: gRPC GetUser(userId)
IAM-->>BFF: 用户信息
and
BFF->>CoreEdu: gRPC GetClassesByTeacher(userId)
CoreEdu-->>BFF: 班级列表
and
BFF->>DataAna: gRPC GetTeacherDashboardStats(userId)
DataAna-->>BFF: 统计数据
end
BFF->>BFF: 内存聚合裁剪
BFF-->>U: 仪表盘聚合数据
8.3 异步事件闭环
flowchart LR
A[教师录入成绩] --> B[CoreEdu 写入 MySQL + Outbox]
B --> C[Outbox Relay 发布事件]
C --> D[teaching.grade.recorded]
D --> E[DataAna 消费更新掌握度]
D --> F[Msg 消费通知学生]
E --> G[insight.mastery.updated]
G --> H[CoreEdu 消费推荐练习]
G --> I[Msg 消费掌握度预警]
8.4 Temporal 工作流编排
flowchart TD
Start[考试发布] --> A1[Activity: 创建考试实例]
A1 --> A2[Activity: 通知学生]
A2 --> A3[Activity: 等待作答窗口]
A3 --> A4[Activity: 收集提交]
A4 --> C1{是否全部提交?}
C1 -- 否 --> A5[Activity: 自动提交未答]
C1 -- 是 --> A6[Activity: 批改]
A5 --> A6
A6 --> A7[Activity: 发布成绩]
A7 --> A8[Activity: 生成分析]
A8 --> End[工作流完成]
9. 核心业务流程
9.1 考试生命周期
sequenceDiagram
participant T as 教师
participant BFF as teacher-bff
participant Core as core-edu
participant Msg as msg
participant DA as data-ana
participant S as 学生
rect rgb(240, 248, 255)
Note over T,Core: 阶段 1: 创建发布
T->>BFF: 创建考试
BFF->>Core: gRPC CreateExam
Core->>Core: 写入 MySQL + Outbox
Core-->>BFF: examId
BFF-->>T: 创建成功
Core->>Msg: ExamPublished 事件
Msg->>S: 推送考试通知
end
rect rgb(240, 255, 240)
Note over S,Core: 阶段 2: 作答提交
S->>BFF: 提交答卷
BFF->>Core: gRPC SubmitExam
Core->>Core: 写入答卷 + Outbox
Core-->>BFF: 提交成功
Core->>DA: ExamSubmitted 事件
DA->>DA: 记录提交行为
end
rect rgb(255, 240, 245)
Note over T,DA: 阶段 3: 批改出分
T->>BFF: 批改答卷
BFF->>Core: gRPC GradeExam
Core->>Core: 写入成绩 + Outbox
Core-->>BFF: 批改完成
Core->>DA: GradeRecorded 事件
DA->>DA: 更新掌握度
Core->>Msg: GradeRecorded 事件
Msg->>S: 推送成绩通知
end
9.2 高并发提交
flowchart TD
S[学生提交] --> GW[API Gateway]
GW --> GW1{限流检查}
GW1 -- 通过 --> BFF[BFF]
GW1 -- 拒绝 --> R1[429 限流响应]
BFF --> Core[CoreEdu]
Core --> C1{Redis 分布式锁}
C1 -- 获取锁 --> C2[写入答卷]
C2 --> C3[Outbox 事件]
C3 --> C4[释放锁]
C4 --> R2[成功]
C1 -- 锁竞争 --> C5[排队等待 500ms]
C5 --> C1
C5 --> C6{超时?}
C6 -- 是 --> R3[排队中,请稍后]
9.3 AI 辅助出题
sequenceDiagram
participant T as 教师
participant BFF as teacher-bff
participant AI as ai 网关
participant Content as content
participant DA as data-ana
T->>BFF: 请求 AI 出题
BFF->>AI: gRPC GenerateQuestions
AI->>Content: gRPC 查询知识点
Content-->>AI: 知识点列表
AI->>DA: gRPC 查询班级学情
DA-->>AI: 学情数据
AI->>AI: LLM 调用生成题目
AI-->>BFF: 生成的题目列表
BFF-->>T: 返回题目供教师审核
T->>BFF: 确认入库
BFF->>Content: gRPC CreateQuestions
Content-->>BFF: 入库成功
10. 可观测性
10.1 三支柱可观测性
graph TB
subgraph App["应用层"]
SVC[业务服务]
GW[网关]
BFF[BFF]
end
subgraph Collect["采集层"]
OTEL[OpenTelemetry SDK]
PROM[Prometheus Exporter]
end
subgraph Storage["存储层"]
LOKI[(Loki<br/>日志)]
TEMPO[(Tempo<br/>Trace)]
PROMDB[(Prometheus<br/>指标)]
end
subgraph Vis["可视化层"]
GRAFANA[Grafana<br/>统一面板]
ALERT[AlertManager<br/>告警]
end
SVC --> OTEL
GW --> OTEL
BFF --> OTEL
OTEL --> LOKI
OTEL --> TEMPO
SVC --> PROM
GW --> PROM
PROM --> PROMDB
LOKI --> GRAFANA
TEMPO --> GRAFANA
PROMDB --> GRAFANA
PROMDB --> ALERT
10.2 Trace 上下文传播
flowchart LR
A[客户端请求] --> B[API Gateway<br/>生成 traceId]
B --> C[BFF<br/>继承 traceId]
C --> D[业务服务<br/>继承 traceId]
D --> E[Kafka 事件<br/>traceId 写入 header]
E --> F[消费者服务<br/>继承 traceId]
F --> G[数据存储<br/>span 记录]
规则:
- 所有跨服务调用必须透传 W3C Trace Context
- Kafka 事件必须将 traceId 写入消息 header
- 日志必须包含 traceId 用于关联查询
- 关键业务操作必须创建 span(创建考试、提交作业、批改等)
11. 契约与 API 架构
11.1 Protobuf 契约体系
graph TB
subgraph Proto["protobuf 契约仓库"]
Identity[identity/v1<br/>user/role/permission]
Org[org/v1<br/>class/subject/enrollment]
Teaching[teaching/v1<br/>course/assignment/exam]
Content[content/v1<br/>textbook/question]
Comm[comm/v1<br/>message/notification]
Insight[insight/v1<br/>report/mastery]
end
subgraph Gen["代码生成"]
Buf[buf generate]
TS[TS 生成代码<br/>@bufbuild/protobuf]
Go[Go 生成代码<br/>protobuf-go]
Py[Python 生成代码<br/>betterproto]
end
subgraph Services["消费服务"]
SvcTS[NestJS 服务]
SvcGo[Go 网关]
SvcPy[Python 服务]
end
Identity --> Buf
Org --> Buf
Teaching --> Buf
Content --> Buf
Comm --> Buf
Insight --> Buf
Buf --> TS
Buf --> Go
Buf --> Py
TS --> SvcTS
Go --> SvcGo
Py --> SvcPy
11.2 契约规则
| 规则 | 说明 |
|---|---|
| 包命名 | edu.[context].[aggregate].v[version] |
| 版本化 | 破坏性变更必须升版本(v1 → v2) |
| 字段编号 | 禁止复用已删除字段编号,使用 reserved |
| 消息命名 | PascalCase |
| 字段命名 | snake_case |
| 注释 | 每个 message 和字段必须注释 |
| CI 强制 | buf lint + buf breaking 必须通过 |
11.3 BFF 聚合模式
graph LR
subgraph Client["客户端"]
Q[GraphQL 查询]
end
subgraph BFF["BFF 层"]
Resolver[GraphQL Resolver]
DataLoader[DataLoader 批量去重]
Cache[Redis 短缓存]
end
subgraph Services["业务服务"]
S1[服务 A]
S2[服务 B]
S3[服务 C]
end
Q --> Resolver
Resolver --> Cache
Cache --> DataLoader
DataLoader --> S1
DataLoader --> S2
DataLoader --> S3
11.4 错误码前缀矩阵
来源:coord 仲裁 ARB-017 §19.6 / ARB-014 / ARB-015 / ARB-018 / G14 / F4 规则:服务名大写 + 下划线分隔,禁止子前缀(如 EXAMS_/HOMEWORK_/GRADES_ 已移除)
| 层级 | 服务 | 前缀 | 示例 | 仲裁依据 |
|---|---|---|---|---|
| 网关 | api-gateway | GW_ |
GW_UNAUTHORIZED / GW_RATE_LIMITED | ARB-014 |
| 网关 | push-gateway | PUSH_ |
PUSH_DEVICE_NOT_FOUND / PUSH_CHANNEL_FAILED | ARB-015 |
| BFF | teacher-bff | BFF_TEACHER_ |
BFF_TEACHER_UNAUTHORIZED / BFF_TEACHER_BAD_GATEWAY | ARB-016 |
| BFF | student-bff | BFF_STUDENT_ |
BFF_STUDENT_UNAUTHORIZED / BFF_STUDENT_VALIDATION_ERROR | ARB-017 |
| BFF | parent-bff | BFF_PARENT_ |
BFF_PARENT_CHILD_NOT_BOUND / BFF_PARENT_BAD_GATEWAY | ARB-018 |
| 业务 | iam | IAM_ |
IAM_USER_NOT_FOUND / IAM_INVALID_CREDENTIALS | ARB-003 |
| 业务 | core-edu | CORE_EDU_ |
CORE_EDU_EXAM_NOT_FOUND / CORE_EDU_GRADE_NOT_FOUND | ARB-004 |
| 业务 | content | CONTENT_ |
CONTENT_QUESTION_NOT_FOUND / CONTENT_TEXTBOOK_NOT_FOUND | ARB-005 |
| 业务 | msg | MSG_ |
MSG_NOTIFICATION_NOT_FOUND / MSG_TEMPLATE_NOT_FOUND | ARB-008 |
| 业务 | data-ana | DATA_ANA_ |
DATA_ANA_DASHBOARD_UNAVAILABLE / DATA_ANA_ANALYTICS_NOT_READY | ARB-009 |
| 业务 | ai | AI_ |
AI_GENERATION_FAILED / AI_QUOTA_EXCEEDED | ARB-010 |
前端 i18n key 映射规则(F4 裁决):
| 服务 | i18n key 模式 | 示例 |
|---|---|---|
| api-gateway | error.gateway.<code_snake> |
error.gateway.unauthorized |
| push-gateway | error.push.<code_snake> |
error.push.device_not_found |
| teacher-bff | error.bffTeacher.<code_snake> |
error.bffTeacher.unauthorized |
| student-bff | error.bffStudent.<code_snake> |
error.bffStudent.validation_error |
| parent-bff | error.bffParent.<code_snake> |
error.bffParent.child_not_bound |
| iam | error.iam.<code_snake> |
error.iam.user_not_found |
| core-edu | error.core_edu.<code_snake> |
error.core_edu.exam_not_found |
| content | error.content.<code_snake> |
error.content.question_not_found |
| msg | error.msg.<code_snake> |
error.msg.notification_not_found |
| data-ana | error.data_ana.<code_snake> |
error.data_ana.dashboard_unavailable |
| ai | error.ai.<code_snake> |
error.ai.generation_failed |
11.5 ActionState 信封规范
来源:coord 仲裁 ARB-017 §19.6 适用范围:所有 BFF / Gateway HTTP 响应、GraphQL response 的 errors 扩展字段 设计原则:统一错误信封 + 降级模式方案 B(degraded 放 error.details 子字段)
信封结构:
interface ActionState<T = unknown> {
success: boolean; // 整体成功/失败
data: T | null; // 成功时返回数据,失败时为 null
error: {
code: string; // 错误码(见 §11.4 前缀矩阵)
message: string; // 人类可读错误消息(i18n key 解析后)
details?: Record<string, unknown>; // 附加详情(含 degraded 字段)
traceId: string; // 链路追踪 ID(X-Request-Id)
} | null; // 成功时为 null
}
降级模式(方案 B):
success = true(整体成功)data内含extensions.degraded: true子字段error仍为 null(非错误)- 用于部分聚合失败场景(如 teacher-bff 聚合 iam 成功但 core-edu 失败)
示例 1:完全成功
{
"success": true,
"data": { "userId": "u001", "name": "张老师" },
"error": null
}
示例 2:完全失败
{
"success": false,
"data": null,
"error": {
"code": "BFF_TEACHER_BAD_GATEWAY",
"message": "下游服务不可用",
"details": { "downstream": "core-edu", "reason": "timeout" },
"traceId": "req-abc123"
}
}
示例 3:降级模式(部分聚合成功)
{
"success": true,
"data": {
"user": { "userId": "u001", "name": "张老师" },
"classes": null,
"extensions": { "degraded": true, "failedServices": ["core-edu"] }
},
"error": null
}
GraphQL errors 数组扩展:
GraphQL 响应中,错误通过 errors[].extensions 携带 ActionState 字段:
{
"errors": [
{
"message": "下游服务不可用",
"extensions": {
"code": "BFF_TEACHER_BAD_GATEWAY",
"details": { "downstream": "core-edu" },
"traceId": "req-abc123"
}
}
]
}
12. 架构约束
12.1 服务独立性约束
| 约束 | 说明 |
|---|---|
| 数据库独占 | 每个微服务独占自身数据库,禁止跨库联表 |
| 契约先行 | 所有跨服务通信必须先定义 protobuf 契约 |
| Outbox 强制 | 所有领域事件必须通过 Outbox 模式发布 |
| 幂等消费 | 所有事件消费者必须实现幂等性 |
| 单一职责 | 每个服务只负责一个限界上下文 |
| 无状态服务 | 业务服务不持有会话状态(Redis 承载) |
12.2 通信约束
| 场景 | 允许 | 禁止 |
|---|---|---|
| 客户端 → Gateway | REST + WebSocket | 直连业务服务 |
| Gateway → BFF | gRPC | REST |
| BFF → 业务服务 | gRPC | 直接访问 DB |
| 业务服务之间(同步) | gRPC + 必要时 | REST、直接 DB |
| 业务服务之间(异步) | Kafka 事件 | 直接 producer 调用 |
| 事件发布 | Outbox 模式 | 直接 Kafka producer |
12.3 数据一致性约束
| 场景 | 一致性级别 | 实现 |
|---|---|---|
| 聚合内 | 强一致 | 单事务 |
| 聚合间 | 最终一致 | Kafka 事件 |
| 服务间 | 最终一致 | Kafka 事件 / Saga |
| 读模型 | 最终一致 | Projection 异步更新 |
| 缓存 | 最终一致 | 事件驱动失效 + 短 TTL |
13. ADR 记录
| 编号 | 决策 | 原因 | 状态 |
|---|---|---|---|
| ADR-001 | 采用 DDD 限界上下文划分服务 | 业务边界清晰,独立演进 | 已采纳 |
| ADR-002 | 采用 NestJS 作为业务服务框架 | TS 生态成熟,装饰器 + DI 适合 DDD | 已采纳 |
| ADR-003 | 采用 Go 作为网关语言 | 高并发、低内存、适合网关场景 | 已采纳 |
| ADR-004 | 采用 Python 作为分析/AI 语言 | 数据科学/AI 生态丰富 | 已采纳 |
| ADR-005 | 采用 CQRS 读写分离 | 读多写少,读模型可独立优化 | 已采纳 |
| ADR-006 | 采用 Outbox 模式发布事件 | 保证事务与事件最终一致 | 已采纳 |
| ADR-007 | 采用 Kafka 作为事件总线 | 高吞吐、持久化、成熟生态 | 已采纳 |
| ADR-008 | 采用 Debezium CDC | 解耦 Outbox Relay,减少业务侵入 | 已采纳 |
| ADR-009 | 采用 protobuf + buf 契约先行 | 多语言契约统一、版本化、CI 强制 | 已采纳 |
| ADR-010 | 采用 JWT RS256 非对称签名 | 网关公钥校验无需共享私钥 | 已采纳 |
| ADR-011 | 采用 DataScope 6 级数据范围 | 满足 K12 多层级数据隔离 | 已采纳 |
| ADR-012 | 采用 Module Federation 微前端 | 独立部署、技术栈无关、渐进迁移 | 已采纳 |
| ADR-013 | 采用 Temporal 工作流编排 | 长流程编排、可观测、可回滚 | 已采纳 |
| ADR-014 | 采用 ClickHouse 读模型宽表 | 分析查询亚秒级响应 | 已采纳 |
14. 附录:6 阶段路线图
14.1 阶段总览
graph LR
P1[P1 地基<br/>M1-M3] --> P2[P2 身份<br/>M4-M6]
P2 --> P3[P3 核心教学<br/>M7-M10]
P3 --> P4[P4 内容分析<br/>M11-M13]
P4 --> P5[P5 沟通AI<br/>M14-M16]
P5 --> P6[P6 硬化<br/>M17-M18]
14.2 服务与阶段映射
| 阶段 | 周期 | 交付服务 | 退出标准 |
|---|---|---|---|
| P1 地基 | M1-M3 | api-gateway、classes(黄金模板)、shared-proto | classes 域 CRUD 端到端跑通 |
| P2 身份 | M4-M6 | iam、teacher-bff、teacher-portal 骨架 | 教师可登录并看到空白 Dashboard |
| P3 核心教学 | M7-M10 | core-edu(合并 classes)、student-bff、student-portal | 考试→作答→批改→成绩全链路 |
| P4 内容分析 | M11-M13 | content、data-ana、parent-bff、parent-portal | 知识图谱查询 + 学情宽表 5s |
| P5 沟通AI | M14-M16 | msg、push-gateway、ai | 全校广播 + AI 辅助出题 |
| P6 硬化 | M17-M18 | admin-portal、Service Mesh | 99.9% 可用性 + 独立扩缩容 |
详细规划见 路线图目录