常用中间件 #

一、内置中间件 #

1.1 Logger #

go
func main() {
    r := gin.New()
    
    // 默认Logger
    r.Use(gin.Logger())
    
    // 自定义格式
    r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
        return fmt.Sprintf("[GIN] %s | %s | %d | %v | %s | %s\n",
            param.TimeStamp.Format("2006/01/02 - 15:04:05"),
            param.ClientIP,
            param.StatusCode,
            param.Latency,
            param.Method,
            param.Path,
        )
    }))
    
    // 写入文件
    file, _ := os.Create("gin.log")
    r.Use(gin.LoggerWithWriter(file))
    
    // 跳过某些路径
    r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
        SkipPaths: []string{"/health", "/metrics"},
    }))
    
    r.Run()
}

1.2 Recovery #

go
func main() {
    r := gin.New()
    
    // 默认Recovery
    r.Use(gin.Recovery())
    
    // 自定义Recovery
    r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
        log.Printf("Panic recovered: %v", recovered)
        c.AbortWithStatusJSON(500, gin.H{
            "code":    500,
            "message": "服务器内部错误",
        })
    }))
    
    r.GET("/panic", func(c *gin.Context) {
        panic("Something went wrong!")
    })
    
    r.Run()
}

1.3 Static #

go
func main() {
    r := gin.New()
    
    // 静态文件服务
    r.Static("/assets", "./assets")
    
    // 静态文件服务(带前缀)
    r.StaticFS("/static", http.Dir("./static"))
    
    // 单个文件
    r.StaticFile("/favicon.ico", "./favicon.ico")
    
    r.Run()
}

二、CORS中间件 #

2.1 基本用法 #

go
func main() {
    r := gin.New()
    
    // 简单CORS
    r.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"http://localhost:3000"},
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
        AllowHeaders:     []string{"Content-Type", "Authorization"},
        ExposeHeaders:    []string{"Content-Length"},
        AllowCredentials: true,
        MaxAge:           12 * time.Hour,
    }))
    
    r.Run()
}

2.2 配置选项 #

go
config := cors.Config{
    // 允许的域名
    AllowOrigins: []string{
        "http://localhost:3000",
        "https://example.com",
    },
    
    // 或使用通配符
    AllowAllOrigins: true,
    
    // 允许的方法
    AllowMethods: []string{
        "GET",
        "POST",
        "PUT",
        "DELETE",
        "OPTIONS",
    },
    
    // 允许的请求头
    AllowHeaders: []string{
        "Content-Type",
        "Authorization",
        "X-Requested-With",
    },
    
    // 暴露的响应头
    ExposeHeaders: []string{
        "Content-Length",
        "X-Request-Id",
    },
    
    // 允许携带凭证
    AllowCredentials: true,
    
    // 预检请求缓存时间
    MaxAge: 12 * time.Hour,
    
    // 动态设置允许的域名
    AllowOriginFunc: func(origin string) bool {
        return strings.HasSuffix(origin, ".example.com")
    },
}

r.Use(cors.New(config))

三、JWT中间件 #

3.1 安装 #

bash
go get github.com/appleboy/gin-jwt/v2

3.2 基本配置 #

go
package main

import (
    "time"
    
    jwt "github.com/appleboy/gin-jwt/v2"
    "github.com/gin-gonic/gin"
)

type User struct {
    ID       string
    Username string
    Password string
}

var identityKey = "user_id"

