# api-gateway 网关服务
> 版本:1.0
> 日期:2026-07-07
> 状态:P1 已交付
> 关联文档:[架构影响地图](../../architecture/004_architecture_impact_map.md)
---
## 1. 模块职责
api-gateway 是整个 Edu 平台的统一入口网关,承担以下核心职责:
- **路由转发**:将外部 HTTP/REST 请求按路径前缀转发到对应 BFF 或业务服务
- **JWT 鉴权**:使用 RS256 公钥校验 JWT 签名,提取 userId/role/dataScope 注入下游 metadata
- **限流熔断**:基于 Redis 令牌桶限流,基于错误率的滑动窗口熔断
- **CORS 跨域**:处理浏览器跨域预检请求
- **访问日志**:结构化访问日志(zap),含 traceId/userId/latency/statusCode
- **健康检查**:`/healthz`、`/readyz` 端点供 Kubernetes 探针使用
**不做的事**:不写业务逻辑、不访问数据库、不持有业务状态。
---
## 2. 技术栈
| 类别 | 技术 | 版本 | 用途 |
|------|------|------|------|
| 语言 | Go | 1.22+ | 高性能网关 |
| Web 框架 | Gin | 1.10+ | HTTP 路由与中间件链 |
| JWT | golang-jwt/v5 | 5.2+ | RS256 签名校验 |
| 缓存/限流 | Redis | 7.x | 令牌桶计数、会话缓存 |
| 配置 | viper | 1.18+ | 多源配置(env/yaml/configmap) |
| 日志 | zap | 1.27+ | 结构化高性能日志 |
| 链路追踪 | OpenTelemetry | 1.24+ | 分布式追踪上下文注入 |
| 指标 | prometheus/client_golang | 1.19+ | RED 指标暴露 |
| 容器化 | Docker | - | 多阶段构建 |
| 编排 | Kubernetes | 1.28+ | 生产部署 |
---
## 3. 架构图
```mermaid
graph TB
subgraph Client["客户端"]
MFE[微前端 4 端]
end
subgraph Gateway["api-gateway"]
subgraph Middleware["中间件链"]
CORS[CORS 中间件]
LOG[访问日志 zap]
TRACE[OpenTelemetry trace]
AUTH[JWT RS256 鉴权]
RATE[限流 Redis 令牌桶]
CB[熔断器 滑动窗口]
end
ROUTER[Gin Router]
PROXY[gRPC 反向代理]
end
subgraph Downstream["下游服务"]
TBFF[teacher-bff]
SBFF[student-bff]
PBFF[parent-bff]
IAM[iam 登录特殊路由]
end
subgraph Infra["基础设施"]
REDIS[(Redis
限流计数)]
JWKSIAM[(IAM JWKS 公钥)]
OTEL[OTLP Collector]
end
MFE -->|HTTP/REST| CORS
CORS --> LOG --> TRACE --> AUTH --> RATE --> CB --> ROUTER
ROUTER --> PROXY
PROXY -->|gRPC| TBFF
PROXY -->|gRPC| SBFF
PROXY -->|gRPC| PBFF
ROUTER -.特殊路由.-> IAM
RATE --> REDIS
AUTH --> JWKSIAM
LOG --> OTEL
TRACE --> OTEL
```
---
## 4. 核心流程图
### 4.1 鉴权与路由流程
```mermaid
sequenceDiagram
participant U as 微前端
participant GW as API Gateway
participant R as Redis
participant SVC as 下游服务
U->>GW: GET /api/classes + Bearer token
GW->>GW: CORS 校验
GW->>GW: 记录访问日志(traceId 生成)
GW->>GW: OpenTelemetry span 创建
GW->>GW: 提取 Authorization header
GW->>GW: RS256 公钥校验签名
GW->>GW: 校验 exp/iss/aud
GW->>GW: 提取 userId/role/dataScope
GW->>R: 令牌桶限流检查(userId 维度)
R-->>GW: 通过
GW->>GW: 熔断器状态检查
GW->>SVC: gRPC 请求 + metadata 透传
SVC-->>GW: gRPC 响应
GW-->>U: HTTP 响应 + traceId header
```
### 4.2 限流流程
```mermaid
flowchart TD
REQ[请求到达] --> Q1{Redis 令牌桶
剩余令牌 >= 1?}
Q1 -- 是 --> DEC[扣减令牌
INCR + EXPIRE]
DEC --> PASS[放行]
Q1 -- 否 --> Q2{是否超量请求?}
Q2 -- 是 --> REJ1[429 Too Many Requests
Retry-After header]
Q2 -- 否 --> WAIT[排队等待
短超时]
WAIT --> Q1
```
### 4.3 登录特殊处理流程
```mermaid
sequenceDiagram
participant U as 用户
participant GW as API Gateway
participant IAM as IAM 服务
Note over GW: /auth/* 路径跳过 JWT 鉴权
U->>GW: POST /auth/login {username, password}
GW->>GW: 限流检查(IP 维度)
GW->>IAM: gRPC Login(username, password)
IAM->>IAM: 校验密码
IAM->>IAM: 生成 JWT(RS256 私钥签发)
IAM-->>GW: {accessToken, refreshToken, userInfo}
GW-->>U: 200 Set-Cookie httpOnly + JSON body
```
---
## 5. 对外契约
### 5.1 REST API 路由表
| 方法 | 路径 | 鉴权 | 转发目标 | 说明 |
|------|------|------|----------|------|
| POST | /auth/login | 否 | iam | 登录签发 JWT |
| POST | /auth/refresh | 否 | iam | 刷新令牌 |
| POST | /auth/logout | 是 | iam | 登出 |
| GET | /api/classes/* | 是 | teacher-bff/student-bff | 班级相关 |
| GET | /api/courses/* | 是 | teacher-bff/student-bff | 课程相关 |
| GET | /api/assignments/* | 是 | teacher-bff/student-bff | 作业相关 |
| GET | /api/grades/* | 是 | teacher-bff/student-bff | 成绩相关 |
| GET | /api/exams/* | 是 | teacher-bff/student-bff | 考试相关 |
| GET | /api/messages/* | 是 | teacher-bff/student-bff | 消息相关 |
| GET | /api/insights/* | 是 | teacher-bff/student-bff | 学情分析 |
| GET | /healthz | 否 | 本地 | 存活探针 |
| GET | /readyz | 否 | 本地 | 就绪探针 |
| GET | /metrics | 否 | 本地 | Prometheus 指标 |
### 5.2 gRPC 服务契约
api-gateway 不对外暴露 gRPC 服务,仅作为 gRPC 客户端调用下游。
### 5.3 事件契约
api-gateway 不发布也不消费任何 Kafka 事件。
---
## 6. 依赖关系
### 6.1 上游依赖(调用方)
| 上游 | 协议 | 说明 |
|------|------|------|
| teacher-portal | HTTP/REST | 教师端微前端 |
| student-portal | HTTP/REST | 学生端微前端 |
| parent-portal | HTTP/REST | 家长端微前端 |
| admin-portal | HTTP/REST | 管理端微前端 |
### 6.2 下游依赖(被调用方)
| 下游 | 协议 | 说明 |
|------|------|------|
| teacher-bff | gRPC | 教师聚合层 |
| student-bff | gRPC | 学生聚合层 |
| parent-bff | gRPC | 家长聚合层 |
| iam | gRPC | 登录/刷新令牌特殊路由 |
| Redis | TCP | 限流计数、JWKS 缓存 |
| IAM JWKS | HTTP | RS256 公钥拉取(5 分钟缓存) |
### 6.3 共享包依赖
| 包 | 用途 |
|----|------|
| shared-proto | gRPC stub 与 protobuf 类型 |
| shared-go | Go 通用工具(错误码、日志字段约定) |
---
## 7. 架构约束
1. **无业务逻辑**:网关只做路由、鉴权、限流、熔断,禁止写任何业务规则
2. **无状态**:不持有业务状态,会话状态由 Redis 管理,支持水平扩缩容
3. **无数据库**:禁止直连任何业务数据库
4. **单一职责**:所有横切关注点以中间件形式实现,顺序明确(CORS → 日志 → 追踪 → 鉴权 → 限流 → 熔断)
5. **配置驱动**:路由表、限流阈值、熔断阈值通过 viper 配置注入,支持热更新
6. **可观测性强制**:每个请求必须生成 traceId,记录 userId/latency/statusCode
7. **优雅停机**:收到 SIGTERM 后停止接受新请求,等待已有请求完成(最长 30s)
---
## 8. 架构决策
### ADR-001:选用 Go + Gin 而非 Node.js
**决策**:网关层使用 Go 1.22 + Gin 框架。
**理由**:
- 网关是 IO 密集型场景,Go 的 goroutine 模型在高并发下内存占用极低
- 单实例可承载 10k+ QPS,是 Node.js 的 3-5 倍
- 编译为单二进制,容器镜像 < 20MB,启动 < 1s
**替代方案**:Node.js (Fastify) - 内存占用高、GC 压力大,不适合作纯转发层。
### ADR-002:JWT 签名算法从 HS256 迁移到 RS256
**决策**:P1 阶段使用 HS256(共享密钥)快速验证,P2 阶段迁移到 RS256(非对称签名)。
**理由**:
- RS256 允许 IAM 私钥签发、网关/服务公钥校验,密钥不需共享
- 公钥可通过 JWKS endpoint 分发,轮换密钥无需重启服务
- HS256 适合 P1 单体调试,但生产环境密钥分发风险高
### ADR-003:限流使用 Redis 令牌桶而非本地内存
**决策**:限流计数器存储在 Redis,使用令牌桶算法。
**理由**:
- 多副本部署下,本地内存限流无法保证全局配额
- Redis 原子操作(INCR + EXPIRE)保证计数准确性
- 令牌桶支持突发流量,优于漏桶的严格匀速
### ADR-004:路由配置通过 viper 静态注入而非服务发现
**决策**:P1 阶段路由表通过 yaml/env 静态配置,不引入服务发现。
**理由**:
- P1 服务数量少(api-gateway + classes),静态配置足够
- 服务发现(Consul/Nacos)增加运维复杂度,留待 P6 引入
- viper 支持热重载,路由变更无需重启
### ADR-005:gRPC 转发而非 HTTP 代理
**决策**:网关到下游 BFF/服务使用 gRPC 而非 HTTP 反向代理。
**理由**:
- gRPC 基于 HTTP/2 多路复用,连接复用率高
- protobuf 二进制编码比 JSON 节省 30%+ 带宽
- 强类型契约,编译期发现接口变更
- Connect-RPC 兼容 HTTP/JSON 客户端,前端仍可用 REST
### ADR-006:熔断器使用滑动窗口而非简单计数
**决策**:熔断器基于滑动窗口错误率,而非固定窗口计数。
**理由**:
- 滑动窗口对突发错误更敏感,避免边界效应
- 半开状态可逐步放量,避免雪崩后全量恢复冲击
- Go 生态有成熟实现(sony/gobreaker)