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:
@@ -76,7 +76,7 @@
|
||||
|
||||
## 4. 我的技术栈
|
||||
|
||||
- **语言**:Go 1.22+(go.mod 声明 1.25.0,需与 Dockerfile 对齐,见审计表)
|
||||
- **语言**:Go 1.22(go.mod 与 Dockerfile 一致)
|
||||
- **框架**:Gin v1.12.0
|
||||
- **核心依赖**:
|
||||
- `github.com/golang-jwt/jwt/v5` v5.2.1(JWT 校验)
|
||||
@@ -97,44 +97,46 @@
|
||||
|
||||
## 6. 我需要对齐的黄金模板项(对照 classes 服务)
|
||||
|
||||
| 项 | classes(黄金模板) | api-gateway 现状 | 差距 |
|
||||
| ----------------- | ------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------- |
|
||||
| 权限装饰器 | `@RequirePermission()` | N/A(Go 无装饰器;用中间件 `AuthMiddleware` 替代) | ✅ 等价实现 |
|
||||
| 错误码前缀 | `CLASSES_*` | 无前缀(基础设施层) | ✅ 设计合理 |
|
||||
| logger(pino) | `shared/observability/logger.ts` | ❌ 用标准库 `log` | ⚠️ 待补 `log/slog` 结构化日志 |
|
||||
| metrics | `shared/observability/metrics.ts` 暴露 `/metrics` | ❌ 无 `/metrics` 端点 | ⚠️ 待补 prom-client |
|
||||
| tracer | `shared/observability/tracer.ts` OTel SDK | ✅ `internal/observability/tracer.go` | ✅ 对齐 |
|
||||
| `/healthz` | ✅ | ✅ | ✅ 对齐 |
|
||||
| `/readyz` | ✅ 检查 DB `SELECT 1` | ❌ stub 直接返回 200 | ⚠️ 待补下游服务健康检查 |
|
||||
| 优雅关闭 | SIGTERM → app.close() | ✅ `srv.Shutdown(ctx)` 5s 超时 | ✅ 对齐 |
|
||||
| 测试覆盖率 | ≥ 80% | ~25%(仅 circuit-breaker + ratelimit) | ⚠️ 待补 auth/cors/security/recovery/requestid/proxy 测试 |
|
||||
| Dockerfile | 多阶段 + 非 root + healthcheck | ✅ | ✅ 对齐 |
|
||||
| Zod 输入验证 | `schema.safeParse(body)` | N/A(Go 无 Zod;用 `ShouldBindJSON`) | ✅ 等价实现 |
|
||||
| GlobalErrorFilter | `GlobalErrorFilter` | ✅ Recovery 中间件兜底 | ✅ 等价实现 |
|
||||
| 项 | classes(黄金模板) | api-gateway 现状 | 差距 |
|
||||
| ----------------- | ------------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| 权限装饰器 | `@RequirePermission()` | N/A(Go 无装饰器;用中间件 `AuthMiddleware` 替代) | ✅ 等价实现 |
|
||||
| 错误码前缀 | `CLASSES_*` | 无前缀(基础设施层) | ✅ 设计合理 |
|
||||
| logger(pino) | `shared/observability/logger.ts` | ✅ `log/slog` 结构化 JSON | ✅ 对齐 |
|
||||
| metrics | `shared/observability/metrics.ts` 暴露 `/metrics` | ✅ 7 个业务指标(promauto) | ✅ 对齐 |
|
||||
| tracer | `shared/observability/tracer.ts` OTel SDK | ✅ `internal/observability/tracer.go`(W6 资源属性完整) | ✅ 对齐 |
|
||||
| `/healthz` | ✅ | ✅ | ✅ 对齐 |
|
||||
| `/readyz` | ✅ 检查 DB `SELECT 1` | ✅ 并行 ping 下游 /healthz(软失败规则) | ✅ 对齐 |
|
||||
| 优雅关闭 | SIGTERM → app.close() | ✅ `srv.Shutdown(ctx)` 5s 超时 | ✅ 对齐 |
|
||||
| 测试覆盖率 | ≥ 80% | ~25%(仅 circuit-breaker + ratelimit) | ⚠️ 待补 auth/cors/security/recovery/requestid/proxy 测试 |
|
||||
| Dockerfile | 多阶段 + 非 root + healthcheck | ✅ | ✅ 对齐 |
|
||||
| Zod 输入验证 | `schema.safeParse(body)` | N/A(Go 无 Zod;用 `ShouldBindJSON`) | ✅ 等价实现 |
|
||||
| GlobalErrorFilter | `GlobalErrorFilter` | ✅ Recovery 中间件兜底 | ✅ 等价实现 |
|
||||
|
||||
## 7. 服务审计表(按 ai-allocation §10 模板)
|
||||
|
||||
| 服务 | 权限装饰器 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile |
|
||||
| ----------- | ------------- | ------------- | ----------- | ------- | ------- | -------- | ------- | ---------- | ---------- | ---------- |
|
||||
| api-gateway | ⚠️ 中间件替代 | ✅ 无前缀合理 | ❌ 标准 log | ❌ 无 | ✅ OTel | ✅ | ⚠️ stub | ✅ 5s 超时 | ~25% | ✅ 多阶段 |
|
||||
| 服务 | 权限装饰器 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile |
|
||||
| ----------- | ------------- | ------------- | ------- | --------- | ------- | -------- | ------------ | ---------- | ---------- | ---------- |
|
||||
| api-gateway | ⚠️ 中间件替代 | ✅ `GW_` 前缀 | ✅ slog | ✅ 7 指标 | ✅ OTel | ✅ | ✅ 并行 ping | ✅ 5s 超时 | ~25% | ✅ 多阶段 |
|
||||
|
||||
### 7.1 详细问题清单(按严重度排序)
|
||||
|
||||
| # | 严重度 | 文件 | 问题 | 修复建议 |
|
||||
| --- | ------ | -------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | 高 | `internal/observability/` 缺失 | 无 `/metrics` 端点,Prometheus 404 | 新增 `metrics.go`,注册 `http_requests_total`/`http_request_duration_seconds`/`circuit_breaker_state`,在 main.go 暴露 `/metrics` |
|
||||
| 2 | 高 | `internal/health/health.go` | `/readyz` 直接返回 200,未检查下游 | 改为并行 ping 9 个下游 `/healthz`,任一不可达返回 503;超时 2s |
|
||||
| 3 | 高 | `internal/middleware/auth.go` L124-139 | 死代码 `RequestIDMiddleware()` + `generateUUID()` 重复 requestid.go 且未使用;`uuid` 包未导入 | 删除 L124-139(已由 `requestid.go` 实现) |
|
||||
| 4 | 高 | 全文件 | 用 `log.Printf`,不符合 coding-standards §3.8 `log/slog` 结构化日志要求 | 引入 `slog.New(slog.NewJSONHandler(os.Stdout))`,所有日志带 `request_id`/`trace_id` |
|
||||
| 5 | 中 | `go.mod` L3 vs `Dockerfile` L1 | go.mod 声明 `go 1.25.0`,Dockerfile 用 `golang:1.22-alpine` | 统一为 `go 1.22`(与 Dockerfile 一致),或升级 Dockerfile 到 `golang:1.25-alpine` |
|
||||
| 6 | 中 | `internal/middleware/auth.go` | P2 待升级 HS256 → RS256 | 新增 `JWKSFetcher` 缓存 IAM 公钥(TTL 1h),`jwt.Parse` 用 `jwt.WithKeySet(jwks)` |
|
||||
| 7 | 中 | `internal/middleware/cors.go` L21 | `CORS_ORIGINS` 直接 `os.Getenv`,未纳入 Config 结构 | 移入 `config.Config.CORSOrigins`,与其他配置统一 |
|
||||
| 8 | 中 | `internal/middleware/ratelimit.go` | 单实例内存令牌桶,水平扩展后限流失效 | P6 引入 Redis 令牌桶(`redis_rate`)或保留单实例但文档标注 |
|
||||
| 9 | 中 | `internal/middleware/auth.go` L68 | DevMode 注入固定 `teacher,admin` 角色,生产风险 | 启动时若 `DevMode=true && ENV=production` 则 panic 拒绝启动 |
|
||||
| 10 | 低 | `README.md` L38 | 提到 `GET /health` 兼容端点,但代码未注册 | 删除 README 描述或补注册 |
|
||||
| 11 | 低 | `internal/proxy/proxy.go` L24 | 连续两次 `TrimPrefix`(`/api/v1` 后再 `/api`)逻辑冗余 | 第二次 `TrimPrefix("/api")` 实际无效果(首字符已是 `/`),可删 |
|
||||
| 12 | 低 | `Dockerfile` L18 | 构建命令 `./main.go` 而非 `./` | 改为 `go build -ldflags="-s -w" -o /app/bin/api-gateway .` 更规范 |
|
||||
| 13 | 低 | 测试 | auth/cors/security/recovery/requestid/proxy 无测试 | 补 `*_test.go`,目标覆盖率 ≥ 80% |
|
||||
> 更新日期:2026-07-10(P2-P5 实施后复核)
|
||||
|
||||
| # | 严重度 | 文件 | 问题 | 状态 | 修复说明 |
|
||||
| --- | ------ | ---------------------------------- | ---------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| 1 | 高 | `internal/observability/` | 无 `/metrics` 端点,Prometheus 404 | ✅ 已修复 | 新增 `metrics.go`,注册 7 个业务指标,main.go 暴露 `/metrics` |
|
||||
| 2 | 高 | `internal/health/health.go` | `/readyz` 直接返回 200,未检查下游 | ✅ 已修复 | 改为并行 ping 9 个下游 `/healthz`,软失败规则(iam/teacher-bff required,其余 optional) |
|
||||
| 3 | 高 | `internal/middleware/auth.go` | 死代码 `RequestIDMiddleware()` + `generateUUID()` 重复 requestid.go 且未使用 | ✅ 已修复 | 死代码已删除(P2.0) |
|
||||
| 4 | 高 | 全文件 | 用 `log.Printf`,不符合 coding-standards §3.8 `log/slog` 结构化日志要求 | ✅ 已修复 | 全部 `log.Printf`/`log.Fatal` 替换为 `slog.Info`/`slog.Error`(W3) |
|
||||
| 5 | 中 | `go.mod` L3 vs `Dockerfile` L1 | go.mod 声明 `go 1.25.0`,Dockerfile 用 `golang:1.22-alpine` | ✅ 已修复 | go.mod 统一为 `go 1.22`,go.work 因 push-gateway 要求升级为 `go 1.25.0`(workspace 兼容更低版本模块) |
|
||||
| 6 | 中 | `internal/middleware/auth.go` | P2 待升级 HS256 → RS256 | ✅ 已修复 | 接入 `shared-go/jwks.Fetcher`,RS256 公钥校验 + kid 路由(W6 资源属性完整) |
|
||||
| 7 | 中 | `internal/middleware/cors.go` L21 | `CORS_ORIGINS` 直接 `os.Getenv`,未纳入 Config 结构 | ✅ 已修复 | `CORS()` 改为 `CORS(cfg *config.Config)`,从 Config 读取白名单 |
|
||||
| 8 | 中 | `internal/middleware/ratelimit.go` | 单实例内存令牌桶,水平扩展后限流失效 | ⏳ P6 | P6 引入 Redis 令牌桶(`redis_rate`),支持多副本一致 |
|
||||
| 9 | 中 | `internal/middleware/auth.go` L68 | DevMode 注入固定 `teacher,admin` 角色,生产风险 | ✅ 已修复 | 启动时 `DevMode=true && ENV=production` panic 拒绝启动(W7 防护,config.go) |
|
||||
| 10 | 低 | `README.md` L38 | 提到 `GET /health` 兼容端点,但代码未注册 | ✅ 已修复 | README 重写,删除 `/health` 描述 |
|
||||
| 11 | 低 | `internal/proxy/proxy.go` L24 | 连续两次 `TrimPrefix`(`/api/v1` 后再 `/api`)逻辑冗余 | ✅ 已修复 | 冗余 `TrimPrefix` 已删除(P2.0) |
|
||||
| 12 | 低 | `Dockerfile` L18 | 构建命令 `./main.go` 而非 `./` | ⚠️ 保留 | `./main.go` 单文件构建可正常工作,`-ldflags="-s -w"` 已添加;改为 `.` 需评估是否有其他 main 包文件(当前无) |
|
||||
| 13 | 低 | 测试 | auth/cors/security/recovery/requestid/proxy 无测试 | ⏳ P6 | P6 补 `*_test.go`,目标覆盖率 ≥ 80% |
|
||||
|
||||
## 8. 风险与假设
|
||||
|
||||
|
||||
Reference in New Issue
Block a user