Files
Edu/docs/architecture/004_architecture_impact_map.md
SpecialX f7e52b5b7f docs(portal-shell): update README to v1.1 with data layer and GraphQL hardening
- 版本 1.0 -> 1.1,日期 2026-07-17
- 新增 §13 数据访问层与 GraphQL 安全栈(6 子节)
- 更新 §5/§9.6/§10/§11/§12/附录 A/B/C
- 修正 004 §16.5 测试数(admin 31->4,sidebar 5->9)
- arch:scan 通过(TS 20 模块/4803 符号)
2026-07-17 13:47:40 +08:00

144 KiB
Raw Blame History

架构影响地图(微服务版)

版本2.1 日期2026-07-15 状态实施状态同步v2.1 架构重设计已落地M0-M10 全部完成) 适用范围Edu 微服务架构DDD + EDA + CQRS + Apollo Federation 关联文档:

v2.1 变更摘要2026-07-15完成 v2 架构重设计的全部阶段M0-M10。核心变更(1) Apollo Federation 联邦3 个手写 BFFteacher-bff / student-bff / parent-bff下线由 apollo-router :3000 替代7 个业务服务暴露 GraphQL 子图(@key/@requires/@extends 指令);(2) DataScope @requires + ScopeTokeniam 计算 visibleClassIds 存入 Redis Set生成短 token 经 @requires 传递core-edu 接收 token 后从 Redis sMembers 获取实际 ID 列表ADR-024/041(3) Temporal 严格边界:仅限 AI 耗时工作流(生成大纲→知识点→题目→组装试卷)+ Saga 分布式事务补偿(购买插件:扣积分+授权CRUD 短事务绝对禁止ADR-030(4) SSE 优先 + Redis Pub/Sub 背板push-gateway 改名 realtime-gatewaySSE 默认推送WS 仅监考msg worker 消费 Kafka → 发 Redis Pub/Sub → realtime-gateway 订阅推送,边缘网关不直接挂 KafkaADR-029/040(5) CDC + Outbox 结合:废弃 OutboxPublisher 轮询线程,改由 Debezium 监听 binlog 自动投递 Outbox 表到 KafkaTransaction Log TailingADR-032(6) portal-shell 单容器4 个旧 portalteacher-portal / student-portal / parent-portal / admin-portal下线由 apps/portal-shell :4010 替代Modular Monolith + Micro-kernel + RSC 预取 + Zustand + SWR(7) iam 拆分 config-service:插件配置 + 布局 + 用户偏好从 iam 拆出为 config-service :3011/50059ADR-026(8) content CQRS 改造MySQL 写 + Neo4j/ES 读投影 + Eager Invalidation + 乐观锁版本号ADR-027/038/039(9) ai 无状态化WorkflowStateStore 改为 Redis-onlyTTL 1hADR-028(10) 外部 GraphQL + 内部 gRPC 边界:前端必须经 Apollo Router 调子图,后端互调走 gRPCADR-037(11) Router-Authorization 信任凭证Apollo Router 向子图请求注入 router-authorization header子图 RouterAuthGuard 校验ADR-036(12) DataLoader 强制:所有子图 @key 解析器必须用 DataLoader 批量加载ADR-035

v2.0 变更摘要2026-07-14基于代码现状截至 2026-07-14全面校准服务清单、模块边界、依赖关系、Kafka topic、可观测性栈、BFF 实现细节;新增 §15 实施状态索引。

arch.db 二次校验2026-07-14:运行 pnpm run arch:scan 重建 arch.db22 模块 / 4715 符号 / 475 契约),据此修正 §11.1 proto 统计8 文件 / 23 service / 305 message / 139 RPC、§11.2 proto 包名(next_edu_cloud.<domain>.v1、§15.4 core-edu gRPC9 service / 43 RPC、§15.6 msg gRPC17 RPC、§15.7 data-ana gRPC18 RPC、§15.10 student-bff 模块8、§15.11 parent-bff 模块6


目录

  1. 项目概述
  2. 技术栈
  3. 分层架构
  4. 服务依赖图
  5. 认证与权限
  6. 数据访问与缓存
  7. 事件驱动架构
  8. 跨服务协作
  9. 核心业务流程
  10. 可观测性
  11. 契约与 API 架构
  12. 架构约束
  13. ADR 记录
  14. 附录6 阶段路线图
  15. 实施状态索引v2.0 新增)
  16. v2.1 实施状态索引v2.1 新增)

1. 项目概述

1.1a 技术分层视角(系统边界)

本图展示部署分层结构(自上而下:用户 → 微前端 → 网关 → Apollo Router → 业务服务 → 总线 → 数据)。 v2.1 关键变化4 个 MF portal 由 portal-shell 单容器替代3 个 BFF 由 apollo-router 联邦替代push-gateway 改名 realtime-gatewaySSE 优先);新增 config-service新增 TemporalOutbox 轮询废弃,改由 Debezium 监听 binlog。 业务领域视角见 1.1b。端口标注见 infra/port-allocation.md(唯一源)。

graph TB
    subgraph Users["用户层(场景域用户)"]
        Teacher["教学场景域用户<br/>(教师 / 教导主任 / 教研组长 共用)"]
        Student["学习场景域用户<br/>(学生)"]
        Parent["家长场景域用户<br/>(家长)"]
        Admin["管理场景域用户<br/>(系统管理员 / 校管理员)"]
    end

    subgraph MFE["微前端层v2.1:单容器 Modular Monolith<br/>Next.js 15 App Router + RSC + Zustand + SWR"]
        PortalShell["portal-shell :4010<br/>Shell 宿主 + 插件化仪表盘<br/>替代 4 个旧 portal"]
    end

    subgraph Gateway["边缘网关层Go 1.25 / Gin"]
        APIGateway["api-gateway :8080<br/>JWT RS256 + JWKS + 限流 + 熔断"]
        RealtimeGw["realtime-gateway :8081<br/>SSE 优先 + WS 仅监考<br/>Redis Pub/Sub 订阅(不直挂 Kafka"]
    end

    subgraph Federation["GraphQL 联邦层v2.1 新增)<br/>Apollo RouterRust 官方二进制)"]
        ApolloRouter["apollo-router :3000<br/>自动查询计划 + @requires DataScope<br/>替代 3 个手写 BFF"]
    end

    subgraph Services["业务微服务NestJS + FastAPI全部暴露 GraphQL 子图)"]
        IAM["iam :3002 / gRPC :50052<br/>认证 + RBAC + JWKS + DataScope拆分后"]
        ConfigSvc["config-service :3011 / gRPC :50059<br/>插件配置 + 布局 + 用户偏好v2.1 新增)"]
        CoreEdu["core-edu :3004 / gRPC :50053<br/>教学核心 + 组织(含 DataScope @requires"]
        Content["content :3005 / gRPC :50054<br/>CQRSMySQL 写 + Neo4j/ES 读投影"]
        DataAna["data-ana :3006 / gRPC :50055<br/>FastAPI + CDC consumer + ClickHouse"]
        Msg["msg :3007 / gRPC :50056<br/>4 模块 + 4 渠道 + msg workerRedis Pub/Sub"]
        AI["ai :3008 / gRPC :50058<br/>FastAPI 无状态化 + Temporal Worker"]
    end

    subgraph Workflow["工作流层v2.1 新增)"]
        Temporal["Temporal Server :7233<br/>AI 工作流 + Saga严格边界<br/>PostgreSQL 持久化 + UI :8085"]
    end

    subgraph Bus["事件总线"]
        Kafka[("Kafka<br/>双 listener 29092/9092")]
        Debezium["Debezium Connect 2.7<br/>监听 outbox 表 binlog → Kafka<br/>Transaction Log Tailing"]
    end

    subgraph Data["数据层"]
        MySQL[("MySQL 8.0<br/>每服务独占 schema")]
        Redis[("Redis 7<br/>缓存/会话/Pub/Sub/ScopeToken/ai workflow")]
        ClickHouse[("ClickHouse 24.3<br/>读模型宽表")]
        Neo4j[("Neo4j 5.20<br/>知识图谱content 读投影)")]
        ES[("Elasticsearch 8.13<br/>题库检索content 读投影)")]
        TemporalPG[("PostgreSQL<br/>Temporal 持久化")]
    end

    Teacher --> PortalShell
    Student --> PortalShell
    Parent --> PortalShell
    Admin --> PortalShell

    PortalShell --> APIGateway
    PortalShell -.SSE 推送.-> RealtimeGw

    APIGateway --> ApolloRouter
    APIGateway -.REST 透传.-> IAM
    APIGateway -.REST 透传.-> Msg
    RealtimeGw -.Redis Pub/Sub 订阅 user:userId:notify.-> Msg

    ApolloRouter -->|子图查询| IAM
    ApolloRouter -->|子图查询| ConfigSvc
    ApolloRouter -->|子图查询| CoreEdu
    ApolloRouter -->|子图查询| Content
    ApolloRouter -->|子图查询| Msg
    ApolloRouter -->|子图查询| DataAna
    ApolloRouter -->|子图查询| AI
    ApolloRouter -.@requires ScopeToken.-> IAM
    CoreEdu -.接收 scopeToken sMembers.-> CoreEdu

    AI -.Temporal Worker.-> Temporal

    CoreEdu <--> Kafka
    Content <--> Kafka
    DataAna <--> Kafka
    Msg <--> Kafka
    IAM <--> Kafka
    AI <--> Kafka
    ConfigSvc <--> Kafka

    MySQL --> Debezium
    Debezium --> Kafka

    IAM --> MySQL
    ConfigSvc --> MySQL
    CoreEdu --> MySQL
    Content --> MySQL
    Msg --> MySQL
    Content -.Kafka 投影器.-> Neo4j
    Content -.Kafka 投影器.-> ES
    DataAna --> ClickHouse
    Temporal --> TemporalPG

    IAM --> Redis
    CoreEdu --> Redis
    ConfigSvc --> Redis
    Content --> Redis
    Msg --> Redis
    DataAna --> Redis
    AI --> Redis
    RealtimeGw --> Redis
    PortalShell -.SWR 客户端轮询.-> ConfigSvc

1.1b 业务领域视角

本图按 DDD 限界上下文展示 7 个业务领域及其依赖关系v2.1 新增 D7 配置域,从 iam 拆出)。同一服务可横跨多个领域(如 core-edu 同时承载"教学组织"与"教学核心")。 技术分层视角见 1.1a

graph TB
    subgraph D1["D1 身份认证领域iam 服务v2.1 拆分后)"]
        IAM[iam 服务]
        IAM_M["认证 + RBAC + JWKS + 审计 + DataScope<br/>users / roles / permissions / refresh_tokens / sessions<br/>totp / audit_logs / user_school_role<br/>子图暴露 visibleClassIds / visibleStudentIds@requires"]
    end

    subgraph D7["D7 配置域config-service 服务v2.1 新增)"]
        CONFIG[config-service 服务]
        CONFIG_M["插件配置 + 布局 + 用户偏好<br/>plugin_registry / role_plugin_mapping / role_layout_default<br/>layout_templates / user_layout_override / plugin_packages<br/>三层合并:系统默认 < 角色默认 < 用户调整"]
    end

    subgraph D2["D2 教学组织领域core-edu 服务)"]
        ORG["core-edu 服务<br/>classes + scheduling + leave-requests"]
        ORG_M["classes / teacher-associations / subjects<br/>schedule / leave-requests"]
    end

    subgraph D3["D3 教学核心领域core-edu 服务)"]
        TEACH["core-edu 服务<br/>exams + homework + grades + attendance"]
        TEACH_M["exams / exam-extensions / homework / grades<br/>attendance / dashboard / admin / iam-consumer<br/>(含 state machine + datascope-injector + @requires ScopeToken"]
    end

    subgraph D4["D4 内容资源领域content 服务v2.1 CQRS 改造)"]
        CONTENT["content 服务"]
        CONTENT_M["7 模块: textbooks / chapters / knowledge-points<br/>questions / electives / lesson-plans / course-plans<br/>MySQL 写 + Outbox → Kafka 投影器 → Neo4j/ES 读<br/>Eager Invalidation + 乐观锁版本号"]
    end

    subgraph D5["D5 沟通通知领域msg 服务)"]
        MSG["msg 服务"]
        MSG_M["4 模块: notifications / preferences / templates / announcements<br/>4 渠道: email / sms / push / in-app<br/>msg worker 消费 Kafka → Redis Pub/Sub → realtime-gateway<br/>v2.1:不再让边缘网关直挂 Kafka"]
    end

    subgraph D6["D6 智能洞察领域data-ana + ai 服务v2.1 ai 无状态化)"]
        DATA["data-ana 服务"]
        AI["ai 服务"]
        DATA_M["FastAPI HTTP /analytics + gRPC 50055<br/>CDC consumer + ClickHouse 宽表<br/>analytics / dashboard / diagnostic / warnings / mastery"]
        AI_M["FastAPI HTTP /v1/ai + gRPC 50058<br/>无状态 LLM 推理引擎WorkflowStateStore → Redis TTL 1h<br/>Temporal WorkerAI 耗时工作流 + Saga<br/>chat / question / expression / lesson-plan / report"]
    end

    IAM --> ORG
    IAM --> TEACH
    IAM --> CONTENT
    IAM --> MSG
    IAM -.DataScope @requires.-> TEACH
    CONFIG -.插件配置.-> IAM
    ORG --> TEACH
    TEACH --> CONTENT
    TEACH --> MSG
    CONTENT --> DATA
    TEACH --> DATA
    AI -.gRPC.-> CONTENT
    AI -.gRPC.-> DATA
    AI -.gRPC.-> IAM
    AI -.Temporal Worker.-> D6

双图并存说明

  • 1.1a 技术分层:描述部署、流量路径、网络边界,关注"如何部署与调用"
  • 1.1b 业务领域:描述 DDD 限界上下文、聚合根、领域依赖,关注"业务边界与归属"
  • 两图互补,分别服务于运维/SRE 与产品/架构视角

1.2 服务清单

