- monorepo: pnpm workspace + go.work + pyproject.toml + commitlint/husky - infra: docker-compose (minimal + full profiles) + init-sql + prometheus - arch-scan: multi-language scanner skeleton (TS/Go/Python/Proto) - shared-proto: buf v2 + classes.proto (ClassService CRUD contract) - api-gateway: Go/Gin + JWT HS256 auth + reverse proxy + request ID - classes: NestJS golden template (error system + observability + middleware + CRUD + tests) - teacher-portal: Next.js + paper-feel UI design system - CI/CD: 4 workflows (go/ts/py/proto) - docs: migration guide + project_rules + coding-standards + git-workflow + ui-design-system + 004 + 9 module READMEs + known-issues + spec/plan migration + roadmap
282 lines
9.1 KiB
Markdown
282 lines
9.1 KiB
Markdown
# 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)
|