zoukankan      html  css  js  c++  java
  • Golang的web框架之Gin

    前言

    Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍。

    如果你是性能和高效的追求者, 你会爱上Gin

    Go语言里最流行的Web框架,Github上有32K+star。 基于httprouter开发的Web框架。 中文文档齐全,简单易用的轻量级框架。

    安装

    D:learning-gin>set GOPROXY=https://goproxy.cn
    -------------------------------------------------------
    D:learning-gin>go get -u github.com/gin-gonic/gin
    go: google.golang.org
    /protobuf upgrade => v1.25.0 go: gopkg.in/yaml.v2 upgrade => v2.4.0 go: github.com/golang/protobuf upgrade => v1.4.3 go: github.com/ugorji/go/codec upgrade => v1.2.1 go: golang.org/x/sys upgrade => v0.0.0-20201211090839-8ad439b19e0f go: github.com/json-iterator/go upgrade => v1.1.10 go: github.com/modern-go/reflect2 upgrade => v1.0.1 go: github.com/go-playground/validator/v10 upgrade => v10.4.1 go: github.com/modern-go/concurrent upgrade => v0.0.0-20180306012644-bacd9c7ef1dd go: golang.org/x/crypto upgrade => v0.0.0-20201208171446-5f87f3452ae9

    Gin简单示例

    package main
    
    import "github.com/gin-gonic/gin"
    
    func index(c *gin.Context) {
        //返回json类型的数据,h=type H map[string]interface{}
        c.JSON(200, gin.H{"msg": "您好呀!"},
        )
    }
    
    func main() {
        //定义1个默认路由(基于httprouter的)
        router := gin.Default()
        //增加url
        router.GET("/index", index)
        //server段开始linsten运行
        router.Run("127.0.0.1:8000")
    
    }

    Gin request

    我们可以通过gin的context获取到客户端请求携带的url参数、form表单、json数据、文件等。

    package main
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
    )
    
    
    type user struct {
        Name string `json:"name"`
        City string `json:"city"`
    }
    var person=&user{}
    //从url获取参数
    func urlData(c *gin.Context) {
        //name:=c.Query("name") //获取url参数,获取不到获取空字符串
         name:=c.DefaultQuery("name","zhanggen") ////获取url参数,获取不到获取默认!
        city:=c.DefaultQuery("city","bj")
        person.Name=name
        person.City=city
        c.JSON(200,person)
    }
    
    //从form表单中获取数据
    func formData(c *gin.Context) {
        c.PostForm("name")
        person.Name=c.DefaultPostForm("name","Martin")
        person.City=c.DefaultPostForm("city","London")
        c.JSON(200,person)
    }
    
    //获取url地址参数
    func pathData(c *gin.Context){
        person.City=c.Param("city")
        person.Name=c.Param("name")
        c.JSON(200,*person)
    }
    //获取json数据
    func jsonData(c *gin.Context){
        c.Bind(person)
        fmt.Println("-----------------",*person)
        c.JSON(200,person)
    }
    
    
    func main()  {
        r:=gin.Default()
        //http://127.0.0.1:8001/user?name=zhanggen&city=beijing
        r.GET("/user",urlData)
        r.POST("/user",formData)
        //http://127.0.0.1:8001/user/bj/zhanggen
        r.GET("/user/:city/:name",pathData)
        r.POST("/user/json/",jsonData)
        r.Run(":8001")
    }

    Gin shouldBind

    默认情况下,我们需要根据客户端请求的content-type,在后端使用不同的方式,获取客户端请求参数。

    获取个请求参还需要c.Query、c.PostForm、c.Bind、C.Param,这也太麻烦了~

    shouldBind可帮助我们根据客户端request的content-type,自动获取请求参数,并赋值给后端struct的字段。

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    )
    
    //0.contentType对应ShouldBind对应的结构体
    type UserInfo struct {
    	Username string `form:"username" json:"username"`
    	Password string `form:"password" json:"password"`
    }
    
    func index(c *gin.Context) {
    	requestMethod := c.Request.Method
    	//1.声明1个值类型uerinfo类型的变量u
    	var user UserInfo
    	//2.把客户端request请求的参数和后端的结合体字段进行绑定
    	err := c.ShouldBind(&user)
    	if err != nil {
    		c.JSON(400, gin.H{"err": err.Error()})
    		return
    	}
    	//3.可以通过反射的方式,根据客户端request的contentType自动获取数据了
    	if requestMethod == "GET" {
    		fmt.Println(user)
    		c.HTML(200, "index.html", gin.H{})
    	}
    	if requestMethod == "POST" {
    		fmt.Println(user)
    		c.JSON(200, gin.H{"data": "postOkay"})
    	}
    
    }
    
    func main() {
    	router := gin.Default()
    	router.Static("/static", "./static")
    	router.LoadHTMLGlob("templates/*")
    	router.GET("/user", index)
    	router.POST("/user", index)
    	router.Run(":8002")
    }
    

      

    Gin response

    我们web开发过程中,大型项目会采用MVVM(前后端分离)的架构,小型项目会采用MTV(模板渲染)的架构。

    疏通同归其目的都是完成数据驱动视图,不同的是数据驱动视图的地方不一样。

    貌似web开发玩得就是这6个字,数据----》 驱动-----》视图。空谈误国,怎么才能更好的驱动视图才是关键。

    MTV模式(模板渲染):后端使用模板语法也就是字符串替换的方式,在后端直接完成数据和HTML的渲染,直接返回给客户端。

    MVVM(前后端分离架构):后端返回json数据,前端使用axios/ajax的方式获取到数据,使用vue等前端框架完成数据到HTML的渲染。

    1.RESTful API

    只要API程序遵循了REST风格,那就可以称其为RESTful API。

    其实核心思想是1个资源对应1个URL,客户端对这1资源操作时(不同的request.get/post/put/delete方法)对应后端的增/删/改/查操作。

    例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作。

    我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

    请求方法URL含义
    GET /book 查询书籍信息
    POST /create_book 创建书籍记录
    POST /update_book 更新书籍信息
    POST /delete_book 删除书籍信息

    我们按照RESTful API设计如下:

    请求方法URL含义
    GET /book 查询书籍信息
    POST /book 创建书籍记录
    PUT /book 更新书籍信息
    DELETE /book 删除书籍信息

    c.JSON响应json数据

    package main
    
    import "github.com/gin-gonic/gin"
    //结构体
    type user struct {
        Name string `json:"name"`
        Age int     `json:"age"`
    }
    //视图函数
    func perosn(c *gin.Context)  {
        var userInfor=user{Name: "张根",Age:18}
        c.JSON(200,userInfor)
    }
    func main(){
        r:=gin.Default()
        r.GET("/person/",perosn)
        r.Run(":8002")
    }

    2.MVC模板渲染

    如果是小型项目、历史原因、SEO优化我们使用模板渲染,Gin也是支持MTV模式的。

    package main
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
    )
    
    func index(c *gin.Context) {
        //3.gin 模板渲染
        c.HTML(200, "index.html", gin.H{"title": "首页", "body": "hello"})
    
    }
    
    func main() {
        //1.创建1个默认的路由引擎
        router := gin.Default()
        router.GET("/", index)
        //2.gin模板解析
        router.LoadHTMLGlob("templates/*")    //正则匹配templates/所有文件
        router.LoadHTMLGlob("templates/**/*") //正则匹配template/目录/所有文件
        err := router.Run(":9001")
        if err != nil {
            fmt.Println("gin启动失败", err)
        }
    }

    3.文件上传

    http请求也可以传输文件,有时候我们可以使用gin搭建1个ftp服务器。

    单个文件上传

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"path"
    )
    
    func handleFile(c *gin.Context) {
    	method := c.Request.Method
    	if method == "GET" {
    		c.HTML(200, "file.html", gin.H{})
    	}
    
    	if method == "POST" {
    		//1.从客户端请求中获取文件
    		fileObj, err := c.FormFile("localFile")
    		if err != nil {
    			c.JSON(400, gin.H{"err": err.Error()})
    			return
    		}
    		//2.保存到服务端
    		fileStorePath := path.Join("./upload/", fileObj.Filename)
    		err = c.SaveUploadedFile(fileObj, fileStorePath)
    		if err != nil {
    			errMsg := fmt.Sprintf("文件保存失败:%s
    ", err.Error())
    			c.JSON(200, gin.H{"err": errMsg})
    		}
    		c.JSON(200, gin.H{"data": "上传成功"})
    	}
    }
    
    func main() {
    	router := gin.Default()
    	router.Static("/static", "./static")
    	router.LoadHTMLGlob("templates/*")
    	router.GET("/file/", handleFile)
    	router.POST("/file/", handleFile)
    	err := router.Run(":8002")
    	if err != nil {
    		fmt.Println("gin启动失败", err)
    		return
    	}
    }

    多个文件上传

    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["file"]
    
    		for index, file := range files {
    			log.Println(file.Filename)
    			dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
    			// 上传文件到指定的目录
    			c.SaveUploadedFile(file, dst)
    		}
    		c.JSON(http.StatusOK, gin.H{
    			"message": fmt.Sprintf("%d files uploaded!", len(files)),
    		})
    	})
    	router.Run()
    }
    

      

    Gin模板渲染 

    现在大部分都是前后端分离的架构,除了seo优化我们基本不会使用gin做模板渲染。

    1.扩展gin模板函数

    package main
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
        "html/template"
    )
    
    func index(c *gin.Context) {
        //3.gin 模板渲染
        c.HTML(200, "index.html", gin.H{"title": "首页", "name": "Martin", "age": "hello", "url": `<a href="https://www.cnblogs.com/sss4/">主页</a>`})
    
    }
    
    func main() {
        //1.创建1个默认的路由引擎
        router := gin.Default()
        router.GET("/", index)
        //1.5 gin框架模板自定义模板函数
        router.SetFuncMap(template.FuncMap{
            "safe": func(safeString string) template.HTML {
                return template.HTML(safeString)
            },
        })
        //2.gin模板解析
        //router.LoadHTMLGlob("templates/*")    //正则匹配templates/所有文件
        router.LoadHTMLGlob("templates/**/*") //正则匹配template/目录/所有文件
    
        err := router.Run(":9001")
        if err != nil {
            fmt.Println("gin启动失败", err)
        }
    }

    模板

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{.title}}</title>
    
    </head>
    <body>
    <ul>
        <li>{{.name}}</li>
        <li>{{.age}}</li>
        <li>{{.url | safe}}</li>
    </ul>
    </body>
    </html>

    2.加载静态文件路径

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"html/template"
    )
    
    func index(c *gin.Context) {
    	//5.gin 模板渲染
    	c.HTML(200, "index.html", gin.H{"title": "首页", "name": "Martin", "age": "hello", "url": `<a href="https://www.cnblogs.com/sss4/">主页</a>`})
    
    }
    
    func main() {
    	//1.创建1个默认的路由引擎
    	router := gin.Default()
    	router.GET("/", index)
    	//2.加载静态文件路径 .css
    	router.Static("/static","./static")
    	//3. 扩展gin框架模板自定义模板函数
    	router.SetFuncMap(template.FuncMap{
    		"safe": func(safeString string) template.HTML {
    			return template.HTML(safeString)
    		},
    	})
    	//4.gin模板解析
    	//router.LoadHTMLGlob("templates/*")    //正则匹配templates/所有文件
    	router.LoadHTMLGlob("templates/**/*") //正则匹配template/目录/所有文件
    
    
    	err := router.Run(":9001")
    	if err != nil {
    		fmt.Println("gin启动失败", err)
    	}
    }

     模板

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{.title}}</title>
        <link rel="stylesheet" href="/static/dist/css/bootstrap.min.css">
        <script src="/static/jquery-3.2.1.min.js"></script>
        <script src="/static/dist/js/bootstrap.min.js"></script>
    </head>
    <body>
    <div class="container">
        <table class="table table-hover">
            <theader>
                <tr>
                    <td>姓名</td>
                    <td>年龄</td>
                    <td>主页</td>
                </tr>
            </theader>
            <tbody>
            <tr>
                <td>{{.name}}</td>
                <td>{{.age}}</td>
                <td>{{.url|safe}}</td>
            </tr>
            </tbody>
        </table>
    </div>
    </body>
    </html>

    3.Gin模板继承

    html/template实现了模板的嵌套和继承,但是gin不包含此功能。但是我们使用第第三方包。github.com/gin-contrib/multitemplate

    Gin 路由组

    URL路由太多了就需要分组管理,类似Flask的蓝图、Django里面的include URL。这些都是基于反射实现的。

    但是Gin框架中的路由使用的是httprouter这个库,其基本原理就是构造一个路由地址的前缀树

    1.单支路由

    //所有请求方式都汇聚到handleBook
    router.Any("/book/", handleBook)
    //处理404错误
    router.NoRoute(handle404)

    2.路由组

        //cmdb路由组
        cmdbRouter := router.Group("/cmdb")
        {
            cmdbRouter.GET("/list/")
            cmdbRouter.GET("/hosts/")
            cmdbRouter.GET("/option/")
        }
        //工单路由组
        workOrder := router.Group("/works")
        {
            workOrder.GET("/daily/")
            cmdbRouter.GET("/momthly")
            cmdbRouter.GET("/quarterly")
        }

    3.路由嵌套

    虽然gin的路由支持嵌套,但是出于对查询性能的考虑我们一般都会不会嵌套很多层路由。

    //cmdb路由组
        cmdbRouter := router.Group("/cmdb")
        {
            cmdbRouter.GET("/list/")
            cmdbRouter.GET("/hosts/")
            //1.cmdb的主机
            hostRouter := cmdbRouter.Group("/host")
            {
                //1.1主机的cpu
                hostRouter.GET("/cup/")
                //1.2主机的内存
                hostRouter.GET("/memory/")
                //1.3主机的硬盘
                hostRouter.GET("/disks/")
                //1.4主机运行的服务
                hostRouter.GET("/process/")
                //1.5网络流量
                hostRouter.GET("/networks/")
    
            }
    
            cmdbRouter.GET("/option/")
        }
        //2.工单路由组
        workOrder := router.Group("/works")
        {
            workOrder.GET("/daily/")
            cmdbRouter.GET("/momthly")
            cmdbRouter.GET("/quarterly")
        }

    Gin中间件

    我们可以在不修改视图函数的前提下,利用Web框架中携带的钩子函数也就是中间件 做权限控制、登录认证、权限校验、数据分页、记录日志、耗时统计.........

    注意我们的中间件不仅可以设置1个,也根据我们的业务逻辑设置N个,相当于对用户请求增加了多层过滤。

    就像Python里面的多层装饰器。

    1.中间件执行流程

    由于http请求包含request、response 2个动作所以中间件是双行线,中间件的执行流程就像1个递归函数的执行过程

    压栈: 用户---------> 认证中间件---------> 用户权限中间件---------> 错误处理中间件---------> 视图函数执行
    
    出栈: 视图函数执行完毕---------> 错误处理中间件---------> 用户权限中间件---------> 认证中间件---------> 用户

    2.控制中间件执行流程

    所为的控制流程我感觉就是设计中间件这个栈里面包含的层层栈针。

    我们在弹匣里装了什么样的子弹,扣动扳机时就会发射出什么子弹,这样想会更简单一些否则很容易被绕进去。

    在中间件执行的过程中我们可以控制进栈和出栈流程。

     

    以上代码执行结果:

    m1 in
    m2 in
    m1 out

    调用context.Next(),继续调用下一个视图函数进行压栈。(子弹装满弹匣)

    调用context.Abort() 阻止继续调用后续的函数,执行完当前栈针(函数)之后出栈。(1发子弹就够了)

    调用context.Abort() + return,当前位置返回,当前位置之后的代码都不需要不执行了。(1发哑弹)

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    )
    
    //中间件1
    func middleWare1(c *gin.Context) {
    	fmt.Println("middleWare1开始----------")
    	c.Next() //调用后续的处理函数
    	fmt.Println("middleWare1结束----------")
    
    }
    
    //中间件2
    func middleWare2(c *gin.Context) {
    	fmt.Println("middleWare2开始========")
    	c.Abort()//终止后续处理函数的调用,执行完本函数返回
    	return  //更极端一些 到这就结束!(本函数也不需要执行完毕了)。
    	fmt.Println("middleWare2结束========")
    }
    
    //index视图函数
    func index(c *gin.Context) {
    	fmt.Println("index开始+++++++++")
    	c.JSON(200, gin.H{"data": "ok"})
    	fmt.Println("index结束+++++++++")
    }
    
    func main() {
    	router := gin.Default()
    	//全局注册中间件:middleWare1, middleWare2
    	router.Use(middleWare1, middleWare2)
    	router.GET("/index/", index)
    	err := router.Run(":9001")
    	if err != nil {
    		fmt.Println("Gin启动失败", err)
    	}
    
    }
    

      

    3.给单个路由(url)设置中间件

    当我们需要对特定的视图函数增加新功能时,可以给它增加1个中间件。

    package main
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
        "time"
    )
    
    //中间件1
    func middleWare1(c *gin.Context) {
        fmt.Println("--------------I`m going through middleWare1----------")
        start := time.Now()
        c.Next() //调用后续的处理函数
        cost := time.Since(start)
        fmt.Printf("耗时----------%v
    ", cost)
        c.Abort() //终止请求
    }
    
    //index handlerfunc类型的函数
    func index(c *gin.Context) {
        fmt.Println("--------------I`m going through handlerfunc----------")
        c.JSON(200, gin.H{"data": "ok"})
        c.Next()
    
    }
    
    //中间件2
    func middleWare2(c *gin.Context) {
        fmt.Println("--------------I`m going through middleWare2----------")
        c.Next()
        c.Abort() //请求终止
    }
    
    func main() {
        router := gin.Default()
        //设置中间件流程:middleWare1-----》index----》middleWare2
        router.GET("/index/", middleWare1, index, middleWare2)
        err := router.Run(":9001")
        if err != nil {
            fmt.Println("Gin启动失败", err)
        }
    
    }

    4.全局注册中间件

    如果我们需要每个视图函数都设置1个中间件,把这一中间件写到每个视图函数前面会非常不方便,我们可以使用use进行全局注册。

    package main
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
    )
    
    //中间件1
    func middleWare1(c *gin.Context) {
        fmt.Println("middleWare1开始----------")
        c.Next() //调用后续的处理函数
        fmt.Println("middleWare1结束----------")
        //c.Abort() //终止请求
    }
    
    //中间件2
    func middleWare2(c *gin.Context) {
        fmt.Println("middleWare2开始========")
        c.Next()
        fmt.Println("middleWare2结束========")
    }
    
    //index handlerfunc类型的函数
    func index(c *gin.Context) {
        fmt.Println("index开始+++++++++")
        c.JSON(200, gin.H{"data": "ok"})
        fmt.Println("index结束+++++++++")
    }
    
    func main() {
        router := gin.Default()
        //全局注册中间件:middleWare1, middleWare2
        router.Use(middleWare1, middleWare2)
        router.GET("/index/", index)
        err := router.Run(":9001")
        if err != nil {
            fmt.Println("Gin启动失败", err)
        }
    
    }

    输出:

    验证web框架里中间件设计思想是的递归思想。

    middleWare1开始----------
    middleWare2开始========
    index开始+++++++++
    index结束+++++++++
    middleWare2结束========
    middleWare1结束----------

    5.路由组注册中间件

    给路由组注册中间件有2种写法

    写法1:

    shopGroup := r.Group("/shop", StatCost())
    {
        shopGroup.GET("/index", func(c *gin.Context) {...})
        ...
    }

    写法2:

    shopGroup := r.Group("/shop")
    shopGroup.Use(StatCost())
    {
        shopGroup.GET("/index", func(c *gin.Context) {...})
        ...
    }
    

    6.闭包的中间件

    以上我们得知:Gin的中间件是以1种gin.HandlerFunc类型存在,在路由和路由组里进行注册。

    router.GET("/index/", authMiddleWare(false), index)

    那我们可以使用闭包将1个开关参数和这个handlerFunc一起包起来。实现对中间进行开关控制比较灵活。

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    )
    
    //使用闭包函数返回,gin.HandlerFunc。可以实现对中间进行开关控制,比较灵活。
    func authMiddleWare(work bool) gin.HandlerFunc {
    	if work{
    		//连接数据库
    		//其他准备工作
    		dbDB := "Martin"
    		return func(c *gin.Context) {
    			username := c.Query("username")
    			if username == dbDB {
    				c.Next()
    			} else {
    				c.Abort()
    				c.JSON(403, gin.H{"data": "没有访问权限"})
    			}
    
    		}
    	}
    	return func(context *gin.Context) {
    	}
    }
    
    //index视图函数
    func index(c *gin.Context) {
    	fmt.Println("index视图函数开始")
    	c.JSON(200, gin.H{"data": "ok"})
    	fmt.Println("index视图函数结束")
    
    }
    
    func main() {
    	router := gin.Default()
    	router.GET("/index/", authMiddleWare(false), index)
    	err := router.Run(":9001")
    	if err != nil {
    		fmt.Println("Gin启动失败", err)
    	}
    
    }
    

      

    7.夸中间件进行传值

    中间件可以有多层,假如我们上游的中间得出的值,如何传递到下游中间件呢?。通过上下文content。

    当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。以保证我们传递的值是一致的。

    c.Set("username", username)
    currentUser, ok := c.Get("username")
    

    8.gin默认中间件

    gin.Defaut生成的路由引擎,默认使用了Logger(), Recovery()的中间件。

    //生成的路由引擎,默认使用了Logger(), Recovery()的中间件
     gin.Default()
    router :
    = gin.New()

    Logger:用于记录日志

    Recovery:用于保证在gin 发生错误时进程不会终止。

    package main
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
    )
    
    //使用闭包函数返回,gin.HandlerFunc。可以实现对中间进行开关控制,比较灵活。
    func authMiddleWare(work bool) gin.HandlerFunc {
        if work {
            //连接数据库
            //其他准备工作
            dbDB := "Martin"
            return func(c *gin.Context) {
                username := c.Query("username")
                if username == dbDB {
                    //1.在中间中设置值进行传递
                    c.Set("username", username)
                    fmt.Println("-----------", username)
                    c.Next()
                } else {
                    c.Abort()
                    c.JSON(403, gin.H{"data": "没有访问权限"})
                }
    
            }
        }
        return func(context *gin.Context) {
        }
    }
    
    //index视图函数
    func index(c *gin.Context) {
        //2.获取上流传递的值
        currentUser, ok := c.Get("username")
        if !ok {
            currentUser = "anonymous"
        }
        fmt.Println("index视图函数开始")
        c.JSON(200, gin.H{"data": currentUser})
        fmt.Println("index视图函数结束")
    
    }
    
    func main() {
        //生成的路由引擎,默认使用了Logger(), Recovery()的中间件
        gin.Default()
        //router := gin.Default()
        router := gin.New()
        router.GET("/index/", authMiddleWare(true), index)
        err := router.Run(":9001")
        if err != nil {
            fmt.Println("Gin启动失败", err)
        }
    
    }
    代码

    Gin设置cookie

    1.cookie是server端保存在browser中的用户信息

    2.每客户端次访问server端时都会携带该域下的cooki信息到server端。

    3.不同域名之间的cookie是不共享的。

     HttpOnly:如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到该cookie信息。

    1.设置cookie

    //设置cookie:key=username,value=zhanggen,maxAge=10秒,path=/,domain=127.0.0.1,secure=false,httpOnly=false
            c.SetCookie("username", currentUser.Username, 10, "/", "127.0.0.1", false, false)

    2.获取cookie

     //获取cookie
     userName, err := c.Cookie("username")

    总结

    Cookie虽然在一定程度上解决了保持客户端状态的需求,但是cookie有一些缺陷。

    1.Cookie保存在客户端,可能在http传输过程中被拦截、窃取。

    2.即使我们对Cookie进行加密,cookie最多也只能保存4096字节(4KB)的数据。

    Gin设置session

    1.cookie和session的数据结构

    客户端cookie:
    用户1的cookie
    ={"sessionID":"8b9c1c99-1e84-4a1f-85f9-d0216980706b"} 用户6的cookie={"sessionID":"d351b75f-042c-4eb6-978f-fc6e31178cdc"} ...........
    服务端端session:
    {
    "8b9c1c99-1e84-4a1f-85f9-d0216980706b":{"islogin":true,"username":"用户1"}, "750e641b-23c3-4b48-ac5d-adda3a6d584a":{"islogin":true,"username":"用户2"}, "d351b75f-042c-4eb6-978f-fc6e31178cdc":{"islogin":true,"username":"用户3"}, "b2c5997d-e958-48b3-bc87-f08d5bd16e22":{"islogin":true,"username":"用户4"}, "750e641b-23c3-4b48-ac5d-adda3a6d584a":{"islogin":true,"username":"用户5"}, "d351b75f-042c-4eb6-978f-fc6e31178cdc":{"islogin":true,"username":"用户6"}, }

    1.首先客户端和服务端约定1个固定的key比如叫sessionID

    2.每次客户端请求到达server端之后,server用cookie中sessionID( key in client side)对应的value做key(key in server side )获取server端对应的value. 

    2.session实现流程

    上面我们说到我们可以在浏览器的cookie信息中设置1个sessionID(唯一标识)之后浏览器每次发送http请求到server端都会携带该域名下的cookie。

    改进:

    session是借助客户端cookie完成服务端数据存储,唯一标识的key通常设置为Session-ID,key对应的value就是服务端的sessionID。

    Session是一种在服务端保存的数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中 etc;

    Gin没有像Django那样自带session中间件,这是我开源的session中间件。

    基于以上总结出:

    1.cookie存储来自各个域名的sessionID。

    2.sessionID就是cookie中存储唯一标识的key,这个key的名字我想叫什么就叫什么叫token也行。

    3.只要能确保客户端每次都携带一个唯一标识到服务端来把sessionID存储到URL参数上都行,服务端就能识别用户身份(无论是通过数据结构还是通过算法校验的方式)。

     

    Gin设置JWT

    虽然我们可以做到使用redis集群对用户session信息进行分布式存储,保证了用户session数据在后端存储的可扩展性,但是这种方案得不偿失。

    1.随着用户量的增加,每次客户端请求到来时server端都要连接redis、查询sessionID会速度比较慢,也无法改变server端存储的session数据与日俱增的态势。

    2.sessionID是存储在浏览器的cookie之中,无法兼容微信小程序、APP这些不支持cookie的客户端类型,所以cookie和session都会无用武之地。

    3.从安全方面考虑:如果hake从浏览器的cookie中获取到了sessionID,服务器端也无法对sessionID做任何来源甄别和校验。

    JWT:json web token其机制类似于session,不同之处在于sever端不再保存client的sessionID/Token,而是通过算法检验的方式计算得出用户的身份是否合法?

    其实现生成和校验token的过程详见我另一篇博客

    1. 下载依赖包 jwt-go

    go get -u github.com/dgrijalva/jwt-go

    2.jwt生成和校验

    // 指定加密密钥
    var jwtSecret=[]byte("ssjwh")
    
    //Claim是一些实体(通常指的用户)的状态和额外的元数据
    type Claims struct{
        Username string `json:"username"`
        Password string `json:"password"`
        jwt.StandardClaims
    }
    
    // 根据用户的用户名和密码产生token
    func GenerateToken(username ,password string)(string,error){
        //设置token有效时间
        nowTime:=time.Now()
        expireTime:=nowTime.Add(3*time.Hour)
    
        claims:=Claims{
            Username:       username,
            Password:       password,
            StandardClaims: jwt.StandardClaims{
                // 过期时间
                ExpiresAt:expireTime.Unix(),
                // 指定token发行人
                Issuer:"zhanggen",
            },
        }
    
        tokenClaims:=jwt.NewWithClaims(jwt.SigningMethodHS256,claims)
        //该方法内部生成签名字符串,再用于获取完整、已签名的token
        token,err:=tokenClaims.SignedString(jwtSecret)
        return token,err
    }
    
    // 根据传入的token值获取到Claims对象信息,(进而获取其中的用户名和密码)
    func ParseToken(token string)(*Claims,error){
    
        //用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*Token
        tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
            return jwtSecret, nil
        })
    
        if tokenClaims!=nil{
            // 从tokenClaims中获取到Claims对象,并使用断言,将该对象转换为我们自己定义的Claims
            // 要传入指针,项目中结构体都是用指针传递,节省空间。
            if claims,ok:=tokenClaims.Claims.(*Claims);ok&&tokenClaims.Valid{
                return claims,nil
            }
        }
        return nil,err
    
    }
    func main() {
        token, _ := GenerateToken("YourUsername", "YourPasword")
        claims,_:=ParseToken(token)
        fmt.Println(claims)
    }
    jwt的使用

    3.Gin中间件

    参考

  • 相关阅读:
    Linux操作_常用命令操作练习
    Linux编程_Shell脚本练习题
    Linux操作_grep/egrep工具的使用
    Linux中的链接文件_软链接和硬链接
    Linux操作_磁盘管理_增加虚拟磁盘
    Linux命令_磁盘管理_查看磁盘或目录的容量
    Linux命令_用户身份切换
    使用Unity中的Box Collider组件完成游戏场景中的碰撞检测功能
    在Unity场景中更改天空盒的步骤
    Linux命令_用户和用户组管理
  • 原文地址:https://www.cnblogs.com/sss4/p/14128138.html
Copyright © 2011-2022 走看看