feat(api-gateway): 实现 W1-W8 网关硬化与 P2-P5 路由扩展

依据 coord-final-decisions §3.8 W1-W8 裁决与
president-final-rulings §2.15/§2.16/§2.19 完整实现网关硬化:

- W1/W2: 错误码 GW_ 前缀 + ActionState 信封响应体
- W3: 全量替换为 log/slog 结构化日志
- W4: /readyz 并行 ping 9 下游 + 软失败规则
- W5: 7 个业务 Prometheus 指标 + /metrics 端点
- W6: tracer 资源属性补全(name/version/env/host)
- W7: DevMode=true && ENV=production panic 防护
- W8: 保持共享 downstream 熔断

P2 RS256 升级:接入 shared-go/jwks.Fetcher(TTL 5min)。
P2.7+P3-P5 路由扩展:student/parent/messages/dashboard。
文档同步:README/01/02/known-issues,arch.db 已更新。
质量校验:go vet + build + test 均通过。
This commit is contained in:
SpecialX
2026-07-10 18:15:48 +08:00
parent 9e767b4e95
commit 4307f6b73c
20 changed files with 797 additions and 382 deletions

View File

@@ -4,66 +4,89 @@ Go (Gin) 实现的 API 网关,所有外部请求的统一入口。
## 职责
- **路由转发**`/api/v1/classes/*` → classes 服务P1后续阶段追加 iam/core-edu/content/msg 等路由
- **JWT 鉴权**P1 HS256测试密钥P2 起 RS256IAM 签发Gateway 公钥校验)
- **请求 ID 注入**:生成或透传 `X-Request-ID`,全链路追踪
- **限流**P6:基于令牌桶的 per-IP 限流,超限返回 429
- **熔断**P6:基于 gobreaker v2 的下游服务熔断5xx 错误率 > 50% 触发 OPEN
- **CORS / 安全头 / Recovery**P6标准化中间件链
- **路由转发**`/api/v1/<service>/*` → 下游服务iam / teacher-bff / student-bff / parent-bff / core-edu / content / msg / ai / data-ana
- **JWT 鉴权**RS256IAM 签发Gateway 通过 JWKS 公钥校验DevMode 下支持 `dev-token` 旁路
- **请求 ID 注入**:生成或透传 `X-Request-Id``req-<uuid-v4>` 格式),全链路追踪
- **限流**:基于令牌桶的 per-IP 限流100 rps突发 20超限返回 429 + `GW_RATE_LIMITED`
- **熔断**:基于 gobreaker v2 的下游服务熔断5xx 错误率 > 50% 触发 OPEN,返回 503 + `GW_CIRCUIT_OPEN`
- **可观测性**slog 结构化日志 + 7 个业务 metrics + OpenTelemetry tracer资源属性完整
- **CORS / 安全头 / Recovery**:标准化中间件链
## 中间件链P6
## 中间件链
请求处理顺序(自外向内):
```
Request → RequestID → CORS → Security → Recovery → RateLimit → Auth → CircuitBreaker → Proxy → Response
Request → Recovery → OTel → RequestID → CORS → SecurityHeaders → RequestBodyLimit → RateLimit
→ [api/v1 组] CircuitBreaker → AuthMiddleware(RS256) → Metrics → Proxy → Response
```
| 中间件 | 文件 | 职责 |
| -------------- | ---------------------------------------- | -------------------------------------------- |
| RequestID | `internal/middleware/requestid.go` | 生成/透传 `X-Request-ID` |
| CORS | `internal/middleware/cors.go` | 跨域允许 |
| Security | `internal/middleware/security.go` | 安全响应头X-Content-Type-Options 等) |
| Recovery | `internal/middleware/recovery.go` | panic 兜底返回 500 |
| RateLimit | `internal/middleware/ratelimit.go` | per-IP 令牌桶限流,超限 429 |
| Auth | `internal/middleware/auth.go` | JWT 校验,注入 `x-user-id`/`x-user-roles` |
| CircuitBreaker | `internal/middleware/circuit-breaker.go` | 下游 5xx 错误率 > 50% 触发熔断,返回 503 |
| 中间件 | 文件 | 职责 |
| --------------- | ---------------------------------------- | ---------------------------------------------------------------------- |
| Recovery | `internal/middleware/recovery.go` | panic 兜底返回 500 + `GW_INTERNAL_ERROR` |
| OTel | `otelgin.Middleware` | OpenTelemetry 自动埋点 |
| RequestID | `internal/middleware/requestid.go` | 生成/透传 `X-Request-Id``req-<uuid-v4>` |
| CORS | `internal/middleware/cors.go` | 跨域允许(从 Config 读取白名单) |
| SecurityHeaders | `internal/middleware/security.go` | 安全响应头 + 请求体限制413 `GW_REQUEST_TOO_LARGE` |
| RateLimit | `internal/middleware/ratelimit.go` | per-IP 令牌桶限流,超限 429 `GW_RATE_LIMITED` |
| CircuitBreaker | `internal/middleware/circuit-breaker.go` | 下游 5xx 错误率 > 50% 触发熔断503 `GW_CIRCUIT_OPEN` |
| Auth | `internal/middleware/auth.go` | JWT RS256 校验JWKS注入 `x-user-id`/`x-user-roles`/`x-data-scope` |
| Metrics | `internal/observability/metrics.go` | HTTP 请求计数 + 延迟统计7 个业务指标) |
## 路由表
| 前缀 | 目标服务 | 端口 | 鉴权 |
| ---------------------------- | ----------- | ---- | ------------------------------------ |
| `/api/v1/iam/*` | iam | 3002 | JWTregister/login/refresh 白名单) |
| `/api/v1/teacher/*` | teacher-bff | 3003 | JWT |
| `/api/v1/student/*` | student-bff | 3009 | JWT |
| `/api/v1/parent/*` | parent-bff | 3010 | JWT |
| `/api/v1/classes/*` | core-edu | 3004 | JWT |
| `/api/v1/exams/*` | core-edu | 3004 | JWT |
| `/api/v1/homework/*` | core-edu | 3004 | JWT |
| `/api/v1/grades/*` | core-edu | 3004 | JWT |
| `/api/v1/textbooks/*` | content | 3005 | JWT |
| `/api/v1/chapters/*` | content | 3005 | JWT |
| `/api/v1/knowledge-points/*` | content | 3005 | JWT |
| `/api/v1/questions/*` | content | 3005 | JWT |
| `/api/v1/notifications/*` | msg | 3007 | JWT |
| `/api/v1/messages/*` | msg | 3007 | JWT |
| `/api/v1/ai/*` | ai | 3008 | JWT |
| `/api/v1/analytics/*` | data-ana | 3006 | JWT |
| `/api/v1/dashboard/*` | data-ana | 3006 | JWT |
## 健康检查
| 端点 | 用途 | 鉴权 |
| -------------- | ---------------------------------------- | ---- |
| `GET /healthz` | 存活探针liveness | 无 |
| `GET /readyz` | 就绪探针readiness | 无 |
| `GET /health` | 兼容端点(返回 `{ status, timestamp }` | 无 |
| 端点 | 用途 | 鉴权 |
| -------------- | ---------------------------------------------------------- | ---- |
| `GET /healthz` | 存活探针liveness | 无 |
| `GET /readyz` | 就绪探针readiness,并行 ping 下游 /healthz软失败规则 | 无 |
| `GET /metrics` | Prometheus 指标端点7 个业务指标 + Go runtime | 无 |
## 开发
```bash
cd services/api-gateway
go mod tidy
go run main.go
```
DevMode接受 `Bearer dev-token` 旁路鉴权):
```bash
DEV_MODE=true go run main.go
```
## 测试
```bash
# 单元 + 集成测试
cd services/api-gateway
go test ./internal/middleware/... -v -cover
# 测试覆盖
# - circuit-breaker_test.go: 5 用例ClosedToOpen / OpenToHalfOpen / HalfOpenToClosed / HalfOpenToOpen / 4xxNotCounted
# - ratelimit_test.go: 5 用例AllowUnderBurst / RejectOverBurst / RefillTokens / PerIPIsolation / CleanupExpiredBuckets
```
## 构建
```bash
# 本地构建
go build ./...
# Docker 构建
docker build -t edu/api-gateway .
```
@@ -71,25 +94,35 @@ docker build -t edu/api-gateway .
通过环境变量配置(见 `internal/config/config.go`
| 变量 | 默认值 | 说明 |
| ----------------------------- | --------------------- | ------------------------------------------------------ |
| `API_GATEWAY_PORT` | 8080 | 监听端口 |
| `JWT_SECRET` | (必填) | HS256 签名密钥P1 |
| `JWT_ISSUER` | next-edu-cloud | JWT 签发者 |
| `JWT_AUDIENCE` | next-edu-cloud | JWT 受众 |
| `DEV_MODE` | false | 开发模式旁路true 时接受 `Bearer dev-token`(仅本地) |
| `CLASSES_SERVICE_URL` | http://localhost:3001 | classes 服务地址 |
| `IAM_SERVICE_URL` | http://localhost:3002 | iam 服务地址 |
| `TEACHER_BFF_URL` | http://localhost:3003 | teacher-bff 服务地址 |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | http://localhost:4318 | OpenTelemetry OTLP 端点 |
| `LOG_LEVEL` | info | 日志级别 |
| 变量 | 默认值 | 说明 |
| ----------------------------- | ------------------------------------------- | -------------------------------------------------------- |
| `API_GATEWAY_PORT` | 8080 | 监听端口 |
| `ENV` | development | 部署环境production 时 W7 防护生效) |
| `DEV_MODE` | false | 开发模式旁路true 时接受 `Bearer dev-token`(仅非生产) |
| `IAM_JWKS_URL` | http://localhost:3002/.well-known/jwks.json | RS256 公钥端点(非 DevMode 必填) |
| `JWT_ISSUER` | next-edu-cloud | JWT 签发者校验 |
| `JWT_AUDIENCE` | next-edu-cloud | JWT 受众校验 |
| `CORS_ORIGINS` | (空,用开发默认白名单) | CORS 白名单(逗号分隔) |
| `CLASSES_SERVICE_URL` | http://localhost:3001 | classes 服务地址(遗留,路由实际走 core-edu |
| `IAM_SERVICE_URL` | http://localhost:3002 | iam 服务地址 |
| `TEACHER_BFF_URL` | http://localhost:3003 | teacher-bff 服务地址 |
| `STUDENT_BFF_URL` | http://localhost:3009 | student-bff 服务地址 |
| `PARENT_BFF_URL` | http://localhost:3010 | parent-bff 服务地址 |
| `CORE_EDU_SERVICE_URL` | http://localhost:3004 | core-edu 服务地址 |
| `CONTENT_SERVICE_URL` | http://localhost:3005 | content 服务地址 |
| `DATA_ANA_SERVICE_URL` | http://localhost:3006 | data-ana 服务地址 |
| `MSG_SERVICE_URL` | http://localhost:3007 | msg 服务地址 |
| `AI_SERVICE_URL` | http://localhost:3008 | ai 服务地址 |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | http://localhost:4318 | OpenTelemetry OTLP 端点 |
| `LOG_LEVEL` | info | 日志级别 |
> **生产环境警告**`DEV_MODE` 必须为 `false` 或不设。设为 `true` 会允许 `dev-token` 旁路鉴权并注入固定 admin 身份
> **生产环境警告**`DEV_MODE=true` 且 `ENV=production` 时服务 panic 拒绝启动W7 防护)
## 关联文档
- [架构影响地图](../../docs/architecture/004_architecture_impact_map.md)
- [项目规则](../../.trae/rules/project_rules.md)
- [模块理解确认书](./docs/01-understanding.md)
- [架构设计文档](./docs/02-architecture-design.md)
- [P6 硬化 Runbook](../../docs/architecture/runbooks/p6-hardening.md)
- [P6 后续工作手册](../../docs/architecture/runbooks/post-p6-followup.md)
- [Helm Chart](../../infra/k8s/helm/api-gateway/)