中间件是 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。