端口、阶段、实施状态基于 v2.1 代码现状2026-07-15 = 已落地,🚧 = 部分落地, = 规划中, = v2.1 已下线。 v2.1 变更:(1) 新增 apollo-router / config-service / portal-shell(2) push-gateway 改名 realtime-gateway(3) 下线 3 个 BFF + 4 个旧 portal。

类别 服务名 语言/框架 HTTP 端口 gRPC 端口 GraphQL 限界上下文 业务领域 阶段 状态 v2.1 变更说明
边缘 api-gateway Go 1.25 (Gin) 8080 网关(路由+鉴权+限流+熔断+CORS P1 移除 BFF 代理路由,新增 apollo-router
边缘 realtime-gateway Go 1.25 (Gin) 8081 推送SSE 优先 + WS 仅监考 + Redis Pub/Sub 订阅) P5/M7 改名(原 push-gatewaySSE 优先
联邦 apollo-router Rust (Apollo Router 官方二进制) 3000 Super GraphQL 联邦(自动查询计划 + @requires DataScope v2.1/M2 新增,替代 3 个 BFF 聚合
业务 iam TS (NestJS) 3002 50052 子图 身份认证 + RBAC + JWKS + DataScope拆分后 D1 身份认证 P2/M3 插件配置移出到 config-service
业务 config-service TS (NestJS) 3011 50059 子图 插件配置 + 布局 + 用户偏好 D7 配置域 v2.1/M3 新增,从 iam 拆出ADR-026
业务 core-edu TS (NestJS) 3004 50053 子图 教学核心(含原 classes+ DataScope @requires D2 教学组织 + D3 教学核心 P3 接收 ScopeToken sMembers
业务 content TS (NestJS) 3005 50054 子图 内容资源CQRSMySQL 写 + Neo4j/ES 读投影) D4 内容资源 P4/M5 CQRS 改造 + Eager Invalidation + 乐观锁
业务 msg TS (NestJS) 3007 50056 子图 消息通知(含 msg worker → Redis Pub/Sub 背板) D5 沟通通知 P5/M7 新增 msg worker
业务 data-ana Python (FastAPI) 3006 50055 子图 数据分析 D6 智能洞察 P4 保留
业务 ai Python (FastAPI) 3008 50058 子图 AI 网关(无状态化 + Temporal Worker D6 智能洞察 P5/M6 WorkflowStateStore → Redis-only
工作流 temporal Temporal 1.23 7233 AI 工作流 + Saga严格边界CRUD 短事务禁用) v2.1/M6.5 新增ADR-030UI :8085PG :5433
微前端 portal-shell TS (Next.js 15 App Router) 4010 教师端 ShellModular Monolith + Micro-kernel 教学场景域 v2.1/M8 新增,替代 4 个旧 portal
共享包 shared-proto protobuf + buf v2 跨语言契约8 proto 文件 + GraphQL 生成器) P1 M0 新增 proto → GraphQL 生成器
共享包 shared-ts TS TS 共享outbox / dataloader / RouterAuthGuard P1 M1 新增 dataloader/guards
共享包 shared-go Go Go 共享env / jwks / logger / tracer P1 保留
共享包 shared-py Python Python 共享 P4 🚧 保留
共享包 contracts TS 跨端权限点常量 P3 🚧 保留
共享包 hooks TS (React) React Hooksauth / permission / viewports / graphql / trace / a11y P2 保留
共享包 ui-components TS (shadcn 风格) UI 组件库data-table / form / modal / chart / filter-bar / status-badge P2 新增 PluginCard / PluginSkeleton 等
共享包 ui-tokens TS + CSS 设计令牌primitive / semantic-light/dark / tailwind-theme P2 保留

v2.1 已下线服务(

  • teacher-bff :3003 / student-bff :3009 / parent-bff :3010 → 由 apollo-router 替代M9
  • teacher-portal :4000 / student-portal :4001 / parent-portal :4002 / admin-portal :4003 → 由 portal-shell 替代M10
  • push-gateway 改名为 realtime-gateway端口不变

历史服务classes端口 3001已合并入 core-eduC1 裁决),目录保留作历史参考,不再构建部署。

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.25 Gin + otelgin + prometheus api-gateway、realtime-gateway
联邦层 Rust Apollo Router 官方二进制 apollo-router@key/@requires/@extends 指令 + 自动查询计划)
业务服务 TypeScript 5.6+ NestJS 10 + Drizzle ORM + @nestjs/graphql + DataLoader iam、config-service、core-edu、content、msg
分析/AI Python 3.12 FastAPI + structlog + prometheus-client + Strawberry data-ana、aiai 含 Temporal Worker
工作流 Go / TS Temporal 1.23 SDK temporal server仅 AI 耗时工作流 + SagaCRUD 短事务禁用)
微前端 TypeScript 5.6+ Next.js 15 App Router + RSC + Zustand + SWR + Tailwind + shadcn 风格 portal-shell单容器 Modular Monolith + Micro-kernel
契约 protobuf buf v2FILE 级 breaking+ proto → GraphQL 生成器 跨语言契约定义与生成M0 工具链扩展 GraphQL 输出)
包管理 pnpm 11 / go.work / uv workspace 多语言 monorepo

v2.1 变更

  • 新增 Apollo Federation 2@key/@requires/@extends 指令)+ Apollo RouterRust官方二进制
  • 新增 Temporal 1.23(仅 AI 工作流 + SagaADR-030
  • 新增 SSEServer-Sent Events替代 WebSocket 单向推送场景)
  • 新增 Debezium Connect Outbox connectorTransaction Log Tailing废弃轮询线程
  • 新增 Zustand + SWRportal-shell 状态管理 + 静默刷新)
  • 新增 StrawberryPython GraphQL 子图库)+ @nestjs/graphqlTS GraphQL 子图库)
  • 下线 Module Federation@module-federation/nextjs-mf
  • 下线 3 个 BFF 的 GraphQL Yoga
  • 下线 WebSocket 单向推送场景(仅保留监考双向场景)

2.2 多存储矩阵

存储 版本 用途 使用服务
MySQL 8.0 写模型主库(每服务独占 schema iam、config-service、core-edu、content、msg
Redis 7-alpine 缓存/会话/限流/分布式锁/Pub/Sub/ScopeToken/ai workflow 状态 iam、config-service、core-edu、content、msg、data-ana、ai、realtime-gateway
ClickHouse 24.3 读模型宽表、分析聚合 data-ana
Neo4j 5.20 知识图谱content 读模型投影,由 neo4j-projector 写入) content
Elasticsearch 8.13 题库检索 / 消息检索(读模型投影,由 es-projector 写入) content、msg
PostgreSQL 16 Temporal 持久化存储(仅 Temporal Server不与业务库混用 temporal

2.3 基础设施矩阵

组件 版本 用途
Kafka cp-kafka 7.6 事件总线,领域事件异步通信(双 listener
Zookeeper cp-zookeeper 7.6 Kafka 协调
Debezium Connect 2.7 CDC + Outbox Transaction Log Tailing监听 outbox 表 binlog → Kafka
Apollo Router 最新稳定版 GraphQL 联邦 Supergraph 入口Rust 官方二进制,:3000
Temporal 1.23 AI 工作流 + Saga 编排Server :7233 + UI :8085 + PG :5433
OpenTelemetry SDK + OTLP 分布式追踪HTTP / gRPC 自动埋点)
Jaeger all-in-one 1.57 分布式 Trace 存储 + UIOTLP 4317/4318
Loki 3.2.1 日志聚合
Promtail 3.2.1 日志采集
Prometheus v2.51.0 指标采集(--web.enable-lifecycle15d 保留)
Alertmanager v0.27.0 告警路由与抑制
Grafana 10.4.0 可观测性可视化
node-exporter v1.8.2 主机指标
mysqld-exporter v0.15.1 MySQL 指标(命令行参数模式)
redis-exporter v1.67.0 Redis 指标
Vault P6 密钥管理

v2.1 修正Temporal 已落地M6.5ADR-030严格立规矩——仅限 AI 耗时工作流 + Saga 分布式事务补偿CRUD 短事务绝对禁止。Trace 存储使用 Jaeger all-in-oneOTLP 接收),原 v1.0 文档误写为 Tempo。


3. 分层架构

3.1 六层架构v2.1 修订)

graph TB
    subgraph L1["L1 客户端层"]
        Browser[浏览器]
        Mobile[移动端]
    end

    subgraph L2["L2 微前端层v2.1:单容器)"]
        MFE[portal-shell :4010<br/>Next.js 15 App Router + RSC 预取<br/>Modular Monolith + Micro-kernel]
    end

    subgraph L3["L3 边缘网关层Go 1.25 / Gin"]
        GW[api-gateway :8080<br/>HTTP 反向代理 + JWT RS256 + 限流 + 熔断]
        RTG[realtime-gateway :8081<br/>SSE 优先 + WS 仅监考<br/>Redis Pub/Sub 订阅(不直挂 Kafka]
    end

    subgraph L4["L4 GraphQL 联邦层v2.1 新增,替代 BFF"]
        ROUTER[apollo-router :3000<br/>Apollo Federation 2<br/>自动查询计划 + @requires DataScope]
    end

    subgraph L5["L5 业务微服务层(全部暴露 GraphQL 子图)"]
        SVC[7 业务服务<br/>iam / config-service / core-edu / content / msg / data-ana / ai<br/>DDD + CQRS + Outbox + DataLoader + RouterAuthGuard]
        TEMPORAL_SVC[Temporal Worker<br/>仅 ai 服务AI 工作流 + Saga]
    end

    subgraph L6["L6 数据与总线层"]
        DB[(MySQL / ClickHouse / Neo4j / ES / PostgreSQL)]
        REDIS[(Redis 7)]
        KAFKA[(Kafka + Debezium Outbox Transaction Log Tailing)]
        TEMPORAL_DB[(Temporal PostgreSQL :5433)]
    end

    Browser --> MFE
    Mobile --> MFE
    MFE --> GW
    MFE -.SSE 推送.-> RTG
    GW -- HTTP 代理REST --> SVC
    GW -- HTTP 代理GraphQL --> ROUTER
    ROUTER -- 子图查询(@requires ScopeToken --> SVC
    SVC -- gRPC内部 RPC禁止 GraphQL 子图互调) --> SVC
    SVC --> DB
    SVC --> REDIS
    ROUTER --> REDIS
    SVC <--> KAFKA
    RTG -.Redis Pub/Sub 订阅.-> REDIS
    SVC -.Outbox 表 + Debezium 监听.-> KAFKA
    TEMPORAL_SVC -- gRPC :7233 --> TEMPORAL_DB

3.2 依赖方向

L1 客户端 → L2 微前端portal-shell → L3 网关 → L4 Apollo Router → L5 业务服务 → L6 数据/总线
                                                                       ↑
                                                            L5 内部互调走 gRPC禁止子图互调

严格规则v2.1 新增 ADR-037

  1. L3 网关层只做路由、鉴权、限流、熔断,不写业务逻辑;只做 HTTP 反向代理,不做协议转换
  2. L4 Apollo Router 层做查询计划 + @requires DataScope 传递,不持有业务状态;外部 GraphQL 必须经 Router
  3. L5 业务服务之间通过 gRPC同步或 Kafka 事件(异步) 通信,禁止 GraphQL 子图互调ADR-037不直接访问对方数据库
  4. L5 业务服务的 GraphQL 子图仅服务前端展现,不可作为内部 RPC
  5. L6 数据层每个微服务独占自身数据库,禁止跨库联表DataScope 通过 @requires + ScopeToken 运行时解析ADR-024/041
  6. realtime-gateway 不直挂 Kafka通过 Redis Pub/Sub 订阅 msg worker 推送ADR-040
  7. 业务代码只写业务表 + Outbox 表,废弃 OutboxPublisher 轮询,由 Debezium 监听 binlog 投递ADR-032

4. 服务依赖图

graph TB
    subgraph Gateway["边缘网关层"]
        APIGW[api-gateway :8080]
        RTGW[realtime-gateway :8081]
    end

    subgraph Federation["GraphQL 联邦层v2.1 新增)"]
        ROUTER[apollo-router :3000<br/>Rust 官方二进制]
    end

    subgraph Portal["微前端层v2.1:单容器)"]
        SHELL[portal-shell :4010]
    end

    subgraph Services["业务服务(全部暴露 GraphQL 子图)"]
        IAM[iam :3002 / gRPC :50052]
        CONFIG[config-service :3011 / gRPC :50059<br/>v2.1 新增]
        CoreEdu[core-edu :3004 / gRPC :50053]
        Content[content :3005 / gRPC :50054]
        DataAna[data-ana :3006 / gRPC :50055]
        Msg[msg :3007 / gRPC :50056]
        AI[ai :3008 / gRPC :50058]
    end

    subgraph Temporal["工作流层v2.1 新增)"]
        TEMP[temporal :7233<br/>AI 工作流 + Saga]
    end

    subgraph Shared["共享包"]
        Proto[shared-proto<br/>8 proto + GraphQL 生成器]
        SharedTS[shared-ts<br/>outbox/dataloader/guards]
        SharedGo[shared-go<br/>env/jwks/logger/tracer]
        SharedPy[shared-py]
        Contracts[contracts<br/>权限点]
        Hooks[hooks<br/>React Hooks]
        UIComps[ui-components<br/>含 PluginCard 系列]
        UITokens[ui-tokens]
    end

    %% Portal → 网关 → Router → 子图
    SHELL -- HTTP/GraphQL --> APIGW
    SHELL -.SSE 推送.-> RTGW
    APIGW -- HTTP 代理GraphQL --> ROUTER
    APIGW -. REST 透传 .-> IAM
    APIGW -. REST 透传 .-> Msg
    ROUTER -- 子图查询(@requires ScopeToken --> IAM
    ROUTER -- 子图查询 --> CONFIG
    ROUTER -- 子图查询 --> CoreEdu
    ROUTER -- 子图查询 --> Content
    ROUTER -- 子图查询 --> Msg
    ROUTER -- 子图查询 --> DataAna
    ROUTER -- 子图查询 --> AI

    %% realtime-gateway → Redis Pub/Sub订阅 msg worker 推送)
    RTGW -. Redis Pub/Sub user:userId:notify .-> Msg

    %% AI → Temporal
    AI -- gRPC Worker :7233 --> TEMP

    %% 业务服务间事件Outbox + Debezium → Kafka
    CoreEdu -. Kafka 事件 .-> Content
    CoreEdu -. Kafka 事件 .-> DataAna
    CoreEdu -. Kafka 事件 .-> Msg
    Content -. Kafka 事件 .-> DataAna
    IAM -. Kafka 事件 .-> CoreEdu
    IAM -. Kafka 事件 .-> Msg
    CONFIG -. Kafka edu.config.entry.changed .-> IAM
    CONFIG -. Kafka edu.config.entry.changed .-> CoreEdu
    CONFIG -. Kafka edu.config.entry.changed .-> Content
    AI -. gRPC内部 RPC .-> Content
    AI -. gRPC内部 RPC .-> DataAna
    AI -. gRPC内部 RPC .-> IAM
    AI -. Kafka edu.ai.usage.recorded .-> DataAna
    DataAna -. Kafka 事件 .-> CoreEdu
    DataAna -. Kafka 事件 .-> Msg

    %% 共享包依赖
    IAM --> Proto
    CONFIG --> Proto
    CoreEdu --> Proto
    Content --> Proto
    DataAna --> Proto
    Msg --> Proto
    AI --> Proto
    APIGW --> SharedGo
    RTGW --> SharedGo
    IAM --> SharedTS
    CONFIG --> SharedTS
    CoreEdu --> SharedTS
    Content --> SharedTS
    Msg --> SharedTS
    SHELL --> UIComps
    SHELL --> UITokens
    SHELL --> Hooks
    SHELL --> Contracts

4.1 服务间通信矩阵v2.1 修订)

