16 KiB
api-gateway 对接契约
负责人:ai01 关联:matrix.md、iam.proto、objections/api-gateway_issue.md、worklines/api-gateway_workline.md、02-architecture-design.md 依据:coord-final-decisions.md §3.8 W1-W8、president-final-rulings.md §2.15/§2.16/§2.19 版本:v2(2026-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 L59r.Group("/api/v1")一致)。API 版本化:按 president-final-rulings.md §2.15 裁决,各服务 Controller 内加
/v1前缀。Gateway 透传时不改路径,前端调用/api/v1/<service>/v1/*(ISSUE-003 待 coord 仲裁最终方案,本表暂列方案 A)。
| Method | Path(Gateway 外部路径) | 目标服务 | 端口 | 鉴权 | 公开子路径 | 阶段 |
|---|---|---|---|---|---|---|
| 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-bff(GraphQL) | 3003 | JWT | — | P2 |
| ANY | /api/v1/student/v1/*path + /api/v1/student/v1 | student-bff(GraphQL) | 3009 | JWT | — | P3 |
| ANY | /api/v1/parent/v1/*path + /api/v1/parent/v1 | parent-bff(GraphQL) | 3010 | JWT | — | P4 |
| ANY | /api/v1/classes/v1/*path + /api/v1/classes/v1 | core-edu(classes 合并) | 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 = false(main.go L34)。注 2:admin-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 验签,不解析 GraphQL(GraphQL 由 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.GetPublicKey和IamService.GetEffectiveAccess,v2 已删除):
- 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 公钥集(JWKS),TTL 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 Fetcher(HTTP 拉 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/push(WebSocket 协作评估) | 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返回固定 JWT(mock 签发)+ UserInfo/api/v1/teacher/v1/*/api/v1/student/v1/*/api/v1/parent/v1/*直接返回各 BFF 的 mock GraphQL 响应/healthz/readyz返回 200
- JWT mock:前端开发期使用固定 mock JWT(api-gateway 就绪前不走真实验签)
4.2 我消费的 mock(在真实上游就绪前)
在真实上游就绪前,api-gateway 使用以下 mock:
- iam JWKS 公钥:使用本地固定 mock RS256 公钥(与 mock 私钥配对),验签 mock JWT(DevMode 下生效)
- 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 |