Gin 框架的中间件
Gin 中间件是一种用于处理 HTTP 请求的钩子函数,可以在请求到达业务逻辑处理函数(如路由处理函数)之前或之后执行特定操作。其核心目标是解耦通用功能(如日志、认证、跨域处理)与业务逻辑,提升代码复用性和可维护性。
一、中间件的核心机制
洋葱模型(Onion Model)
Gin 中间件的执行流程类似 “洋葱” 分层结构:请求按中间件注册顺序进入,执行 c.Next()
后跳转到下一中间件或业务逻辑,返回时再以相反顺序执行剩余代码。例如:
请求 → 中间件1 → 中间件2 → 业务逻辑 → 中间件2 → 中间件1 → 响应
这种机制允许在请求前后执行逻辑(如统计耗时、记录日志)
上下文(gin.Context)
中间件通过 gin.Context
共享数据和控制流程:
- 数据传递:使用
c.Set("key", value)
存储数据,c.Get("key")
获取数据(如传递请求 ID)。 - 流程控制:
c.Next()
继续执行后续中间件,c.Abort()
终止请求并直接返回响应(如认证失败时)
二、中间件的分类与使用
1. 全局中间件
作用于所有请求,通过 r.Use()
注册。例如:
r := gin.Default()
r.Use(gin.Logger()) // 内置日志中间件
r.Use(AuthMiddleware) // 自定义认证中间件
常用于日志记录、全局异常捕获等场景。
2. 路由组中间件
仅作用于特定路由组,通过 group.Use()
注册。例如:
admin := r.Group("/admin")
admin.Use(AuthMiddleware) // 仅对 /admin 路径生效
admin.GET("/dashboard", DashboardHandler)
适用于权限分层(如后台管理接口需认证)。
3. 局部中间件
仅作用于单个路由,直接在路由定义中指定。例如:
r.GET("/profile", AuthMiddleware, ProfileHandler)
适用于特定接口需要额外校验的场景(如敏感数据接口)。
三、中间件的使用示例
单独注册中间件
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func indexHandler(c *gin.Context) {
fmt.Println("index.....")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定义一个中间件
func m1(c *gin.Context) {
fmt.Println("m1 in.........")
}
func main() {
r := gin.Default()
//m1处于indexHandler函数的前面,请求来之后,先走m1,再走index
r.GET("/index", m1, indexHandler)
_ = r.Run()
}
浏览器访问localhost:8080/index
控制台输出
m1 in…
index…
多个中间件
router.GET 后面可以跟很多 HandlerFunc 方法,这些方法其实都可以叫中间件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func m1(c *gin.Context) {
fmt.Println("m1 ...in")
}
func m2(c *gin.Context) {
fmt.Println("m2 ...in")
}
func main() {
router := gin.Default()
router.GET("/", m1, func(c *gin.Context) {
fmt.Println("index ...")
c.JSON(200, gin.H{"msg": "响应数据"})
}, m2)
router.Run(":8080")
}
// m1 ...in
// index ...
// m2 ...in
中间件拦截响应
c.Abort(): 调用该函数会立即终止当前中间件函数的执行,并且不会再调用后续的中间件函数或路由处理函数
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func m1(c *gin.Context) {
fmt.Println("m1 ...in")
c.JSON(200, gin.H{"msg": "第一个中间件拦截了"})
c.Abort()
}
func m2(c *gin.Context) {
fmt.Println("m2 ...in")
}
func main() {
router := gin.Default()
router.GET("/", m1, func(c *gin.Context) {
fmt.Println("index ...")
c.JSON(200, gin.H{"msg": "响应数据"})
}, m2)
router.Run(":8080")
}
// m1 ...in
中间件放行
c.Next(): 调用该函数会将控制权交给下一个中间件函数,如果没有下一个中间件函数,则将控制权交给处理请求的路由处理函数
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func m1(c *gin.Context) {
fmt.Println("m1 ...in")
c.Next()
fmt.Println("m1 ...out")
}
func m2(c *gin.Context) {
fmt.Println("m2 ...in")
c.Next()
fmt.Println("m2 ...out")
}
func main() {
router := gin.Default()
router.GET("/", m1, func(c *gin.Context) {
fmt.Println("index ...in")
c.JSON(200, gin.H{"msg": "响应数据"})
c.Next()
fmt.Println("index ...out")
}, m2)
router.Run()
}
// m1 ...in
// index ...in
// m2 ...in
// m2 ...out
// index ...out
// m1 ...out
全局注册中间件
使用 Use 去注册全局中间件,Use 接收的参数也是多个 HandlerFunc
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func m10(c *gin.Context) {
fmt.Println("m1 ...in")
c.Next()
fmt.Println("m1 ...out")
}
func main() {
router := gin.Default()
router.Use(m10)
router.GET("/", func(c *gin.Context) {
fmt.Println("index ...in")
c.JSON(200, gin.H{"msg": "index"})
c.Next()
fmt.Println("index ...out")
})
router.Run()
}
// m1 ...in
// index ...in
// index ...out
// m1 ...out
路由
路由分组
将一系列的路由放到一个组下,统一管理。例如,以下的路由前面统一加上 api 的前缀
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
r := router.Group("/api")
r.GET("/index", func(c *gin.Context) {
c.String(200, "index")
})
r.GET("/home", func(c *gin.Context) {
c.String(200, "home")
})
router.Run()
}
路由分组注册中间件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func middle(c *gin.Context) {
fmt.Println("middle ...in")
}
func main() {
router := gin.Default()
r := router.Group("/api").Use(middle) // 可以链式,也可以直接r.Use(middle)
r.GET("/index", func(c *gin.Context) {
c.String(200, "index")
})
r.GET("/home", func(c *gin.Context) {
c.String(200, "home")
})
router.Run()
}
这样写我们就可以指定哪一些分组下可以使用中间件了