func main() {
    r := gin.New()
    
    authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
        Realm:       "test zone",
        Key:         []byte("secret key"),
        Timeout:     time.Hour,
        MaxRefresh:  time.Hour,
        IdentityKey: identityKey,
        
        PayloadFunc: func(data interface{}) jwt.MapClaims {
            if v, ok := data.(*User); ok {
                return jwt.MapClaims{
                    identityKey: v.ID,
                }
            }
            return jwt.MapClaims{}
        },
        
        IdentityHandler: func(c *gin.Context) interface{} {
            claims := jwt.ExtractClaims(c)
            return &User{
                ID: claims[identityKey].(string),
            }
        },
        
        Authenticator: func(c *gin.Context) (interface{}, error) {
            var loginVals struct {
                Username string `form:"username" json:"username" binding:"required"`
                Password string `form:"password" json:"password" binding:"required"`
            }
            
            if err := c.ShouldBind(&loginVals); err != nil {
                return nil, jwt.ErrMissingLoginValues
            }
            
            userID := loginVals.Username
            password := loginVals.Password
            
            if userID == "admin" && password == "admin" {
                return &User{
                    ID:       userID,
                    Username: userID,
                }, nil
            }
            
            return nil, jwt.ErrFailedAuthentication
        },
        
        Authorizator: func(data interface{}, c *gin.Context) bool {
            if v, ok := data.(*User); ok && v.ID == "admin" {
                return true
            }
            return false
        },
        
        Unauthorized: func(c *gin.Context, code int, message string) {
            c.JSON(code, gin.H{
                "code":    code,
                "message": message,
            })
        },
        
        TokenLookup:   "header: Authorization, query: token, cookie: jwt",
        TokenHeadName: "Bearer",
        TimeFunc:      time.Now,
    })
    
    if err != nil {
        log.Fatal("JWT Error:" + err.Error())
    }
    
    // 登录路由
    r.POST("/login", authMiddleware.LoginHandler)
    
    // 刷新Token
    r.POST("/refresh", authMiddleware.RefreshHandler)
    
    // 需要认证的路由
    auth := r.Group("/auth")
    auth.Use(authMiddleware.MiddlewareFunc())
    {
        auth.GET("/hello", func(c *gin.Context) {
            claims := jwt.ExtractClaims(c)
            c.JSON(200, gin.H{
                "user_id": claims[identityKey],
            })
        })
    }
    
    r.Run()
}

四、限流中间件 #

4.1 基于令牌桶 #

go
import (
    "golang.org/x/time/rate"
)

func RateLimitMiddleware(rps int) gin.HandlerFunc {
    limiter := rate.NewLimiter(rate.Limit(rps), rps)
    
    return func(c *gin.Context) {
        if !limiter.Allow() {
            c.AbortWithStatusJSON(429, gin.H{
                "code":    429,
                "message": "请求过于频繁",
            })
            return
        }
        c.Next()
    }
}

func main() {
    r := gin.New()
    r.Use(RateLimitMiddleware(100))
    r.Run()
}

4.2 基于IP限流 #

go
type IPRateLimiter struct {
    ips map[string]*rate.Limiter
    mu  sync.RWMutex
    r   rate.Limit
    b   int
}

func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
    return &IPRateLimiter{
        ips: make(map[string]*rate.Limiter),
        r:   r,
        b:   b,
    }
}

func (l *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
    l.mu.Lock()
    defer l.mu.Unlock()
    
    limiter, exists := l.ips[ip]
    if !exists {
        limiter = rate.NewLimiter(l.r, l.b)
        l.ips[ip] = limiter
    }
    
    return limiter
}

func RateLimitByIP(rps int) gin.HandlerFunc {
    limiter := NewIPRateLimiter(rate.Limit(rps), rps)
    
    return func(c *gin.Context) {
        ip := c.ClientIP()
        
        if !limiter.GetLimiter(ip).Allow() {
            c.AbortWithStatusJSON(429, gin.H{
                "code":    429,
                "message": "请求过于频繁",
            })
            return
        }
        
        c.Next()
    }
}

4.3 使用第三方库 #

bash
go get github.com/ulule/limiter/v3
go
import (
    "github.com/ulule/limiter/v3"
    mgin "github.com/ulule/limiter/v3/drivers/middleware/gin"
    "github.com/ulule/limiter/v3/drivers/store/memory"
)

func main() {
    r := gin.New()
    
    // 创建存储
    store := memory.NewStore()
    
    // 创建限流器
    rate := limiter.Rate{
        Period: 1 * time.Minute,
        Limit:  100,
    }
    
    instance := limiter.New(store, rate, limiter.WithTrustForwardHeader(true))
    
    // 创建中间件
    middleware := mgin.NewMiddleware(instance)
    
    r.Use(middleware)
    r.Run()
}

