8.5 KiB
8.5 KiB
10 — 中间件链
一句话概括:Logger → Recovery → Metrics → Auth → RateLimit → Handler,洋葱模型,层层守护请求处理。
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.enter → Recovery.enter → Metrics.enter → Auth.enter → RateLimit.enter → Handler
响应 ← Logger.leave ← Recovery.leave ← Metrics.leave ← Auth.leave ← RateLimit.leave ← Handler
📝 Logger — 请求日志
职责:为每个请求生成唯一 ID,记录请求详情。
核心逻辑
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 生成
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,防止服务崩溃。
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 请求的性能指标。
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() 之后 |
需要计算总耗时 |
💡 FullPath() 的关键作用:使用路由模板
/api/v1/tasks/:taskId而非实际路径,避免高基数标签导致 Prometheus 内存爆炸。
🔐 Auth — JWT 认证
职责:验证 Bearer token,提取用户身份。
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 — 限流
职责:按路由配置执行双层限流(全局 + 用户)。
// 每个路由独立配置
v1Auth.POST("/generate",
ratelimit.RateLimit(globalLimiter, ..., "global", "generate"),
ratelimit.RateLimit(userLimiter, ..., "user", "generate"),
handler.Generate,
)
详见 09 — 限流。
🔗 中间件挂载
全局链(所有请求)
r := gin.New()
r.Use(mildware.Logger()) // 1. 请求日志
r.Use(mildware.Recovery()) // 2. Panic 恢复
r.Use(mildware.Metrics()) // 3. 指标采集
路由组级链(需认证)
v1Auth := r.Group("/api/v1")
v1Auth.Use(mildware.AuthMiddleware(cfg.JWT.Secret)) // 4. JWT 认证
端点级链(需限流)
v1Auth.POST("/generate",
ratelimit.RateLimit(globalLimiter, ...), // 5. 全局限流
ratelimit.RateLimit(userLimiter, ...), // 6. 用户限流
handler.Generate, // 7. 业务处理
)
📋 端点链示例:/api/v1/generate
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 端点的中间件配置