--- 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 无效 | 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
生成 request_id"] B --> C["Recovery
注册 panic 恢复"] C --> D["Metrics
InFlight++, 记录 reqSize"] D --> E["Auth
验证 JWT, 提取 userID"] E --> F["RateLimit Global
检查全局限额"] F --> G["RateLimit User
检查用户限额"] G --> H["Handler.Generate
执行生成逻辑"] H --> I["Metrics
记录 duration, respSize"] I --> J["Recovery
检查是否 panic"] J --> K["Logger
记录请求日志"] 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 端点的中间件配置