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

8.5 KiB
Raw Blame History

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 端点级

🔗 关联文档