调用方 → 被调用方 协议 场景
portal-shell → api-gateway HTTP / GraphQL 前端查询经 Gateway → apollo-router
portal-shell → realtime-gateway SSE 推送通道(默认)
portal-shell → realtime-gateway WebSocket 监考双向场景(仅此场景用 WS
api-gateway → apollo-router HTTP 反向代理 GraphQL 查询透传到 Router
api-gateway → iam / msg HTTP 反向代理 REST 透传(登录、通知等非 GraphQL 路径)
apollo-router → 业务子图 HTTP / GraphQL 自动查询计划 + @requires ScopeToken 传递 + DataLoader
业务子图 → 业务子图 gRPC内部 RPC 服务间同步调用,禁止 GraphQL 子图互调ADR-037
realtime-gateway → Redis Redis Pub/Sub 订阅 user:{userId}:notify channel不直挂 Kafka
msg worker → Redis Redis Pub/Sub 消费 Kafka → 按 userId 发到 Redis ChannelADR-040
CoreEdu → Content Kafka 事件 教学内容变更通知
CoreEdu → DataAna Kafka 事件 学情数据投递
CoreEdu → Msg Kafka 事件 通知触发(考试/作业/成绩/考勤)
IAM → CoreEdu Kafka 事件 用户变更同步
IAM → Msg Kafka 事件 用户/角色变更通知
ConfigService → 全部服务 Kafka 事件 edu.config.entry.changed 失效各服务本地缓存
Content → DataAna Kafka 事件 内容发布同步
DataAna → CoreEdu Kafka 事件 掌握度更新 → 推荐练习
DataAna → Msg Kafka 事件 掌握度预警触发
AI → Content gRPC 题库查询 / 知识点查询
AI → DataAna gRPC 学情数据查询
AI → IAM gRPC 用户信息查询
AI → Temporal gRPC :7233 AI 耗时工作流 + SagaWorker 注册)
AI → Kafka Kafka 生产 AI 用量事件发布(edu.ai.usage.recorded

4.2 Apollo Router 子图清单v2.1 替代 BFF 下游矩阵)

7 个业务服务暴露 GraphQL 子图,由 apollo-router 自动组装 Supergraph。每个子图必须实现 RouterAuthGuardADR-036+ DataLoaderADR-035

子图 HTTP 端口 关键 @key 类型 DataScope 依赖
iam 3002 User / Role / Permission / School 无(权限源,暴露 ScopeToken Resolver
config-service 3011 PluginConfig / LayoutTemplate / UserOverride
core-edu 3004 Exam / Homework / Grade / Attendance / Class / Schedule visibleClassIds / visibleStudentIds
content 3005 Textbook / Chapter / Question / KnowledgePoint / LessonPlan editableSubjectIds
msg 3007 Notification / Template / Preference visibleNotificationScopes
data-ana 3006 Analytics / Dashboard / Mastery / Warning visibleClassIds / visibleStudentIds
ai 3008 Chat / Question / Expression / LessonPlan / Report userId(个人级)

v2.1 已下线teacher-bff / student-bff / parent-bff 的 BFF 下游客户端矩阵(原 §4.2)已废弃,由本节替代。


5. 认证与权限

5.1 JWT RS256 认证流程

实施细节api-gateway 通过 JWKS Fetchershared-go/jwks缓存 RS256 公钥校验 JWT 校验通过后注入 x-user-id / x-user-roles / x-user-data-scope / x-request-id 头部HTTP 反向代理到下游服务; 下游服务NestJS通过 AuthMiddleware 解析头部、PermissionGuardAPP_GUARD做权限校验。 DEV_MODE=true 时跳过 JWT 校验,接受 dev-token

sequenceDiagram
    participant U as 用户
    participant GW as API Gateway (Go)
    participant IAM as iam 服务
    participant SVC as 下游服务 (NestJS / FastAPI)

    rect rgb(240, 248, 255)
        Note over U,IAM: 阶段 1: 登录签发
        U->>GW: POST /api/v1/iam/login {username, password}
        GW->>IAM: HTTP 反向代理(无鉴权路由)
        IAM->>IAM: bcrypt 校验 + 查询权限 (PermissionCacheService)
        IAM->>IAM: 生成 JWTRS256 私钥签发)+ refresh_token
        IAM->>IAM: 写 iam_outboxUSER_EVENTS
        IAM-->>GW: {accessToken, refreshToken, userInfo}
        GW-->>U: 200 Set-Cookie httpOnly + ActionState 信封
    end

    rect rgb(240, 255, 240)
        Note over U,SVC: 阶段 2: 请求鉴权
        U->>GW: GET /api/v1/classes + Authorization: Bearer <token>
        GW->>GW: JWKS Fetcher 取 RS256 公钥
        GW->>GW: 校验签名 + exp/iss/aud
        GW->>GW: 提取 userId/roles/dataScope
        GW->>SVC: HTTP 代理 + 注入 x-user-* 头部
        SVC->>SVC: AuthMiddleware 解析头部
        SVC->>SVC: PermissionGuard (APP_GUARD) 校验权限
        SVC->>SVC: DataScopeInjector 注入 WHERE 条件
        SVC-->>GW: ActionState 响应
        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管辖年级

v2.1 场景域复用策略(替代 v1 的 BFF 复用):

按"使用场景域"在 portal-shell 内通过插件配置差异化admin 在 config-service 配置角色插件集 + 角色 Layout 默认 + 角色级 props而非按角色分 BFF。新角色复用 portal-shell + apollo-router通过插件配置 + DataScope 差异化。

场景域 portal-shell 插件集 复用角色
教学场景 universal 插件 + teacher 插件lesson-plan-editor / question-bank 等) 教师、教导主任、教研组长
学习场景 universal 插件 + student 插件error-book / ai-tutor 等) 学生
家长场景 universal 插件 + parent 插件child-overview / leave-approval 等) 家长
管理场景 universal 插件 + admin 插件user-management / rbac-manager / plugin-manager 等) 系统管理员、校管理员

实现:教导主任归入教学场景 + 额外管理视口L1 导航增加管理菜单项L4 数据范围扩大到年级),通过 config-service 配置 role_plugin_mapping 实现。

iam 服务职责v2.1 拆分后ADR-026

  • 认证:登录/登出/JWT/2FA
  • RBAC角色/权限/角色-权限映射 CRUD
  • 视口配置:导航/路由/组件级视口配置 CRUD
  • DataScope数据范围解析all/grade_managed/class_taught/children/owned + 自定义)
  • 权限解析 APIgetEffectivePermissions(userId) → {permissions, viewports, dataScope}
  • 子图暴露 ScopeToken ResolverclassScopeToken / studentScopeToken,由 Router 通过 @requires 调用ADR-041

config-service 服务职责v2.1 新增ADR-026

  • 插件配置plugin_registry / role_plugin_mapping CRUD
  • 布局配置layout_templates / role_layout_default / user_layout_override CRUD
  • 用户偏好user_layout_override用户自定义 Layout + 插件位置 + 插件 props
  • 三层配置合并getPluginConfig(userId) 返回三层合并后的最终配置ADR-026 详细描述)
  • 配置变更通知:通过 Outbox + Debezium 发布 edu.config.entry.changed,各服务消费后失效本地 Redis 缓存

5.5 Router-Authorization 信任凭证v2.1 新增ADR-036

强制约束Apollo Router 请求子图时必须携带内部信任凭证 router-authorization Header各业务服务的 NestJS GuardRouterAuthGuard必须拦截并校验该 Header拒绝任何非 Router 发起的 GraphQL 请求。

理由GraphQL 子图暴露后,若不校验来源,恶意请求可直接绕过 Router 的查询计划与权限聚合,直接调子图。

实现要点

  • Router 侧router.yaml通过 headers.all.request.insert 自动注入 router-authorization: ${ROUTER_AUTH_SECRET}
  • 子图侧(各业务服务通用 Guard
@Injectable()
export class RouterAuthGuard implements CanActivate {
  canActivate(ctx: ExecutionContext): boolean {
    const req = ctx.switchToHttp().getRequest();
    const routerAuth = req.headers["router-authorization"];
    const expected = this.config.get("ROUTER_AUTH_SECRET");
    if (routerAuth !== expected) {
      throw new ForbiddenException(
        "Direct GraphQL access denied; must go through Apollo Router",
      );
    }
    return true;
  }
}
  • ROUTER_AUTH_SECRET 通过 Docker Secret 或 K8s Secret 注入,禁止硬编码
  • 开发模式DEV_MODE=true可放宽校验便于本地调试
  • 通用实现位于 packages/shared-ts/src/guards/,所有子图复用

配置管理

  • 仅 apollo-router 可调用子图 /graphql 端点
  • 业务服务之间互调走 gRPCADR-037不走 GraphQL 子图

6. 数据访问与缓存

6.1 CQRS 读写分离v2.1 content 改造ADR-027

graph LR
    subgraph Write["写路径content 服务仅写 MySQL"]
        Cmd[Command 命令] --> App[Application Service]
        App --> Domain[Domain 领域模型]
        Domain --> Repo[Repository 写模型]
        Repo --> mysql_w[(MySQL 主库)]
        App --> Outbox[(Outbox 表<br/>同事务原子)]
    end

    subgraph Eager["Eager Invalidationv2.1 ADR-038"]
        Outbox --> SyncDel[事务提交后同步<br/>Redis DEL 主动失效]
    end

    subgraph Sync["同步链路Debezium 监听 binlog"]
        Outbox --> Debezium[Debezium Connect<br/>监听 outbox 表 binlog]
        Debezium --> kafka_sync[(Kafka<br/>Transaction Log Tailing)]
        kafka_sync --> Proj[投影器 worker]
        Proj --> ch_sync[(ClickHouse 宽表)]
        Proj --> redis_sync[(Redis 缓存<br/>兜底清理)]
        Proj --> es_sync[(ES 索引)]
        Proj --> neo4j_sync[(Neo4j 知识图谱)]
    end

    subgraph Read["读路径content 子图读 MySQL + Neo4j + ES"]
        Query[Query 查询] --> ReadModel[Read Model]
        ReadModel --> ch_read[(ClickHouse 宽表)]
        ReadModel --> redis_read[(Redis 缓存)]
        ReadModel --> es_read[(ES 索引<br/>含 version 字段)]
        ReadModel --> neo4j_read[(Neo4j 知识图谱)]
        ReadModel --> mysql_read[(MySQL 强一致穿透读)]
    end

v2.1 变更

  • content 服务只写 MySQL不再直接写 Neo4j/ES由投影器消费 Kafka 同步)
  • OutboxPublisher 轮询线程废弃,改由 Debezium 监听 binlog 自动投递ADR-032
  • 新增 Eager Invalidation事务提交后同步发 Redis DEL 主动失效ADR-038
  • 新增乐观锁版本号:写返回 version读携带 expectedVersionES 落后则穿透读 MySQLADR-039

6.2 Apollo Router 混合读策略v2.1 替代 BFF 混合读)

flowchart TD
    Q[Apollo Router 收到 Query] --> C1{子图本地 Redis 命中?}
    C1 -- 是 --> R1[返回缓存]
    C1 -- 否 --> C2{需要跨子图聚合?}
    C2 -- 否 --> C3[调单一子图 /graphql]
    C3 --> C4[子图内 Redis 缓存]
    C4 --> R2[返回]
    C2 -- 是 --> C5[并发调多子图 /graphql<br/>@requires 传递 ScopeToken]
    C5 --> C6[Router 内存聚合]
    C6 --> R3[返回聚合结果]
    R2 --> C7[子图回填 Redis]
    R3 --> C7

6.3 缓存策略矩阵v2.1 修订)

