308 lines
8.2 KiB
Markdown
308 lines
8.2 KiB
Markdown
---
|
||
tags: [middleware, gin, onion-pattern, auth, logging, recovery]
|
||
create time: 2026-06-03 10:45
|
||
---
|
||
|
||
# 10. 中间件链
|
||
|
||
## 概述
|
||
|
||
Logger → Recovery → Metrics → Auth → RateLimit → Handler,洋葱模型,层层守护请求处理。
|
||
|
||
## 正文
|
||
|
||
### 请求处理链路
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A["Request"] --> B["Logger"]
|
||
B --> C["Recovery"]
|
||
C --> D["Metrics"]
|
||
D --> E["Auth"]
|
||
E --> F["RateLimit"]
|
||
F --> G["Handler"]
|
||
G --> F
|
||
F --> E
|
||
E --> D
|
||
D --> C
|
||
C --> B
|
||
B --> H["Response"]
|
||
|
||
style A fill:#e3f2fd,stroke:#1976d2
|
||
style B fill:#e8f5e9,stroke:#388e3c
|
||
style C fill:#fff3e0,stroke:#f57c00
|
||
style D fill:#fce4ec,stroke:#c62828
|
||
style E fill:#f3e5f5,stroke:#7b1fa2
|
||
style F fill:#e0f7fa,stroke:#00838f
|
||
style G fill:#fff8e1,stroke:#f9a825
|
||
style H fill:#e8eaf6,stroke:#303f9f
|
||
```
|
||
|
||
### 洋葱模型
|
||
|
||
Gin 的中间件采用**洋葱模型**:请求从外到内穿过各中间件,响应从内到外返回。每个中间件可以在 `c.Next()` 前后执行逻辑。
|
||
|
||
---
|
||
|
||
### Logger — 请求日志
|
||
|
||
**职责**:为每个请求生成唯一 ID,记录请求详情。
|
||
|
||
#### 核心逻辑
|
||
|
||
```go
|
||
func Logger() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
requestID := generateRequestID() // 时间戳 + 8位随机hex
|
||
c.Set("request_id", requestID)
|
||
c.Header("X-Request-ID", requestID)
|
||
c.Next()
|
||
// 根据状态码选择日志级别
|
||
if status >= 500 → Error
|
||
if status >= 400 → Warn
|
||
else → Info
|
||
}
|
||
}
|
||
```
|
||
|
||
#### RequestID 生成
|
||
|
||
```go
|
||
func generateRequestID() string {
|
||
b := make([]byte, 8)
|
||
rand.Read(b)
|
||
return fmt.Sprintf("%d-%x", time.Now().UnixMilli(), b)
|
||
}
|
||
```
|
||
|
||
格式:`1717286400000-a1b2c3d4e5f67890`
|
||
|
||
| 组件 | 作用 |
|
||
|------|------|
|
||
| 时间戳(毫秒) | 保证时间有序性 |
|
||
| 8 字节随机 hex | 保证唯一性 |
|
||
|
||
#### 日志级别映射
|
||
|
||
| HTTP 状态码 | 日志级别 | 含义 |
|
||
|:-----------:|:-------:|------|
|
||
| 5xx | ERROR | 服务器错误 |
|
||
| 4xx | WARN | 客户端错误 |
|
||
| 2xx/3xx | INFO | 正常请求 |
|
||
|
||
---
|
||
|
||
### Recovery — Panic 恢复
|
||
|
||
**职责**:捕获未处理的 panic,防止服务崩溃。
|
||
|
||
```go
|
||
func Recovery() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
// 记录完整堆栈
|
||
l.Error("panic recovered",
|
||
"error", fmt.Sprintf("%v", r),
|
||
"stack", string(debug.Stack()),
|
||
)
|
||
// 返回结构化 500
|
||
c.AbortWithStatusJSON(500, gin.H{
|
||
"code": 500,
|
||
"message": "服务器内部错误",
|
||
})
|
||
}
|
||
}()
|
||
c.Next()
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键特性**:
|
||
- 捕获所有未处理的 panic
|
||
- 记录完整的 request 上下文和堆栈信息
|
||
- 返回统一格式的 500 错误,避免泄露内部信息
|
||
- 使用 `defer` 保证即使 panic 也能执行清理逻辑
|
||
|
||
---
|
||
|
||
### Metrics — 指标采集
|
||
|
||
**职责**:自动采集 HTTP 请求的性能指标。
|
||
|
||
```go
|
||
func Metrics() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
path := c.FullPath() // 路由模板,避免高基数
|
||
HTTPRequestsInFlight.Inc()
|
||
defer HTTPRequestsInFlight.Dec()
|
||
|
||
reqSize := c.Request.ContentLength // 请求体大小(before)
|
||
c.Next()
|
||
elapsed := time.Since(start) // 耗时(after)
|
||
HTTPRequestsTotal.WithLabelValues(...).Inc()
|
||
}
|
||
}
|
||
```
|
||
|
||
**采集时机**:
|
||
|
||
| 指标 | 采集时机 | 原因 |
|
||
|------|---------|------|
|
||
| InFlight | 进入时 +1,离开时 -1 | 使用 defer 保证异常时也能递减 |
|
||
| RequestSize | `c.Next()` 之前 | Content-Length 此时已知 |
|
||
| ResponseSize | `c.Next()` 之后 | Writer 此时已写入 |
|
||
| Duration | `c.Next()` 之后 | 需要计算总耗时 |
|
||
|
||
> [!tip] FullPath() 的关键作用
|
||
> 使用路由模板 `/api/v1/tasks/:taskId` 而非实际路径,避免高基数标签导致 Prometheus 内存爆炸。
|
||
|
||
---
|
||
|
||
### Auth — JWT 认证
|
||
|
||
**职责**:验证 Bearer token,提取用户身份。
|
||
|
||
```go
|
||
func AuthMiddleware(jwtSecret string) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
// 1. 提取 Authorization header
|
||
authHeader := c.GetHeader("Authorization")
|
||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||
|
||
// 2. 解析和验证 JWT
|
||
token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, []byte) {
|
||
return []byte(jwtSecret), nil
|
||
})
|
||
|
||
// 3. 提取 sub claim → userID
|
||
claims := token.Claims.(jwt.MapClaims)
|
||
c.Set("userID", claims["sub"])
|
||
|
||
c.Next()
|
||
}
|
||
}
|
||
```
|
||
|
||
**作用范围**:仅应用于 `v1Auth` 路由组,公开接口(如 `/health`)不经过认证。
|
||
|
||
**错误处理**:
|
||
|
||
| 场景 | HTTP 状态码 | 消息 |
|
||
|------|:-----------:|------|
|
||
| 无 token | 401 | 未提供认证令牌 |
|
||
| 格式错误 | 401 | 认证格式错误,需为 Bearer <token> |
|
||
| token 无效 | 401 | 令牌无效或已过期 |
|
||
| 解析失败 | 401 | 令牌解析失败 |
|
||
|
||
---
|
||
|
||
### RateLimit — 限流
|
||
|
||
**职责**:按路由配置执行双层限流(全局 + 用户)。
|
||
|
||
```go
|
||
// 每个路由独立配置
|
||
v1Auth.POST("/generate",
|
||
ratelimit.RateLimit(globalLimiter, ..., "global", "generate"),
|
||
ratelimit.RateLimit(userLimiter, ..., "user", "generate"),
|
||
handler.Generate,
|
||
)
|
||
```
|
||
|
||
详见 [[09-限流]]。
|
||
|
||
---
|
||
|
||
### 中间件挂载
|
||
|
||
#### 全局链(所有请求)
|
||
|
||
```go
|
||
r := gin.New()
|
||
r.Use(mildware.Logger()) // 1. 请求日志
|
||
r.Use(mildware.Recovery()) // 2. Panic 恢复
|
||
r.Use(mildware.Metrics()) // 3. 指标采集
|
||
```
|
||
|
||
#### 路由组级链(需认证)
|
||
|
||
```go
|
||
v1Auth := r.Group("/api/v1")
|
||
v1Auth.Use(mildware.AuthMiddleware(cfg.JWT.Secret)) // 4. JWT 认证
|
||
```
|
||
|
||
#### 端点级链(需限流)
|
||
|
||
```go
|
||
v1Auth.POST("/generate",
|
||
ratelimit.RateLimit(globalLimiter, ...), // 5. 全局限流
|
||
ratelimit.RateLimit(userLimiter, ...), // 6. 用户限流
|
||
handler.Generate, // 7. 业务处理
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
### 端点链示例:/api/v1/generate
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
A["POST /api/v1/generate"] --> B["Logger<br/>生成 request_id"]
|
||
B --> C["Recovery<br/>注册 panic 恢复"]
|
||
C --> D["Metrics<br/>InFlight++, 记录 reqSize"]
|
||
D --> E["Auth<br/>验证 JWT, 提取 userID"]
|
||
E --> F["RateLimit Global<br/>检查全局限额"]
|
||
F --> G["RateLimit User<br/>检查用户限额"]
|
||
G --> H["Handler.Generate<br/>执行生成逻辑"]
|
||
H --> I["Metrics<br/>记录 duration, respSize"]
|
||
I --> J["Recovery<br/>检查是否 panic"]
|
||
J --> K["Logger<br/>记录请求日志"]
|
||
K --> L["Response"]
|
||
|
||
style A fill:#e3f2fd,stroke:#1976d2
|
||
style B fill:#e8f5e9,stroke:#388e3c
|
||
style C fill:#fff3e0,stroke:#f57c00
|
||
style D fill:#fce4ec,stroke:#c62828
|
||
style E fill:#f3e5f5,stroke:#7b1fa2
|
||
style F fill:#e0f7fa,stroke:#00838f
|
||
style G fill:#e0f7fa,stroke:#00838f
|
||
style H fill:#fff8e1,stroke:#f9a825
|
||
style L fill:#e8eaf6,stroke:#303f9f
|
||
```
|
||
|
||
#### 完整请求生命周期
|
||
|
||
| 阶段 | 中间件 | 动作 |
|
||
|:----:|--------|------|
|
||
| 1 | Logger | 生成 request_id,注入 context 和 header |
|
||
| 2 | Recovery | 注册 defer panic 恢复 |
|
||
| 3 | Metrics | InFlight +1,记录请求体大小 |
|
||
| 4 | Auth | 验证 JWT,提取 userID |
|
||
| 5 | RateLimit | 检查全局限额 |
|
||
| 6 | RateLimit | 检查用户限额 |
|
||
| 7 | Handler | 执行业务逻辑 |
|
||
| 8 | Metrics | 记录耗时、响应体大小,InFlight -1 |
|
||
| 9 | Recovery | 检查是否发生 panic |
|
||
| 10 | Logger | 记录请求日志(含状态码、耗时) |
|
||
|
||
---
|
||
|
||
### 中间件职责矩阵
|
||
|
||
| 中间件 | 请求进入 | 请求离开 | 异常处理 | 作用范围 |
|
||
|--------|---------|---------|---------|---------|
|
||
| Logger | 生成 request_id | 记录日志 | — | 全局 |
|
||
| Recovery | 注册 defer | — | panic → 500 | 全局 |
|
||
| Metrics | InFlight++, reqSize | duration, respSize | — | 全局 |
|
||
| Auth | 验证 JWT | — | 无效 → 401 | v1Auth |
|
||
| RateLimit | 检查配额 | — | 超限 → 429 | 端点级 |
|
||
|
||
---
|
||
|
||
## 关联文档
|
||
|
||
- [[07-可观测性]] — Metrics 中间件采集的指标
|
||
- [[09-限流]] — RateLimit 中间件的详细实现
|
||
- [[08-SSE实时推送]] — SSE 端点的中间件配置
|