Files
cs-note/hzh/Gen2D/10-中间件链.md

308 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 端点的中间件配置