数据类型 存储 TTL 失效策略 ADR
用户会话 Redis 30 分钟 滑动过期
权限列表 Redis 5 分钟 事件驱动失效
班级/年级列表 Redis 5 分钟 事件驱动失效
教学资源详情 Redis 10 分钟 Eager DEL + 投影器兜底 ADR-038
Router 聚合结果 Redis 5-30 秒 短 TTL
学情宽表 ClickHouse 实时 CDC 同步
题库检索 ES 实时 CDC 同步(含 version 字段) ADR-039
DataScope 缓存 Redis 5 分钟 角色变更时 DEL
ScopeToken 缓存 Redis 5 分钟 自动过期key: scope:usr:{id}:cls_scope ADR-041
配置缓存 Redis 5 分钟 Kafka edu.config.entry.changed 失效 + TTL 兜底 ADR-026
AI workflow 状态 Redis 1 小时 自动过期key: ai:workflow:{id} ADR-028
content 查询缓存 Redis 10 分钟 Eager DEL + content-cache-projector 兜底 ADR-038
msg 查询缓存 Redis 10 分钟 Eager DEL + msg-cache-projector 兜底 ADR-038

6.4 Eager Invalidation + 乐观锁版本号v2.1 新增ADR-038/039

Eager InvalidationADR-038

  • 写请求在 MySQL 事务提交后的代码行,直接同步发 Redis DEL 命令删除查询缓存(毫秒级主动失效)
  • Redis DEL 在事务执行(事务提交后),避免 Redis 故障导致业务回滚
  • 若 Redis DEL 失败,不影响业务结果(兜底由 content-cache-projector 处理)
  • Kafka 的 content-cache-projector 仅作为防止网络抖动的兜底清理,不是主失效路径

乐观锁版本号ADR-039

  • 写接口返回记录的 updated_atversion
  • 前端携带 expectedVersion 发起 QueryRouter 透传到 content 子图
  • content 子图先读 ES检查 ES 中的 version
    • ES.version >= expectedVersion → 返回 ES 数据(最终一致)
    • ES.version < expectedVersion → 穿透读 MySQL强一致返回 MySQL 数据
  • 适用场景:用户编辑后立即刷新页面查看、批量导入后立即查询列表
  • 不适用高频查询场景version 校验开销),走最终一致即可

6.5 config-service 三层配置合并v2.1 新增ADR-026

优先级:用户覆盖 > 角色模板 > 系统默认

// PropsMerger 三层合并(深合并 + 数组覆盖)
const finalProps = deepMerge(
  registry.defaultProps, // Layer 1 系统默认plugin_registry.default_props
  roleMapping.widgetProps, // Layer 2 角色默认role_plugin_mapping.widget_props
  userPlacement.props, // Layer 3 用户调整user_layout_override.plugin_placements.props
);

配置表清单config-service 独占 schema

表名 用途
plugin_registry 插件注册表系统级admin 维护)
role_plugin_mapping 角色-插件映射admin 配置角色可用插件集)
role_layout_default 角色 Layout 默认admin 配置角色默认模板)
layout_templates 5 种内置 Layout 模板(系统预置,不可改)
user_layout_override 用户布局覆盖(用户自定义)
plugin_packages 二期:第三方插件包存储

配置变更生效路径

  1. Admin 通过 REST API 修改 plugin_registry / role_plugin_mapping / role_layout_default
  2. config-service 写 MySQL + Outbox 表
  3. Debezium 监听 binlog发布 edu.config.entry.changed 到 Kafka
  4. 各服务消费事件,失效本地 Redis 缓存key: config:{type}:{key}
  5. 下次查询从 config-service 拉新值,回填 RedisTTL 5 分钟兜底)

portal-shell 侧采用 SWR 静默后台刷新revalidateOnFocus + refreshInterval 5min用户切回 Tab 时自动检测配置变化Toast 提示"发现新布局配置,点击刷新生效"。无需 Kafka + WebSocket 推送链路,简化架构。


7. 事件驱动架构

7.1 Outbox + Debezium Transaction Log Tailingv2.1 ADR-032

v2.1 关键变更:废弃 OutboxPublisher 轮询线程,改由 Debezium 监听 binlog 自动投递 Outbox 表到 KafkaTransaction Log Tailing。CDC 与 Outbox 不再二选一而是分工结合Outbox 定义"发什么"业务语义CDC 解决"怎么发"(技术传输)。

graph LR
    subgraph Service["业务服务"]
        Cmd[Command 处理]
        Domain[Domain 聚合]
        Repo[Repository]
    end

    subgraph MySQL["MySQL 主库"]
        BizTable[(业务表)]
        OutboxTable[(outbox 表<br/>同事务原子)]
    end

    subgraph CDC["Debezium Connect监听 binlogv2.1 替代轮询)"]
        Debezium[Debezium 2.7<br/>伪装成 MySQL Slave<br/>监听 outbox 表 binlog]
        SMT[SMT Topic Router<br/>重写为业务语义 topic]
    end

    subgraph Bus["事件总线"]
        kafka_bus[(Kafka topic<br/>edu.<domain>.<aggregate>.<action>)]
    end

    subgraph Consumers["消费者"]
        Proj[投影器 worker<br/>CQRS 读模型同步]
        OtherSvc[其他服务<br/>业务订阅]
        RealtimeGW[realtime-gateway<br/>via msg worker + Redis Pub/Sub]
    end

    Cmd --> Domain
    Domain --> Repo
    Repo --> BizTable
    Repo --> OutboxTable
    OutboxTable -.binlog row event.-> Debezium
    Debezium --> SMT
    SMT --> kafka_bus
    kafka_bus --> Proj
    kafka_bus --> OtherSvc
    kafka_bus --> MsgWorker[msg worker<br/>消费 edu.notify.* ]
    MsgWorker --> RedisPUB[Redis Pub/Sub<br/>user:userId:notify]
    RedisPUB --> RealtimeGW
    Proj --> read_stores[(ClickHouse / Redis / ES / Neo4j)]

关键原则

  • 业务代码只写业务表 + Outbox 表(业务语义由业务代码控制)
  • Outbox 表的投递由 Debezium 自动完成(技术传输由基础设施完成,业务代码不感知 MQ
  • v1 的 OutboxPublisher 轮询线程在 v2.1 完全废弃(被 Debezium 替代)
  • 保证 at-least-once 语义Debezium offset 管理)
  • 消费者基于 event_id 去重DB 唯一索引或 Redis SETNX
  • 投影器消费 Kafka 事件做 CQRS 读模型同步(与业务事件发布解耦)

7.2 事件 Topic 分类v2.1 修订)

