19 KiB
19 KiB
api-gateway 工作排期
负责人:ai01 关联:workline.md、coord.md、contracts/api-gateway_contract.md、objections/api-gateway_issue.md 模式:全并行(各 AI 一口气完成 P2-P6 全部代码,最后统一集成测试) 依据:coord-final-decisions.md §3.8 W1-W8、president-final-rulings.md §2.15/§2.16/§2.19、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 §0.4)。
§2 全阶段甘特图(P2-P6)
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.modL3 改为go 1.22(修复 ISSUE-005)go.workL1 改为go 1.22- 删除 01-understanding.md §7.1 issue #3 死代码引用(ISSUE-008)
- 修正 01-understanding.md §6 审计表 metrics 行(ISSUE-007)
- 修正 README.md L38 删除不存在的
/health兼容端点描述 - 修正 proxy.go L24 删除冗余
TrimPrefix("/api")
- 验收标准:
go build ./...+go vet ./...通过;文档与代码一致 - 对应 ISSUE:ISSUE-005 / ISSUE-007 / ISSUE-008
P2.1:shared-go 包接入
- 负责人:ai01
- 依赖:packages/shared-go 已建立(批次 0 已完成);coord 仲裁 ISSUE-002 / ISSUE-004
- 交付物:
- 验收标准:
go build ./...通过;import shared-go 成功;logger 输出结构化 JSON - 对应 ISSUE:ISSUE-002 / ISSUE-004
P2.2:JWT RS256 升级 + JWKS 缓存
- 负责人:ai01
- 依赖:iam (ai06) 暴露
GET /.well-known/jwks.jsonHTTP 端点;coord 仲裁 ISSUE-001(确认 HTTP JWKS) - 交付物:
- 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)
- auth.go 改用 shared-go/jwks.Fetcher(
- 验收标准:JWT RS256 验签通过;JWKS 缓存命中率达 99%+;kid 未命中自动刷新
- 对应裁决:W1(错误码加 GW_ 前缀)、§2.16(HTTP JWKS,非 gRPC)
P2.3:错误码 GW_ 前缀 + ActionState 信封
- 负责人:ai01
- 依赖:无
- 交付物:
- auth.go 错误码改为
GW_UNAUTHORIZED/GW_INVALID_TOKEN/GW_INVALID_CLAIMS - ratelimit.go 响应体改为
{success:false,error:{code:"GW_RATE_LIMITED",message:"...",retry_after:60}} - circuit-breaker.go 响应体改为
{success:false,error:{code:"GW_CIRCUIT_OPEN",message:"...",retry_after:30}} - recovery.go 响应体改为
{success:false,error:{code:"GW_INTERNAL_ERROR",message:"...",request_id:"..."}} RequestBodyLimit超限响应改为{success:false,error:{code:"GW_REQUEST_TOO_LARGE",message:"..."}}
- auth.go 错误码改为
- 验收标准:所有错误响应符合 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 资源属性补全:
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
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)
- health.go
- 验收标准:/readyz 真实检查下游;某服务下线时返回 503
- 对应裁决:W4 / G2
P2.6:DevMode 生产防护
- 负责人:ai01
- 依赖:无
- 交付物:
- config.go
Load()增加ENV环境变量读取 - 若
DevMode=true && ENV=production则panic拒绝启动 - 启动日志打印
ENV/DevMode状态
- config.go
- 验收标准:
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 §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.goclasses 路由目标改为 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 §4.1 路由表
- 按 ISSUE-003 仲裁结果,全服务路由统一加
- 验收标准:所有业务路由含
/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 §7 交互点清单
- 验收标准:WebSocket 推送链路可用(透传或直连)
P6.1:限流迁 Redis
- 负责人:ai01
- 依赖:Redis 基础设施就绪
- 交付物:
- ratelimit.go 改用 Redis 令牌桶(
github.com/go-redis/redis_rate/v10) - 支持多副本一致限流
- 增加用户级限流(基于
x-user-id头) - 登录接口额外加用户级限流(防爆破)
- 保留 DevMode 下内存令牌桶回退
- ratelimit.go 改用 Redis 令牌桶(
- 验收标准:多副本部署时限流一致;用户级限流生效
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:JWKS 验签 / claims 解析 / DevMode 旁路 / 公开路径白名单
- 补全 cors_test.go:白名单匹配 / 预检请求 / Vary 头
- 补全 security_test.go:安全头设置 / Server 头移除
- 补全 recovery_test.go:panic 捕获 / request_id 生成 / ActionState 信封
- 补全 requestid_test.go:透传 / 生成 / 响应头
- 补全 proxy_test.go:路径前缀去除 / Host 改写
- 补全 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 仲裁 | 持续 |