docs(api-gateway): ai01 阶段1+2 模块理解确认书与架构设计文档

阶段1交付:services/api-gateway/docs/01-understanding.md
- 8节内容:架构位置/限界上下文/契约/技术栈/阶段归属/黄金模板对齐审计
- 审计13项差距(3高:缺/metrics、/readyz stub、auth.go死代码;4中:log/slog缺失、go.mod版本不匹配、HS256待升RS256、DevMode风险;6低)

阶段2交付:services/api-gateway/docs/02-architecture-design.md
- 9节内容:内部分层图/路由表矩阵9下游/限流策略表/熔断阈值表/JWT RS256流程含JWKS缓存/CORS白名单/请求ID注入/metrics 7项指标/P0-P3实施优先级

同步更新 docs/troubleshooting/known-issues.md 工作经验日志(追加ai01条目)

AI Agent: ai01 (api-gateway/push-gateway)
Branch: main
Coordinator: coord
This commit is contained in:
SpecialX
2026-07-09 17:21:03 +08:00
parent f1e466a772
commit 834e2c61fd
3 changed files with 619 additions and 35 deletions

View File

@@ -334,7 +334,7 @@
### 2.12 teacher-portal微前端宿主P1 测试页)
| 场景 | 技术/规则 |
| -------------------- | --------------------------------------------------------------------------------------- |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| P1 测试页 | 单一 Next.js 应用,验证 classes CRUD 端到端链路 |
| API 调用 | `fetch(${API_BASE}/api/v1/classes)` + `Authorization: Bearer ${TEST_JWT}` |
| P1 测试 JWT | 开发工具生成 HS256 tokenP2 起由 IAM 签发 RS256 |
@@ -344,6 +344,13 @@
| P2 视口驱动侧边栏 | AppShell fetch `/teacher/viewports` 渲染左侧导航active 路由高亮 |
| P2 根路径重定向 | `app/page.tsx` 客户端组件 `router.replace(isAuthenticated() ? '/dashboard' : '/login')` |
| fetch headers 类型 | `authHeaders(): Record<string, string>` 显式标注,避免 `{}` 与 `HeadersInit` 不兼容 |
| P2 前端权限校验等价物 | `usePermission().hasPermission()` Hook + `<RequirePermission>` 组件,镜像后端 `@RequirePermission()` 装饰器,禁止 `role === "xxx"` 硬编码§3.1 |
| P2 MF Shell+Remote 架构 | teacher-portal:3000 为 Shellstudent:3001/parent:3002/admin:3003 为 Remote共享登录态/布局/组件库/权限体系 |
| P2 统一 API 请求层 | ApiClient 封装 401 自动刷新 token 轮转 + ActionState `{success,data,error}` 解析 + 错误码前缀路由 i18n替代页面级 `authHeaders()+fetch` 重复 |
| P2 设计令牌三层模型 | primitive原始色板→ semantic-light/dark语义→ tailwind-theme`@theme inline` 暴露 `bg-*`ESLint `no-restricted-syntax` 禁 `#hex` + `design-tokens/no-hardcoded-fonts` 禁字面量字体§3.10 |
| P2 5 层状态管理 | nuqsURL 同步)/ TanStack Query服务端缓存/ Zustand客户端业务状态/ Zustand UIUI 临时态)/ react-hook-form表单 |
| P2 4 端错误码前缀对齐 | TP_=teacher-bff / SP_=student-bff / PP_=parent-bff / AP_=admin-bff前端按前缀路由 i18n key |
| P2 ErrorBoundary | 路由级 `<ErrorBoundary>` 包裹避免白屏,搭配 `<Suspense>` 流式加载 |
---
@@ -352,7 +359,10 @@
> 按时间倒序50 条上限。AI 发现更好方案时可更新本节。
| 日期 | 时间 | 模块 | 做了什么 + 学到什么 |
| ---------- | ---- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ---------- | ---- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2026-07-09 | 夜间 | teacher-portal/前端4端ai07 | **ai07 阶段 1+2 交付(前端 4 端)**(1) 按 ai-allocation.md §4 必读清单读完 7 份全局文档README/MIGRATION_GUIDE/004/pending-features/project_rules/coding-standards/multi-ai-collaboration+ classes 黄金模板源码controller/service/repository/schema/dto+ shared-proto 8 份 proto + ai-allocation.md §5 ai07 分工。(2) 运行 `pnpm run arch:scan` 更新 arch.db10 TS/264 符号、2 Go/33 符号、2 Py/45 符号、138 proto 契约)。(3) 阶段 1 产出 4 端模块理解确认书(架构定位/限界上下文/契约依赖/技术栈/阶段归属/黄金模板对齐审计),合并到 apps/teacher-portal/README.md。(4) teacher-portal 现状审计 19 维度,发现 7 项高优违规AppShell.tsx L143 硬编码 `user.roles.join(",")`(违反 §3.1 前端禁 role 硬编码)/ globals.css+tailwind.config.js 硬编码 `hsl()` 字面量与 `#hex`(违反 §3.10 设计令牌)/ layout.tsx 直接 import `'Inter'/'Fraunces'/'JetBrains Mono'` 字面量(违反 §3.10 禁硬编码字体)/ 缺统一 ApiClient 层4 页面重复 local authHeaders()+fetch/ 缺 usePermission Hook前端无 @RequirePermission 等价物)/ 缺 ErrorBoundary白屏风险/ 缺 5 层状态管理nuqs/TanStack Query/Zustand/Zustand-UI/react-hook-form 全缺)/ next.config.js 无 Module Federation 配置4 端无法 Shell+Remote 组合)。(5) 阶段 2 产出模块架构设计文档MF 2.0 Shell+Remote 架构teacher-portal:3000 为 Shellstudent:3001/parent:3002/admin:3003 为 Remote含 mermaid 架构图 + MF config 代码示例 + 领域模型Session/Viewport/Permission TS 接口)+ 数据模型缓存策略表5 层 DataScope × 5 类缓存键)+ API 请求层设计ApiClient 401 自动刷新 + ActionState 解析 + 错误码前缀路由 i18n+ WebSocket/SSE 事件设计 + 横切关注点对齐清单(权限表 4 端前缀/错误码前缀 TP_/SP_/PP_/AP_/logger/metrics/tracer/health。(6) 4 端差异化对照表 5 张(总体/L1 导航/L2 路由/L3 组件/L4 数据)。(7) 交互点契约清单 12 项 + 风险 7 项 + 假设 5 项 + 4 项待 coord 仲裁packages 归属/GraphQL vs REST/i18n key 命名/MF 暴露粒度)。(8) coord 交叉审查信息:端口矩阵 3000-3003、5 个 shared 包待建shared-ts/shared-tokens/shared-ui/shared-mf/shared-perm、11 个后端契约依赖、错误码前缀对齐、无 Kafka 事件消费。**学到**:前端权限校验等价物是 `usePermission().hasPermission()` Hook + `<RequirePermission>` 组件(镜像后端 `@RequirePermission()` 装饰器MF Shell+Remote 架构选择优于 4 独立 Shell共享登录态/布局/组件库/权限体系)和单 Next.js 应用4 端独立部署/独立 CI/独立回滚teacher-portal 现状有 7 项高优违规需 P2 闭环前修复4 端 API 错误码前缀需与后端服务对齐TP_=teacher-bff、SP_=student-bff、PP_=parent-bff、AP_=admin-bff设计令牌三层模型primitive/semantic-light+dark/tailwind-theme必须 ESLint 强制约束no-restricted-syntax 禁 #hex + design-tokens/no-hardcoded-fonts 禁字面量字体)。 |
| 2026-07-09 | 夜间 | api-gateway/push-gatewayai01 | **ai01 阶段 1+2 交付Go 网关层)**(1) 按 ai-allocation.md §4 必读清单读完 7 份全局文档README/MIGRATION_GUIDE/004/pending-features/project_rules/coding-standards/multi-ai-collaboration+ classes 黄金模板全部源码 + shared-proto 8 份 protoiam/msg/events+ api-gateway/push-gateway 全部 Go 源码。(2) 运行 `pnpm run arch:scan` 更新 arch.db10 TS/264 符号、2 Go/33 符号、2 Py/45 符号、138 proto 契约)。(3) 阶段 1 产出 2 份模块理解确认书services/{api-gateway,push-gateway}/docs/01-understanding.md+ 审计表api-gateway 审计出 13 项差距3 高优先:缺 /metrics 端点、/readyz 是 stub 返回 ok 不检查依赖、auth.go L124-139 死代码 RequestIDMiddleware+generateUUID 与 requestid.go 重复4 中logger 用 fmt 非 log/slog、go.mod go 1.25.0 与 Dockerfile golang:1.22-alpine 版本不匹配、HS256 待 P2 升 RS256、DevMode 生产环境风险6 低push-gateway 审计出 16 项差距6 高:无 Redis Pub/Sub 横向扩展、CheckOrigin 直接 return true 安全风险、用文本 "ping"/"pong" 心跳非 RFC 6455 控制帧、无单用户连接数上限、/internal/* 无鉴权、Dockerfile 单阶段且 root 用户无 healthcheck。(4) 阶段 2 产出 2 份模块架构设计文档02-architecture-design.mdapi-gateway 覆盖 9 节(内部分层图、路由表矩阵 9 下游含端口+鉴权规则、限流策略表按路由差异化 RPS/burst、熔断阈值表按服务、JWT RS256 流程含 JWKS 缓存、CORS 白名单、请求 ID 注入、metrics 7 项指标清单、P0-P3 实施优先级push-gateway 覆盖 13 节内部分层图、Connection/Hub 领域模型、Redis 4 个 key pattern、WebSocket 端点+子协议 JSON 格式、内部推送 API+X-Internal-Token 鉴权、双通道协议 HTTP 同步+Kafka 异步、WebSocket 生命周期状态图、心跳协议 RFC 6455 控制帧 30s 间隔 60s 超时、单用户最大 5 连接、重连协议 P6 预留、多实例架构图、Redis Pub/Sub 跨实例流程、容量目标 10w+ 连接 <50ms 本地推送 <200ms 跨实例)。**学到**gobreaker v2 ReadyToTrip 在 Requests=1 时 1*2>1=true 即 1 次失败就触发 OPEN与 P6 集成测试观察一致gorilla/websocket 不支持并发写同一连接Hub.Send 必须用 send chan + 单写协程串行化(已在 P5 修复但设计文档需明确标注此约束push-gateway 骨架用文本 "ping"/"pong" 违反 RFC 6455应用 SetPongHandler 处理控制帧004 §7.2 事件 topic 用 `edu.teaching.exam.published` 前缀但代码 TOPIC_MAP 用 `edu.exam.events`ai03 已提请 coord 仲裁,本 AI 在 push-gateway 设计中消费 `edu.notification.events` 待 coord 统一命名后同步api-gateway 与 push-gateway 重复 tracer.go/logger.go/jwks.go/env.go建议提取到 `packages/shared-go/`(需 coord 创建包后多 AI 协同迁移Go 服务 .env 不会自动加载DevMode 必须在启动前 `export DEV_MODE=true` 或集成 godotenv。 |
| 2026-07-09 | 夜间 | teacher-bff/core-eduai03 | **ai03 阶段 1 全局理解交付**(1) 按 ai-allocation.md §4 必读清单读完 7 份全局文档 + classes 黄金模板全部源码 + shared-proto 8 份 proto + teacher-bff/core-edu 现有实现。(2) 运行 arch:scan 更新 arch.db10 TS/264、2 Go/33、2 Py/45、138 proto 契约arch:query deps/stats 发现 arch.db 仅记录模块/符号统计不记录跨模块调用边。(3) 按 §6 模板产出两份模块理解确认书 + §10 审计表,交付 docs/architecture/ai03-phase1-understanding.md。(4) 审计发现 teacher-bff 7 项差距REST 非 gRPC/无 GraphQL/无 DataLoader/无 Redis 缓存/无 readyz/无 Zod/无测试、core-edu 12 项差距(考试状态机缺失/作业状态机不完整/成绩无校验/无并发锁/homework.graded 与 grade.updated 事件未触发/未消费 IAM user.created/Drizzle db 导出 vs classes getDb() 不一致/kafka.ts 用 console 非 logger/classes 模块仅占位待合并/REST 未转 gRPC/无 Zod/无测试)。(5) 提请 coord 交叉审查 6 项跨模块契约对齐iam getEffectivePermissions 聚合 API proto / iam user.created topic / 端口 3004 / Kafka topic 命名 004 文档与代码不一致 / data-ana 消费契约 / msg 消费契约)。**学到**004 §7.2 事件 topic 用 `edu.teaching.exam.published` 前缀,但 core-edu outbox.publisher.ts TOPIC_MAP 用 `edu.exam.events`,文档与代码不一致需 coord 仲裁统一teacher-bff 当前用 REST fetch 但 P2 退出标准要求 GraphQL Yoga + DataLoader阶段 2 设计需补通信方式迁移core-edu 与 classes 黄金模板的 Drizzle 访问方式不一致core-edu 直接 `export const db`classes 用 `getDb()` 函数),建议统一为 `getDb()` 函数式以匹配 HealthController 已有约定。 |
| 2026-07-09 | 下午 | classes/全局 | **一键启动脚本 NestJS dist/ 不生成根因定位 + classes 健康检查修复**(1) 根因定位:`tsconfig.base.json` 的 `incremental: true` + `nest-cli.json` 的 `deleteOutDir: true` 冲突。`nest start --watch` 启动时先删除 dist/tsc 读残留 .tsbuildinfo 认为无变化跳过 emitdist/ 不生成 → `Cannot find module dist/main`。(2) 修复6 个 NestJS 服务classes/iam/teacher-bff/core-edu/content/msgtsconfig.json 显式加 `"incremental": false` 覆盖 base 配置,删除所有残留 .tsbuildinfo 文件。(3) classes AppModule 缺 HealthModule 导入导致 /healthz 404iam 同样问题,修复 app.module.ts 加 `imports: [..., HealthModule]`。(4) classes HealthController 误用 TypeORM `DataSource` DI与 iam 不一致),运行时报 `Nest can't resolve dependencies of the HealthController (DataSource)`。修复:改为 Drizzle `getDb()` 函数式调用,与 iam 一致。(5) 一键启动验证11/11 应用 + 11/11 基础设施 + 5/5 可观测性端点全绿。**学到**NestJS + TypeScript incremental 编译是陷阱组合——nest-cli deleteOutDir 删 dist 但 tsc 读 tsbuildinfo 认为无变化,必须在服务级 tsconfig 显式 `incremental: false`HealthModule 必须在 AppModule imports 中显式声明才能被 NestFactory 扫描到5 个 NestJS 服务的 HealthController 应统一用 Drizzle `getDb()` 函数式调用而非 TypeORM DataSource DI项目已弃 TypeORM 改 Drizzle。 |
| 2026-07-09 | 下午 | 全局 | **OTel auto-instrumentations 全服务补全**(1) NestJS 6 服务iam/classes/core-edu/content/msg/teacher-bfftracer.ts 补 `getNodeAutoInstrumentations()`NodeSDK 传 instrumentations 参数自动埋点 HTTP/Express/DB。(2) Python 2 服务data-ana/aimain.py 补 `FastAPIInstrumentor.instrument_app(app)`ai 补缺失的 `opentelemetry-exporter-otlp` 依赖。(3) teacher-bff 从零补完整 OTelenv.ts 加 OTEL_EXPORTER_OTLP_ENDPOINT 字段 + 新建 shared/observability/tracer.ts + main.ts 调用 initTracer/shutdownTracer + package.json 加 sdk-node/exporter/auto-instrumentations 依赖。(4) Go 2 服务api-gateway/push-gateway新建 internal/observability/tracer.goOTLP HTTP exporter + resource + TracerProvider + W3C propagator+ main.go 调用 InitTracer + otelgin.Middleware 注册 Gin 中间件push-gateway config.go 补 OTLPEndpoint 字段。(5) 质量校验全通过TS typecheck 9 服务 + ESLint 6 服务 + ruff 2 服务 + go vet/build 2 服务零错误。**学到**`getNodeAutoInstrumentations()` 一次注册所有 Node.js 自动埋点http/express/dns/fs/net/grpc 等),比手动逐个注册 HttpInstrumentation 更简洁Go OTel 用 `otlptracehttp.WithEndpoint(host)` + `WithInsecure()` 需从 "http://host:port" URL 解析出 hostotelgin.Middleware 必须在 Recovery 之后其他中间件之前注册,确保所有后续 handler 都被 tracePython FastAPIInstrumentor.instrument_app(app) 在 app 创建后立即调用lifespan 不受影响。 |
| 2026-07-09 | 下午 | 全局 | **P6 硬化:可观测性 + 部署 + CI 硬化**(1) 可观测性栈完善5 个 NestJS 服务 main.ts 添加 `/metrics` Prometheus 端点(用 `app.getHttpAdapter().get('/metrics', ...)` 绕过 DI 容器 get 方法prometheus.yml 从 2 个目标扩展到 8 个应用服务 + MySQL/Redis + node-exporter + prometheus 自身 + rule_files + alertmanager 关联monitoring compose 用 Loki + Promtail 替换未配置的 blackbox-exporterGrafana datasource 新增 Loki新建 promtail/config.yml 用 docker_sd_configs 仅采集 `edu-*` 容器日志。(2) 部署 compose 扩展docker-compose.deploy.yml 从 3 服务扩展到 11 服务(+ iam/teacher-bff/core-edu/content/msg/ai/data-ana/push-gateway每个服务带 healthcheck + depends_on 条件 + edu-net/edu-shared 双网络deploy.env.example 补全 Neo4j/ES/ClickHouse/LLM/Kafka 可选依赖配置。(3) teacher-bff 补 health.controller.ts原缺失 /healthz 导致 deploy depends_on service_healthy 失败)。(4) CI 硬化:移除 lint 步骤的 continue-on-errorESLint 9 flat config 已配置完成test 保留 continue-on-error部分服务无 test 脚本)。**学到**NestJS `app.get('/metrics')` 会被解析为 DI 容器 `get(typeOrToken)`,必须用 `app.getHttpAdapter().get()` 才能注册 Express 路由Promtail docker_sd_configs 通过 relabel_configs 的 `regex: '/(edu-.*).*'` 过滤容器名前缀docker-compose.depends_on.condition: service_healthy 要求被依赖服务必须有 healthcheck 配置,否则启动失败。 |

View File

@@ -0,0 +1,145 @@
# 模块理解确认书 — api-gateway
> AIai01Go 网关层)
> 阶段:阶段 1 交付物
> 日期2026-07-09
> 关联:[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)、[AI 分配方案](../../../docs/architecture/ai-allocation.md)
---
## 1. 我在架构中的位置
- **层级**L3 网关层004 §3.1 六层架构)
- **上游**4 个微前端teacher-portal / student-portal / parent-portal / admin-portal通过浏览器/移动端 HTTP 请求
- **下游**3 个 BFF + 6 个业务服务,共 9 个反向代理目标
- BFFteacher-bff3003、student-bff待建、parent-bff待建
- 业务iam3002、core-edu3004、content3005、data-ana3006、msg3007、ai3008
- **通信方式**
- 入口HTTP/REST含 WebSocket 升级透传到 push-gateway不在本服务
- 出口HTTP 反向代理(`httputil.ReverseProxy`P3 起部分链路改 gRPC
- **不持有业务状态**:仅做路由/鉴权/限流/熔断/可观测,无 DB
## 2. 我的限界上下文
- **我负责**所有外部请求的统一入口、JWT 校验、用户身份注入、限流、熔断、CORS、安全头、请求 ID 注入、反向代理
- **聚合/实体**:无(网关无领域模型)
- **业务领域**:不属于 D1-D6 任一业务领域,属于基础设施层
- **我不负责**
- WebSocket 长连接管理push-gateway 负责)
- 用户身份认证逻辑iam 负责,本服务只校验 JWT 签名)
- 权限点解析(业务服务 Controller 通过 `@RequirePermission` 自行校验,本服务只透传 `x-user-id`/`x-user-roles` 头)
- 业务数据持久化(无 DB
## 3. 我与外部的契约
### 3.1 我消费的 proto message
| proto | message | 用途 |
| --------- | ------------------------ | ----------------------------------------------------------------------- |
| iam.proto | `UserInfo` / `TokenPair` | P2 起 RS256 公钥校验时通过 IAM `/auth/jwks` 端点拉公钥HTTP非 gRPC |
> P1 阶段用 HS256 共享密钥,**不消费任何 proto**。P2 起 RS256 通过 IAM 暴露的 JWKS 端点(`/.well-known/jwks.json`)拉取公钥,仍是 HTTP无需 gRPC 客户端。
### 3.2 我暴露的 API 端点
| 方法 | 路径 | 鉴权 | 说明 |
| ---- | --------------------------------------------------------------------------- | --------------------------------------- | ----------------------------------------- |
| GET | `/healthz` | 无 | liveness 探针 |
| GET | `/readyz` | 无 | readiness 探针(当前 stub需补真实检查 |
| ANY | `/api/v1/classes` + `/api/v1/classes/*path` | JWT | 代理到 classes/core-edu 服务 |
| ANY | `/api/v1/iam` + `/api/v1/iam/*path` | JWT除 register/login/refresh 白名单) | 代理到 iam 服务 |
| ANY | `/api/v1/teacher` + `/api/v1/teacher/*path` | JWT | 代理到 teacher-bff |
| ANY | `/api/v1/exams` `/homework` `/grades` + `/*path` | JWT | 代理到 core-edu |
| ANY | `/api/v1/textbooks` `/chapters` `/knowledge-points` `/questions` + `/*path` | JWT | 代理到 content |
| ANY | `/api/v1/notifications` + `/*path` | JWT | 代理到 msg |
| ANY | `/api/v1/ai` + `/*path` | JWT | 代理到 ai |
| ANY | `/api/v1/analytics` + `/*path` | JWT | 代理到 data-ana |
| GET | `/metrics` | 无(待实现) | Prometheus 指标端点 |
### 3.3 我发布/消费的 Kafka 事件
**无**。api-gateway 不接入 Kafka是纯同步 HTTP 反向代理。
### 3.4 错误码前缀
| 错误码 | HTTP | 触发条件 |
| ------------------- | ---- | ------------------------------------------------------------------- |
| `UNAUTHORIZED` | 401 | 缺失 Authorization 头 |
| `INVALID_TOKEN` | 401 | JWT 签名/格式错误 |
| `INVALID_CLAIMS` | 401 | JWT claims 解析失败 |
| `RATE_LIMITED` | 429 | 超出令牌桶限流(响应体当前写 `error: rate_limited`,需统一加 code |
| `CIRCUIT_OPEN` | 503 | 下游熔断打开(响应体当前写 `error: circuit_open` |
| `INTERNAL_ERROR` | 500 | panic 兜底Recovery 中间件) |
| `REQUEST_TOO_LARGE` | 413 | 请求体超 10MB由 MaxBytesReader 自动触发,但响应非 JSON 信封) |
> **错误码统一约定**:本服务无业务错误码前缀(无业务),仅上述 7 个 HTTP 语义错误。响应体需统一为 `{ success: false, error: { code, message } }` 信封,与 classes 黄金模板 `ApplicationError` 对齐。
## 4. 我的技术栈
- **语言**Go 1.22+go.mod 声明 1.25.0,需与 Dockerfile 对齐,见审计表)
- **框架**Gin v1.12.0
- **核心依赖**
- `github.com/golang-jwt/jwt/v5` v5.2.1JWT 校验)
- `github.com/sony/gobreaker/v2` v2.1.0(熔断)
- `github.com/google/uuid` v1.6.0(请求 ID
- `go.opentelemetry.io/otel` v1.44.0 + `otelgin` v0.69.0(链路追踪)
- **存储**:无 DB无 Redis限流用内存令牌桶 `sync.Map`
- **构建**:多阶段 Dockerfilegolang:1.22-alpine → alpine:3.20,非 root 用户)
## 5. 我的阶段归属
- **阶段**P1已实现+ P2 升级RS256+ P6 硬化(限流策略表、熔断阈值细化)
- **当前阶段目标**
- P1 退出标准已达成classes 域 CRUD 端到端跑通(见 known-issues 工作日志 2026-07-09 00:24
- P2 待升级JWT HS256 → RS256IAM 签发Gateway 公钥校验)
- **依赖上游阶段产出**
- P2 iam 服务须暴露 JWKS 端点(`/.well-known/jwks.json`)供本服务拉公钥
## 6. 我需要对齐的黄金模板项(对照 classes 服务)
| 项 | classes黄金模板 | api-gateway 现状 | 差距 |
| ----------------- | ------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------- |
| 权限装饰器 | `@RequirePermission()` | N/AGo 无装饰器;用中间件 `AuthMiddleware` 替代) | ✅ 等价实现 |
| 错误码前缀 | `CLASSES_*` | 无前缀(基础设施层) | ✅ 设计合理 |
| loggerpino | `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/AGo 无 Zod`ShouldBindJSON` | ✅ 等价实现 |
| GlobalErrorFilter | `GlobalErrorFilter` | ✅ Recovery 中间件兜底 | ✅ 等价实现 |
## 7. 服务审计表(按 ai-allocation §10 模板)
| 服务 | 权限装饰器 | 错误码前缀 | logger | metrics | tracer | /healthz | /readyz | 优雅关闭 | 测试覆盖率 | Dockerfile |
| ----------- | ------------- | ------------- | ----------- | ------- | ------- | -------- | ------- | ---------- | ---------- | ---------- |
| api-gateway | ⚠️ 中间件替代 | ✅ 无前缀合理 | ❌ 标准 log | ❌ 无 | ✅ OTel | ✅ | ⚠️ stub | ✅ 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% |
## 8. 风险与假设
- **假设**iam 服务在 P2 会暴露 JWKS 端点,否则 RS256 升级阻塞
- **假设**:所有下游服务在 P1 都已实现 `/healthz`(用于 `/readyz` 真实检查)
- **风险**单实例限流在多副本部署后失效P6 需迁 Redis
- **风险**DevMode 旁路若误开到生产,可绕过鉴权注入 admin 角色
- **未决**:是否在 Gateway 层做权限校验(当前仅透传角色,由下游服务自校验)?建议保持现状,避免 Gateway 持有权限点常量造成耦合

View File

@@ -0,0 +1,429 @@
# 模块架构设计文档 — api-gateway
> AIai01Go 网关层)
> 阶段:阶段 2 交付物
> 日期2026-07-09
> 关联:[01 理解确认书](./01-understanding.md)、[004 架构影响地图](../../../docs/architecture/004_architecture_impact_map.md)
>
> 本文档覆盖 ai-allocation §5 设计重点路由表矩阵、限流策略表、熔断阈值、JWT RS256 流程、CORS 白名单、请求 ID 注入。
---
## 1. 模块内部分层图
```mermaid
graph TB
subgraph Client["客户端"]
B[浏览器/移动端]
end
subgraph MW["中间件链(按注册顺序)"]
M1[1. Recovery<br/>panic 兜底]
M2[2. OTelgin<br/>HTTP span]
M3[3. RequestID<br/>X-Request-Id]
M4[4. CORS<br/>Origin 白名单]
M5[5. SecurityHeaders<br/>安全响应头]
M6[6. RequestBodyLimit<br/>10MB]
M7[7. RateLimit<br/>令牌桶]
M8[8. Auth<br/>JWT RS256]
M9[9. CircuitBreaker<br/>gobreaker v2]
M10[10. Metrics<br/>prom-client]
end
subgraph Routes["路由"]
H[/healthz /readyz /metrics/]
V1[/api/v1/* 代理路由/]
end
subgraph Proxy["反向代理"]
P[httputil.ReverseProxy<br/>去 /api/v1 前缀]
end
subgraph Downstream["下游服务"]
D1[iam :3002]
D2[classes/core-edu :3001/:3004]
D3[teacher-bff :3003]
D4[content :3005]
D5[msg :3007]
D6[ai :3008]
D7[data-ana :3006]
end
B --> M1 --> M2 --> M3 --> M4 --> M5 --> M6 --> M7
M7 --> H
M7 --> M8 --> M9 --> M10 --> V1 --> P
P --> D1
P --> D2
P --> D3
P --> D4
P --> D5
P --> D6
P --> D7
```
**拦截点说明**
- Recovery 必须最外层(捕获后续所有 panic
- OTelgin 第二(覆盖全链路 span
- RequestID 第三(后续中间件日志可引用)
- Auth 在 CircuitBreaker 之前(避免未鉴权请求消耗下游配额)
- Metrics 在 CircuitBreaker 之后(统计通过鉴权且未被熔断的请求)
- Health/Metrics 端点旁路 Auth在 Auth 之前注册)
## 2. 领域模型
**无领域模型**。api-gateway 是基础设施层,不持有业务聚合/实体/值对象。
仅有的值对象:
| 值对象 | 字段 | 用途 |
| ----------------------------- | -------------------------------------- | ------------------ |
| `Config` | 见 §3 配置项 | 启动时加载,不可变 |
| `bucket`(限流) | `tokens float64`, `lastTime time.Time` | per-IP 令牌桶 |
| `CircuitBreaker`gobreaker | 内部状态机 | per-服务熔断状态 |
## 3. 数据模型
**无数据库**。所有状态在内存:
- `rateLimiter.buckets sync.Map[string]*bucket`IP → 令牌桶
- `CircuitBreaker` 实例(每个下游服务一个,当前共享一个 "downstream"
**P6 演进**
- 限流迁 Redis`redis_rate` 包),支持多副本一致
- 熔断状态保持本地(每个副本独立判断下游健康)
### 3.1 配置项Config 结构)
| 字段 | 环境变量 | 默认值 | 说明 |
| --------------- | ----------------------------- | --------------------------------------- | ------------------------- |
| Port | `API_GATEWAY_PORT` | 8080 | 监听端口 |
| JWTSecret | `JWT_SECRET` | (必填) | HS256 密钥P1P2 弃用) |
| JWKSURL | `IAM_JWKS_URL` | `http://iam:3002/.well-known/jwks.json` | RS256 公钥端点P2 |
| JWTIssuer | `JWT_ISSUER` | next-edu-cloud | JWT iss 校验 |
| JWTAudience | `JWT_AUDIENCE` | next-edu-cloud | JWT aud 校验 |
| CORSOrigins | `CORS_ORIGINS` | * | 逗号分隔白名单 |
| DevMode | `DEV_MODE` | false | 开发旁路(生产禁用) |
| RateLimitRPS | `RATE_LIMIT_RPS` | 100 | 全局默认 RPS |
| RateLimitBurst | `RATE_LIMIT_BURST` | 20 | 突发容量 |
| BodyLimitBytes | `BODY_LIMIT_BYTES` | 10485760 | 请求体上限10MB |
| OTLPEndpoint | `OTEL_EXPORTER_OTLP_ENDPOINT` | http://localhost:4318 | OTLP trace 端点 |
| LogLevel | `LOG_LEVEL` | info | slog 级别 |
| ServicesURL | `*_SERVICE_URL` | 见路由表 | 9 个下游服务地址 |
| ShutdownTimeout | `SHUTDOWN_TIMEOUT` | 5s | 优雅关闭超时 |
## 4. API 设计
### 4.1 路由表矩阵(核心交付物)
| 路径前缀 | 目标服务 | 默认端口 | 鉴权 | 公开子路径 | 阶段 |
| --------------------------------------------------------------------------- | ------------------------------- | ----------- | ---- | ------------------------------------------- | ---- |
| `/api/v1/iam` + `/*path` | iam | 3002 | JWT | `/iam/register` `/iam/login` `/iam/refresh` | P2 |
| `/api/v1/classes` + `/*path` | classesP3 起合并入 core-edu | 3001 → 3004 | JWT | — | P1 |
| `/api/v1/teacher` + `/*path` | teacher-bff | 3003 | JWT | — | P2 |
| `/api/v1/student` + `/*path` | student-bff | 3009 | JWT | — | P3 |
| `/api/v1/parent` + `/*path` | parent-bff | 3010 | JWT | — | P4 |
| `/api/v1/exams` `/homework` `/grades` + `/*path` | core-edu | 3004 | JWT | — | P3 |
| `/api/v1/textbooks` `/chapters` `/knowledge-points` `/questions` + `/*path` | content | 3005 | JWT | — | P4 |
| `/api/v1/notifications` `/messages` + `/*path` | msg | 3007 | JWT | — | P5 |
| `/api/v1/ai` + `/*path` | ai | 3008 | JWT | — | P5 |
| `/api/v1/analytics` `/dashboard` + `/*path` | data-ana | 3006 | JWT | — | P4 |
| `/healthz` | 本服务 | 8080 | 无 | — | P1 |
| `/readyz` | 本服务 | 8080 | 无 | — | P1 |
| `/metrics` | 本服务 | 8080 | 无 | — | P6 |
**路由注册规则**(强制):
- 每个前缀同时注册无尾斜杠与通配符两条路由(`/classes` + `/classes/*path`
- `r.RedirectTrailingSlash = false`(避免 Next.js rewrites 代理循环,见 known-issues
- 新增服务时按本表追加,禁止改其他服务路由
### 4.2 限流策略表
| 路由类别 | RPS | Burst | 备注 |
| ------------------------------- | --- | ----- | ---------------------------------- |
| `/api/v1/iam/login` | 5 | 5 | 登录接口额外加用户级限流(防爆破) |
| `/api/v1/iam/register` | 10 | 10 | 注册接口 |
| `/api/v1/iam/refresh` | 20 | 20 | 刷新 token |
| `/api/v1/ai/*` | 20 | 10 | AI 接口成本高,单独限流 |
| `/api/v1/analytics/*` | 30 | 20 | 分析查询较重 |
| 其他 `/api/v1/*` | 100 | 20 | 默认全局限流 |
| `/healthz` `/readyz` `/metrics` | ∞ | ∞ | 不限流(探针高频访问) |
**实现方式**
- P1内存令牌桶`sync.Map` per-IP
- P6Redis 令牌桶(`redis_rate`),支持多副本一致 + 用户级限流
### 4.3 熔断阈值配置
| 服务 | Interval | Timeout | MaxRequests | ReadyToTrip | 备注 |
| ---------------- | -------- | ------- | ----------- | ------------ | -------------------- |
| iam | 5s | 30s | 1 | 错误率 > 50% | 鉴权失败影响全链路 |
| classes/core-edu | 5s | 30s | 1 | 错误率 > 50% | 默认 |
| teacher-bff | 5s | 30s | 1 | 错误率 > 50% | 默认 |
| content | 10s | 60s | 1 | 错误率 > 30% | ES 慢查询容忍度低 |
| msg | 5s | 30s | 1 | 错误率 > 50% | 默认 |
| ai | 10s | 60s | 1 | 错误率 > 30% | LLM 延迟高,容忍度低 |
| data-ana | 10s | 60s | 1 | 错误率 > 30% | ClickHouse 查询较重 |
**熔断失败定义**:下游返回 5xx 视为失败4xx/2xx 不计入。
**熔断打开响应**HTTP 503 `{ success: false, error: { code: "CIRCUIT_OPEN", message: "downstream unhealthy", retry_after: 30 } }`
### 4.4 JWT RS256 校验流程P2 升级)
```mermaid
sequenceDiagram
participant U as 用户
participant GW as api-gateway
participant IAM as iam 服务
participant JWKS as JWKS 缓存
rect rgb(255, 250, 240)
Note over GW,JWKS: 启动时与定期刷新
GW->>IAM: GET /.well-known/jwks.json
IAM-->>GW: { keys: [...] }
GW->>JWKS: 缓存公钥集TTL 1h
end
rect rgb(240, 248, 255)
Note over U,GW: 请求鉴权
U->>GW: GET /api/v1/classes + Authorization: Bearer <RS256 token>
GW->>GW: 解析 token headerkid
GW->>JWKS: 查 kid 对应公钥
alt 缓存命中
JWKS-->>GW: 公钥
else 缓存未命中
GW->>IAM: GET /.well-known/jwks.json强制刷新
IAM-->>GW: 新公钥集
GW->>JWKS: 更新缓存
end
GW->>GW: jwt.Verify(token,公钥,iss,aud,exp)
alt 校验通过
GW->>GW: 提取 sub/roles/dataScope
GW->>GW: 注入 x-user-id / x-user-roles / x-data-scope 头
GW-->>U: 代理转发到 classes 服务
else 校验失败
GW-->>U: 401 INVALID_TOKEN
end
end
```
**JWKS 缓存策略**
- TTL 1h到期后台异步刷新不阻塞请求
- kid 未命中时强制同步刷新一次
- 刷新失败保留旧公钥集继续服务fail-open 1 次后 fail-close
- 启动时同步拉取一次,失败则 panic 拒绝启动
### 4.5 CORS 白名单
```go
// 默认白名单(生产环境通过 CORS_ORIGINS 覆盖)
allowedOrigins = []string{
"https://teacher.edu.example.com",
"https://student.edu.example.com",
"https://parent.edu.example.com",
"https://admin.edu.example.com",
"http://localhost:3000", // 开发
}
```
**规则**
- 未配置 `CORS_ORIGINS` 时默认 `*`(仅开发环境)
- 生产环境必须显式配置白名单,禁止 `*`
- 允许方法GET POST PUT DELETE OPTIONS PATCH
- 允许头Authorization Content-Type X-Request-Id X-Trace-Id
- 暴露头X-Request-Id X-Trace-Id
- 预检缓存12 小时(`Access-Control-Max-Age: 43200`
### 4.6 请求 ID 注入
```go
// 规则:
// 1. 优先透传客户端 X-Request-Id 头
// 2. 缺失则生成 req-<uuid-v4>
// 3. 写入 context + 响应头 X-Request-Id
// 4. 注入到下游请求头ReverseProxy.Director 中保留)
// 5. 日志/metrics/trace 全部引用此 request_id
```
**W3C Trace Context 透传**
- `traceparent` / `tracestate` 头透传OTel SDK 自动处理)
- `X-Request-Id` 是业务层 ID`traceparent` 是 OTel 层 ID两者并存
- 日志同时记录两者,便于 Loki → Tempo 关联查询
## 5. 事件设计
**无**。api-gateway 是纯同步代理,不发布/消费任何 Kafka 事件。
## 6. 横切关注点对齐清单
### 6.1 权限装饰器(等价实现)
| 端点 | 鉴权方式 | 备注 |
| ------------------------------------------ | ---------------- | ----------------------- |
| `/healthz` `/readyz` `/metrics` | 无 | 探针端点 |
| `/api/v1/iam/register` `/login` `/refresh` | 白名单 | 公开接口 |
| 其他 `/api/v1/*` | `AuthMiddleware` | JWT RS256 校验 + 头注入 |
> Go 无装饰器用中间件链等价实现。Gateway 不做权限点校验(不持有 Permissions 常量),仅透传 `x-user-id`/`x-user-roles`/`x-data-scope` 头,由下游业务服务 Controller 自校验。
### 6.2 错误码清单
| 错误码 | HTTP | 触发条件 | 响应体 |
| ------------------- | ---- | --------------------- | -------------------------------------- |
| `UNAUTHORIZED` | 401 | 缺失 Authorization 头 | `{success:false,error:{code,message}}` |
| `INVALID_TOKEN` | 401 | JWT 签名/格式错误 | 同上 |
| `INVALID_CLAIMS` | 401 | JWT claims 解析失败 | 同上 |
| `RATE_LIMITED` | 429 | 超出令牌桶限流 | 同上 + `retry_after: 60` |
| `CIRCUIT_OPEN` | 503 | 下游熔断打开 | 同上 + `retry_after: 30` |
| `REQUEST_TOO_LARGE` | 413 | 请求体超 10MB | 同上 |
| `INTERNAL_ERROR` | 500 | panic 兜底 | 同上 + `request_id` |
### 6.3 Logger 初始化
```go
// internal/observability/logger.go待新增
import "log/slog"
var Logger *slog.Logger
func InitLogger(level string) {
var lv slog.Level
_ = lv.UnmarshalText([]byte(level))
Logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: lv}))
slog.SetDefault(Logger)
}
```
**日志字段规范**
- `timestamp` ISO8601
- `level` INFO/WARN/ERROR
- `service` "api-gateway"
- `request_id` 从 context 取
- `trace_id` 从 OTel span 取
- `user_id` 从注入头取
- `method` `path` `status` `latency_ms`
### 6.4 Metrics 指标清单
| 指标名 | 类型 | 标签 | 描述 |
| --------------------------------------------- | --------- | ---------------------- | ----------------------------------------- |
| `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 | 熔断器状态0=CLOSED 1=OPEN 2=HALF_OPEN |
| `api_gateway_rate_limited_total` | Counter | ip | 被限流请求数 |
| `api_gateway_proxy_upstream_duration_seconds` | Histogram | upstream | 下游响应延迟 |
| `api_gateway_jwks_refresh_total` | Counter | result | JWKS 刷新次数 |
| `go_*` | — | — | prom-client 默认 Go runtime 指标 |
**暴露端点**`GET /metrics`prom-client 默认 handler
### 6.5 Tracer 初始化
已实现:`internal/observability/tracer.go`OTLP HTTP exporter + W3C TraceContext 传播。
**待补**
- 启动时记录 `service.name` / `service.version` / `deployment.environment` 资源属性
- 关键业务 span 命名规范:`HTTP GET /api/v1/classes`otelgin 自动)
### 6.6 /healthz 检查逻辑
```go
func Healthz(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",
"service": "api-gateway",
"version": Version, // 编译时注入
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
}
```
### 6.7 /readyz 检查逻辑(待重构)
```go
func Readyz(downstreams []string) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
defer cancel()
var wg errgroup.Group
unhealthy := make([]string, 0)
var mu sync.Mutex
for _, url := range downstreams {
url := url
wg.Go(func() error {
req, _ := http.NewRequestWithContext(ctx, "GET", url+"/healthz", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != 200 {
mu.Lock()
unhealthy = append(unhealthy, url)
mu.Unlock()
}
if resp != nil { resp.Body.Close() }
return nil
})
}
_ = wg.Wait()
if len(unhealthy) > 0 {
c.JSON(503, gin.H{"status":"error","unhealthy":unhealthy})
return
}
c.JSON(200, gin.H{"status":"ok"})
}
}
```
**下游清单**iam / classes / teacher-bff / core-edu / content / msg / ai / data-ana 的 `/healthz`
### 6.8 优雅关闭顺序
```go
// 1. 收到 SIGTERM
// 2. srv.Shutdown(ctx) 停止接受新请求等待在途请求完成5s 超时)
// 3. tracerShutdown() flush 待发送 span
// 4. log.Println("exited")
// 5. os.Exit(0)
```
## 7. 与其他模块的交互点(契约清单)
| 方向 | 对方服务 | 协议 | 接口/事件 | 用途 |
| ------ | ---------- | ---- | -------------------------------------------- | ------------------- |
| 调用 | iam | HTTP | `GET /.well-known/jwks.json` | 拉 RS256 公钥P2 |
| 透传 | 所有下游 | HTTP | `x-user-id` `x-user-roles` `x-data-scope` 头 | 用户身份传递 |
| 透传 | 所有下游 | HTTP | `X-Request-Id` `traceparent` 头 | 链路追踪 |
| 调用 | 所有下游 | HTTP | `GET /healthz` | /readyz 健康检查 |
| 被调用 | 微前端 | HTTP | `/api/v1/*` | 业务请求 |
| 被调用 | Prometheus | HTTP | `GET /metrics` | 指标采集 |
| 被调用 | K8s/Docker | HTTP | `GET /healthz` `GET /readyz` | 探针 |
## 8. 风险与假设
- **假设**iam P2 暴露 JWKS 端点若延期RS256 升级阻塞,临时保留 HS256
- **假设**:所有下游服务实现 `/healthz`;若某服务未实现,`/readyz` 误报
- **风险**单实例限流在多副本部署后失效P6 迁 Redis 解决)
- **风险**JWKS 缓存过期时若 iam 不可达fail-open 1 次后 fail-close可能导致全量 401
- **风险**DevMode 旁路误开到生产 → 启动时强制校验 `DevMode=true && ENV=production` panic
- **未决**:是否在 Gateway 层做 IP 黑名单WAF建议 P6 在 Istio 层做,本服务不介入
## 9. 实施优先级
| 优先级 | 任务 | 阶段 |
| ------ | --------------------------------------- | -------------- |
| P0 | 删除 auth.go 死代码L124-139 | 立即 |
| P0 | 修复 go.mod 与 Dockerfile Go 版本不一致 | 立即 |
| P0 | 新增 `/metrics` 端点 + prom-client | P2 |
| P0 | 引入 `log/slog` 替换标准 log | P2 |
| P1 | `/readyz` 真实健康检查 | P2 |
| P1 | JWT RS256 升级 + JWKS 缓存 | P2依赖 iam |
| P2 | 限流策略表细化per-路由) | P6 |
| P2 | 熔断 per-服务实例 | P6 |
| P3 | 补全测试覆盖率到 80% | P6 |
| P3 | DevMode 生产防护 | P2 |