Files
Edu/docs/architecture/issues/contracts/api-gateway_contract.md

16 KiB
Raw Permalink Blame History

api-gateway 对接契约

负责人ai01 关联:matrix.mdiam.protoobjections/api-gateway_issue.mdworklines/api-gateway_workline.md02-architecture-design.md 依据:coord-final-decisions.md §3.8 W1-W8、president-final-rulings.md §2.15/§2.16/§2.19 版本v22026-07-10 修正:路径前缀 /api→/api/v1、JWKS 拉取方式 gRPC→HTTP、删除 GetEffectiveAccess、错误码加 GW_ 前缀)


§1 我提供什么(对外接口)

1.1 gRPC 接口

无。api-gateway 是 HTTP 入口,不对外提供 gRPC依据 president-final-rulings.md §2.16 裁决gateway 保持 HTTP 透传,不改为 gRPC 客户端)。

1.2 HTTP 端点

1.2.1 健康检查与指标端点(公开,无鉴权)

Method Path 用途 认证 阶段
GET /healthz 网关存活探针liveness 公开 P1
GET /readyz 网关就绪探针readiness并行 ping 下游 /healthz 公开 P2
GET /metrics Prometheus 指标端点7 个业务指标 + Go runtime 公开(内网) P2

1.2.2 反向代理路由JWT 必需,除白名单外)

