• 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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述
    可以看到其实我们可以传入不只一个回调处理函数,这些回调函数会组成一个处理器链绑定到当前路由上,而当请求被当前路由拦截时,就会被绑定到当前路由上的拦截器链所处理。

    可以类比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")
    		})
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里next提供的功能,可以提供类似于spring mvc中拦截器的preHandle和afterHandle的功效。

    前置处理
    c.next()
    后置处理
    
    • 1
    • 2
    • 3

    一个路由配置多个中间件的执行顺序

    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")
    		})
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 控制台输出的结果
    one- init middleware start
    Two- init middleware start
    首页
    Two- init middleware end
    one- init middleware end
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述


    全局中间件

    当有一个处理器需要应用到所有路由上时,难道还需要我们一个个挨个去重复绑定吗 ?显然,这个时候通常会有一个全局绑定,即一次全局设置后,会应用到所有的路由上,这里也被称为全局中间件。

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在路由分组中配置中间件

    我们不仅可以全局应用中间件,也可以缩小范围到一个路由组上:

    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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 写法 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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    中间件和对应控制器之间数据共享

    说白了就是如何在拦截器链执行过程中传递数据,显然在整个拦截器链执行过程中,只有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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    中间件注意事项

    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)
    	}()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这一点在官方文档也有说明,可以点击查看

    原因:

    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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 当请求被某个路由拦截时
    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
    	}
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 拦截器调用的入口
    func (c *Context) Next() {
    	c.index++
    	//每调用一次Next索引加一
    	for c.index < int8(len(c.handlers)) {
    		//相关处理器回调函数会放在一个数组中,通过索引定位到具体的处理器函数,然后执行调用
    		c.handlers[c.index](c)
    		c.index++
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • abort就是设置当前索引为整数最大值
    func (c *Context) Abort() {
    	c.index = abortIndex
    }
    
    • 1
    • 2
    • 3

    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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述


    多文件上传–不同名字的多个文件

    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")
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述


    多文件上传–相同名字的多个文件

    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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述


    文件上传示例演示

    请提供一个可以上传图片的接口,要求图片按照天数分割到不同的文件夹下存储,并且提供一个查询接口,可以查询某一天上传的图片有哪些。

    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(401, "文件格式不合法")
    		return
    	}
    	//创建图片保存目录
    	now := time.Now()
    	year := now.Year()
    	month := now.Month()
    	day := now.Day()
    	hour := now.Hour()
    	minute := now.Minute()
    	join := "./" + strconv.Itoa(year) + "-" + month.String() + "-" + strconv.Itoa(day) + "-" + strconv.Itoa(hour) + "-" + strconv.Itoa(minute)
    	if err := os.MkdirAll(join, 0666); err != nil {
    		fmt.Println(err)
    	}
    	join = path.Join(join, imgFile.Filename)
    	fmt.Printf("上传图片保存路径为: %v\n", join)
    	//上传文件
    	c.SaveUploadedFile(imgFile, join)
    }
    
    func (img *ImgController) getImg(c *gin.Context) {
    
    }
    
    func RegisterImgRouter(c *gin.Engine) {
    	imgController := &ImgController{
    		allowExtMap: map[string]bool{
    			".jpg":  true,
    			".png":  true,
    			".gif":  true,
    			".jpeg": true,
    		},
    	}
    	imgGroup := c.Group("/img")
    	imgGroup.POST("/", imgController.uploadImg)
    	imgGroup.GET("/", imgController.uploadImg)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    package main
    
    import (
    	"GinStudy/controller"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	engine := gin.Default()
    	registerRouter(engine)
    	engine.Run()
    }
    
    func registerRouter(engine *gin.Engine) {
    	controller.RegisterImgRouter(engine)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    查询接口没有实现,感兴趣大家可自行实现,并且建议将上传文件保存的相关逻辑放到Dao层完成


    Gin 中的 Cookie

    • HTTP 是无状态协议。简单地说,当你浏览了一个页面,然后转到同一个网站的另一个页面,服务器无法认识到这是同一个浏览器在访问同一个网站。每一次的访问,都是没有任何关系的。如果我们要实现多个页面之间共享数据的话我们就可以使用Cookie 或者Session 实 现
    • cookie 是存储于访问者计算机的浏览器中。可以让我们用同一个浏览器访问同一个域名 的时候共享数据

    Cookie 能实现的功能

    • 保持用户登录状态
    • 保存用户浏览的历史记录
    • 猜你喜欢,智能推荐
    • 电商网站的加入购物车

    前面我们已经使用过ctx.Set(“username”) 和 ctx.Get(“username”)来进行数据的保存和共享,但这个使用的只针对是单页面的数据共享,要想实现多页面的共享,就需要Cookie或者Session。


    设置Cookie

    c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)其中:

    • 第一个参数key
    • 第二个参数value
    • 第三个参数过期时间.如果只想设置Cookie 的保存路径而不想设置存活时间,可以在第三个参数中传递nil
    • 第四个参数cookie 的路径
    • 第五个参数cookie 的路径Domain 作用域本地调试配置成localhost , 正式上线配置成域名
    • 第六个参数是secure ,当secure 值为true 时,cookie 在HTTP 中是无效,在HTTPS 中才有效
    • 第七个参数httpOnly,是微软对COOKIE 做的扩展。如果在COOKIE 中设置了“httpOnly”属性,则通过程序(JS 脚本、applet 等)将无法读取到COOKIE 信息,防止XSS 攻击产生

    获取Cookie

    cookie, err := c.Cookie("name")
    
    • 1

    删除Cookie

    把第三个参数时间,即过期时间设置为-1
    
    • 1

    演示

    func ApiRouter(r *gin.Engine) {
    	apiRouter := r.Group("/api")
    	{
    		apiRouter.GET("/", func(ctx *gin.Context) {
    			// 设置Cookie
    			ctx.SetCookie("username", "张三", 3600, "/", "localhost", false, false)
    			fmt.Println("首页")
    			ctx.String(200, "/api")
    		})
    
    		apiRouter.GET("/news", func(ctx *gin.Context) {
    			// 获取Cookie
    			username, _ := ctx.Cookie("username")
    			fmt.Println(username)
    			ctx.String(200, "/news/"+username)
    		})
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    多个二级域名共享cookie

    我们想的是用户在a.test.com 中设置Cookie 信息后在b.test.com 中获取刚才设置的cookie,也就是实现多个二级域名共享cookie, 这时候的话我们就可以这样设置cookie。

    c.SetCookie("usrename", "张三", 3600, "/", ".test.com", false, true)
    
    • 1

    Gin 中的 Session

    session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 session 保存在服务器上。

    Session 的工作流程:

    当客户端浏览器第一次访问服务器并发送请求时,服务器端会创建一个 session 对象,生成 一个类似于 key,value 的键值对,然后将 value 保存到服务器 将 key(cookie)返回到浏览器(客 户)端。

    浏览器下次访问时会携带 key(cookie),找到对应的 session(value)。

    Gin 中使用 Session Gin 官方没有给我们提供 Session 相关的文档,这个时候我们可以使用第三方的 Session 中间 件来实现.

    https://github.com/gin-contrib/sessions

    gin-contrib/sessions中间件支持的存储引擎:

    • cookie
    • memstore
    • redis
    • memcached
    • mongodb


    基于 Cookie 存储 Session

    1、安装 session 包

    go get github.com/gin-contrib/sessions
    
    • 1

    2、基本的 session 用法

    package main
    
    import (
    	"github.com/gin-contrib/sessions"
    	"github.com/gin-contrib/sessions/cookie"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
    	//创建基于cookie的存储引擎,参数是用于加密的秘钥
    	store := cookie.NewStore([]byte("secret111"))
    	//设置session中间件,参数mysession,指的是session的名字,也是cookie的名字
    	r.Use(sessions.Sessions("mysession", store))
    	r.GET("/", func(c *gin.Context) {
    		//初始化session对象
    		session := sessions.Default(c)
    		//设置过期时间
    		session.Options(sessions.Options{
    			MaxAge: 3600 * 6, //默认过期时间为30天
    		})
    		//设置Session
    		session.Set("username", "dhy")
    		session.Save()
    		c.String(200, "success")
    	})
    	r.POST("/", func(c *gin.Context) {
    		//初始化session对象
    		session := sessions.Default(c)
    		//通过session.Get读取session值
    		username := session.Get("username")
    		c.String(200, username.(string))
    	})
    	r.Run()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    基于 Redis 存储 Session

    如果我们想将 session 数据保存到 redis 中,只要将 session 的存储引擎改成 redis 即可。

    使用 redis 作为存储引擎的例子:

    • 首先安装 redis 存储引擎的包
    go get github.com/gin-contrib/sessions/redis
    
    • 1

    例子:

    package main
    
    import (
    	"github.com/gin-contrib/sessions"
    	"github.com/gin-contrib/sessions/redis"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
    	// 初始化基于 redis 的存储引擎 
    	//参数说明: 
    	//第 1 个参数 - redis 最大的空闲连接数 
    	//第 2 个参数 - 数通信协议 tcp 或者 udp 
    	//第 3 个参数 - redis 地址, 格式,host:port 
    	//第 4 个参数 - redis 密码 
    	//第 5 个参数 - session 加密密钥
    	store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret111"))
    
    	//设置session中间件,参数mysession,指的是session的名字,也是cookie的名字
    	r.Use(sessions.Sessions("mysession", store))
    	r.GET("/", func(c *gin.Context) {
    		//初始化session对象
    		session := sessions.Default(c)
    		//设置过期时间
    		session.Options(sessions.Options{
    			MaxAge: 3600 * 6, //默认过期时间为30天
    		})
    		//设置Session
    		session.Set("username", "dhy")
    		session.Save()
    		c.String(200, "success")
    	})
    	r.POST("/", func(c *gin.Context) {
    		//初始化session对象
    		session := sessions.Default(c)
    		//通过session.Get读取session值
    		username := session.Get("username")
    		c.String(200, username.(string))
    	})
    	r.Run()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
  • 相关阅读:
    导数求函数最大值和最小值
    Python画小仓鼠
    operator=中自我赋值和异常安全问题
    MongoDB UPDATE使用$setOnInsert为新插入数据设置默认值
    Linux之文件/目录搜索
    redis
    5.1 主机扫描:路由信息的收集
    C++ 学习笔记(七)(关联容器篇)
    Docker+Nginx部署前后端分离项目(SpringBoot+Vue)的详细教程
    【云原生】K8s pod优雅退出(postStart、terminationGracePeriodSeconds、preStop)
  • 原文地址:https://blog.csdn.net/m0_53157173/article/details/126445849