五、压缩中间件 #

5.1 Gzip压缩 #

bash
go get github.com/gin-contrib/gzip
go
import "github.com/gin-contrib/gzip"

func main() {
    r := gin.New()
    
    // 默认压缩
    r.Use(gzip.Gzip(gzip.DefaultCompression))
    
    // 自定义压缩级别
    r.Use(gzip.Gzip(gzip.BestCompression))
    
    // 排除某些路径
    r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{"/api/"})))
    
    // 排除某些扩展名
    r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".pdf", ".mp4"})))
    
    r.Run()
}

六、安全中间件 #

6.1 Secure #

bash
go get github.com/gin-contrib/secure
go
import "github.com/gin-contrib/secure"

func main() {
    r := gin.New()
    
    r.Use(secure.New(secure.Config{
        SSLRedirect:          true,
        SSLHost:              "example.com",
        SSLProxyHeaders:      map[string]string{"X-Forwarded-Proto": "https"},
        STSSeconds:           315360000,
        STSIncludeSubdomains: true,
        FrameDeny:            true,
        ContentTypeNosniff:   true,
        BrowserXssFilter:     true,
        ContentSecurityPolicy: "default-src 'self'",
    }))
    
    r.Run()
}

6.2 CSRF #

bash
go get github.com/utrack/gin-csrf
go
import csrf "github.com/utrack/gin-csrf"

func main() {
    r := gin.New()
    
    r.Use(csrf.Middleware(csrf.Options{
        Secret: "secret123",
        ErrorFunc: func(c *gin.Context) {
            c.String(400, "CSRF token mismatch")
            c.Abort()
        },
    }))
    
    r.GET("/protected", func(c *gin.Context) {
        c.String(200, csrf.GetToken(c))
    })
    
    r.POST("/protected", func(c *gin.Context) {
        c.String(200, "OK")
    })
    
    r.Run()
}

七、监控中间件 #

7.1 Prometheus #

bash
go get github.com/gin-contrib/prometheus
go
import "github.com/gin-contrib/prometheus"

func main() {
    r := gin.New()
    
    p := prometheus.NewPrometheus("gin")
    p.Use(r)
    
    // 自定义指标
    p.ReqCntURLLabelMappingFn = func(c *gin.Context) string {
        return c.Request.URL.Path
    }
    
    r.GET("/metrics", func(c *gin.Context) {
        prometheus.Handler().ServeHTTP(c.Writer, c.Request)
    })
    
    r.Run()
}

7.2 健康检查 #

go
func HealthCheck() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.JSON(200, gin.H{
            "status": "ok",
            "time":   time.Now().Format(time.RFC3339),
        })
    }
}

func ReadinessCheck(db *sql.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        if err := db.Ping(); err != nil {
            c.JSON(503, gin.H{
                "status": "not ready",
                "error":  err.Error(),
            })
            return
        }
        
        c.JSON(200, gin.H{
            "status": "ready",
        })
    }
}

func main() {
    r := gin.New()
    
    r.GET("/health", HealthCheck())
    r.GET("/ready", ReadinessCheck(db))
    
    r.Run()
}

八、总结 #

8.1 常用中间件列表 #

中间件 用途 包名
Logger 日志记录 gin内置
Recovery 错误恢复 gin内置
CORS 跨域处理 gin-contrib/cors
JWT 身份认证 appleboy/gin-jwt
Gzip 响应压缩 gin-contrib/gzip
Secure 安全头 gin-contrib/secure
CSRF CSRF防护 utrack/gin-csrf
Prometheus 指标监控 gin-contrib/prometheus

8.2 最佳实践 #

实践 说明
按需引入 只使用必要的中间件
合理配置 根据环境调整配置
性能监控 关注中间件性能影响
错误处理 统一错误处理机制

8.3 下一步 #

现在你已经掌握了常用中间件,接下来让我们学习 请求处理,深入了解Gin的请求处理机制!

最后更新:2026-03-28