Gin简单明了的教程---下
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gin简单明了的教程---下相关的知识,希望对你有一定的参考价值。
Gin简单明了的教程---下
Gin 中间件
Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函 数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、 记录日志、耗时统计等。
通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作
路由中间件
Gin
中的中间件必须是一个 gin.HandlerFunc
类型,配置路由的时候可以传递多个 func
回调函数。中间件要放在最后一个回调函数的前面 ,触发的方法都可以称为中间件。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func InitMiddleWare(c *gin.Context)
fmt.Println("init middle ware ")
func main()
r := gin.Default()
r.GET("/hello", InitMiddleWare, func(c *gin.Context)
c.String(200, "hello world")
)
r.Run()
可以看到其实我们可以传入不只一个回调处理函数,这些回调函数会组成一个处理器链绑定到当前路由上,而当请求被当前路由拦截时,就会被绑定到当前路由上的拦截器链所处理。
可以类比spring提供的拦截器功能,当然gin框架这里给我们提供了更大的灵活性,因为并没有严格将拦截器和处理请求的处理器区分开来
ctx.Next()调用该请求的剩余处理程序
中间件里面加上 ctx.Next()后,c.Next()的语句后面先不执行,跳转到下一个处理器执行。
可以让我们在路由匹配完成后执行一些操作。比如我们统计一个请求的执行时间
func InitMiddleWare(c *gin.Context)
fmt.Println("1- init middle ware ")
start := time.Now().UnixNano()
// 调用c.Next()请求的剩余处理程序
// c.Next()的语句后面先不执行,先跳转拦截器链中下一个处理器执行,等到拦截器链执行到完往回返时
// 才执行c.Next()后面的语句
c.Next()
fmt.Println("3-程序执行完成 计算时间")
end := time.Now().UnixNano()
fmt.Println(end - start)
func ApiRouter(r *gin.Engine)
apiRouter := r.Group("/api")
// 中间件要放在最后一个回调函数的前面
apiRouter.GET("/", InitMiddleWare, func(ctx *gin.Context)
fmt.Println("2 - 中间件")
ctx.String(200, "/api")
)
这里next提供的功能,可以提供类似于spring mvc中拦截器的preHandle和afterHandle的功效。
前置处理
c.next()
后置处理
一个路由配置多个中间件的执行顺序
func InitMiddleWareOne(c *gin.Context)
fmt.Println("one- init middleware start")
c.Next()
fmt.Println("one- init middleware end")
func InitMiddleWareTwo(c *gin.Context)
fmt.Println("Two- init middleware start")
c.Next()
fmt.Println("Two- init middleware end")
func ApiRouter(r *gin.Engine)
apiRouter := r.Group("/api")
// 中间件要放在最后一个回调函数的前面
apiRouter.GET("/", InitMiddleWareOne, InitMiddleWareTwo, func(ctx *gin.Context)
fmt.Println("首页")
ctx.String(200, "/api")
)
- 控制台输出的结果
one- init middleware start
Two- init middleware start
首页
Two- init middleware end
one- init middleware end
ctx.Abort()
Abort是终止的意思,ctx.Abort()表示终止调用处理器链中后续的处理器,当前处理器会执行完毕。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func InitMiddleWare(c *gin.Context)
fmt.Println("preHandle ")
c.Next()
fmt.Println("afterHandle ")
func main()
r := gin.Default()
// 引入路由模块
r.GET("/hello", InitMiddleWare, func(c *gin.Context)
fmt.Println("pre hello")
c.Abort()
fmt.Println("after hello")
c.String(200, "hello world")
)
r.Run()
全局中间件
当有一个处理器需要应用到所有路由上时,难道还需要我们一个个挨个去重复绑定吗 ?显然,这个时候通常会有一个全局绑定,即一次全局设置后,会应用到所有的路由上,这里也被称为全局中间件。
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func InitMiddleWareOne(c *gin.Context)
fmt.Println("one- init middleware start")
c.Next()
fmt.Println("one- init middleware end")
func InitMiddleWareTwo(c *gin.Context)
fmt.Println("Two- init middleware start")
c.Next()
fmt.Println("Two- init middleware end")
func main()
r := gin.Default()
r.Use(InitMiddleWareOne,InitMiddleWareTwo)
// 引入路由模块
r.GET("/hello", func(c *gin.Context)
c.String(200, "hello world")
)
r.Run()
在路由分组中配置中间件
我们不仅可以全局应用中间件,也可以缩小范围到一个路由组上:
1、为路由组注册中间件有以下两种写法
- 写法 1:
func InitMiddleWareOne(c *gin.Context)
fmt.Println("one- init middleware start")
c.Next()
fmt.Println("one- init middleware end")
func UserRouter(r *gin.Engine)
userRouter := r.Group("/user", InitMiddleWareOne)
u := new(controller.UserController)
userRouter.GET("/", u.UserGet)
userRouter.POST("/", u.UserPost)
userRouter.DELETE("/", u.UserDelete)
userRouter.PUT("/", u.UserPut)
- 写法 2:
func InitMiddleWareOne(c *gin.Context)
fmt.Println("one- init middleware start")
c.Next()
fmt.Println("one- init middleware end")
func UserRouter(r *gin.Engine)
userRouter := r.Group("/user")
userRouter.Use(InitMiddleWareOne)
u := new(controller.UserController)
userRouter.GET("/", u.UserGet)
userRouter.POST("/", u.UserPost)
userRouter.DELETE("/", u.UserDelete)
userRouter.PUT("/", u.UserPut)
中间件和对应控制器之间数据共享
说白了就是如何在拦截器链执行过程中传递数据,显然在整个拦截器链执行过程中,只有context是一直被传递的,所以如果我们想要在拦截器链执行过程中传递数据,只需要往context中设置数据即可。
package main
import (
"github.com/gin-gonic/gin"
)
func InitMiddleWareOne(c *gin.Context)
c.Set("name", "大忽悠")
func main()
r := gin.Default()
r.Use(InitMiddleWareOne)
r.GET("/hello", func(c *gin.Context)
//返回的value是空接口类型,需要先进行类型转换
val, _ := c.Get("name")
if name, ok := val.(string); ok
c.String(200, "hello %v\\n", name)
)
r.Run()
中间件注意事项
gin默认中间件
gin.Default()默认使用了Logger和Recovery中间件,
其中:
- Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
- Recovery中间件会recover任何panic,如果有panic的话,写入500响应码
如果不想使用上面的默认中间件,可以使用gin.New()新建一个没有任何中间件的路由
gin中间件中使用goroutine
当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())
func LoginMiddleWare(c *gin.Context)
fmt.Println("login middle ware")
c.Set("username", "张三")
// 定义一个goroutine统计日志
cCp := c.Copy()
go func ()
time.Sleep(2 * time.Second)
// 用了c.Request.URL.Path 也没有问题?
fmt.Println("Done in path " + cCp.Request.URL.Path)
()
原因:
gin 使用 sync.Pool 重用 context,请求结束会将 context 放回对象池,供其他请求复用。异步任务不 copy context 的话,其他请求可能从对象池中拿到同一个 context,会有问题
对象复用这一点其实很常见,在tomcat底层或者spring底层都有大量对象复用的应用案例。
处理器链源码分析
这里针对处理器的执行过程进行一下简明扼要的源码流程介绍:
- 程序启动,会去注册相关路由,如: r.Get , r.Post等,而这些方法底层最终都会调用到handle方法
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes
//计算当前路由真实的拦截请求路径---这里主要会加上当前group对应的请求前缀
absolutePath := group.calculateAbsolutePath(relativePath)
//整合全局处理器,当前group的处理器和应用到当前路由上的处理器,最终整合为一个处理器链后返回
handlers = group.combineHandlers(handlers)
//注册路由
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
- 当请求被某个路由拦截时
func (engine *Engine) handleHTTPRequest(c *Context)
....
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++
if t[i].method != httpMethod
continue
root := t[i].root
// Find route in tree
//从这个tree中寻找到处理当前请求的router
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil
c.Params = *value.params
//执行当前路由绑定的处理器链
if value.handlers != nil
c.handlers = value.handlers
c.fullPath = value.fullPath
//调用处理器的入口处
c.Next()
c.writermem.WriteHeaderNow()
return
....
break
...
- 拦截器调用的入口
func (c *Context) Next()
c.index++
//每调用一次Next索引加一
for c.index < int8(len(c.handlers))
//相关处理器回调函数会放在一个数组中,通过索引定位到具体的处理器函数,然后执行调用
c.handlers[c.index](c)
c.index++
- abort就是设置当前索引为整数最大值
func (c *Context) Abort()
c.index = abortIndex
Gin 文件上传
注意:需要在上传文件的 form 表单上面需要加入 enctype=“multipart/form-data”
单文件上传
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"path"
)
func main()
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context)
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
dst := path.Join("./", file.Filename)
// 上传文件至指定的完整文件路径
c.SaveUploadedFile(file, dst)
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
)
router.Run(":8080")
多文件上传–不同名字的多个文件
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"path"
)
func main()
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context)
file1, _ := c.FormFile("file1")
file2, _ := c.FormFile("file2")
dst := path.Join("./", file1.Filename)
c.SaveUploadedFile(file1, dst)
dst = path.Join("./", file2.Filename)
c.SaveUploadedFile(file2, dst)
c.String(http.StatusOK, "success !")
)
router.Run(":8080")
多文件上传–相同名字的多个文件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"log"
"net/http"
"path"
)
func main()
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context)
// Multipart form
form, _ := c.MultipartForm()
files := form.File["upload[]"]
for _, file := range files
log.Println(file.Filename)
dst := path.Join("./", file.Filename)
// 上传文件至指定目录
c.SaveUploadedFile(file, dst)
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
)
router.Run(":8080")
文件上传示例演示
请提供一个可以上传图片的接口,要求图片按照天数分割到不同的文件夹下存储,并且提供一个查询接口,可以查询某一天上传的图片有哪些。
package controller
import (
"fmt"
"github.com/gin-gonic/gin"
"os"
"path"
"strconv"
"time"
)
type ImgController struct
//图片允许的后缀名
allowExtMap map[string]bool
func (img *ImgController) AllowExtMap() map[string]bool
return img.allowExtMap
func (img *ImgController) uploadImg(c *gin.Context)
imgFile, err := c.FormFile("img")
if err != nil
c.Error(err)
return
//获取上传图片文件的后缀名
extName := path.Ext(imgFile.Filename)
//校验
if _, ok := img.AllowExtMap()[extName]; !ok
c.String以上是关于Gin简单明了的教程---下的主要内容,如果未能解决你的问题,请参考以下文章