六 、中间件
Gin 是一个高性能的 Go Web 框架,其核心特性之一就是强大的中间件(Middleware)机制。中间件允许开发者在 HTTP 请求处理流程的不同阶段插入自定义逻辑,例如日志记录、身份验证、请求限流等。
1.基本概念
在gin中,中间件是一个函数,接受 gin.Context
作为参数,用于在请求到达路由处理函数之前或之后执行某些操作。
中间件可以:
- 修改请求或响应的数据
- 中断请求处理流程(如权限校验失败时终止请求)
- 传递数据给后续中间件或路由处理函数
场景应用场景:
- 日志记录 :记录请求的路径、方法、状态码、耗时等。
- 身份认证:校验 JWT、Session 或 API Key。
- 限流:限制单位时间内的请求数量。
- 跨域处理(CORS):设置响应头允许跨域请求。
- 缓存控制:为特定路由添加缓存头。
2.内置中间件
Gin 提供了一些常用中间件:
当使用 gin.Default()
时,会自动启用日志和恢复中间件
r := gin.Default() // 自动启用日志和恢复中间件
实现代码如下:
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default(opts ...OptionFunc) *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery()) //默认启动日志和恢复中间件return engine.With(opts...)
}
如果不想使用默认中间件可以使用:
r := gin.New()
3.自定义中间件
你也可以创建自定义的中间件来执行一些操作,例如记录请求的开始时间和结束时间:
package mainimport ("github.com/gin-gonic/gin""log""time"
)func MyMiddleware(c *gin.Context) {// 在请求处理前执行逻辑(如记录日志、校验权限)start := time.Now()//Set是在Context上下文中设置键值对c.Set("address", "Nanjin")//--------请求前--------// 将控制权交给下一个中间件或路由处理函数c.Next()//--------请求后--------// 请求处理后执行逻辑(如记录耗时)latency := time.Since(start)log.Printf("Request took %v\n", latency)}func main() {r := gin.Default()r.Use(MyMiddleware)r.GET("/test", func(c *gin.Context) {//GetString是在Context上下文中获取键对应的值并且设置为字符串类型example := c.GetString("address")c.String(200, example)})r.Run(":8080")
}
此代码MyMiddleware
中间件会在请求前设置 address
的值为Nanjin
, 并在请求后输出程序执行时间
4.注册中间件
- 全局中间件
所有路由都会生效:
r:= gin.Default()r.Use(MyMiddleware)
- 路由组中间件
仅对特定路由组生效:
admin := router.Group("/admin")admin.Use(AuthMiddleware)
- 单个路由中间件
直接在路由定义中添加:
router.GET("/user", MyMiddleware, func(c *gin.Context) {c.String(200, "User Info")})
r.Use(m1,m2,m3)
或者
router.GET("/user", M1, func(c *gin.Context) {c.String(200, "User Info")},M2)
5.中间件的常用方法
在 Gin 框架中,中间件的常用方法主要围绕 gin.Context
对象和中间件的流程控制展开。以下是中间件开发中高频使用的方法及其详细说明和示例:
5.1 Next()
作用:将控制权交给后续中间件或路由处理函数,之后会继续执行当前中间件的剩余逻辑。
应用场景:在中间件中需要区分请求处理前和请求处理后的逻辑。
- 示例:
func LogTimeMiddleware(c *gin.Context) {start := time.Now()c.Next() // 触发后续中间件和路由处理函数latency := time.Since(start)fmt.Printf("请求耗时: %v\n", latency)}
5.2 Abort()
作用:立即终止当前请求的后续处理流程,直接返回响应,不再执行后续中间件或路由处理函数。
应用场景:限校验失败、参数校验错误等需要中断请求的场景
示例:
func AuthMiddleware(c *gin.Context) {token := c.GetHeader("Authorization")if token == "" {c.AbortWithStatusJSON(401, gin.H{"error": "未授权"}) // 终止并返回错误c.Abort() //c.Abort()拦截,后续的HandlerFunc就不会执行了}c.Next()}
5.3 Set()
作用:在上下文中存储数据,供后续中间件或路由处理函数使用。
示例:
func UserMiddleware(c *gin.Context) {user, _ := GetCurrentUser(c.Request)c.Set("user", user) // 存储用户信息c.Next()}// 在路由处理函数中获取func ProfileHandler(c *gin.Context) {user, _ := c.Get("user")c.JSON(200, user)}
5.4 Get()
作用:从上下文中获取存储的数据,需判断 exists
确认是否存在。
示例:
if val, ok := c.Get("user"); ok {user := val.(User) // 类型断言// 使用 user 对象}
5.5 AbortWithStatus()/ AbortWithStatusJSON()
作用:终止请求并返回指定 HTTP 状态码(可选返回 JSON 数据)。
示例:
if !user.IsAdmin {c.AbortWithStatusJSON(403, gin.H{"error": "权限不足"})return}
6.中间件的执行流程
Gin 中间件的执行遵循“洋葱模型”:
假设拿一根针插入洋葱,针尖会依次从一侧的最外层插入最里层,然后再从最里层依次插入另一侧的最外层,这个针尖就代表着中间件的执行顺序,一层一层的洋葱皮代表中间件,洋葱的中心代表路由处理函数
可以抽象为:
┌───────────────────────────┐│ 中间件1(进入) │└─────────────┬─────────────┘↓┌───────────────────────────┐│ 中间件2(进入) │└─────────────┬─────────────┘↓┌───────────────────────────┐│ 路由处理函数 │└─────────────┬─────────────┘↓┌───────────────────────────┐│ 中间件2(退出) │└─────────────┬─────────────┘↓┌───────────────────────────┐│ 中间件1(退出) │└───────────────────────────┘
请求会依次经过中间件的“进入”逻辑,到达路由处理函数,然后逆序执行中间件的“退出”逻辑。
通过以下代码演示:
package mainimport ("fmt""github.com/gin-gonic/gin"
)func Middleware1(c *gin.Context) {fmt.Println("Middleware1 - Before")c.Next()fmt.Println("Middleware1 - After")
}func Middleware2(c *gin.Context) {fmt.Println("Middleware2 - Before")c.Next()fmt.Println("Middleware2 - After")
}func main() {r := gin.Default()r.Use(Middleware1, Middleware2)r.GET("/", func(c *gin.Context) {fmt.Println("main")c.String(200, "OK")})r.Run()
}
输出结果:
Middleware1 - Before
Middleware2 - Before
main
Middleware2 - After
Middleware1 - After