Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等
即比如,如果访问一个网页的话,不管访问什么路径都需要进行登录,此时就需要为所有路径的处理函数进行统一一个中间件
Gin中的中间件必须是一个gin.HandlerFunc类型
我们先看一下Get函数能够接收的参数:
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
type HandlerFunc func(*Context)
从这个函数里,我们能看到它可以,它可以接收很多的HandlerFunc类型的数据,并且发现是func(*Context)数据类型都可以,所以,我们可以定义很多个中间件,它必须是*gin.Context
类型
func m1(c *gin.Context) {
fmt.Println("m1 in.........")
}
func indexHandler(c *gin.Context) {
fmt.Println("index.....")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
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(":8000")
}
注意:m1处于indexHandler函数的前面,请求来之后,先走m1,再走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")
}
这里就不演示了~~
c.Abort()函数
,作用:拦截,后续的HandlerFunc就不会执行了
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()函数
,作用: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(":8080")
}
输出结果:
m1 ...in
index ...in
m2 ...in
m2 ...out
index ...out
m1 ...out
输出的方式,有点像栈,先进去的m1...out
最后再输出~
在Gin框架中,可以使用Use
方法注册全局中间件。全局中间件会对所有的请求都生效。
func main() {
r := gin.Default()
r.Use(Logger()) // 注册全局中间件
r.GET("/hello", HelloHandler)
r.Run(":8080")
}
// Logger 是一个全局中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
end := time.Now()
latency := end.Sub(start)
log.Printf("[%s] %s %s %v", c.Request.Method, c.Request.URL.Path, c.Request.RemoteAddr, latency)
}
}
func HelloHandler(c *gin.Context) {
c.String(http.StatusOK, "Hello, Gin!")
}
在上面的示例中,Logger
函数是一个全局中间件。它在处理请求之前记录了请求的相关信息,并在请求处理完成后打印了请求的耗时。
在中间件之间传递自定义参数是一种常见的需求。在Gin框架中,可以使用Context.Set
和Context.Get
方法来传递自定义参数,并且传递的数据是一个key-value
func main() {
r := gin.Default()
r.Use(AddCustomData("custom data"))
r.GET("/hello", HelloHandler)
r.Run(":8080")
}
func AddCustomData(data string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("customData", data) // 设置自定义参数
c.Next()
}
}
func HelloHandler(c *gin.Context) {
customData, exists := c.Get("customData") // 获取自定义参数
if exists {
c.String(http.StatusOK, "Hello, Gin! Custom data: %s", customData)
} else {
c.String(http.StatusOK, "Hello, Gin!")
}
}
在上面的示例中,AddCustomData
函数返回了一个中间件函数,用于在Context中设置自定义参数。在HelloHandler
处理函数中,通过Context.Get
方法获取自定义参数并使用。
将一系列的路由放到一个组下,统一管理。例如,以下的路由前面统一加上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(":8080")
}
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(":8080")
}
这样写我们就可以指定哪一些分组下可以使用中间件了
当然,中间件还有一种写法,就是使用函数加括号的形式
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func middle(c *gin.Context) {
fmt.Println("middle ...in")
}
func middle1() gin.HandlerFunc {
// 这里的代码是程序一开始就会执行
return func(c *gin.Context) {
// 这里是请求来了才会执行
fmt.Println("middle1 ...inin")
}
}
func main() {
router := gin.Default()
r := router.Group("/api").Use(middle, middle1())
r.GET("/index", func(c *gin.Context) {
c.String(200, "index")
})
r.GET("/home", func(c *gin.Context) {
c.String(200, "home")
})
router.Run(":8080")
}
设置了一个中间件进行身份验证,并且还对路由进行分组,分为两组来使用
func main() {
r := gin.Default()
r.Use(Logger()) // 注册全局中间件
v1 := r.Group("/v1")
v1.Use(Auth()) // 注册 v1 路由组的局部中间件
{
v1.GET("/hello", HelloHandler)
v1.GET("/user", UserHandler)
}
r.GET("/hello", HelloHandler) // 其他路由不使用 Auth 中间件
r.Run(":8080")
}
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
// 进行身份验证的逻辑
if IsAuthenticated {
c.Next()
} else {
c.AbortWithStatus(http.StatusUnauthorized)
}
}
}
func HelloHandler(c *gin.Context) {
c.String(http.StatusOK, "Hello, Gin!")
}
func UserHandler(c *gin.Context) {
c.String(http.StatusOK, "User Info")
}
// Logger 是一个全局中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
end := time.Now()
latency := end.Sub(start)
log.Printf("[%s] %s %s %v", c.Request.Method, c.Request.URL.Path, c.Request.RemoteAddr, latency)
}
}
Gin框架内置了一些常用的中间件,可以直接使用。以下是一些常用的内置中间件和示例:
gin.Logger()
:记录请求日志gin.Recovery()
:处理请求时的恢复机制func main() {
r := gin.Default()
r.Use(gin.Logger()) // 使用 gin.Logger() 中间件
r.Use(gin.Recovery()) // 使用 gin.Recovery() 中间件
r.GET("/hello", HelloHandler)
r.Run(":8000")
}
func HelloHandler(c *gin.Context) {
c.String(http.StatusOK, "Hello, Gin!")
}
不过,平常我们不使用这两个中间件的时候,一样会有记录,所以我们看一下gin.Default函数
。
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
从这段代码就可以看出,是默认使用了这两个函数,一般自动调用了~~~
以前后端最流行的jwt为例,如果用户登录了,前端发来的每一次请求都会在请求头上携带上token
后台拿到这个token进行校验,验证是否过期,是否非法
如果通过就说明这个用户是登录过的
不通过就说明用户没有登录
package main
import (
"github.com/gin-gonic/gin"
)
func JwtTokenMiddleware(c *gin.Context) {
// 获取请求头的token
token := c.GetHeader("token")
// 调用jwt的验证函数
if token == "1234" {
// 验证通过
c.Next()
return
}
// 验证不通过
c.JSON(200, gin.H{"msg": "权限验证失败"})
c.Abort()
}
func main() {
router := gin.Default()
api := router.Group("/api")
apiUser := api.Group("")
{
apiUser.POST("login", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "登录成功"})
})
}
apiHome := api.Group("system").Use(JwtTokenMiddleware)
{
apiHome.GET("/index", func(c *gin.Context) {
c.String(200, "index")
})
apiHome.GET("/home", func(c *gin.Context) {
c.String(200, "home")
})
}
router.Run(":8080")
}
统计每一个视图函数的执行时间
func TimeMiddleware(c *gin.Context) {
startTime := time.Now()
c.Next()
since := time.Since(startTime)
// 获取当前请求所对应的函数
f := c.HandlerName()
fmt.Printf("函数 %s 耗时 %d\n", f, since)
}