路径前缀:所有业务路由统一 /api/v1/* 前缀(与 main.go L59 r.Group("/api/v1") 一致)。

API 版本化:按 president-final-rulings.md §2.15 裁决,各服务 Controller 内加 /v1 前缀。Gateway 透传时不改路径,前端调用 /api/v1/<service>/v1/*ISSUE-003 待 coord 仲裁最终方案,本表暂列方案 A

Method PathGateway 外部路径) 目标服务 端口 鉴权 公开子路径 阶段
ANY /api/v1/iam/v1/*path + /api/v1/iam/v1 iam 3002 JWT /iam/v1/register /login /refresh P2
ANY /api/v1/teacher/v1/*path + /api/v1/teacher/v1 teacher-bffGraphQL 3003 JWT P2
ANY /api/v1/student/v1/*path + /api/v1/student/v1 student-bffGraphQL 3009 JWT P3
ANY /api/v1/parent/v1/*path + /api/v1/parent/v1 parent-bffGraphQL 3010 JWT P4
ANY /api/v1/classes/v1/*path + /api/v1/classes/v1 core-educlasses 合并) 3004 JWT P3
ANY /api/v1/exams/v1/*path + /api/v1/homework/v1/*path + /api/v1/grades/v1/*path core-edu 3004 JWT P3
ANY /api/v1/textbooks/v1/*path + /api/v1/chapters/v1/*path + /api/v1/knowledge-points/v1/*path + /api/v1/questions/v1/*path content 3005 JWT P4
ANY /api/v1/analytics/v1/*path + /api/v1/dashboard/v1/*path data-ana 3006 JWT P4
ANY /api/v1/notifications/v1/*path + /api/v1/messages/v1/*path msg 3007 JWT P5
ANY /api/v1/ai/v1/*path + /api/v1/ai/v1 ai 3008 JWT P5
ANY /api/v1/admin/v1/*path + /api/v1/admin/v1 teacher-bff admin namespace 3003 JWT + admin 角色 P6

注 1:每个前缀同时注册无尾斜杠与通配符两条路由(/iam/v1 + /iam/v1/*path),因为 r.RedirectTrailingSlash = falsemain.go L34

注 2admin-portal P6 阶段复用 teacher-bff + admin schema 命名空间(依据 coord.md §1.3 ARB-001 裁决)。

注 3:当前 P1 代码暂未加 /v1 前缀(如 /api/v1/iam/*path),待 ISSUE-003 仲裁后 P2.7 任务统一迁移。

1.2.3 透传请求头Gateway 注入,下游读取)

头名 来源 用途 下游消费方
x-user-id JWT sub claim 用户身份传递 所有下游
x-user-roles JWT roles claim逗号分隔 角色传递 所有下游
x-data-scope JWT data_scope claim 数据范围传递P2 新增) 所有下游
X-Request-Id 客户端透传或 Gateway 生成 UUID 请求 ID全链路追踪 所有下游
traceparent OTel SDK 自动处理 W3C Trace Context链路追踪 所有下游
tracestate OTel SDK 自动处理 W3C Trace Context 扩展 所有下游

1.3 GraphQL schema

不适用。api-gateway 仅做 HTTP 反向代理 + JWT 验签,不解析 GraphQLGraphQL 由 BFF 层处理)。

1.4 Kafka 事件发布

无。api-gateway 不发布 Kafka 事件(纯同步 HTTP 反向代理)。

1.5 错误码前缀

GW_(依据 coord-final-decisions.md W1 / G14 裁决)

1.6 错误码清单

错误码 HTTP 触发条件 响应体ActionState 信封)
GW_UNAUTHORIZED 401 缺失 Authorization 头 {success:false,error:{code:"GW_UNAUTHORIZED",message:"..."}}
GW_INVALID_TOKEN 401 JWT 签名/格式错误 同上
GW_INVALID_CLAIMS 401 JWT claims 解析失败 同上
GW_RATE_LIMITED 429 超出令牌桶限流 {success:false,error:{code:"GW_RATE_LIMITED",message:"...",retry_after:60}}
GW_CIRCUIT_OPEN 503 下游熔断打开 {success:false,error:{code:"GW_CIRCUIT_OPEN",message:"...",retry_after:30}}
GW_REQUEST_TOO_LARGE 413 请求体超 10MB {success:false,error:{code:"GW_REQUEST_TOO_LARGE",message:"..."}}
GW_INTERNAL_ERROR 500 panic 兜底 {success:false,error:{code:"GW_INTERNAL_ERROR",message:"...",request_id:"..."}}

依据 W1 / W2 裁决:错误码统一 GW_ 前缀,响应体统一 ActionState 信封 {success,error:{code,message}}


§2 我消费什么(依赖上游)

2.1 gRPC 调用(同步)

。依据 president-final-rulings.md §2.16 裁决"gateway 保持 HTTP 透传,不改为 gRPC 客户端"api-gateway 不消费任何 gRPC 接口。

勘误v1 版本曾错误声明消费 IamService.GetPublicKeyIamService.GetEffectiveAccessv2 已删除):

  • JWT 公钥拉取走 HTTP JWKS 端点,非 gRPC见 §2.3
  • Gateway 不做权限点校验,不消费 GetEffectiveAccess(权限由下游服务 Controller @RequirePermission 自校验,见 01-understanding.md §2 + B3 裁决)
  • matrix.md §2 gRPC 接口提供方矩阵中"iam 消费方"应移除 api-gateway已提请 ISSUE-001

2.2 Kafka 事件订阅

无。api-gateway 不订阅 Kafka 事件。

2.3 HTTP 调用(同步)

被调用方 Method.Path 用途 mock 策略 阶段
iam (ai06) GET /.well-known/jwks.json 拉 RS256 公钥集JWKSTTL 5min 缓存 iam 就绪前使用本地固定 mock RS256 公钥(与 mock 私钥配对签发 mock JWT P2
iam (ai06) GET /healthz /readyz 下游健康检查 iam 就绪前 /readyz 软失败(返回 200 + degraded P2
teacher-bff (ai03) POST /graphql(反向代理) 代理教师端 GraphQL 请求 teacher-bff 就绪前返回 503 + Retry-After前端降级到本地 mock P2
student-bff (ai04) POST /graphql(反向代理) 代理学生端 GraphQL 请求 student-bff 就绪前返回 503 P3
parent-bff (ai05) POST /graphql(反向代理) 代理家长端 GraphQL 请求 parent-bff 就绪前返回 503 P4
core-edu (ai08) GET /healthz + 业务 REST反向代理 /readyz 检查 + 业务请求代理 core-edu 就绪前 /readyz 软失败 P3
content (ai09) GET /healthz + 业务 REST /readyz 检查 + 业务请求代理 content 就绪前 /readyz 软失败 P4
data-ana (ai11) GET /healthz + 业务 REST /readyz 检查 + 业务请求代理 data-ana 就绪前 /readyz 软失败 P4
msg (ai10) GET /healthz + 业务 REST /readyz 检查 + 业务请求代理 msg 就绪前 /readyz 软失败 P5
ai (ai12) GET /healthz + 业务 REST /readyz 检查 + 业务请求代理 ai 就绪前 /readyz 软失败 P5

JWKS 缓存策略packages/shared-go/jwks/jwks.go

  • TTL 5min到期后台异步刷新不阻塞请求
  • kid 未命中时强制同步刷新一次
  • 刷新失败保留旧公钥集继续服务fail-open 1 次后 fail-close
  • 启动时同步拉取一次,失败则 panic 拒绝启动

2.4 shared-go 包依赖

依据 president-final-rulings.md §2.19 裁决ai01 直接 import packages/shared-go

模块 用途 接入阶段
shared-go/jwks JWKS FetcherHTTP 拉 RS256 公钥 + 缓存) P2.2
shared-go/logger 结构化日志zap 或 slog待 ISSUE-004 仲裁) P2.1
shared-go/tracer OTel tracer 初始化(评估接入,若接口兼容) P2.1
shared-go/env 环境变量加载(评估接入,若接口兼容) P2.1

§3 就绪信号

3.1 我依赖的上游就绪标志

上游 就绪标志 阶段 状态
coord shared-go 包骨架tracer/logger/jwks/env 4 模块) 批次 0 已完成
coord ISSUE-001 仲裁JWKS HTTP 确认) P2 启动前 待仲裁
coord ISSUE-002 仲裁shared-go 接入) P2 启动前 待仲裁
coord ISSUE-003 仲裁API 版本化路由方案) P2.7 前 待仲裁
coord ISSUE-004 仲裁zap vs slog P2.1 前 待仲裁
iam (ai06) GET /.well-known/jwks.json HTTP 端点 + RS256 JWT 签发 P2
iam (ai06) GET /healthz 端点 P2
teacher-bff (ai03) POST /graphql :3003 启用 + GET /healthz P2
student-bff (ai04) POST /graphql :3009 启用 + GET /healthz P3
core-edu (ai08) GET /healthz + classes 合并 P3
parent-bff (ai05) POST /graphql :3010 启用 + GET /healthz P4
content (ai09) GET /healthz + REST 端点 P4
data-ana (ai11) GET /healthz + REST 端点 P4
msg (ai10) GET /healthz + REST 端点 P5
ai (ai12) GET /healthz + REST 端点 P5
push-gateway (ai02) :8081 启用 + /internal/pushWebSocket 协作评估) P5

3.2 我的就绪标志(供下游消费)

阶段 就绪标志 状态
P2 api-gateway HTTP :8080 启用(/healthz 返回 200
P2 /readyz 返回 200含 iam + teacher-bff 连通性检查通过)
P2 JWT RS256 验签链路打通(使用 iam JWKS 公钥校验 access_token
P2 /api/v1/iam/v1/* 代理到 iam 认证链路可用
P2 /api/v1/teacher/v1/* 反向代理到 teacher-bff GraphQL 可用
P2 限流IP 级令牌桶)+ 熔断(共享 downstream+ CORS 白名单生效
P2 7 个业务指标暴露在 /metrics
P2 错误响应统一 ActionState 信封 + GW_ 前缀
P3 /api/v1/student/v1/* + /api/v1/exams/v1/* 等路由可用
P4 /api/v1/parent/v1/* + /api/v1/textbooks/v1/* + /api/v1/analytics/v1/* 路由可用
P5 /api/v1/notifications/v1/* + /api/v1/ai/v1/* 路由可用
P6 限流迁 Redis + 测试覆盖率 ≥ 80%

§4 Mock 策略

4.1 我提供的 mock供下游各前端 portal 消费)

在 api-gateway 真实就绪前为下游teacher-portal / student-portal / parent-portal / admin-portal提供以下 mock

  • HTTP mock(由各前端 portal 自行用 MSW 拦截api-gateway 不提供 mock 服务):
    • /api/v1/iam/v1/login 返回固定 JWTmock 签发)+ UserInfo
    • /api/v1/teacher/v1/* /api/v1/student/v1/* /api/v1/parent/v1/* 直接返回各 BFF 的 mock GraphQL 响应
    • /healthz /readyz 返回 200
  • JWT mock:前端开发期使用固定 mock JWTapi-gateway 就绪前不走真实验签)

4.2 我消费的 mock在真实上游就绪前

在真实上游就绪前api-gateway 使用以下 mock

  • iam JWKS 公钥:使用本地固定 mock RS256 公钥(与 mock 私钥配对),验签 mock JWTDevMode 下生效)
  • iam /readyz 检查iam 就绪前软失败,返回 200 + degraded: true
  • 各 BFF 代理BFF 就绪前返回 503 + Retry-After,前端降级到本地 mock 数据
  • 下游 /healthz 检查:未就绪服务软失败(返回 200 + degraded已就绪服务硬失败返回 503

§5 变更记录

版本 日期 变更内容 变更者
v1 2026-07-09 初始创建(含错误:/api/* 路径、gRPC GetPublicKey、GetEffectiveAccess ai01
v2 2026-07-10 修正:路径 /api→/api/v1、JWKS 拉取 gRPC→HTTP、删除 GetEffectiveAccess、错误码加 GW_ 前缀、对齐 ActionState 信封、补充 shared-go 依赖、补充 admin-portal P6 路由 ai01