docs: update api-gateway workline and contract based on new arbitration

update workline to detail full P2-P6 tasks with clear milestones and dependencies
revise contract to correct route prefix, JWKS method and dependency descriptions
clean up outdated issue content
This commit is contained in:
SpecialX
2026-07-10 15:06:22 +08:00
parent 9ba368477d
commit 88f6822473
3 changed files with 518 additions and 95 deletions

View File

@@ -1,40 +1,90 @@
# api-gateway 对接契约
> 负责人ai01
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)
> 关联:[matrix.md](./matrix.md)、[iam.proto](../../../packages/shared-proto/proto/iam.proto)、[objections/api-gateway_issue.md](../objections/api-gateway_issue.md)、[worklines/api-gateway_workline.md](../worklines/api-gateway_workline.md)、[02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md)
> 依据:[coord-final-decisions.md](../../coord-final-decisions.md) §3.8 W1-W8、[president-final-rulings.md](../../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 接口(如有)
### 1.1 gRPC 接口
无。api-gateway 是 HTTP 入口,不对外提供 gRPC。
无。api-gateway 是 HTTP 入口,不对外提供 gRPC(依据 [president-final-rulings.md](../../president-final-rulings.md) §2.16 裁决gateway 保持 HTTP 透传,不改为 gRPC 客户端)
### 1.2 HTTP 端点(如有)
### 1.2 HTTP 端点
| Method | Path | 用途 | 认证 |
| ------ | -------------- | ------------------------------------------- | ---------------------- |
| ANY | /api/auth/* | 代理到 iam 认证相关(登录/注册/刷新 token | 公开(登录注册免认证) |
| ANY | /api/teacher/* | 代理到 teacher-bff GraphQL:3003 | JWT 必需 |
| ANY | /api/student/* | 代理到 student-bff GraphQL:3009 | JWT 必需 |
| ANY | /api/parent/* | 代理到 parent-bff GraphQL:3010 | JWT 必需 |
| ANY | /api/admin/* | 代理到 teacher-bff GraphQL admin namespace | JWT 必需 + admin 角色 |
| GET | /healthz | 网关健康检查liveness | 公开 |
| GET | /readyz | 网关就绪检查readiness含 iam 连通性) | 公开 |
| GET | /metrics | Prometheus 指标端点 | 公开(内网) |
#### 1.2.1 健康检查与指标端点(公开,无鉴权)
### 1.3 GraphQL schema如 BFF
| Method | Path | 用途 | 认证 | 阶段 |
| ------ | ---------- | ------------------------------------------- | ---- | ---- |
| GET | /healthz | 网关存活探针liveness | 公开 | P1 |
| GET | /readyz | 网关就绪探针readiness并行 ping 下游 /healthz| 公开 | P2 |
| GET | /metrics | Prometheus 指标端点7 个业务指标 + Go runtime| 公开(内网)| P2 |
不适用。api-gateway 仅做 HTTP 反向代理 + JWT 验签,不解析 GraphQL。
#### 1.2.2 反向代理路由(JWT 必需,除白名单外)
### 1.4 Kafka 事件发布(如有)
> **路径前缀**:所有业务路由统一 `/api/v1/*` 前缀(与 [main.go](../../../services/api-gateway/main.go) L59 `r.Group("/api/v1")` 一致)。
>
> **API 版本化**:按 [president-final-rulings.md](../../president-final-rulings.md) §2.15 裁决,各服务 Controller 内加 `/v1` 前缀。Gateway 透传时不改路径,前端调用 `/api/v1/<service>/v1/*`ISSUE-003 待 coord 仲裁最终方案,本表暂列方案 A
无。api-gateway 不发布事件。
| 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 = false`[main.go](../../../services/api-gateway/main.go) L34
>
> **注 2**admin-portal P6 阶段复用 teacher-bff + admin schema 命名空间(依据 [coord.md](../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_`如 GW_UNAUTHORIZED、GW_RATE_LIMITED、GW_CIRCUIT_OPEN、GW_BACKEND_UNAVAILABLE
`GW_`依据 [coord-final-decisions.md](../../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}}`。
---
@@ -42,22 +92,48 @@
### 2.1 gRPC 调用(同步)
| 被调用方 | Service.RPC | 用途 | mock 策略 |
| ---------- | ----------------------------- | ---------------------------------------- | ----------------------------------------------------------------- |
| iam (ai06) | IamService.GetPublicKey | 启动时拉取 RS256 公钥,用于 JWT 验签 | iam 就绪前使用本地固定 mock 公钥(与 mock 私钥配对签发 mock JWT |
| iam (ai06) | IamService.GetEffectiveAccess | 权限校验(可选,部分路由需要细粒度权限) | iam 就绪前放行所有请求(仅校验 JWT 签名) |
**无**。依据 [president-final-rulings.md](../../president-final-rulings.md) §2.16 裁决"gateway 保持 HTTP 透传,不改为 gRPC 客户端"api-gateway 不消费任何 gRPC 接口。
### 2.2 Kafka 事件订阅(异步)
> **勘误**v1 版本曾错误声明消费 `IamService.GetPublicKey` 和 `IamService.GetEffectiveAccess`v2 已删除):
> - JWT 公钥拉取走 HTTP JWKS 端点,非 gRPC见 §2.3
> - Gateway 不做权限点校验,不消费 `GetEffectiveAccess`(权限由下游服务 Controller `@RequirePermission` 自校验,见 [01-understanding.md](../../../services/api-gateway/docs/01-understanding.md) §2 + B3 裁决)
> - [matrix.md](./matrix.md) §2 gRPC 接口提供方矩阵中"iam 消费方"应移除 api-gateway已提请 ISSUE-001
### 2.2 Kafka 事件订阅
无。api-gateway 不订阅 Kafka 事件。
### 2.3 HTTP 调用(如有
### 2.3 HTTP 调用(同步
| 被调用方 | Method.Path | 用途 | mock 策略 |
| ------------------ | ------------- | --------------------------- | -------------------------------------------------- |
| teacher-bff (ai03) | POST /graphql | 反向代理教师端 GraphQL 请求 | teacher-bff 就绪前返回 502前端使用本地 mock 数据 |
| student-bff (ai04) | POST /graphql | 反向代理学生端 GraphQL 请求 | student-bff 就绪前返回 502 |
| parent-bff (ai05) | POST /graphql | 反向代理家长端 GraphQL 请求 | parent-bff 就绪前返回 502 |
| 被调用方 | 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](../../../packages/shared-go/jwks/jwks.go)
> - TTL 5min到期后台异步刷新不阻塞请求
> - kid 未命中时强制同步刷新一次
> - 刷新失败保留旧公钥集继续服务fail-open 1 次后 fail-close
> - 启动时同步拉取一次,失败则 panic 拒绝启动
### 2.4 shared-go 包依赖
依据 [president-final-rulings.md](../../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 |
---
@@ -65,38 +141,70 @@
### 3.1 我依赖的上游就绪标志
- [ ] iam gRPC 50052 启用ai06—— GetPublicKey 拉取验签公钥
- [ ] teacher-bff GraphQL :3003 启用ai03
- [ ] student-bff GraphQL :3009 启用ai04
- [ ] parent-bff GraphQL :3010 启用ai05
| 上游 | 就绪标志 | 阶段 | 状态 |
| ---- | -------- | ---- | ---- |
| 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 我的就绪标志(供下游消费)
- [ ] api-gateway HTTP :8080 启用(/healthz 返回 200
- [ ] /readyz 返回 200含 iam 连通性检查通过)
- [ ] JWT 验签链路打通(使用 iam 公钥校验 access_token
- [ ] /api/auth/* 代理到 iam 认证链路可用
- [ ] /api/teacher/* /api/student/* /api/parent/* 反向代理到各 BFF 可用
- [ ] 限流IP 级令牌桶)+ 熔断(各后端独立熔断器)生效
| 阶段 | 就绪标志 | 状态 |
| ---- | -------- | ---- |
| 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
### 4.1 我提供的 mock(供下游各前端 portal 消费)
在 api-gateway 真实就绪前,为下游(各前端 portal提供以下 mock
在 api-gateway 真实就绪前,为下游(teacher-portal / student-portal / parent-portal / admin-portal提供以下 mock
- **HTTP mock**:使用 MSWMock Service Worker或本地 nginx 拦截
- /api/auth/login 返回固定 JWTmock 签发)+ UserInfo
- /api/teacher/* /api/student/* /api/parent/* 直接返回各 BFF 的 mock GraphQL 响应
- /healthz /readyz 返回 200
- **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
### 4.2 我消费的 mock(在真实上游就绪前)
在真实上游就绪前api-gateway 使用以下 mock
- **iam 公钥**:使用本地固定 mock RS256 公钥(与 mock 私钥配对),验签 mock JWT
- **iam 权限校验**GetEffectiveAccess 返回 allowed=true放行所有请求
- **各 BFF 代理**BFF 就绪前返回 503 + Retry-After前端降级到本地 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 |