# api-gateway 工作排期

> 负责人：ai01
> 关联：[workline.md](../workline.md)、[coord.md](../coord.md)、[contracts/api-gateway_contract.md](../contracts/api-gateway_contract.md)、[objections/api-gateway_issue.md](../objections/api-gateway_issue.md)
> 模式：全并行（各 AI 一口气完成 P2-P6 全部代码，最后统一集成测试）
> 依据：[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、[02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md) §9

---

## §1 总览

api-gateway 是 Edu 系统统一入口（L3 网关层），负责路由转发、JWT RS256 验签、限流、熔断、CORS、可观测性。无业务状态，纯 HTTP 反向代理。

**全阶段目标**：
- **P2**：路由表 + JWT RS256（HTTP JWKS） + shared-go 接入 + 错误码 GW_ 前缀 + ActionState 信封 + slog + /readyz 真实检查 + 业务 metrics + tracer 资源属性 + DevMode 防护（遵循 W1-W8 / G1-G17 裁决）
- **P3**：路由扩展（student-bff :3009）+ core-edu 路由
- **P4**：路由扩展（parent-bff :3010）+ content / data-ana 路由
- **P5**：路由扩展（msg / ai 路由）+ 接入 push-gateway 协作（WebSocket 升级透传评估）
- **P6**：限流迁 Redis + per-服务实例熔断评估 + 测试覆盖率 ≥ 80% + 安全加固

**当前状态（2026-07-10）**：P1 已交付（classes 域 CRUD 端到端跑通），P2 升级未启动。已有仲裁核查发现 6 项未遵循裁决（见 [objections/api-gateway_issue.md](../objections/api-gateway_issue.md) §0.4）。

---

## §2 全阶段甘特图（P2-P6）

```mermaid
gantt
    title ai01 api-gateway 全阶段排期（P2-P6）
    dateFormat YYYY-MM-DD
    axisFormat %m-%d

    section P2 基础升级（批次1）
    P2.0 修复P0遗留 :crit, p2a, 2026-07-10, 1d
    P2.1 shared-go接入 :crit, p2b, after p2a, 2d
    P2.2 JWT RS256+JWKS :crit, p2c, after p2b, 3d
    P2.3 错误码GW_+ActionState :crit, p2d, after p2c, 1d
    P2.4 slog+metrics+tracer :crit, p2e, after p2d, 2d
    P2.5 /readyz真实检查 :crit, p2f, after p2e, 1d
    P2.6 DevMode防护 :p2g, after p2f, 1d
    P2.7 路由表扩展iam/teacher :p2h, after p2g, 1d

    section P3 路由扩展（批次2）
    P3.1 student-bff路由 :p3a, after p2h, 1d
    P3.2 core-edu路由 :p3b, after p3a, 1d
    P3.3 API版本化/v1迁移 :p3c, after p3b, 2d

    section P4 路由扩展（批次3）
    P4.1 parent-bff路由 :p4a, after p3c, 1d
    P4.2 content路由 :p4b, after p4a, 1d
    P4.3 data-ana路由 :p4c, after p4b, 1d

    section P5 路由扩展（批次4）
    P5.1 msg路由 :p5a, after p4c, 1d
    P5.2 ai路由 :p5b, after p5a, 1d
    P5.3 push-gateway协作评估 :p5c, after p5b, 2d

    section P6 硬化（批次5）
    P6.1 限流迁Redis :p6a, after p5c, 3d
    P6.2 per-服务熔断评估 :p6b, after p6a, 2d
    P6.3 测试覆盖率80% :p6c, after p6b, 3d
    P6.4 安全加固 :p6d, after p6c, 2d
```

**关键路径**（红色 crit）：P2.0 → P2.1 → P2.2 → P2.3 → P2.4 → P2.5 → P2.6 → P2.7
**总时间线**：P2 约 11 天 + P3-P5 约 9 天 + P6 约 10 天 = 约 30 天

---

## §3 详细任务

### P2.0：修复 P0 遗留问题

- **负责人**：ai01
- **依赖**：无
- **交付物**：
  - `services/api-gateway/go.mod` L3 改为 `go 1.22`（修复 ISSUE-005）
  - `go.work` L1 改为 `go 1.22`
  - 删除 [01-understanding.md](../../../services/api-gateway/docs/01-understanding.md) §7.1 issue #3 死代码引用（ISSUE-008）
  - 修正 [01-understanding.md](../../../services/api-gateway/docs/01-understanding.md) §6 审计表 metrics 行（ISSUE-007）
  - 修正 [README.md](../../../services/api-gateway/README.md) L38 删除不存在的 `/health` 兼容端点描述
  - 修正 [proxy.go](../../../services/api-gateway/internal/proxy/proxy.go) L24 删除冗余 `TrimPrefix("/api")`
- **验收标准**：`go build ./...` + `go vet ./...` 通过；文档与代码一致
- **对应 ISSUE**：ISSUE-005 / ISSUE-007 / ISSUE-008

### P2.1：shared-go 包接入

- **负责人**：ai01
- **依赖**：[packages/shared-go](../../../packages/shared-go/) 已建立（批次 0 已完成）；coord 仲裁 ISSUE-002 / ISSUE-004
- **交付物**：
  - `go.work` 增加 `./packages/shared-go`
  - `services/api-gateway/go.mod` 增加 `github.com/edu-cloud/shared-go` 依赖
  - 按 ISSUE-004 仲裁结果接入 shared-go/logger（zap 或 slog，取决于 coord 裁决）
  - [tracer.go](../../../services/api-gateway/internal/observability/tracer.go) 评估接入 shared-go/tracer（若接口兼容）
  - [config.go](../../../services/api-gateway/internal/config/config.go) 评估接入 shared-go/env
- **验收标准**：`go build ./...` 通过；import shared-go 成功；logger 输出结构化 JSON
- **对应 ISSUE**：ISSUE-002 / ISSUE-004

### P2.2：JWT RS256 升级 + JWKS 缓存

- **负责人**：ai01
- **依赖**：iam (ai06) 暴露 `GET /.well-known/jwks.json` HTTP 端点；coord 仲裁 ISSUE-001（确认 HTTP JWKS）
- **交付物**：
  - [auth.go](../../../services/api-gateway/internal/middleware/auth.go) 改用 shared-go/jwks.Fetcher（`jwks.NewFetcher(cfg.JWKSURL)`）
  - HS256 逻辑废弃，`cfg.JWTSecret` 仅 DevMode 下用作 mock 密钥
  - JWKS 缓存策略：TTL 5min（shared-go/jwks 默认），kid 未命中时强制刷新，刷新失败保留旧公钥
  - 启动时同步拉取一次 JWKS，失败则 panic 拒绝启动
  - claims 增加 `data_scope` 字段提取，注入 `x-data-scope` 头
  - `internal/config/config.go` 增加 `JWKSURL` 字段（环境变量 `IAM_JWKS_URL`）
- **验收标准**：JWT RS256 验签通过；JWKS 缓存命中率达 99%+；kid 未命中自动刷新
- **对应裁决**：W1（错误码加 GW_ 前缀）、§2.16（HTTP JWKS，非 gRPC）

### P2.3：错误码 GW_ 前缀 + ActionState 信封

- **负责人**：ai01
- **依赖**：无
- **交付物**：
  - [auth.go](../../../services/api-gateway/internal/middleware/auth.go) 错误码改为 `GW_UNAUTHORIZED` / `GW_INVALID_TOKEN` / `GW_INVALID_CLAIMS`
  - [ratelimit.go](../../../services/api-gateway/internal/middleware/ratelimit.go) 响应体改为 `{success:false,error:{code:"GW_RATE_LIMITED",message:"...",retry_after:60}}`
  - [circuit-breaker.go](../../../services/api-gateway/internal/middleware/circuit-breaker.go) 响应体改为 `{success:false,error:{code:"GW_CIRCUIT_OPEN",message:"...",retry_after:30}}`
  - [recovery.go](../../../services/api-gateway/internal/middleware/recovery.go) 响应体改为 `{success:false,error:{code:"GW_INTERNAL_ERROR",message:"...",request_id:"..."}}`
  - `RequestBodyLimit` 超限响应改为 `{success:false,error:{code:"GW_REQUEST_TOO_LARGE",message:"..."}}`
- **验收标准**：所有错误响应符合 ActionState 信封；错误码统一 `GW_` 前缀
- **对应裁决**：W1 / W2 / G14
- **对应 ISSUE**：ISSUE-009

### P2.4：slog + 业务 metrics + tracer 资源属性

- **负责人**：ai01
- **依赖**：P2.1 shared-go 接入完成
- **交付物**：
  - 按 ISSUE-004 仲裁结果统一 logger（zap 或 slog）
  - 所有 `log.Printf` / `log.Println` / `log.Fatal` 改为结构化日志（带 `request_id` / `trace_id` / `user_id` / `method` / `path` / `status` / `latency_ms` 字段）
  - 新增 `internal/observability/metrics.go`，注册 7 个业务指标：
    - `api_gateway_http_requests_total`（Counter，method/endpoint/status）
    - `api_gateway_http_request_duration_seconds`（Histogram，method/endpoint）
    - `api_gateway_circuit_breaker_state`（Gauge，service/state）
    - `api_gateway_rate_limited_total`（Counter，ip）
    - `api_gateway_proxy_upstream_duration_seconds`（Histogram，upstream）
    - `api_gateway_jwks_refresh_total`（Counter，result）
    - `api_gateway_auth_failures_total`（Counter，reason）
  - [tracer.go](../../../services/api-gateway/internal/observability/tracer.go) 资源属性补全：`service.name` + `service.version`（编译时注入）+ `deployment.environment`（ENV 变量）+ `host.name`
  - Metrics 中间件：在 Auth 之后、CircuitBreaker 之前注册（统计通过鉴权的请求）
- **验收标准**：`/metrics` 端点返回 7 个业务指标；日志为 JSON 结构化；tracer 资源属性完整
- **对应裁决**：W3 / W5 / W6 / G4 / G5 / G6

### P2.5：/readyz 真实健康检查

- **负责人**：ai01
- **依赖**：所有下游服务实现 `/healthz`（P2 阶段 iam / teacher-bff 已就绪）
- **交付物**：
  - [health.go](../../../services/api-gateway/internal/health/health.go) `Readyz` 重构为并行 ping 下游 `/healthz`
  - 下游清单从 `cfg.ServicesURL` 动态读取（iam / classes / teacher-bff / core-edu / content / msg / ai / data-ana）
  - 超时 2s，任一不可达返回 503 + `{"status":"error","unhealthy":["iam","core-edu"]}`
  - 全部可达返回 200 + `{"status":"ok"}`
  - 可选依赖软失败规则：未启用 gRPC 的下游（P3-P5 阶段未就绪的服务）失败仅告警，返回 200 + `degraded: true`（依据 president-final-rulings.md §3.3）
- **验收标准**：/readyz 真实检查下游；某服务下线时返回 503
- **对应裁决**：W4 / G2

### P2.6：DevMode 生产防护

- **负责人**：ai01
- **依赖**：无
- **交付物**：
  - [config.go](../../../services/api-gateway/internal/config/config.go) `Load()` 增加 `ENV` 环境变量读取
  - 若 `DevMode=true && ENV=production` 则 `panic` 拒绝启动
  - 启动日志打印 `ENV` / `DevMode` 状态
- **验收标准**：`DEV_MODE=true ENV=production` 启动失败；`DEV_MODE=true ENV=development` 启动成功
- **对应裁决**：W7

### P2.7：路由表扩展（iam / teacher）

- **负责人**：ai01
- **依赖**：coord 仲裁 ISSUE-003（API 版本化路由规则）；iam (ai06) / teacher-bff (ai03) P2 就绪
- **交付物**：
  - 按 ISSUE-003 仲裁结果更新路由（方案 A/B/C 之一）
  - 若方案 A：iam 路由改为 `/api/v1/iam/v1/*path`，proxy 透传 `/iam/v1/*path`
  - teacher-bff 路由保持 `/api/v1/teacher/*path`
  - [02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md) §4.1 路由表同步更新
- **验收标准**：路由表与代码一致；前端调用 `/api/v1/iam/v1/auth/login` 透传到 iam 服务
- **对应裁决**：§2.15

---

### P3.1：student-bff 路由

- **负责人**：ai01
- **依赖**：student-bff (ai04) P3 就绪
- **交付物**：
  - `main.go` 增加 student-bff 路由：`/api/v1/student` + `/api/v1/student/*path` → `cfg.StudentBffURL`（:3009）
  - `config.go` 增加 `StudentBffURL` 字段（环境变量 `STUDENT_BFF_URL`）
- **验收标准**：`/api/v1/student/*` 代理到 student-bff:3009

### P3.2：core-edu 路由

- **负责人**：ai01
- **依赖**：core-edu (ai08) P3 就绪；classes 服务已合并入 core-edu（C1 裁决）
- **交付物**：
  - `main.go` classes 路由目标改为 core-edu（`cfg.ClassesServiceURL` → `cfg.CoreEduServiceURL`）
  - 或保留 classes 路由别名，proxy 到 core-edu
  - exams / homework / grades 路由已在 P1 实现，无需修改
- **验收标准**：`/api/v1/classes/*` 代理到 core-edu:3004

### P3.3：API 版本化 /v1 迁移

- **负责人**：ai01
- **依赖**：P2.7 路由规则仲裁结果；core-edu (ai08) P3 就绪
- **交付物**：
  - 按 ISSUE-003 仲裁结果，全服务路由统一加 `/v1` 前缀
  - core-edu 路由：`/api/v1/exams/v1/*path` 等（若方案 A）
  - 更新 [02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md) §4.1 路由表
- **验收标准**：所有业务路由含 `/v1` 版本前缀

---

### P4.1：parent-bff 路由

- **负责人**：ai01
- **依赖**：parent-bff (ai05) P4 就绪
- **交付物**：
  - `main.go` 增加 parent-bff 路由：`/api/v1/parent` + `/api/v1/parent/*path` → `cfg.ParentBffURL`（:3010）
  - `config.go` 增加 `ParentBffURL` 字段
- **验收标准**：`/api/v1/parent/*` 代理到 parent-bff:3010

### P4.2：content 路由

- **负责人**：ai01
- **依赖**：content (ai09) P4 就绪
- **交付物**：
  - `main.go` 已有 content 路由（P1 实现：textbooks / chapters / knowledge-points / questions）
  - 验证路由目标 `cfg.ContentServiceURL`（:3005）正确
  - 按 P3.3 版本化规则加 `/v1` 前缀
- **验收标准**：content 路由可用 + 版本化

### P4.3：data-ana 路由

- **负责人**：ai01
- **依赖**：data-ana (ai11) P4 就绪
- **交付物**：
  - `main.go` 已有 data-ana 路由（P1 实现：analytics）
  - 增加 `dashboard` 路由别名：`/api/v1/dashboard` + `/*path` → data-ana
  - 按版本化规则加 `/v1` 前缀
- **验收标准**：data-ana 路由可用 + dashboard 别名可用

---

### P5.1：msg 路由

- **负责人**：ai01
- **依赖**：msg (ai10) P5 就绪
- **交付物**：
  - `main.go` 已有 msg 路由（P1 实现：notifications）
  - 增加 `messages` 路由别名：`/api/v1/messages` + `/*path` → msg
  - 按版本化规则加 `/v1` 前缀
- **验收标准**：msg 路由可用 + messages 别名可用

### P5.2：ai 路由

- **负责人**：ai01
- **依赖**：ai (ai12) P5 就绪
- **交付物**：
  - `main.go` 已有 ai 路由（P1 实现）
  - 按版本化规则加 `/v1` 前缀
- **验收标准**：ai 路由可用

### P5.3：push-gateway 协作评估

- **负责人**：ai01
- **依赖**：push-gateway (ai02) P5 就绪
- **交付物**：
  - 评估 WebSocket 升级请求是否需要 Gateway 透传到 push-gateway
  - 若需要：增加 `/ws` + `/sse` 路由透传到 push-gateway（:8081）
  - 若不需要：文档说明 WebSocket 直连 push-gateway，不经过 Gateway
  - 更新 [02-architecture-design.md](../../../services/api-gateway/docs/02-architecture-design.md) §7 交互点清单
- **验收标准**：WebSocket 推送链路可用（透传或直连）

---

### P6.1：限流迁 Redis

- **负责人**：ai01
- **依赖**：Redis 基础设施就绪
- **交付物**：
  - [ratelimit.go](../../../services/api-gateway/internal/middleware/ratelimit.go) 改用 Redis 令牌桶（`github.com/go-redis/redis_rate/v10`）
  - 支持多副本一致限流
  - 增加用户级限流（基于 `x-user-id` 头）
  - 登录接口额外加用户级限流（防爆破）
  - 保留 DevMode 下内存令牌桶回退
- **验收标准**：多副本部署时限流一致；用户级限流生效

### P6.2：per-服务实例熔断评估

- **负责人**：ai01
- **依赖**：P6.1 完成
- **交付物**：
  - 评估是否将共享 `downstream` 熔断器拆分为 per-服务实例（iam / core-edu / teacher-bff 等）
  - 若拆分：每个下游服务独立熔断状态，互不影响
  - 若不拆分：保持 W8 裁决现状，文档说明理由
  - 按 W8 裁决，此项 P6 单独评估，不强制拆分
- **验收标准**：评估报告 + 决策记录

### P6.3：测试覆盖率 80%

- **负责人**：ai01
- **依赖**：P2-P5 全部完成
- **交付物**：
  - 补全 [auth_test.go](../../../services/api-gateway/internal/middleware/auth_test.go)：JWKS 验签 / claims 解析 / DevMode 旁路 / 公开路径白名单
  - 补全 [cors_test.go](../../../services/api-gateway/internal/middleware/cors_test.go)：白名单匹配 / 预检请求 / Vary 头
  - 补全 [security_test.go](../../../services/api-gateway/internal/middleware/security_test.go)：安全头设置 / Server 头移除
  - 补全 [recovery_test.go](../../../services/api-gateway/internal/middleware/recovery_test.go)：panic 捕获 / request_id 生成 / ActionState 信封
  - 补全 [requestid_test.go](../../../services/api-gateway/internal/middleware/requestid_test.go)：透传 / 生成 / 响应头
  - 补全 [proxy_test.go](../../../services/api-gateway/internal/proxy/proxy_test.go)：路径前缀去除 / Host 改写
  - 补全 [health_test.go](../../../services/api-gateway/internal/health/health_test.go)：/readyz 下游检查 / 软失败规则
- **验收标准**：`go test ./... -cover` 覆盖率 ≥ 80%

### P6.4：安全加固

- **负责人**：ai01
- **依赖**：P6.1-P6.3 完成
- **交付物**：
  - 评估 IP 黑名单 / WAF 规则是否在 Gateway 层实现（建议在 Istio 层做，本服务不介入）
  - 限流策略表 per-路由细化（02 §4.2）
  - 熔断阈值 per-服务配置（02 §4.3）
  - CORS 白名单生产环境强制配置（禁止 `*`）
  - 审计日志：记录所有 401 / 403 / 429 / 503 响应
- **验收标准**：安全扫描通过；审计日志可追溯

---

## §4 依赖与就绪信号

### 4.1 我依赖的上游就绪标志

| 上游 | 就绪标志 | 阶段 | 状态 |
| ---- | -------- | ---- | ---- |
| coord | shared-go 包骨架（tracer/logger/jwks/env）| 批次 0 | ✅ 已完成 |
| coord | ISSUE-001 仲裁（JWKS vs gRPC）| 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 签发 | P2 | ⏳ |
| iam (ai06) | gRPC 50052 启用（不影响 Gateway，Gateway 走 HTTP）| P2 | ⏳ |
| teacher-bff (ai03) | `POST /graphql` :3003 启用 | P2 | ⏳ |
| student-bff (ai04) | `POST /graphql` :3009 启用 | P3 | ⏳ |
| core-edu (ai08) | gRPC 50053 启用 + classes 合并 | P3 | ⏳ |
| parent-bff (ai05) | `POST /graphql` :3010 启用 | P4 | ⏳ |
| content (ai09) | gRPC 50054 启用 | P4 | ⏳ |
| data-ana (ai11) | gRPC 50055 启用 | P4 | ⏳ |
| msg (ai10) | gRPC 50056 启用 | P5 | ⏳ |
| ai (ai12) | gRPC 50058 启用 | P5 | ⏳ |
| push-gateway (ai02) | :8081 启用 + /internal/push | P5 | ⏳ |

### 4.2 我的就绪标志（供下游消费）

| 阶段 | 就绪标志 | 状态 |
| ---- | -------- | ---- |
| P2 | api-gateway :8080 可访问 + JWT RS256 验签可用 + 7 个业务指标 + /readyz 真实检查 | ⏳ |
| P2 | /api/v1/iam/* + /api/v1/teacher/* 路由可用 | ⏳ |
| P3 | /api/v1/student/* + /api/v1/exams/* 等路由可用 | ⏳ |
| P4 | /api/v1/parent/* + /api/v1/textbooks/* + /api/v1/analytics/* 路由可用 | ⏳ |
| P5 | /api/v1/notifications/* + /api/v1/ai/* 路由可用 | ⏳ |
| P6 | 限流迁 Redis + 测试覆盖率 ≥ 80% | ⏳ |

---

## §5 风险与应对

| 风险 | 概率 | 影响 | 应对 |
| ---- | ---- | ---- | ---- |
| coord 仲裁延期（ISSUE-001/002/003/004）| 中 | P2 阻塞 | ai01 先按 HTTP JWKS + shared-go + 方案 A 推进，仲裁后调整 |
| iam JWKS 端点延期 | 中 | P2.2 阻塞 | DevMode 下用本地 mock RS256 公钥（与 mock 私钥配对） |
| shared-go 接口不兼容 | 低 | P2.1 阻塞 | ai01 自行适配，或反馈 coord 修改 shared-go |
| 下游服务未实现 /healthz | 中 | /readyz 误报 | 软失败规则：未就绪服务失败仅告警，返回 200 + degraded |
| JWKS 缓存过期时 iam 不可达 | 低 | 全量 401 | fail-open 1 次后 fail-close；监控 `jwks_refresh_total` 指标 |
| DevMode 旁路误开到生产 | 低 | 鉴权绕过 | P2.6 生产防护（W7 裁决） |

---

## §6 与其他模块的协作

| 模块 | 协作内容 | 时机 |
| ---- | -------- | ---- |
| iam (ai06) | JWKS HTTP 端点 + JWT RS256 签发 | P2 |
| teacher-bff (ai03) | 反向代理 :3003 GraphQL | P2 |
| student-bff (ai04) | 反向代理 :3009 GraphQL | P3 |
| parent-bff (ai05) | 反向代理 :3010 GraphQL | P4 |
| core-edu (ai08) | 反向代理 :3004 + classes 合并 | P3 |
| content (ai09) | 反向代理 :3005 | P4 |
| data-ana (ai11) | 反向代理 :3006 | P4 |
| msg (ai10) | 反向代理 :3007 | P5 |
| ai (ai12) | 反向代理 :3008 | P5 |
| push-gateway (ai02) | WebSocket 升级透传评估 | P5 |
| coord | shared-go 包维护 + ISSUE 仲裁 | 持续 |
