# 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<br/>限流计数)]
        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 令牌桶<br/>剩余令牌 >= 1?}
    Q1 -- 是 --> DEC[扣减令牌<br/>INCR + EXPIRE]
    DEC --> PASS[放行]
    Q1 -- 否 --> Q2{是否超量请求?}
    Q2 -- 是 --> REJ1[429 Too Many Requests<br/>Retry-After header]
    Q2 -- 否 --> WAIT[排队等待<br/>短超时]
    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）
