gin中的中间件ctx.Next()和ctx.Abort和全局中间件

Gin 中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子函数。

这个钩子函数就叫中间件。

中间件适合处理一些公共的业务逻辑,

比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作

复制代码
package main

import (
	"fmt"
	"gin-quickstart/routers"

	"github.com/gin-gonic/gin"
)

// "gin-quickstart/routers" 表示引入 routers 包(也就是引入我们创建的路由文件夹),用于注册路由

func getTime(ctx *gin.Context) {
	fmt.Println("这是一个中间件1")
}
func getTime2(ctx *gin.Context) {
	fmt.Println("这是一个中间件2")
}

func main() {
	// 创建一个默认的路由引擎
	r := gin.Default()
	// 允许跨域中间件(注意:必须写在路由注册【之前】才对已注册路由生效)
	r.Use(func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", "*") // 允许所有来源(学习阶段够用)
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Header("Access-Control-Allow-Headers", "Content-Type")
		// 浏览器发复杂请求前会先发 OPTIONS 预检,直接放行
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}
		c.Next()
	})

	// 注册用户路由,routers表示的是路由包,
	// UserRouters表示的是路由函数(也就是这个包中user.go文件中的函数)
	routers.UserRouters(r)

	// 注册商品路由
	r.GET("/shop/list",
		// 这里写了2个中间件
		getTime, getTime2,
		// 这个回调函数前面的都可以称为中间件
		func(ctx *gin.Context) {
            fmt.Println("这是商品列表页面")
			ctx.JSON(200, gin.H{
				"msg": "获取商品列表成功",
			})
		})

	// 启动服务,端口为8080,当然你也可以更改的哈
	r.Run(":8087")
}

执行顺序是:

这是一个中间件1

这是一个中间件2

这是商品列表页面

ctx.Next()调用该请求的剩余处理程序

中间件里面加上ctx.Next()可以让我们在路由匹配完成之后执行一些操作。

比如我们统计一个请求的执行时间。

复制代码
func getTime(ctx *gin.Context) {
	fmt.Println("这是一个中间件1")
	ctx.Next()
	fmt.Println("最后执行这里")
}

func main() {
  // 其他代码...
  // 注册商品路由
  r.GET("/shop/list",
  	getTime,
  	// 这个回调函数前面的都可以称为中间件
  	func(ctx *gin.Context) {
  	fmt.Println("这是商品列表页面")
  	ctx.JSON(200, gin.H{
  		"msg": "获取商品列表成功",
  	})
  })
}

执行顺序:匹配到这里路由后,会先执行中间件。

执行:fmt.Println("这是一个中间件1")

然后执行中间件后面的这一坨代码

复制代码
func(ctx *gin.Context) {
	fmt.Println("这是商品列表页面")
	ctx.JSON(200, gin.H{
		"msg": "获取商品列表成功",
	})
}

当它执行完成之后,

最后才会执行:fmt.Println("最后执行这里")

ctx.Abort 终止调用该请求的剩余处理程序

复制代码
func getTime(ctx *gin.Context) {
	// 纳秒时间戳;统计处理完业务的耗时
	startTime := time.Now().UnixNano()
	fmt.Println("这是一个中间件1")
	ctx.Abort()
	endTime := time.Now().UnixNano()
	fmt.Println("这是一个中间件2")
	fmt.Println("请求耗时:", endTime-startTime)
}
func main() {
	... 其他代码
	// 注册商品路由
	r.GET("/shop/list",
		// 这里写了2个中间件
		getTime,
		// 这个回调函数前面的都可以称为中间件
		func(ctx *gin.Context) {
			fmt.Println("这是商品列表页面")
			ctx.JSON(200, gin.H{
				"msg": "获取商品列表成功",
			})
		})

	// 启动服务,端口为8080,当然你也可以更改的哈
	r.Run(":8087")
}

先执行:这是一个中间件1

遇见ctx.Abort(),会终止调用该请求的剩余处理程序

此时不会再去执行路由中的业务逻辑fmt.Println("这是商品列表页面")

而是执行:这是一个中间件2

执行顺序是:

15:5:35 app | 这是一个中间件1

15:5:35 app | 这是一个中间件2

请求耗时: 0

多个中间件调用ctx.Next()的执行顺序

复制代码
func getTime(ctx *gin.Context) {
	fmt.Println("这是一个中间件A1")
	// 调用该请求的剩余处理程序
	ctx.Next()
	fmt.Println("这是一个中间件A2")
}

func intercept(ctx *gin.Context) {
	fmt.Println("这是一个中间件B1")
	// 调用该请求的剩余处理程序
	ctx.Next()
	fmt.Println("这是一个中间件B2")
}

func main() {
	... 其他代码
	// 注册商品路由
	r.GET("/shop/list",
		getTime, intercept,
		// 这个回调函数前面的都可以称为中间件
		func(ctx *gin.Context) {
			fmt.Println("这是商品列表页面")
			ctx.JSON(200, gin.H{
				"msg": "获取商品列表成功",
			})
		})

	// 启动服务,端口为8080,当然你也可以更改的哈
	r.Run(":8087")
}

首先执行:这是一个中间件A1,遇见ctx.Next(),会调用该请求的剩余处理程序

然后执行:这是一个中间件B1,遇见ctx.Next(),会调用该请求的剩余处理程序

然后执行:这是商品列表页面

然后开始先进后出

执行:这是一个中间件B2,

最后执行:这是一个中间件A2

全局中间件 r.Use()

为啥会有全局中间件。因为一个一个的配置路由太麻烦了。

我们可以一次性给所有路由都添加一个中间件。

于是就产生了全局中间件。

复制代码
func getTime(ctx *gin.Context) {
	fmt.Println("这是一个中间件A1")
	// 调用该请求的剩余处理程序
	ctx.Next()
	fmt.Println("这是一个中间件A2")
}

func intercept(ctx *gin.Context) {
	fmt.Println("这是一个中间件B1")
	// 调用该请求的剩余处理程序
	ctx.Next()
	fmt.Println("这是一个中间件B2")
}

func globalIntercept(ctx *gin.Context) {
	fmt.Println("这是一个全局中间件1")
	ctx.Next()
	fmt.Println("这是一个全局中间件2")
}

func main() {
	... 其他代码
	r.Use(globalIntercept)

	// 注册商品路由
	r.GET("/shop/list",
		getTime, intercept,
		// 这个回调函数前面的都可以称为中间件
		func(ctx *gin.Context) {
		fmt.Println("这是商品列表页面")
		ctx.JSON(200, gin.H{
			"msg": "获取商品列表成功",
		})
	})

	// 启动服务,端口为8080,当然你也可以更改的哈
	r.Run(":8087")
}

执行顺序是:

这是一个全局中间件1

这是一个中间件A1

这是一个中间件B1

这是商品列表页面

这是一个中间件B2

这是一个中间件A2

这是一个全局中间件2