实际命名(基于 services/*/src/shared/kafka/topic-map.tsservices/iam/src/config/kafka.ts 模式 edu.<domain>.<aggregate>.<action>。msg 服务统一使用 edu.notify.notification.* 发布ARB-013。 v2.1 新增 edu.ai.usage.recorded / edu.config.entry.changed topic。

Topic 生产者 消费者 说明
edu.identity.user.created iam core-edu (iam-consumer)、msg 用户创建
edu.identity.user.updated iam core-edu、msg 用户更新
edu.identity.user.deleted iam msg 用户删除
edu.identity.user.role_changed iam core-edu、msg 用户角色变更
edu.identity.role.created iam msg 角色创建
edu.identity.role.updated iam msg 角色更新
edu.teaching.exam.published core-edu msg / data-ana 考试发布
edu.teaching.exam.extended core-edu msg 考试延时(实时)
edu.teaching.exam.force_submitted core-edu msg 考试强制交卷(实时)
edu.teaching.exam.question_reordered core-edu msg 题序打乱(实时)
edu.teaching.homework.assigned core-edu msg / data-ana 作业布置
edu.teaching.assignment.submitted core-edu data-ana、msg 作业提交
edu.teaching.assignment.graded core-edu msg / data-ana 作业批改完成
edu.teaching.grade.recorded core-edu data-ana、msg 成绩录入
edu.teaching.attendance.recorded core-edu msg / data-ana 考勤记录
edu.content.question.created content es-projector / neo4j-projector 题目创建v2.1 改)
edu.content.question.updated content es-projector / neo4j-projector 题目更新v2.1 新)
edu.content.knowledge_point.linked content neo4j-projector 知识点关联v2.1 新)
edu.content.textbook.updated content data-ana 教材更新
edu.insight.mastery.updated data-ana core-edu、msg 掌握度更新
edu.notify.notification.sent msg msg worker → Redis Pub/Sub → realtime-gateway 通知投递驱动推送v2.1 改)
edu.notify.notification.read msg 通知已读
edu.notify.notification.recalled msg msg worker → Redis Pub/Sub → realtime-gateway 通知撤回v2.1 改)
edu.notify.notification.failed msg 通知投递失败
edu.notify.notification.events msg 兜底 topic
edu.ai.usage.recorded ai data-ana AI 用量记录v2.1 新)
edu.config.entry.changed config-service 全部服务 配置变更失效缓存v2.1 新)

Outbox 表命名:每服务独立 outbox 表iam_outbox / config_outbox / core_edu_outbox / content_outbox / msg_outbox / ai_outbox由 shared-ts/outbox 模块统一管理。投递由 Debezium 自动完成。

必需依赖软失败标注ARB-015 §17.6ISSUE-058 覆盖 ISSUE-055

  • realtime-gateway → Redis软失败Redis 故障仅告警 + degraded: true + /readyz 返 200不返 503、不触发 Pod 重启)
  • 理由Redis 故障时单实例仍能服务本地 SSE 连接(仅跨实例广播失效);重启会丢失本地连接表,加剧雪崩风险
  • ISSUE-055 必需依赖列表应排除 realtime-gateway → Redis

7.3 核心领域事件

事件 触发场景 消费者动作
UserRegistered 新用户注册 CoreEdu 初始化默认班级关联Msg 发送欢迎通知
ExamPublished 考试发布 Msg 推送考试通知给学生DataAna 创建考试分析骨架
HomeworkSubmitted 学生提交作业 DataAna 记录提交行为Msg 通知教师
HomeworkGraded 教师批改完成 DataAna 更新掌握度Msg 通知学生
MasteryUpdated 掌握度计算完成 CoreEdu 推荐个性化练习Msg 触发预警
NotificationRequested 通知请求 Msg 投递通知到 4 渠道email/sms/push/in-app
NotificationSent 通知投递完成 msg worker 消费 Kafka → Redis Pub/Sub → realtime-gateway SSE 推送v2.1 改,原为 push-gateway Kafka 消费)
ConfigEntryChanged 配置变更 各服务消费失效本地 Redis 缓存v2.1 新增)

7.4 Redis Pub/Sub 推送背板v2.1 新增ADR-040

核心原则边缘网关realtime-gateway不直接挂载 Kafka避免在边缘节点引入重量级 Kafka Client。采用 Redis Pub/Sub 作为状态路由背板。

msg 服务发布通知
  ↓ Outbox 表 + Debezium → Kafkaedu.notify.notification.*
  ↓
msg 服务的后台 worker 消费 Kafka
  · 持久化通知记录
  · 脱敏处理
  · 分类聚合
  ↓
msg worker 将需要实时推送的消息,按 userId 发到 Redis Pub/Sub
  · Channel: user:{userId}:notify
  · Payload: { notificationId, type, title, body, createdAt }
  ↓
realtime-gateway 实例仅在用户接入 SSE 时,才动态 SUBSCRIBE 该用户的 Redis Channel
  · 用户上线 → SADD online:users {userId} + SUBSCRIBE user:{userId}:notify
  · 用户下线 → SREM online:users {userId} + UNSUBSCRIBE
  ↓
  ├── SSE 推送(默认,单向)
  │   · 客户端 GET /sse?token=JWT保持长连接
  │   · realtime-gateway 收到 Redis Pub/Sub 消息 → 推送到 SSE 连接
  │   · 适合 99% 场景(考试发布/成绩推送/通知)
  │
  └── WebSocket 推送(仅监考场景)
      · 客户端 WS /ws双向通信
      · 用于在线监考(心跳/防作弊/实时指令)

架构优势

  • 边缘网关轻量:只依赖 Redis Client不依赖 Kafka Client内存占用降低 80%+
  • 按需订阅realtime-gateway 仅订阅在线用户的 Channel离线用户的消息由 msg worker 持久化待取
  • 水平扩展realtime-gateway 多实例时Redis Pub/Sub 自动广播到所有订阅实例
  • 故障隔离Kafka 故障不影响已建立 SSE 连接的实时推送Redis Pub/Sub 独立)

realtime-gateway 端点

  • GET /sse — SSE 推送(默认),连接时 SUBSCRIBE Redis Channel
  • GET /ws — WebSocket 升级(监考专用)
  • GET /online/:userId — 查询在线状态(检查 Redis SET online:users
  • POST /internal/broadcast — 管理员全量广播
  • GET /healthz / GET /readyz / GET /metrics

8. 跨服务协作

8.1 同步 vs 异步决策v2.1 修订)

flowchart TD
    Req[跨服务协作需求] --> C1{需要强一致性?}
    C1 -- 是 --> C2{调用方需要立即结果?}
    C2 -- 是 --> C2a{是 CRUD 短事务?}
    C2a -- 是 --> Sync[同步 gRPC + 分布式锁<br/>ADR-030 严禁 Temporal]
    C2a -- 否 --> Saga[Saga 编排<br/>TemporalADR-030]
    C1 -- 否 --> C3{需要事件最终一致?}
    C3 -- 是 --> Async[异步 Kafka 事件<br/>Outbox + DebeziumADR-032]
    C3 -- 否 --> C4{仅查询读取?}
    C4 -- 是 --> Read[读模型冗余 / GraphQL @requires]
    C4 -- 否 --> Sync

8.2 Apollo Router 同步聚合v2.1 替代 BFF 同步聚合)

sequenceDiagram
    participant U as portal-shell
    participant R as apollo-router
    participant IAM as iam 子图
    participant CoreEdu as core-edu 子图
    participant DataAna as data-ana 子图

    U->>R: GraphQL Query 班级仪表盘
    R->>R: 解析查询计划Query Plan
    par 并行子图查询
        R->>IAM: 子图查询 + @requires classScopeToken
        IAM->>IAM: 计算 visibleClassIds → Redis Set
        IAM-->>R: ScopeToken usr:123:cls_scope
    and
        R->>CoreEdu: 子图查询 @requires(fields: "classScopeToken")
        CoreEdu->>CoreEdu: 从 Redis sMembers 获取实际 ID
        CoreEdu-->>R: 班级列表DataScope 过滤后)
    and
        R->>DataAna: 子图查询
        DataAna-->>R: 统计数据
    end
    R->>R: 内存聚合DataLoader 批量去重)
    R-->>U: 仪表盘聚合数据

8.3 异步事件闭环

flowchart LR
    A[教师录入成绩] --> B[CoreEdu 写 MySQL + Outbox 表]
    B --> C[Debezium 监听 binlog → Kafka]
    C --> D[teaching.grade.recorded]
    D --> E[DataAna 消费更新掌握度]
    D --> F[Msg 消费通知学生]
    F --> F1[msg worker → Redis Pub/Sub → realtime-gateway SSE]
    E --> G[insight.mastery.updated]
    G --> H[CoreEdu 消费推荐练习]
    G --> I[Msg 消费掌握度预警]
    I --> I1[msg worker → Redis Pub/Sub → realtime-gateway SSE]

8.4 Temporal 工作流编排v2.1 ADR-030 严格边界)

v2.1 严格立规矩:仅限 AI 耗时工作流 + Saga 分布式事务补偿CRUD 短事务绝对禁止。

工作流类型 时长 推荐方案 示例
AI 耗时工作流 分钟~小时 Temporal Workflow 生成大纲 → 生成知识点 → 生成题目 → 组装试卷
Saga 分布式事务补偿 秒~分钟 Temporal Saga 购买高级插件:扣减积分服务(+补偿)+ 授权服务(+补偿)
长工作流(非 AI 分钟~天 Redis 状态机 + Outbox 事件 备课工作流(非 AI 部分)、报告生成
短事务 毫秒~秒 同步调用 + 分布式锁(绝对禁止 Temporal 作业提交、成绩录入、考勤记录
批处理 小时 Cron + Batch Job 成绩统计、掌握度计算

禁止

  • CRUD 短事务绝对禁止用 Temporal:作业提交、成绩录入、考勤记录等毫秒~秒级事务走同步调用 + 分布式锁Temporal 的入库开销会拖垮吞吐量
  • 纯读操作走工作流(直接走缓存)

允许

  • AI 耗时工作流用 Temporal自动重试 / 休眠 / 状态持久化)
  • 跨服务 Saga 分布式事务补偿用 Temporal自动补偿 / 状态可查询)
  • 跨天长流程用 Redis 状态机 + Outbox 事件(非 AI 场景)

Temporal 部署

  • 独立 Temporal Server 集群(不与业务服务混部,:7233 + UI :8085 + PG :5433
  • ai 服务作为 Temporal Worker注册 Workflow 与 Activity
  • Workflow 状态持久化在 Temporal DBPostgreSQL业务数据仍在各服务 DB
flowchart TD
    Start[AI 生成试卷请求] --> A1[Temporal Workflow 启动]
    A1 --> A2[Activity: 生成大纲]
    A2 --> A3[Activity: 生成知识点]
    A3 --> A4[Activity: 生成题目]
    A4 --> A5[Activity: 组装试卷]
    A5 --> C1{质量校验通过?}
    C1 -- 否 --> A6[Activity: 补偿回滚]
    C1 -- 是 --> A7[Activity: 持久化试卷]
    A6 --> End[工作流失败]
    A7 --> End2[工作流完成]

8.5 ScopeToken 优化大规模 ID 列表v2.1 新增ADR-041

问题:大规模 ID 列表(如管理员可见 500 个班)直接在 GraphQL federation 中传递全量数组,会导致 Router ↔ 子图之间的 HTTP 请求 payload 膨胀,影响延迟。

方案

  • iam 计算 visibleClassIds 存入 Redis Setkey: scope:usr:{userId}:cls_scopeTTL 5min
  • 生成极短的 ScopeTokenusr:123:cls_scope
  • Router 通过 @requires 传递 token不传全量 ID 数组)
  • core-edu 接收 token 后从同机房 Redis SMEMBERS scope:usr:123:cls_scope 获取实际 ID 列表
  • Redis 调用延迟 < 1ms远小于 HTTP payload 传输 500 个 ID 的开销
  • ScopeToken 与 Redis Set 共享 TTL5min自动过期清理

K12 场景规模分析

角色 可见范围 ID 量级 性能评估
学生 1 个班 无压力
教师 5-10 个班 IN 查询可扛
教研组长 20-50 个班 IN 查询可扛
管理员 100-500 个班 需 ScopeToken 优化
超管 全校 走 DataScope=ALL不传 ID

@requires 查询计划示例(教研组长查"本组所有班级的考试成绩"

步骤 1: 调 iam 子图
  scopeToken(userId=x-user-id, scopeType="class")
  → iam 查 Redis 缓存key: scope:usr:{userId}:cls_scopeTTL 5min
  → 未命中则查 user_school_role 表
  → 将 ID 列表存入 Redis Set
  → 返回极短的 scopeToken: "usr:123:cls_scope"(不传全量 ID 数组)

步骤 2: 调 core-edu 子图
  gradesForCurrentUser(
    scopeToken: "usr:123:cls_scope",
    termId: "2024-spring"
  )
  @requires(fields: "scopeToken")
  → core-edu 从同机房 Redis SMEMBERS scope:usr:123:cls_scope 拿到 ID 数组
  → 查 MySQL: WHERE class_id IN (...) AND term_id = ?
  → 返回 [Grade, Grade, ...]

步骤 3: 聚合返回客户端

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 三支柱可观测性

实际落地Jaeger all-in-one 接收 OTLPHTTP 4318 / gRPC 4317Loki + Promtail 采集容器日志Prometheus 抓取 /metrics 端点。 所有 NestJS 服务通过 shared/observability/{logger,metrics,tracer}.ts 注册Go 网关通过 shared-go/{logger,tracer} + otelginPython 服务通过 structlog + opentelemetry-instrumentation-fastapi + prometheus-client。 v2.1 新增apollo-routerRust 官方二进制,内置 OTel spans + Prometheus 指标 + spec_compliant JSON 日志config-serviceNestJS复用 pino + prom-client + OTLPportal-shellNext.js复用 web-vitals + Sentry + OTel Web

graph TB
    subgraph App["应用层"]
        NESTJS[NestJS 服务<br/>pino + prom-client + OTLP]
        GO[Go 网关<br/>slog + prometheus + otelgin]
        PY[Python 服务<br/>structlog + prometheus-client + OTLP]
        APOLLO[apollo-router :3000<br/>Rust 内置 OTel spans +<br/>Prometheus 指标 + spec_compliant 日志]
        PORTAL[portal-shell :4010<br/>web-vitals + Sentry + OTel Web]
    end

    subgraph Collect["采集层"]
        OTEL[OpenTelemetry SDK<br/>OTLP exporter]
        PROM[Prometheus 抓取<br/>/metrics]
        PROMTAIL[Promtail<br/>容器日志]
    end

    subgraph Storage["存储层"]
        LOKI[(Loki 3.2.1<br/>日志)]
        JAEGER[(Jaeger 1.57<br/>TraceOTLP 4317/4318)]
        PROMDB[(Prometheus v2.51<br/>指标 15d 保留)]
    end

    subgraph Exporters["Exporters"]
        NODE[node-exporter :9100<br/>host.docker.internal]
        MYSQL[mysqld-exporter :9104]
        REDIS[redis-exporter :9121]
    end

    subgraph Vis["可视化层"]
        GRAFANA[Grafana 10.4<br/>统一面板 :3030]
        ALERT[Alertmanager v0.27<br/>告警路由]
    end

    NESTJS --> OTEL
    GO --> OTEL
    PY --> OTEL
    APOLLO -.内置 spans.-> OTEL
    PORTAL -.OTLP HTTP.-> OTEL
    OTEL --> JAEGER
    NESTJS --> PROM
    GO --> PROM
    PY --> PROM
    APOLLO -.内置 /metrics.-> PROM
    PROM --> PROMDB
    PROMTAIL --> LOKI
    NODE --> PROM
    MYSQL --> PROM
    REDIS --> PROM
    LOKI --> GRAFANA
    JAEGER --> GRAFANA
    PROMDB --> GRAFANA
    PROMDB --> ALERT

v2.1 各服务可观测性栈

服务 日志 指标 链路 健康检查
apollo-router spec_compliant JSON内置 内置 Prometheus请求量/延迟/错误率/子图延迟/持续查询统计) 内置 OpenTelemetry /health(独立 :8088 端口,见 router.yaml
config-service pino prom-client + /metrics OTLP exporter /healthz + /readyz
portal-shell —(前端) web-vitalsCLS/INP/LCP/FCP/TTFB OTel Web + Sentry —(前端无健康端点,由容器探针替代)
realtime-gateway slog JSON prometheus + /metrics otelgin /healthz + /readyz
iam/core-edu/... pino prom-client + /metrics OTLP exporter /healthz + /readyz

10.2 Trace 上下文传播

flowchart LR
    A[客户端请求] --> B[API Gateway<br/>otelgin 生成 traceId]
    B --> C[apollo-router<br/>继承 W3C traceparent<br/>生成子图查询计划 span]
    C --> D[业务服务子图 /graphql<br/>继承 HTTP traceparent]
    D --> E[Kafka 事件<br/>traceId 写入 header]
    E --> F[消费者服务<br/>继承 traceId]
    F --> G[下游存储<br/>span 记录]
    G --> H[Jaeger UI<br/>查询链路]

v2.1 变更BFF 环节由 apollo-router 替代。Router 在子图查询计划中生成独立 spanquery_plan / fetch_subgraph便于在 Jaeger 中追踪每个子图的延迟贡献。

规则

  • 所有跨服务调用必须透传 W3C Trace ContextHTTP traceparent 头 / gRPC metadata
  • Kafka 事件必须将 traceId 写入消息 header
  • 日志必须包含 traceId / requestId 用于关联查询
  • 关键业务操作必须创建 span创建考试、提交作业、批改、AI 生成、通知投递等)
  • 每服务暴露 /metricsPrometheus 抓取)+ /healthzliveness+ /readyzreadiness
  • apollo-router 在子图查询计划中生成 query_plan / fetch_subgraph span便于定位联邦层延迟v2.1 新增)

10.3 健康检查与降级

服务类型 /healthz 行为 /readyz 行为
网关层 进程存活即 ok 检查下游可达性soft-failure
apollo-router 进程存活即 ok /health(独立 :8088 端口)+ 子图可达性探针
业务服务 进程存活即 ok 检查 DB + Redis + Kafka + gRPC server
config-service 进程存活即 ok 检查 DB + Redis配置缓存
realtime-gateway 进程存活即 ok Redis 软失败(不返 503+ Redis Pub/Sub 订阅状态
data-ana 进程存活即 ok ClickHouse 1s 超时 + CDC lag < 1000 + Redis + iam
Temporal 进程存活即 ok Temporal Server 自带 /healthgRPC health check

11. 契约与 API 架构

11.1 Protobuf 契约体系

实际 proto 文件位于 packages/shared-proto/proto/,共 8 个:ai.proto / analytics.proto / classes.proto / content.proto / core_edu.proto / events.proto / iam.proto / msg.proto。 arch.db 统计2026-07-148 proto 文件 / 23 service / 305 message / 139 RPC。 buf v2 配置:lint STANDARD(含 5 项 except 豁免)+ breaking FILE 级别。 buf.gen.yaml 生成 6 套代码protocolbuffers {go/js/python} + grpc {go/node/python},输出到 shared-{go,ts,py}/gen/proto/

graph TB
    subgraph Proto["shared-proto/proto/ (8 文件)"]
        IamProto[iam.proto<br/>IamService]
        CoreEduProto[core_edu.proto<br/>9 Service: Exam/Homework/Grade/<br/>Attendance/Class/Schedule/<br/>LeaveRequest/Dashboard/Admin]
        ClassesProto[classes.proto<br/>ClassService历史保留]
        ContentProto[content.proto<br/>Textbook/Chapter/<br/>KnowledgeGraph/Question/<br/>Elective/LessonPlan/CoursePlan]
        MsgProto[msg.proto<br/>Notification/Preference/<br/>Template Service]
        DataAnaProto[analytics.proto<br/>AnalyticsService]
        AiProto[ai.proto<br/>AiService]
        EventsProto[events.proto<br/>领域事件 schema]
    end

    subgraph Gen["buf generate (buf v2)"]
        BufGen[buf generate]
        TSGen[shared-ts/gen/proto<br/>@bufbuild/protobuf + grpc-node]
        GoGen[shared-go/gen/proto<br/>protobuf-go + grpc-go]
        PyGen[shared-py/gen/proto<br/>protobuf + grpc-python]
    end

    subgraph Services["消费服务"]
        SvcTS[NestJS 服务<br/>iam/core-edu/content/msg<br/>teacher-bff/student-bff/parent-bff]
        SvcGo[Go 网关<br/>api-gateway/push-gateway]
        SvcPy[Python 服务<br/>data-ana/ai]
    end

    IamProto --> BufGen
    CoreEduProto --> BufGen
    ClassesProto --> BufGen
    ContentProto --> BufGen
    MsgProto --> BufGen
    DataAnaProto --> BufGen
    AiProto --> BufGen
    EventsProto --> BufGen

    BufGen --> TSGen
    BufGen --> GoGen
    BufGen --> PyGen

    TSGen --> SvcTS
    GoGen --> SvcGo
    PyGen --> SvcPy

11.2 契约规则

规则 说明
包命名 proto 包名采用 next_edu_cloud.<domain>.v1 格式(如 next_edu_cloud.iam.v1next_edu_cloud.core_edu.v1
Topic 命名 Kafka topic 采用 edu.<domain>.<aggregate>.<action> 格式(如 edu.identity.user.created
版本化 破坏性变更必须升版本v1 → v2buf breaking FILE 级别 CI 强制
字段编号 禁止复用已删除字段编号,使用 reserved
消息命名 PascalCase
字段命名 snake_case
注释 每个 message 和字段必须注释
CI 强制 buf lint + buf breaking 必须通过
代码生成 buf generate 生成 6 套代码go/js/python × protobuf/grpc

11.3 Apollo Router 联邦聚合模式v2.1 替代 BFF 手写聚合)

v2.1 变更:原 3 个手写 BFFteacher-bff / student-bff / parent-bff的 GraphQL Resolver + gRPC 调用聚合职责,由 apollo-router 自动查询计划Query Plan替代零手写聚合代码。详见 ADR-023 / ADR-025。

graph LR
    subgraph Client["客户端"]
        Q[GraphQL 查询]
    end

    subgraph Router["apollo-router 联邦层"]
        Plan[查询计划 Query Plan<br/>自动拆分 + 并发调度]
        Requires["@requires DataScope 传递"]
    end

    subgraph Subgraphs["业务子图(各服务 /graphql"]
        S1[iam 子图]
        S2[core-edu 子图]
        S3[content 子图]
        S4[msg / data-ana / ai / config 子图]
    end

    Q --> Plan
    Plan --> Requires
    Requires --> S1
    Plan --> S2
    Plan --> S3
    Plan --> S4
    S1 -.visibleClassIds via @requires.-> S2

联邦 vs 手写 BFF 对比

维度 v1 手写 BFF v2.1 Apollo Federation
聚合代码 每个 BFF 手写 Resolver + gRPC Router 自动生成查询计划,零聚合代码
字段变更 proto → 3 套 schema/resolver ×3 proto → 自动生成 GraphQL schema仅改 proto + 前端查询
DataScope 网关透传 + 服务注入 @requires 运行时解析ADR-024
批量去重 每个 BFF 手写 DataLoader 子图 @key 解析器强制 DataLoaderADR-035
协议 GraphQL Yoga每 BFF 独立) Apollo Router唯一入口 :3000

11.4 Apollo Federation 子图契约v2.1 新增)

详见 ADR-023 / ADR-025 / ADR-035 / ADR-036 / ADR-037。子图契约由 proto 自动生成(buf generate + proto → GraphQL 生成器)。

子图暴露规则

规则 说明
端点暴露 每个业务服务暴露 /graphql 子图端点iam:3002 / config-service:3011 / core-edu:3004 / content:3005 / msg:3007 / data-ana:3006 / ai:3008
契约生成 proto → GraphQL 自动生成ADR-025业务服务只实现 Resolver禁止手写 schema
@key 必备 每个可被联邦引用的类型必须声明 @key(fields: "..."),并实现 __resolveReference
DataLoader 强制 所有 @key Reference Resolver 必须使用 DataLoader 批量加载ADR-035ESLint 规则强制校验,禁止 N+1
信任凭证 Router 请求子图时携带 Router-Authorization header子图 RouterAuthGuard 校验,拒绝非 Router 的直接 GraphQL 请求ADR-036
外部/内部边界 外部查询(前端 → 后端)必经 Apollo Router内部调用后端 → 后端)必走 gRPCGraphQL 子图不可作为内部 RPCADR-037
DataScope 跨服务数据范围通过 @requires 指令运行时解析,禁止跨库 JOINADR-024

子图清单与 @key 类型

子图 @key 关键类型 DataScope 依赖
iam User / Role / Permission / School 无(权限源)
config-service PluginConfig / LayoutTemplate / UserOverride
core-edu Exam / Homework / Grade / Attendance / Class / Schedule visibleClassIds / visibleStudentIds
content Textbook / Chapter / Question / KnowledgePoint / LessonPlan editableSubjectIds
msg Notification / Template / Preference visibleNotificationScopes
data-ana Analytics / Dashboard / Mastery / Warning visibleClassIds / visibleStudentIds
ai Chat / Question / Expression / LessonPlan / Report userId(个人级)

Router-Authorization 信任凭证校验示例(见 §5.5 / ADR-036

子图 NestJS Guard 拦截所有 /graphql 请求,校验 Router-Authorization header仅放行来自 apollo-router 的请求,防止外部直接访问子图绕过聚合层。

11.5 错误码前缀矩阵

来源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.6 ActionState 信封规范

来源coord 仲裁 ARB-017 §19.6 适用范围:所有 BFF / Gateway HTTP 响应、GraphQL response 的 errors 扩展字段 设计原则:统一错误信封 + 降级模式方案 Bdegraded 放 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; // 链路追踪 IDX-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"
      }
    }
  ]
}

11.7 portal-shell 前端数据访问层 + GraphQL 安全栈v2.1 M3 新增)

来源:portal-shell 数据抽象与 GraphQL 加固 spec v1.0 / 实施 plan 关联 ADRADR-023Apollo Federation 替代 BFF、ADR-036Router-Authorization、ADR-042前端数据访问四层分层、ADR-043PQ Manifest + APQ 安全加固) 实施阶段v2.1 M1数据抽象层+ M3GraphQL 安全加固)+ M4resolver 守卫补齐)

11.7.1 四层数据访问分层Widget → API → Operations → Hook

v2.1 变更:废弃 widget 内联 gql\...`字面量模式,统一抽取到lib/api/` 四层架构。原 31 个 widget 全部迁移0 处内联 gql 残留。

graph TD
    subgraph Widget["Widget 层UI"]
        W[widgets/*.tsx<br/>只关心渲染]
    end
    subgraph API["API 层(语义化函数)"]
        A1[lib/api/parent.ts]
        A2[lib/api/teacher.ts]
        A3[lib/api/admin.ts]
        A4[lib/api/student.ts]
        A5[lib/api/universal.ts]
        A6[lib/api/sidebar.ts]
        A7[lib/api/topbar.ts]
    end
    subgraph Ops["Operations 层gql 文档集中)"]
        O[lib/api/operations/*.graphql.ts<br/>51 个 DocumentNode 常量]
    end
    subgraph Hook["Hook 层Apollo 封装)"]
        H1[useWidgetQuery]
        H2[useWidgetMutation]
    end
    subgraph Codegen["类型生成"]
        C[graphql-codegen<br/>7 子图 schema.graphql → types.ts]
    end
    W --> A1 & A2 & A3 & A4 & A5 & A6 & A7
    A1 & A2 & A3 & A4 & A5 & A6 & A7 --> O
    A1 & A2 & A3 & A4 & A5 & A6 & A7 --> H1 & H2
    O --> C

层职责矩阵

位置 职责 禁止
Widget src/widgets/*.tsx UI 渲染、用户交互 内联 gql 字面量、直接 import Apollo hooks
API src/lib/api/<domain>.ts 语义化函数(useMyChildren() / saveLessonPlan() 等) 写 gql 字符串、直接操作 Apollo cache
Operations src/lib/api/operations/*.graphql.ts gql DocumentNode 常量集中存放51 个 query/mutation 包含业务逻辑
Hook src/lib/useWidgetQuery/useWidgetMutation.ts Apollo useQuery/useMutation 封装 + ApiError 归一化 引用具体 domain
Types src/lib/api/types.ts + codegen 生成 TS 类型Widget 友好形) 手写

7 个 domain API 文件

文件 Widget 数 主要场景
universal.ts 7 公告 / 通知 / 课表 / 作业 / 考试 / 成绩 / 考勤(多角色复用)
sidebar.ts 3 当前用户 / 我的班级 / 学期列表(侧边栏)
topbar.ts 2 搜索 / 通知铃铛(顶栏,useNotificationBell 限 N 条)
teacher.ts 5 教材 / 教案 / 题目 / 课表规则 / 教案保存 / 课表更新
student.ts 5 AI Tutor / 选修课 / 错题本 / 学习路径 / 错题掌握标记
parent.ts 3 我的子女 / 请假审批 / 请假驳回
admin.ts 11 用户 / 角色 / 权限 / 学校 / 插件注册 / 角色插件映射 / 布局模板 / 邀请码 / 审计日志

类型生成graphql-codegen

  • 配置:apps/portal-shell/codegen.yml
  • schema 来源7 个子图的 services/<svc>/src/graphql/generated/schema.graphql
  • 生成产物:src/lib/api/operations/types.ts(仅类型,无运行时代码)
  • 关键配置:skipDocumentsValidation: true(避免子图未启动时 codegen 失败)
  • schema 归一化:scripts/normalize-schema.ts 移除 federation 指令(@key / @requires / @extends)防止 codegen 误解析

11.7.2 GraphQL 安全栈APQ + PQ Manifest + 深度/成本限制)

v2.1 M3 安全加固:解决"前端 gql 字面量暴露 schema攻击者可构造任意查询探测"风险。

graph LR
    subgraph FE["portal-shell前端"]
        APQ[createPersistedQueryLink<br/>sha256 query → hash]
    end
    subgraph Router["apollo-router :3000"]
        Manifest[pq-manifest.json<br/>hash → query 白名单]
        Limits[limits.max_depth=10<br/>max_cost=1000<br/>max_batch_size=5]
        Intro[introspection<br/>环境变量控制]
    end
    subgraph Sub["子图 /graphql"]
        Guard[RouterAuthGuard<br/>+ @RequirePermission]
    end
    APQ -->|只发 hash| Manifest
    Manifest -->|未知 hash 拒绝| APQ
    Manifest --> Limits
    Limits --> Intro
    Intro --> Guard

安全机制矩阵

机制 位置 防御目标 配置
APQAutomatic Persisted Queries apps/portal-shell/src/lib/apollo-client.ts 前端只发 query hash不发明文 query createPersistedQueryLink({ sha256 })env NEXT_PUBLIC_APOLLO_APQ=false 关闭
PQ Manifest apps/portal-shell/public/pq-manifest.json Router 仅解析白名单 hash拒绝未知 hash 任意查询 51 个 query 的 sha256 → query 映射,由 scripts/generate-pq-manifest.ts 生成
Router 强制 manifest infra/apollo-router/router.yaml 生产模式(require_manifest: true)拒绝未注册查询 env APOLLO_REQUIRE_PQ_MANIFEST=trueentrypoint.sh 启动前检查文件存在性
深度限制 router.yaml limits.max_depth=10 防止深度嵌套查询 DoS 11 层嵌套被 router 拒绝(QUERY_DEPTH_EXCEEDED
成本限制 router.yaml limits.max_cost=1000 防止高成本查询 DoS 按字段复杂度评分累加
批量限制 router.yaml limits.max_batch_size=5 防止批量查询 DoS 单次请求最多 5 个 query
Introspection 控制 router.yaml supergraph.introspection 生产关闭 introspection 防止 schema 泄露 env APOLLO_ROUTER_INTROSPECTION=false(开发默认 true
Router-Authorization 信任 子图 RouterAuthGuard(见 §5.5 防止绕过 Router 直接访问子图 子图校验 Router-Authorization header拒绝非 Router 请求ADR-036
Resolver 字段级权限 子图 @RequirePermission() 装饰器 防止越权访问字段 每个 Resolver 必须声明权限点(见 §3.8

PQ Manifest 生成流程

  1. pnpm --filter @edu/portal-shell run codegen → 从 7 子图 schema 生成 TS 类型
  2. pnpm --filter @edu/portal-shell run generate-pq-manifest → 遍历 lib/api/operations/index.ts 中所有 DocumentNodeprint(doc)sha256(query) 生成映射,写入 public/pq-manifest.json
  3. prebuild 钩子自动串联 codegen + generate-pq-manifest
  4. Docker compose 挂载 pq-manifest.json 到 apollo-router /etc/apollo-router/pq-manifest.json:ro
  5. apollo-router entrypoint.sh 启动前校验 manifest 存在性(require_manifest=true 时缺失即 exit 1

安全栈测试覆盖apps/portal-shell/src/lib/api/__tests__/security.test.ts

  • PQ Manifest 完整性6 casesDocumentNode 校验 / sha256 稳定性 / 确定性 / 唯一性 / manifest 文件有效性 / hash 一致性
  • Query depth limit2 cases11 层嵌套构造 / 合法查询构造(实际拒绝由 router 执行)
  • APQ behavior2 cases默认启用 / NEXT_PUBLIC_APOLLO_APQ=false 关闭

11.7.3 Resolver 权限守卫(@RequirePermission 字段级)

v2.1 M4补齐 19 个 TS resolver 文件的 @RequirePermission() 装饰器。Python 子图data-ana/ai权限基础设施待补审计报告 follow-up。

已审计范围50 个 resolver35 TS + 15 Python

状态 数量 说明
已有 @RequirePermission 37 18 原有 + 19 M4 补齐
缺失守卫 13 4 个 TS/graphql 路径未覆盖,由 RouterAuthGuard 兜底)+ 9 个 Python待补基础设施

TS 审计 follow-up(不阻断 M3 验收):

  • 4 个 TS 子图iam / core-edu / content / msgAuthMiddleware 仅覆盖 REST 路径,未覆盖 /graphql(由 RouterAuthGuard 兜底,仍建议补齐字段级守卫)
  • Python 子图data-ana / ai@RequirePermission 基础设施,需补 Strawberry / Ariadne 中间件

详见:docs/security/graphql-auth-audit-2026-07.md


12. 架构约束

12.1 服务独立性约束

约束 说明
数据库独占 每个微服务独占自身数据库,禁止跨库联表
契约先行 所有跨服务通信必须先定义 protobuf 契约
Outbox 强制 所有领域事件必须通过 Outbox 模式发布
幂等消费 所有事件消费者必须实现幂等性
单一职责 每个服务只负责一个限界上下文
无状态服务 业务服务不持有会话状态Redis 承载)

12.2 通信约束

v2.1 变更BFF 环节由 apollo-router 替代push-gateway 改名 realtime-gateway外部 GraphQL 必经 Router内部 RPC 必走 gRPCADR-037

场景 允许 禁止
客户端 → Gateway REST + SSE 直连业务服务
Gateway → apollo-router HTTP 反向代理(路径重写剥离前缀) gRPC 转换、REST 业务逻辑
Gateway → 业务服务 HTTP 反向代理(剥离 /api 前缀,降级路径) gRPC 转换
apollo-router → 业务子图 GraphQL /graphql携带 Router-Authorization 直接访问 DB、绕过 RouterAuthGuard
业务子图 → 业务子图 禁止(外部 GraphQL 不可作内部 RPC 子图互调 GraphQLADR-037
业务服务之间(同步) gRPC REST、直接 DB、GraphQL 子图互调
业务服务之间(异步) Kafka 事件Outbox + Debezium 模式) 直接 producer 调用
realtime-gateway → msg Redis Pub/Sub 订阅ADR-040 直挂 Kafka、gRPC豁免ARB-015
msg worker → realtime-gw Redis Pub/Sub PUBLISH按 userId 路由) 直连 Kafka、HTTP 轮询
事件发布 Outbox 表 + Debezium Transaction Log Tailing OutboxPublisher 轮询线程、直接 producer
DataScope 跨服务 @requires 运行时解析ADR-024/041 跨库 JOIN、HTTP 传全量 ID 数组

12.3 数据一致性约束

场景 一致性级别 实现
聚合内 强一致 单事务
聚合间 最终一致 Kafka 事件
服务间 最终一致 Kafka 事件 / Saga
读模型 最终一致 Projection 异步更新
缓存 最终一致 Eager Invalidation写后同步 DEL+ Kafka 投影器兜底ADR-038

12.4 v2.1 架构约束新增ADR-023~041

约束 说明 依据
外部 GraphQL 必经 Router 前端 → 后端的所有 GraphQL 查询必须且只能经过 apollo-router 聚合层 ADR-023/037
内部 RPC 必走 gRPC 后端服务之间的所有同步调用必须使用 gRPC禁止 REST/GraphQL 子图互调 ADR-037
子图不可作内部 RPC GraphQL 子图纯粹服务前端展现,禁止作为微服务间内部 RPC 协议 ADR-037
DataScope 必用 @requires 跨服务数据范围必须通过 @requires 指令运行时解析,禁止跨库 JOIN ADR-024/041
@key 必用 DataLoader 所有子图 @key Reference Resolver 必须使用 DataLoader 批量加载 ADR-035
Router-Authorization Router 请求子图携带信任凭证 header子图 Guard 校验,拒绝非 Router ADR-036
Outbox + CDC 结合 业务代码只写 Outbox 表Debezium 监听 binlog 投递,禁止轮询线程 ADR-032
Temporal 严格边界 仅限 AI 耗时工作流 + SagaCRUD 短事务绝对禁止用 Temporal ADR-030
SSE 优先 推送默认走 SSEWS 仅限监考等强双向场景 ADR-029
Eager Invalidation 写后同步 Redis DELKafka 投影器仅作兜底 ADR-038
乐观锁版本号回传 写接口返回 version读携带 expectedVersionES 落后则穿透读 MySQL ADR-039

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 微前端 独立部署、技术栈无关、渐进迁移 废弃v2.1 由 ADR-033 portal-shell 替代)
ADR-013 采用 Temporal 工作流编排 长流程编排、可观测、可回滚 已采纳v2.1 ADR-030 严格边界:仅限 AI 工作流 + SagaCRUD 短事务禁用)
ADR-014 采用 ClickHouse 读模型宽表 分析查询亚秒级响应 已采纳
ADR-015 api-gateway 采用 HTTP 反向代理(非 gRPC 转换) Go 网关只做 HTTP简化部署下游 HTTP/gRPC 双协议 已采纳
ADR-016 push-gateway 豁免 gRPC 基础设施层无流式推送场景msg/student-bff/parent-bff 改用 HTTP /internal/* 已采纳v2.1 push-gateway 改名 realtime-gatewayADR-034
ADR-017 classes 服务合并入 core-edu 教学组织与教学核心共享聚合根边界 已采纳
ADR-018 BFF 统一 GraphQL Yoga + DataLoader + opossum 熔断 协议统一、批量去重、降级模式 Bdegraded 子字段) 废弃v2.1 由 ADR-023 Apollo Federation 替代)
ADR-019 admin-portal GraphQL 经 teacher-bff 命名空间 复用 teacher-bff resolveradmin role 中间件强制隔离 废弃v2.1 由 ADR-033 portal-shell 替代)
ADR-020 parent-portal 作为 MF Remote 被 teacher-portal 加载 家长入口可由教师端嵌入,统一 Shell 宿主 废弃v2.1 由 ADR-033 portal-shell 替代)
ADR-021 Trace 存储 Jaeger 替代 Tempo all-in-one 镜像部署简单OTLP 原生支持 已采纳
ADR-022 共享包 ui-tokens 替代原 shared-tokens 命名 三层令牌primitive/semantic/tailwind-theme实际落地 已采纳
ADR-023 采用 Apollo Federation 替代 3 个手写 BFF 工业标准,消灭样板代码(每字段改动从 6 处 → 2 处) 已采纳v2.1
ADR-024 DataScope 通过 @requires 运行时解析 解决跨库 JOIN 悖论,子图间传递可见范围 已采纳v2.1
ADR-025 proto → GraphQL 自动生成 单一数据源,字段变更只改 proto禁止手写 schema 已采纳v2.1
ADR-026 iam 拆分 config-service 避免上帝服务iam 承载认证+RBAC+审计+JWKS+插件配置,职责过载) 已采纳v2.1config-service :3011/:50059
ADR-027 content CQRS 改造 写读分离故障隔离MySQL 写 + Neo4j/ES 读投影) 已采纳v2.1
ADR-028 ai 无状态化 明确边界可扩展WorkflowStateStore → Redis-onlyTTL 1h 已采纳v2.1
ADR-029 SSE 优先 + WS 仅监考 K12 场景单向为主WS 心跳/握手开销过大 已采纳v2.1
ADR-030 Temporal 严格边界引入v2.1 修订) AI 工作流 + Saga 用 TemporalCRUD 短事务绝对禁止,走同步调用 + 分布式锁 已采纳v2.1Temporal :7233/UI :8085/PG :5433
ADR-031 config-service + Redis 轻量配置中心 K12 场景,不引入 etcd/Consul过重 已采纳v2.1
ADR-032 Outbox + CDC 结合v2.1 修订) 业务写 Outbox 表Debezium 监听 binlog 自动投递Transaction Log Tailing废弃轮询线程 已采纳v2.1
ADR-033 portal-shell 单容器替代 4 portal Modular Monolith + Micro-kernel减少重复单 Dockerfile 已采纳v2.1portal-shell :4010
ADR-034 realtime-gateway 改名 SSE 优先定位(原 push-gateway 已采纳v2.1
ADR-035 DataLoader 强制(@key 解析器) 消除 N+1 查询ESLint 规则强制校验 已采纳v2.1
ADR-036 Router-Authorization 信任凭证 拒绝非 Router 的直接 GraphQL 请求,防止绕过聚合层 已采纳v2.1
ADR-037 外部 GraphQL + 内部 gRPC 边界 GraphQL 仅服务前端展现,后端互调走 gRPC性能 + 强类型 + 流式) 已采纳v2.1
ADR-038 Eager Invalidation主动失效 写后同步 Redis DELKafka 投影器仅作兜底,降低读延迟 已采纳v2.1
ADR-039 乐观锁版本号回传 CQRS 读后一致性:写返回 version读携带 expectedVersionES 落后则穿透读 MySQL 已采纳v2.1
ADR-040 Redis Pub/Sub 推送背板 边缘网关不挂 Kafkamsg worker → Redis Pub/Sub → realtime-gateway 订阅,轻量 + 按需订阅 已采纳v2.1
ADR-041 ScopeToken 优化大规模 ID 列表 不传全量数组,传极短 token子图从 Redis SMEMBERS 获取,降低 HTTP payload 开销 已采纳v2.1
ADR-042 portal-shell 前端数据访问四层分层2026-07-17 Widget → API → Operations → Hook废弃 widget 内联 gql 字面量,集中管理 GraphQL 文档 已采纳v2.1lib/api/ 7 domain + 51 operations
ADR-043 PQ Manifest + APQ 安全加固2026-07-17 前端只发 query hashRouter 仅解析白名单 manifest叠加深度/成本/introspection 限制 已采纳v2.1pq-manifest.json + router.yaml limits

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 服务与阶段映射

状态基于代码现状2026-07-14 已落地 / 🚧 部分落地 / 规划中。

阶段 周期 交付服务 退出标准 实际状态
P1 地基 M1-M3 api-gateway、classes黄金模板、shared-proto classes 域 CRUD 端到端跑通 完成classes 已合并入 core-edu
P2 身份 M4-M6 iam、teacher-bff、teacher-portal 骨架 教师可登录并看到空白 Dashboard 完成
P3 核心教学 M7-M10 core-edu合并 classes、student-bff、student-portal 考试→作答→批改→成绩全链路 完成(含 LeaveRequests/Dashboard/Admin/IamConsumer 扩展)
P4 内容分析 M11-M13 content、data-ana、parent-bff、parent-portal 知识图谱查询 + 学情宽表 5s 完成(含 7 模块 + Neo4j + ES sync + CDC consumer + 11 analytics 端点)
P5 沟通AI M14-M16 msg、push-gateway、ai 全校广播 + AI 辅助出题 完成msg 4 模块 4 渠道 + push-gateway WS + Kafka + ai 11 端点 + 备课工作流)
P6 硬化 M17-M18 admin-portal、Service Mesh 99.9% 可用性 + 独立扩缩容 🚧 admin-portal 已落地Service Mesh / Vault / 99.9% SLO 待硬化

详细规划见 路线图目录tech-debtpending-features


15. 实施状态索引v2.0 新增)

本节按服务/包罗列实际落地的模块、Controller、关键组件作为代码现状的快速索引。 详细模块设计见各服务 README.mddocs/01-understanding.md

15.1 api-gatewayGo :8080

  • 中间件链Recovery → otelgin → RequestID → CORS → SecurityHeaders → RequestBodyLimit(10MB) → RateLimit(100rps+20burst) → CircuitBreaker → AuthMiddleware(JWKS RS256) → Metrics
  • 路由组
    • /healthz/readyz/metrics(公开)
    • /api/v1/{classes,iam,exams,homework,grades,textbooks,chapters,knowledge-points,questions,notifications,messages,announcements,ai,analytics,dashboard}/* → HTTP 反向代理
    • /api/v1/{teacher,student,parent}/* → BFF 反向代理(剥离 /api/v1/{bff} 前缀)
    • /api/admin/graphql → teacher-bff /graphqlAdminRoleMiddleware 强制)
  • 关键依赖shared-go/jwks(公钥缓存)、shared-go/envshared-go/loggershared-go/tracer

15.2 push-gatewayGo :8081

  • WebSocket 端点/wsJWT RS256 via query ?token= 或 Authorization 头)
  • 内部 HTTP API/internal/push/internal/broadcast/internal/online/:userIDX-Internal-Key 鉴权)
  • 跨实例广播Redis Pub/SubSubscribeAll + RebuildPresenceOnStartup
  • Kafka 消费edu.notify.notification.sentARB-013
  • 健康检查/healthzliveness/readyzRedis 软失败 + Kafka consumer 状态)
  • 依赖hubin-memory connection registryredisclientkafkaconsumerwsobservability

15.3 iamNestJS :3002 / gRPC :50052

  • ControllersIamController、RbacController、AuditController、JwksController、IamGrpcController
  • ServicesIamService、JwksService、PermissionCacheServiceRedis、TokenBlacklistService
  • Outboxiam_outbox 表,发布到 edu.identity.user.* topic
  • APP_GUARDPermissionGuardDB 驱动 + Redis 缓存I3 裁决)
  • AuthMiddleware 应用范围:v1/iam/melogoutchange-passwordviewportspermissions/effectivechildrenrolespermissionsusersaudittotp

15.4 core-eduNestJS :3004 / gRPC :50053

  • 业务模块10
    • exams含 exam-extensions、exam-state-machine
    • homework含 homework-state-machine
    • grades含 grade-calculator
    • attendance
    • classes含 teacher-associationsC1 合并自原 classes 服务)
    • scheduling含 schedule-conflict 检测)
    • leave-requestsP3.13
    • dashboard
    • admin
    • iam-consumer消费 IAM 6 类事件)
  • 共享组件datascope-injectorRepository 层 WHERE 注入、outboxcore_edu_outbox、global-error.filter
  • gRPC 服务943 RPCExamService / HomeworkService / GradeService / AttendanceService / ClassService / ScheduleService / LeaveRequestService / DashboardService / AdminService

15.5 contentNestJS :3005 / gRPC :50054

  • 业务模块7textbooks、chapters、knowledge-points、questions、electives、lesson-plans、course-plans
  • gRPC controllers7Textbook / Chapter / KnowledgeGraph / Question / Elective / LessonPlan / CoursePlan
  • 同步 workeres-sync.worker题库 ES 索引、neo4j-sync.worker知识图谱节点
  • Outboxcontent_outbox发布 edu.content.* 事件
  • 存储MySQL业务表+ Neo4j知识图谱+ Elasticsearch题库检索

15.6 msgNestJS :3007 / gRPC :50056

  • 业务模块4notifications、preferences、templates、announcements
  • gRPC controllers317 RPCNotificationService9 RPC/ NotificationPreferenceService2 RPC/ NotificationTemplateService6 RPCARB-008 裁剪)
  • 渠道4email、sms、push、in-appchannel-dispatcher 路由)
  • Kafka 消费16 类事件iam 6 + core-edu 9 + data-ana 1shared/kafka/topic-map.ts
  • Kafka 生产4 类 edu.notify.notification.* 事件 + 1 兜底 topic
  • 辅助组件push-gateway.clientHTTP 调用 /internal/push、idempotency.guardRedis SETNX 去重、Outboxmsg_outbox
  • 存储MySQL + Elasticsearch消息全文检索+ Redis幂等性

15.7 data-anaFastAPI :3006 / gRPC :50055

  • HTTP 端点3 基础 + 11 业务 = 14
    • 基础://healthz/readyz
    • 业务(全部 ActionState 信封):
      • /analytics/class/{class_id}/performance
      • /analytics/student/{student_id}/{weakness,trend,errorbook,mastery}
      • /analytics/{teacher,student,parent,admin}/dashboard
      • /analytics/warnings + /analytics/warnings/trigger
      • /analytics/class/{class_id}/mastery-distribution
  • gRPC 服务AnalyticsService18 RPC
  • CDC 消费者:手动 commitlag 阈值 1000超过 readyz 返 503
  • /readyz 4 依赖ClickHouse1s+ CDC consumerlag<1000+ Redis200ms+ iam gRPC2s
  • 存储ClickHouse宽表+ Redis缓存+ iam gRPC用户信息

15.8 aiFastAPI :3008 / gRPC :50058

  • HTTP 端点11
    • /healthz/readyz
    • /v1/ai/chat(非流式)+ /v1/ai/chat/streamSSE
    • /v1/ai/generate/question + /v1/ai/generate/question/streamSSE
    • /v1/ai/optimize/expression
    • /v1/ai/lesson-plan/generate + /v1/ai/lesson-plan/status/{workflow_id} + /v1/ai/lesson-plan/confirm/{workflow_id}
    • /v1/ai/generate/reportclass_summary / student_detail / exam_analysis
  • gRPC 服务AiService9 RPC
  • 核心组件LLM FailoverChain4 适配器 + 熔断 + 故障切换、PromptTemplateServiceJinja2 + YAML、QualityGateRuleValidator + LLMJudge
  • 用量与配额UsageRecorderRedis+ QuotaEnforcer + KafkaProduceredu.ai.usage.*
  • 限流RateLimiterRedis 三维度令牌桶user/ip/school
  • 安全PII redactor + 输入清洗 + 输出审核
  • 备课工作流4 步编排 + WorkflowStateStoreRedis TTL
  • 下游 gRPC 客户端ContentClientGrpc / DataAnaClientGrpc / IamClientGrpc连接失败降级不阻断启动

15.9 teacher-bffNestJS :3003 / GraphQL Yoga

  • 模块GraphQLModuleTeacher + Admin ResolversARB-001 扁平合并)+ HealthModule + MiddlewareModule
  • 下游客户端6iam / core-edu / content / data-ana / msg / aiB8 裁决统一抽象gRPC + mock 双实现)
  • GraphQL 端点/graphql(同时承载 teacher 与 admin 命名空间)
  • SSE 控制器ai-chat-sse.controller.ts(透传 ai 服务流式响应)
  • Health probes6iam-grpc / core-edu-grpc / content-grpc / data-ana-grpc / ai-grpc / msg-grpc + redis probe

15.10 student-bffNestJS :3009 / GraphQL Yoga

  • 模块8CacheModuleGlobal Redis+ DownstreamModuleGlobal gRPC+ CircuitBreakerModuleGlobal opossum+ HealthModule + DataLoaderModule + StudentModuleGraphQL Yoga + Resolver+ PushGatewayModuleHTTP /internal/push+ EventModuleKafka 事件订阅 + push-gateway 推送)
  • 下游客户端iam / core-edu / data-ana

15.11 parent-bffNestJS :3010 / GraphQL Yoga

  • 模块6HealthModule + GraphqlModuleYoga /v1/graphql+ ClientsModuleiam + core-edu + data-ana + msg + push-http+ KafkaModulecache-invalidation + notification-push handler+ AggregationModuleorchestrator + fallback-strategy + child-guard + response-mapper+ DataLoaderModuledataloader.factory + loaders 批量去重)
  • 缓存Redis + LRU cache + cache-key.builder

15.12 teacher-portalNext.js 15 :4000MF Shell

  • 路由组25+ 业务页面dashboard、classes、schedule、exams、homework、grades、attendance、analytics、knowledge-graph、lesson-plans、course-plans、textbooks、questions、notifications、students、ai-assist、ai-lesson-plan、ai-report、diagnostic、error-book、practice、leave、elective、schedule-changes、settings
  • MF Remote 加载ParentPortalRemote在 teacher-portal 内嵌入家长端视图)
  • GraphQL 客户端文件graphql.tsbase+ graphql-p4.ts / p5.ts / p7-admin.ts / p7-advanced.ts / p7-exams.ts / p7-grades.ts / p7-insights.ts按阶段渐进扩展
  • 可观测性observability-providerOTel Web + Sentry + web-vitals + performance-dashboard
  • 国际化messages/{en,zh-CN}.json
  • 认证lib/auth.tscookie 迁移 + token 刷新 + cross-tab sync

15.13 student-portal / parent-portal / admin-portalNext.js 15 :4001/:4002/:4003MF Remote

  • student-portal:路由组 leave + 主页exam-types 类型mocks/handlers + server
  • parent-portallogin + 主页 + providerschild-storeZustandmiddleware.tsPWAmanifest + sw.js
  • admin-portallogin + admin layouthooksuse-classes/files/roles/school/students/teachers/users/graphqllib/{auth,i18n,permissions,web-vitals}

15.14 共享包实施状态

关键导出 状态
shared-proto 8 proto 文件 + buf.yaml + buf.gen.yaml生成 6 套代码)
shared-ts bff/loggeroutbox/OutboxModule + publisher + schema + types被 iam/core-edu/content/msg/BFF 共享)
shared-go env/config.Loadjwks/FetcherRS256 公钥缓存)、logger/slog JSONtracer/OTLP init
shared-py pyproject.tomluv workspace member 🚧
contracts STUDENT_PERMISSIONS 常量12 权限点) 🚧
hooks use-auth / use-permission / use-viewports / use-graphql-client / use-trace-id / use-a11y-id / use-aria-live / use-api / use-toast
ui-components data-table / filter-bar / form / modal / chart / calendar / status-badge / empty / loading + utils/cn
ui-tokens primitive.css / semantic-light.css / semantic-dark.css / tailwind-theme.css / all.css + colors/shadows/spacing/typography (.ts)

15.15 部署与运维实施状态

  • 本地开发infra/docker-compose.yml(基础设施)+ pnpm dev / go run / uv run
  • 最小化部署infra/docker-compose.minimal.yml
  • 生产部署infra/docker-compose.deploy.ymlbuild: 替代 image:
  • 测试部署infra/docker-compose.test.yml
  • 监控栈infra/docker-compose.monitoring.ymlobservability profile
  • K8s Helm chartinfra/k8s/helm/edu-platform umbrella + 各子 chart
  • CI/CD.github/workflows/ci.ymlquality-ts / quality-go / quality-proto / deployno-push 本地构建模式)
  • 备份infra/backup/backup-mysql.sh(每服务独立,保留 7 天)
  • 混沌工程infra/chaos/experiments.yaml
  • WAFinfra/security/waf-rules.conf
  • 端口分配infra/port-allocation.md(唯一源)

15.16 文档体系实施状态

路径 用途
docs/architecture/004_architecture_impact_map.md 本文件(架构影响地图,设计意图唯一源)
docs/architecture/0010_architecture.md 理想蓝图(目标态)
docs/architecture/004-p6-addendum.md P6 硬化附录
docs/architecture/roadmap/ tech-debt / pending-features / integration-test-phase
docs/architecture/runbooks/ p6-hardening / post-p6-followup / incident-response
docs/architecture/issues/ 协调记录coord / matrix / workline+ contracts/ + objections/ + worklines/
docs/architecture/ai-allocation.md AI 模块分配
docs/architecture/ai-work-orchestration.md AI 工作编排
docs/architecture/coord-cross-review.md coord 交叉审查
docs/architecture/coord-final-decisions.md coord 最终裁决ARB-001~022+
docs/architecture/president-final-rulings.md president 最终裁决batch 0.9+
docs/modules/<service>/README.md 各服务模块文档iam/core-edu/content/msg/data-ana/ai/api-gateway/push-gateway/classes
docs/standards/ coding-standards / cicd-runbook / full-stack-runbook / git-workflow / local-dev-runbook / multi-ai-collaboration / ui-design-system
docs/troubleshooting/known-issues.md 已知问题速查(索引式场景→技术映射)

16. v2.1 实施状态索引v2.1 新增)

本节记录 v2.1 架构重设计Apollo Federation + portal-shell的实施进度。 设计源:v2 重设计 spec v2.1 + portal-shell 仪表盘 spec v2.1。 实施遵循 11 阶段迁移计划M0-M11跨模块变更顺序shared-proto → 业务服务 → apollo-router → portal-shell项目规则 §14.4)。

16.1 迁移阶段状态

阶段 内容 退出标准 依赖 状态
M0 proto → GraphQL 代码生成工具链 buf generate 输出 GraphQL schema 完成
M0.5 Debezium Connect 部署 + Outbox connector Debezium 监听 outbox 表推送到 Kafka 完成
M1 各服务暴露 GraphQL 子图 + DataLoader + RouterAuthGuard /graphql 端点可查询,非 Router 请求被拒 M0 完成
M2 apollo-router 部署 + Supergraph 组装 Router 可聚合查询 M1 完成
M3 iam 拆分 config-service config-service 独立运行(:3011/:50059 M1 完成
M4 DataScope @requires + ScopeToken 实现 教师查询可见班级成绩正确 M2 完成
M5 content CQRS 改造(投影器 + Eager Invalidation + 乐观锁) Neo4j/ES 通过投影器同步,写后读一致 M0.5/M1 完成
M6 ai 无状态化 ai 不存业务数据,状态在 RedisTTL 1h M1 完成
M6.5 Temporal Server 部署 + ai 接入 Worker AI 耗时工作流可运行(:7233/UI :8085/PG :5433 M6 完成
M7 realtime-gateway SSE 优先 + Redis Pub/Sub SSE 推送可用,边缘不挂 Kafka 完成
M8 portal-shell 接入 apollo-router portal-shell 查询走 Router M2/M4 完成
M9 旧 BFF 下线teacher/student/parent-bff 流量为 0 M8 完成
M10 旧 portal 下线 流量为 0 M8 完成
M11 废弃各服务 OutboxPublisher 轮询线程 无服务使用轮询投递 M0.5 完成
M12 arch.db + 004 文档同步 arch:scan 通过 全部 完成

16.2 v2.1 新增/变更服务实施状态

服务 类型 端口 关键变更 状态
apollo-router 新增 :3000 Apollo Federation 聚合层,替代 3 BFFADR-023 完成
config-service 新增 :3011/:50059 从 iam 拆分,插件配置 + 布局 + 用户偏好ADR-026 完成
portal-shell 新增 :4010 Modular Monolith 单容器,替代 4 portalADR-033 完成
Temporal 新增 :7233/:8085/:5433 AI 工作流 + Saga 引擎严格边界ADR-030 完成
realtime-gateway 改名 :8081 原 push-gatewaySSE 优先 + Redis Pub/SubADR-029/034/040 完成
iam 拆分 :3002/:50052 移除插件配置职责,专注认证+RBAC+JWKS+DataScope 完成
content 改造 :3005/:50054 CQRSMySQL 写 + Neo4j/ES 读投影ADR-027 完成
ai 改造 :3008/:50058 无状态化WorkflowStateStore → RedisADR-028 完成

16.3 v2.1 下线服务

服务 / 应用 下线原因 对应阶段
teacher-bff 流量切到 apollo-router 后下线 M9
student-bff 流量切到 apollo-router 后下线 M9
parent-bff 流量切到 apollo-router 后下线 M9
teacher-portal 流量切到 portal-shell 后下线 M10
student-portal 流量切到 portal-shell 后下线 M10
parent-portal 流量切到 portal-shell 后下线 M10
admin-portal 流量切到 portal-shell 后下线 M10
OutboxPublisher 轮询线程 由 Debezium Transaction Log Tailing 替代ADR-032 M11

16.4 v2.1 关联 spec 文档

文档 用途
docs/superpowers/specs/2026-07-14-architecture-v2-redesign-design.md v2.1 架构重设计主 specADR-023~041 决策源)
docs/superpowers/specs/2026-07-14-portal-shell-widget-dashboard-design.md portal-shell 插件化仪表盘设计 spec
docs/superpowers/specs/2026-07-17-portal-shell-data-abstraction-and-graphql-hardening-design.md portal-shell 前端数据访问层 + GraphQL 安全栈加固 specADR-042/043
docs/superpowers/plans/2026-07-17-portal-shell-data-abstraction-and-graphql-hardening.md portal-shell 数据抽象与 GraphQL 加固 20-task 实施 plan
docs/security/graphql-auth-audit-2026-07.md 50 resolver @auth 审计报告35 TS + 15 Python
infra/port-allocation.md 端口分配唯一源(含 apollo-router/config-service/Temporal

16.5 portal-shell 数据抽象与 GraphQL 加固子阶段2026-07-17 新增)

来源:spec v1.0 + plan 关联 ADRADR-042前端数据访问四层分层、ADR-043PQ Manifest + APQ 安全加固)

子阶段 内容 退出标准 状态
M1 lib/api 四层架构 + 7 domain 迁移 + 31 widget 全量切换 0 处内联 gql 字面量、55 domain 测试通过 完成
M2 迁移完整性验证typecheck + lint + test 全绿) 0 error / 0 warning / 95 测试通过30 原有 + 55 domain + 10 安全) 完成
M3 GraphQL 安全加固APQ + PQ Manifest + router limits + 测试) apollo-router 启用 manifest + 深度/成本限制 + 10 安全测试 完成
M4 Resolver @RequirePermission 审计 + 补齐 50 resolver 审计完成、19 TS resolver 补齐守卫 完成

Follow-up不阻断 M3 验收)

  • 4 个 TS 子图 AuthMiddleware 覆盖 /graphql 路径(当前由 RouterAuthGuard 兜底)
  • Python 子图data-ana / ai@RequirePermission 基础设施Strawberry / Ariadne 中间件)
  • 生产环境部署前将 APOLLO_REQUIRE_PQ_MANIFEST=true + APOLLO_ROUTER_INTROSPECTION=false 写入部署 env

本文件 v2.1 已基于代码现状2026-07-15全面校准反映 Apollo Federation + portal-shell 架构重设计M0-M11 全部完成)+ 2026-07-17 portal-shell 数据抽象与 GraphQL 加固M1-M4 完成)。后续代码变更须按 项目规则 §1 同步更新本文件 + 运行 pnpm run arch:scan