← 返回博客
go2026-09-12 20:29:482 分钟 · 619 0

Gin 中间件:执行顺序、自定义与常用中间件

讲清 Gin 中间件的注册方式、Next 与 Abort 的控制流,手撸一个耗时统计中间件,再接上 Recovery、CORS 和简单限流。

#中间件#gin#路由

中间件是 Gin 最好用的机制:在请求真正进到 handler 之前和之后插一段逻辑,用来做日志、鉴权、跨域、限流。理解它的执行顺序,比记 API 更重要。

1. 中间件就是个函数

签名和 handler 一样,都是 func(*gin.Context)。区别在它可以选择调用 c.Next() 把流程交还给后面的中间件和 handler:

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next() // 往下走,handler 跑完再回来
        cost := time.Since(start)
        log.Printf("%s %s 耗时 %v", c.Request.Method, c.Request.URL.Path, cost)
    }
}

c.Next() 之前的代码在 handler 前执行,c.Next() 之后在 handler 后执行。不写 c.Next() 也不会报错,但后面的链路就不会跑了。

2. 两种注册位置

全局中间件作用于所有路由,r.Use(...) 注册:

r := gin.New()
r.Use(Logger(), gin.Recovery())

路由组级别的中间件只作用于该组,写在 Group 的第二个参数:

auth := r.Group("/api", AuthRequired())
{
    auth.GET("/profile", getProfile)
}

单个路由也能挂多个中间件,写在 handler 前面:

r.GET("/admin", AdminOnly(), getAdmin)

执行顺序按注册先后:全局 Use 先跑,再路由组,再单路由。同一层里 c.Next() 串成一条链。

3. 提前中断:c.Abort

鉴权失败时不能让请求继续往下走。用 c.Abort() 截断后续 handler,但当前中间件 c.Next() 之后的代码仍会执行:

func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            c.JSON(401, gin.H{"error": "未登录"})
            c.Abort()
            return
        }
        c.Next()
    }
}

要彻底截断(连当前中间件后续都不跑),用 c.AbortWithStatusJSON(401, gin.H{...}),它内部已经调用了 Abort

4. 在中间件里传值

中间件解析出用户后,用 c.Set 存起来,handler 里 c.Get 取:

func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        uid := parseToken(c.GetHeader("Authorization"))
        c.Set("uid", uid) // 存进 context
        c.Next()
    }
}

func getProfile(c *gin.Context) {
    uid, _ := c.Get("uid")
    c.JSON(200, gin.H{"uid": uid})
}

c.Set 挂的是请求级 context,每个请求独立,不会串。

5. 跨域 CORS

前后端分离必配。用官方包 gin-contrib/cors

import "github.com/gin-contrib/cors"

r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://www.wxbuluo.com"},
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
    AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
    AllowCredentials: true,
}))

本地联调可以把 AllowOrigins 换成 cors.AllowAllOrigins: true,上线前一定收回来。

6. 简单限流

golang.org/x/time/rate 给单实例加个令牌桶,防突发刷接口:

import "golang.org/x/time/rate"

var limiter = rate.NewLimiter(rate.Every(time.Second), 5) // 每秒 5 个,突发 5

func RateLimit() gin.HandlerFunc {
    return func(c *gin.Context) {
        if !limiter.Allow() {
            c.JSON(429, gin.H{"error": "请求过于频繁"})
            c.Abort()
            return
        }
        c.Next()
    }
}

多实例部署时单机能限流不够用,要换成 Redis 令牌桶或网关层限流。这一篇不讲,记住本地限流只对单进程有效。

7. 一个典型的中间件栈

项目起步阶段,全局挂这几个就够:

r := gin.New()
r.Use(gin.Logger())      // 访问日志
r.Use(gin.Recovery())    // panic 恢复,避免进程挂掉
r.Use(RateLimit())       // 基础限流

业务路由组再叠加 AuthRequired。中间件顺序影响行为:限流放日志后面无所谓,但 Recovery 要尽早挂,保证后面任何 panic 都能兜住。

下一篇把响应格式和错误处理统一掉,别在每个 handler 里手写 gin.H

相关推荐

本文为原创文章,采用CC BY-NC-SA 4.0协议授权,转载请保留署名与原文链接。原文链接:https://www.wxbuluo.com/article/179