gin_gini - CSDN
gin 订阅
琴酒,日本漫画《名侦探柯南》及其衍生作品中的角色。黑衣组织的重要成员,经常和伏特加一起出现。身穿黑色风衣,银色(早期动画为金色)长发,脸总被帽子和刘海半遮掩着,性格冷酷残忍,头脑冷静,似乎可以毫不犹豫地杀死任何人。经常执行各种暗杀和清除组织叛徒的任务,是给工藤新一灌下APTX4869使其身体变小的罪魁祸首,亦是柯南所面对的主要敌人之一。 展开全文
琴酒,日本漫画《名侦探柯南》及其衍生作品中的角色。黑衣组织的重要成员,经常和伏特加一起出现。身穿黑色风衣,银色(早期动画为金色)长发,脸总被帽子和刘海半遮掩着,性格冷酷残忍,头脑冷静,似乎可以毫不犹豫地杀死任何人。经常执行各种暗杀和清除组织叛徒的任务,是给工藤新一灌下APTX4869使其身体变小的罪魁祸首,亦是柯南所面对的主要敌人之一。
信息
座    驾
黑色保时捷356A(德国生产的古董车)
真实姓名
黑泽阵
饰    演
佐佐木藏之介
搭    档
伏特加
配    音
日语原版:堀之纪  、、大陆版:王明军、文涛、冯盛
身    份
黑衣组织成员
别    名
杜松子酒、金恩、阿进等
中文名
琴酒
性    别
外文名
Gin ジン(Jin)
年    龄
不详
惯用手
左手
登场作品
《名侦探柯南》及其衍生系列
琴酒人物信息
组织代号:Gin日文假名:ジン罗马音:Jin中文译名:琴酒其他译名:金恩、吉恩、阿进、杜松子酒等真实姓名:黑泽 阵 [1]  [2]  (日文姓名:黑泽 阵;日文假名:くろさわ じん;罗马音:Kurosawa Jin) 在小学馆出版的《LOVE·Conan-名侦探柯南Official fanbook》中,作者青山刚昌透露内部设定琴酒的名字为“黑泽阵”,原因是“阵”的日文片假名写法“ジン”与日文中的“琴酒”相同。 [1]  [2]  File.281中的名册上,宫野志保的名字旁边的名字为“鱼冢三郎”,疑为伏特加的名字,而“鱼冢三郎”边的名字被部分遮挡,仅露出“泽”一字;而在对应动画TV 223《人鱼失踪记(推理篇)》中,也只露出了“黑”字的一部分。不过据青山刚昌透露,“鱼冢三郎”的旁边写着的名字的确为“黑泽阵”。 [1]  另外在英文版漫画里,Gin和Vodka的真名被透露,分别是Melkior和Kaspar,不过后来这两个名字就没有下文了。性别:男年龄:不详出生地:不详血统:疑似混血儿身份:黑衣组织重要成员座驾:黑色保时捷356A(早期TV版中的车牌号为“新宿34-4869”,TV 345后的剧场版与TV版统一使用“新宿54 み43-68”)手机型号:iPhone 4S黑款(File.820)武器:伯莱塔M92F手枪爱好:开车时抽烟,并喜欢用汽车点烟器来点烟。 [3]  烟的品牌为“JILOISES”(虚构品牌)。 [4]  初登场:配音演员:真人版演员:佐佐木藏之介(真人版单发第二部)
收起全文
精华内容
参与话题
  • 轻量级的Web框架Gin教程

    千人学习 2019-08-28 09:50:29
    Gin 是一个 go 写的 web 框架,封装比较优雅,API友好,源码注释比较明确,具有高性能的优点。 Golang的高并发一大利器就是协程。Gin里可以借助协程实现异步任务。
  • Go语言Web框架--Gin介绍和使用

    万次阅读 2018-07-23 11:05:11
    所谓框架 框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了。成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些...

    所谓框架

    框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了。成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些方面都得心应手的时候,可以尝试改造一些框架,或是自己创造一个。

    曾经我以为Python世界里的框架已经够多了,后来发现相比golang简直小巫见大巫。golang提供的net/http库已经很好了,对于http的协议的实现非常好,基于此再造框架,也不会是难事,因此生态中出现了很多框架。既然构造框架的门槛变低了,那么低门槛同样也会带来质量参差不齐的框架。

    考察了几个框架,通过其github的活跃度,维护的team,以及生产环境中的使用率。发现Gin还是一个可以学习的轻巧框架。


    Gin

    Gin是一个golang的微框架封装比较优雅API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活容错方便等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

    下面就Gin的用法做一个简单的介绍。

    首先需要安装,安装比较简单,使用go get -u(更新) 即可:

    go get gopkg.in/gin-gonic/gin.v1

    gin的版本托管再 gopkg的网站上。我在安装的过程中,gokpg卡住了,后来不得不根据gin里的godep的文件,把响应的源码从github上下载,然后copy到对应的目录。


    Hello World

    使用Gin实现Hello world非常简单,创建一个router,然后使用其Run的方法:

    import ( 
        "gopkg.in/gin-gonic/gin.v1" 
        "net/http" 
    ) 
    func main(){ 
        router := gin.Default() 
        router.GET("/", func(c *gin.Context) { 
        c.String(http.StatusOK, "Hello World") 
        }) 
    router.Run(":8000") 
    }
    

    简单几行代码,就能实现一个web服务。使用gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。最后是启动路由的Run方法监听端口。麻雀虽小,五脏俱全。当然,除了GET方法,gin也支持POST,PUT,DELETE,OPTION等常用的restful方法。


    restful路由

    gin的路由来自httprouter库。因此httprouter具有的功能,gin也具有,不过gin不支持路由正则表达式:

    func main(){ 
        router := gin.Default() 
        router.GET("/user/:name", func(c *gin.Context) { 
            name := c.Param("name") 
            c.String(http.StatusOK, "Hello %s", name) 
        }) 
    }
    
    

    冒号:加上一个参数名组成路由参数。可以使用c.Params的方法读取其值。当然这个值是字串string。诸如/user/rsj217,和/user/hello都可以匹配,而/user//user/rsj217/不会被匹配。

    ☁ ~ curl http://127.0.0.1:8000/user/rsj217 
    Hello rsj217%                  
    
    ☁ ~ curl http://127.0.0.1:8000/user/rsj217/ 
    404 page not found%            
    
    ☁ ~ curl http://127.0.0.1:8000/user/ 
    404 page not found%
    
    
    

    除了:,gin还提供了*号处理参数,*号能匹配的规则就更多。

    func main(){
        router := gin.Default() 
    
        router.GET("/user/:name/*action", func(c *gin.Context) { 
            name := c.Param("name") 
            action := c.Param("action") 
            message := name + " is " + action 
            c.String(http.StatusOK, message) 
        }) 
    }
    
    

    访问效果如下

    ☁  ~  curl http://127.0.0.1:8000/user/rsj217/
    rsj217 is /%                       
    
    ☁  ~  curl http://127.0.0.1:8000/user/rsj217/中国
    rsj217 is /中国%

    query string参数与body参数

    web提供的服务通常是client和server的交互。其中客户端向服务器发送请求,除了路由参数,其他的参数无非两种,查询字符串query string和报文体body参数。所谓query string,即路由用,用?以后连接的key1=value2&key2=value2的形式的参数。当然这个key-value是经过urlencode编码。

    query string

    对于参数的处理,经常会出现参数不存在的情况,对于是否提供默认值,gin也考虑了,并且给出了一个优雅的方案:

    func main(){ 
        router := gin.Default() 
    
        router.GET("/welcome", func(c *gin.Context) { 
            firstname := c.DefaultQuery("firstname", "Guest") 
            lastname := c.Query("lastname") 
            c.String(http.StatusOK, "Hello %s %s", firstname, lastname) 
            }) 
    
        router.Run() 
    }
    
    

    使用c.DefaultQuery方法读取参数,其中当参数不存在的时候,提供一个默认值。使用Query方法读取正常参数,当参数不存在的时候,返回空字串:

    ☁ ~ curl http://127.0.0.1:8000/welcome 
    Hello Guest % 
    
    ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=中国 
    Hello 中国 % 
    
    ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=中国\&lastname\=天朝 
    Hello 中国 天朝% 
    
    ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=\&lastname\=天朝 
    Hello 天朝% 
    
    ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=%E4%B8%AD%E5%9B%BD 
    Hello 中国 %
    
    

    之所以使用中文,是为了说明urlencode。注意,当firstname为空字串的时候,并不会使用默认的Guest值,空值也是值,DefaultQuery只作用于key不存在的时候,提供默认值。

    body

    http的报文体传输数据就比query string稍微复杂一点,常见的格式就有四种。例如application/jsonapplication/x-www-form-urlencoded, application/xmlmultipart/form-data。后面一个主要用于图片上传。json格式的很好理解,urlencode其实也不难,无非就是把query string的内容,放到了body体里,同样也需要urlencode。默认情况下,c.PostFROM解析的是x-www-form-urlencodedfrom-data的参数。

    func main(){ 
        router := gin.Default() 
        router.POST("/form_post", func(c *gin.Context) { 
            message := c.PostForm("message") 
            nick := c.DefaultPostForm("nick", "anonymous") 
            c.JSON(http.StatusOK, gin.H{ 
                "status": gin.H{ 
                    "status_code": http.StatusOK, 
                    "status": "ok", 
                }, 
                "message": message, 
                "nick": nick,         
            }) 
        }) 
    }

    与get处理query参数一样,post方法也提供了处理默认参数的情况。同理,如果参数不存在,将会得到空字串。

    ☁ ~ curl -X POST http://127.0.0.1:8000/form_post -H "Content-Type:application/x-www-form-urlencoded" -d "message=hello&nick=rsj217" | python -m json.tool % Total % Received % Xferd 
    
    Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 104 100 79 100 25 48555 15365 --:--:-- --:--:-- --:--:-- 79000 
    { 
        "message": "hello", 
        "nick": "rsj217", 
        "status": { 
            "status": "ok", 
            "status_code": 200 
        } 
    }

    前面我们使用c.String返回响应,顾名思义则返回string类型。content-type是plain或者text。调用c.JSON则返回json数据。其中gin.H封装了生成json的方式,是一个强大的工具。使用golang可以像动态语言一样写字面量的json,对于嵌套json的实现,嵌套gin.H即可。

    发送数据给服务端,并不是post方法才行,put方法一样也可以。同时querystring和body也不是分开的,两个同时发送也可以:

    func main(){ 
        router := gin.Default() 
        router.PUT("/post", func(c *gin.Context) { 
            id := c.Query("id") 
            page := c.DefaultQuery("page", "0") 
            name := c.PostForm("name") 
            message := c.PostForm("message") 
            fmt.Printf("id: %s; page: %s; name: %s; message: %s \n", id, page, name, message)                   
            c.JSON(http.StatusOK, gin.H{ "status_code": http.StatusOK, }) 
        }) 
    }
    
    

    上面的例子,展示了同时使用查询字串和body参数发送数据给服务器。


    文件上传

    前面介绍了基本的发送数据,其中multipart/form-data转用于文件上传。gin文件上传也很方便,和原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中了。

    func main(){ 
        router := gin.Default() 
        router.POST("/upload", func(c *gin.Context) { 
            name := c.PostForm("name") 
            fmt.Println(name) 
            file, header, err := c.Request.FormFile("upload") if err != nil {     
                c.String(http.StatusBadRequest, "Bad request") 
                return 
            } 
            filename := header.Filename 
            fmt.Println(file, err, filename) 
            out, err := os.Create(filename) 
            if err != nil {
                log.Fatal(err) 
            } 
            defer out.Close() 
            _, err = io.Copy(out, file) 
            if err != nil 
            { 
                log.Fatal(err)
            } 
            c.String(http.StatusCreated, "upload successful") 
        }) 
        router.Run(":8000") 
    }

    使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。一种方式是直接返回。然后使用os的操作,把文件数据复制到硬盘上。

    使用下面的命令可以测试上传,注意upload为c.Request.FormFile指定的参数,其值必须要是绝对路径:

    curl -X POST http://127.0.0.1:8000/upload -F "upload=@/Users/ghost/Desktop/pic.jpg" -H "Content-Type: multipart/form-data"

    上传多个文件

    单个文件上传很简单,别以为多个文件就会很麻烦。依葫芦画瓢,所谓多个文件,无非就是多一次遍历文件,然后一次copy数据存储即可。下面只写handler,省略main函数的初始化路由和开启服务器监听了:

    router.POST("/multi/upload", func(c *gin.Context) { 
        err := c.Request.ParseMultipartForm(200000) 
        if err != nil { 
            log.Fatal(err) 
        } 
        formdata := c.Request.MultipartForm 
        files := formdata.File["upload"] 
    
        for i, _ := range files { / 
            file, err := files[i].Open() 
            defer file.Close() 
            if err != nil { 
                log.Fatal(err) 
            } 
    
            out, err := os.Create(files[i].Filename) 
            defer out.Close() 
            if err != nil { 
                log.Fatal(err) 
            } 
    
            _, err = io.Copy(out, file) 
            if err != nil { 
                log.Fatal(err) 
            } 
    
            c.String(http.StatusCreated, "upload successful") 
        } 
    })

    与单个文件上传类似,只不过使用了c.Request.MultipartForm得到文件句柄,再获取文件数据,然后遍历读写。

    使用curl上传

    curl -X POST http://127.0.0.1:8000/multi/upload -F "upload=@/Users/ghost/Desktop/pic.jpg" -F "upload=@/Users/ghost/Desktop/journey.png" -H "Content-Type: multipart/form-data"

    表单上传

    上面我们使用的都是curl上传,实际上,用户上传图片更多是通过表单,或者ajax和一些requests的请求完成。下面展示一下web的form表单如何上传。

    我们先要写一个表单页面,因此需要引入gin如何render模板。前面我们见识了c.String和c.JSON。下面就来看看c.HTML方法。

    首先需要定义一个模板的文件夹。然后调用c.HTML渲染模板,可以通过gin.H给模板传值。至此,无论是String,JSON还是HTML,以及后面的XML和YAML,都可以看到Gin封装的接口简明易用。

    创建一个文件夹templates,然后再里面创建html文件upload.html:

    <!DOCTYPE html> 
    <html lang="en"> 
    <head> 
        <meta charset="UTF-8"> 
        <title>upload</title> 
    </head> 
    
    <body> 
    <h3>Single Upload</h3> 
    <form action="/upload", method="post" enctype="multipart/form-data"> 
        <input type="text" value="hello gin" /> 
        <input type="file" name="upload" /> 
        <input type="submit" value="upload" /> 
    </form> 
    
    <h3>Multi Upload</h3> 
    <form action="/multi/upload", method="post" enctype="multipart/form-data"> 
        <input type="text" value="hello gin" /> 
        <input type="file" name="upload" /> 
        <input type="file" name="upload" /> 
        <input type="submit" value="upload" /> 
    </form> 
    
    </body> 
    </html>

    upload 很简单,没有参数。一个用于单个文件上传,一个用于多个文件上传。

      router.LoadHTMLGlob("templates/*")
        router.GET("/upload", func(c *gin.Context) {
            c.HTML(http.StatusOK, "upload.html", gin.H{})
        })
    

    使用LoadHTMLGlob定义模板文件路径。


    参数绑定

    我们已经见识了x-www-form-urlencoded类型的参数处理,现在越来越多的应用习惯使用JSON来通信,也就是无论返回的response还是提交的request,其content-type类型都是application/json的格式。而对于一些旧的web表单页还是x-www-form-urlencoded的形式,这就需要我们的服务器能改hold住这多种content-type的参数了。

    Python的世界里很好解决,毕竟动态语言不需要实现定义数据模型。因此可以写一个装饰器将两个格式的数据封装成一个数据模型。golang中要处理并非易事,好在有gin,他们的model bind功能非常强大。

    type User struct { 
        Username string `form:"username" json:"username" binding:"required"` 
        Passwd string `form:"passwd" json:"passwd" bdinding:"required"` 
        Age int `form:"age" json:"age"` 
    } 
    
    func main(){ 
        router := gin.Default() 
    
        router.POST("/login", func(c *gin.Context) { 
            var user User 
            var err error 
            contentType := c.Request.Header.Get("Content-Type") 
    
            switch contentType { 
                case "application/json": 
                    err = c.BindJSON(&user) 
                case "application/x-www-form-urlencoded": 
                    err = c.BindWith(&user, binding.Form) 
            } 
            if err != nil { 
                fmt.Println(err) 
                log.Fatal(err) 
            } 
    
            c.JSON(http.StatusOK, gin.H{ 
                "user": user.Username, 
                "passwd": user.Passwd, 
                "age": user.Age, 
            }) 
        }) 
    }

    先定义一个User模型结构体,然后针对客户端的content-type,一次使BindJSONBindWith方法。

    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&age=21" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 79 100 46 100 33 41181 29543 --:--:-- --:--:-- --:--:-- 46000
    { 
        "age": 21, 
        "passwd": "123", 
        "username": "rsj217" 
    }
    
     ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&new=21" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 78 100 45 100 33 37751 27684 --:--:-- --:--:-- --:--:-- 45000 
    { 
        "age": 0, 
        "passwd": "123", 
        "username": "rsj217" 
    } 
    
    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&new=21" | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
    No JSON object could be decoded

    可以看到,结构体中,设置了binding标签的字段(username和passwd),如果没传会抛错误。非banding的字段(age),对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。

    改成json的效果类似:

    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "age": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 96 100 46 100 50 32670 35511 --:--:-- --:--:-- --:--:-- 50000 
    { 
        "age": 21, 
        "passwd": "123", 
        "username": "rsj217" 
    } 
    
    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    100 95 100 45 100 50 49559 55066 --:--:-- --:--:-- --:--:-- 50000 
    { 
        "age": 0, 
        "passwd": "123", 
        "username": "rsj217" 
    } 
    
    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
    No JSON object could be decoded 
    
    ☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": 123, "new": 21}' | python -m json.tool % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 
    0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server 
    No JSON object could be decoded
    

    使用json还需要注意一点,json是有数据类型的,因此对于 {"passwd": "123"}{"passwd": 123}是不同的数据类型,解析需要符合对应的数据类型,否则会出错。

    当然,gin还提供了更加高级方法,c.Bind,它会更加content-type自动推断是bind表单还是json的参数。

    router.POST("/login", func(c *gin.Context) 
    { 
        var user User err := c.Bind(&user) 
        if err != nil { 
            fmt.Println(err) 
            log.Fatal(err) 
        } 
        c.JSON(http.StatusOK, gin.H{ 
            "username": user.Username, 
            "passwd": user.Passwd, 
            "age": user.Age, 
        }) 
    })

    多格式渲染

    既然请求可以使用不同的content-type,响应也如此。通常响应会有html,text,plain,json和xml等。
    gin提供了很优雅的渲染方法。到目前为止,我们已经见识了c.String, c.JSON,c.HTML,下面介绍一下c.XML。

    router.GET("/render", func(c *gin.Context) { 
        contentType := c.DefaultQuery("content_type", "json") 
        if contentType == "json" { 
            c.JSON(http.StatusOK, gin.H{ 
            "user": "rsj217", 
            "passwd": "123", 
            }) 
        } else if contentType == "xml" { 
            c.XML(http.StatusOK, gin.H{ 
                "user": "rsj217", 
                "passwd": "123", 
            }) 
        } 
    })

    结果如下:

    ☁ ~ curl http://127.0.0.1:8000/render\?content_type\=json 
    {"passwd":"123","user":"rsj217"} 
    
    ☁ ~ curl http://127.0.0.1:8000/render\?content_type\=xml 
    <map><user>rsj217</user><passwd>123</passwd></map>%

    重定向

    gin对于重定向的请求,相当简单。调用上下文的Redirect方法:

    router.GET("/redict/google", func(c *gin.Context) {
            c.Redirect(http.StatusMovedPermanently, "https://google.com")
    })

    分组路由

    熟悉Flask的同学应该很了解蓝图分组。Flask提供了蓝图用于管理组织分组api。gin也提供了这样的功能,让你的代码逻辑更加模块化,同时分组也易于定义中间件的使用范围。

    v1 := router.Group("/v1") 
    v1.GET("/login", func(c *gin.Context) { 
        c.String(http.StatusOK, "v1 login") 
    }) 
    
    v2 := router.Group("/v2") 
    v2.GET("/login", func(c *gin.Context) { 
        c.String(http.StatusOK, "v2 login") 
    })

    访问效果如下:

    ☁  ~  curl http://127.0.0.1:8000/v1/login
    v1 login%                                                                                 
    ☁  ~  curl http://127.0.0.1:8000/v2/login
    v2 login%

    middleware中间件

    golang的net/http设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。中间件分为全局中间件,单个路由中间件和群组中间件。


    全局中间件

    先定义一个中间件函数:

    func MiddleWare() gin.HandlerFunc { 
        return func(c *gin.Context) { 
            fmt.Println("before middleware") 
            c.Set("request", "clinet_request") 
            c.Next() 
            fmt.Println("before middleware") 
        } 
    }

    该函数很简单,只会给c上下文添加一个属性,并赋值。后面的路由处理器,可以根据被中间件装饰后提取其值。需要注意,虽然名为全局中间件,只要注册中间件的过程之前设置的路由,将不会受注册的中间件所影响。只有注册了中间件一下代码的路由函数规则,才会被中间件装饰。

    router.Use(MiddleWare()) { 
        router.GET("/middleware", func(c *gin.Context) { 
            request := c.MustGet("request").(string) 
            req, _ := c.Get("request") 
            c.JSON(http.StatusOK, gin.H{ 
                "middile_request": request, "request": req, 
            }) 
        }) 
    }

    使用router装饰中间件,然后在/middlerware即可读取request的值,注意在router.Use(MiddleWare())代码以上的路由函数,将不会有被中间件装饰的效果。

    使用花括号包含被装饰的路由函数只是一个代码规范,即使没有被包含在内的路由函数,只要使用router进行路由,都等于被装饰了。想要区分权限范围,可以使用组返回的对象注册中间件。

    ☁  ~  curl  http://127.0.0.1:8000/middleware
    {"middile_request":"clinet_request","request":"clinet_request"}

    如果没有注册就使用MustGet方法读取c的值将会抛错,可以使用Get方法取而代之。

    上面的注册装饰方式,会让所有下面所写的代码都默认使用了router的注册过的中间件。


    单个路由中间件

    当然,gin也提供了针对指定的路由函数进行注册。

    router.GET("/before", MiddleWare(), func(c *gin.Context) { 
        request := c.MustGet("request").(string) 
        c.JSON(http.StatusOK, gin.H{ 
            "middile_request": request, 
        }) 
    })

    群组中间件

    群组的中间件也类似,只要在对于的群组路由上注册中间件函数即可:

    authorized := router.Group("/", MyMiddelware()) 
    // 或者这样用: 
    authorized := router.Group("/") 
    authorized.Use(MyMiddelware()) { 
        authorized.POST("/login", loginEndpoint) 
    }

    群组可以嵌套,因为中间件也可以根据群组的嵌套规则嵌套。


    中间件实践

    中间件最大的作用,莫过于用于一些记录log,错误handler,还有就是对部分接口的鉴权。下面就实现一个简易的鉴权中间件。

    router.GET("/auth/signin", func(c *gin.Context) { 
        cookie := &http.Cookie{ 
            Name: "session_id", 
            Value: "123", 
            Path: "/", 
            HttpOnly: true, 
        } 
        http.SetCookie(c.Writer, cookie) 
        c.String(http.StatusOK, "Login successful") 
    }) 
    
    router.GET("/home", AuthMiddleWare(), func(c *gin.Context) { 
        c.JSON(http.StatusOK, gin.H{"data": "home"}) 
    })

    登录函数会设置一个session_id的cookie,注意这里需要指定path为/,不然gin会自动设置cookie的path为/auth,一个特别奇怪的问题。/home的逻辑很简单,使用中间件AuthMiddleWare注册之后,将会先执行AuthMiddleWare的逻辑,然后才到/home的逻辑。

    AuthMiddleWare的代码如下:

    func AuthMiddleWare() gin.HandlerFunc { 
        return func(c *gin.Context) { 
            if cookie, err := c.Request.Cookie("session_id"); err == nil { 
                value := cookie.Value fmt.Println(value) 
                if value == "123" { c.Next() return } 
            } 
            c.JSON(http.StatusUnauthorized, gin.H{ 
                "error": "Unauthorized", 
            })
            c.Abort() 
            return 
        } 
    }

    从上下文的请求中读取cookie,然后校对cookie,如果有问题,则终止请求,直接返回,这里使用了c.Abort()方法。

    In [7]: resp = requests.get('http://127.0.0.1:8000/home') 
    
    In [8]: resp.json() 
    Out[8]: {u'error': u'Unauthorized'} 
    
    In [9]: login = requests.get('http://127.0.0.1:8000/auth/signin') 
    
    In [10]: login.cookies 
    Out[10]: <RequestsCookieJar[Cookie(version=0, name='session_id', value='123', port=None, port_specified=False, domain='127.0.0.1', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)]> 
    
    In [11]: resp = requests.get('http://127.0.0.1:8000/home', cookies=login.cookies) 
    
    In [12]: resp.json() 
    Out[12]: {u'data': u'home'}
    

    异步协程

    golang的高并发一大利器就是协程。gin里可以借助协程实现异步任务。因为涉及异步过程,请求的上下文需要copy到异步的上下文,并且这个上下文是只读的。

    router.GET("/sync", func(c *gin.Context) { 
        time.Sleep(5 * time.Second) 
        log.Println("Done! in path" + c.Request.URL.Path) 
    }) 
    
    router.GET("/async", func(c *gin.Context) { 
        cCp := c.Copy() 
        go func() { 
            time.Sleep(5 * time.Second) 
            log.Println("Done! in path" + cCp.Request.URL.Path) 
        }() 
    })

    在请求的时候,sleep5秒钟,同步的逻辑可以看到,服务的进程睡眠了。异步的逻辑则看到响应返回了,然后程序还在后台的协程处理。


    自定义router

    gin不仅可以使用框架本身的router进行Run,也可以配合使用net/http本身的功能:

    func main() {
        router := gin.Default()
        http.ListenAndServe(":8080", router)
    }

    或者

    func main() { 
        router := gin.Default() 
        s := &http.Server{ 
            Addr: ":8000", 
            Handler: router, 
            ReadTimeout: 10 * time.Second, 
            WriteTimeout: 10 * time.Second, 
            MaxHeaderBytes: 1 << 20, 
        } 
        s.ListenAndServe() 
    }

    当然还有一个优雅的重启和结束进程的方案。后面将会探索使用supervisor管理golang的进程。


    总结

    Gin是一个轻巧而强大的golang web框架。涉及常见开发的功能,我们都做了简单的介绍。关于服务的启动,请求参数的处理和响应格式的渲染,以及针对上传和中间件鉴权做了例子。更好的掌握来自实践,同时gin的源码注释很详细,可以阅读源码了解更多详细的功能和魔法特性。


    End

    展开全文
  • Gin 入门实战

    千次阅读 2019-06-30 20:58:36
    #Gin 入门实战 Agenda 拨开云雾见天日:前置知识讲解 万丈高楼平地起:基础中的精髓 及 搭建企业级golang脚手架 秤砣虽小压千斤:实战学习开发用户管理系统 拨开云雾见天日 ##1-1 前置知识 Go开发web的优势 在 ...

    Gin 入门实战

    Agenda

    • 拨开云雾见天日:前置知识讲解
    • 万丈高楼平地起:基础中的精髓 及 搭建企业级golang脚手架
    • 秤砣虽小压千斤:实战学习开发用户管理系统

    拨开云雾见天日

    1-1 前置知识

    • Go开发web的优势
      在 Go 语言出现之前,开发者们总是面临非常艰难的抉择,
      究竟是使用执行速度快但是编译速度并不理想的语言(如:C++),还是
      使用编译速度较快但执行效率不佳的语言(如:.NET、Java),
      开发难度较低但执行速度一般的动态语言呢?(如:Python)
      显然,Go 语言在这 3 个条件之间做到了最佳的平衡:快速编译,高效执行,易于开发。
      在这里插入图片描述
    • gin是什么?
      Gin 框架 现在是 github 上 start 最多 Go 语言编写的 Web 框架,相比其他它的几个 start 数量差不多的框架,它更加的轻量,有更好的性能。
    • 性能对比

    在这里插入图片描述

    1. 在固定时间内重复完成的总数,数值越高的是越好的结果
    2. 单次重复持续时间(ns /op),越低越好
    3. 堆内存(B /op),越低越好
    4. 每次重复的平均分配(allocs /op),越低越好

    从内存分配、单词相应时间、Qps三个纬度分析。gin基本是碾压其他对手的。

    • httprouter 为gin插上了翅膀

    在这里插入图片描述

    https://chai2010.gitbooks.io/advanced-go-programming-book/ch5-web/ch5-02-router.html
    https://www.cnblogs.com/foxy/p/9469401.html

    1-2 golang开发环境准备

    下载与解压
    wget https://dl.google.com/go/go1.11.4.linux-amd64.tar.gz
    tar -xvf go1.11.4.linux-amd64.tar.gz
    
    修改环境变量
    vim ~/.bash_profile
    
    export GOROOT=/root/go
    export GOPATH=/root/go_path
    export GOBIN=$GOROOT/bin
    export PATH=$GOBIN:$GOROOT/bin:$GOPATH/bin:$PATH
    source ~/.bash_profile
    
    查看go的版本号
    go version
    

    1-2 go mod 包管理工具

    https://blog.csdn.net/e421083458/article/details/89762113

    万丈高楼平地起

    2-1 安装gin及快速开始

    测试使用版本:
    github.com/gin-gonic/gin v1.4.0

    go get -v github.com/gin-gonic/gin
    
    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
    	r := gin.Default()
    	r.GET("/ping", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.Run() // listen and serve on 0.0.0.0:8080
    }
    

    2-2 请求路由

    • 设置多种请求类型
    func main() {
    	// Creates a gin router with default middleware:
    	// logger and recovery (crash-free) middleware
    	router := gin.Default()
    
    	router.GET("/someGet", getting)
    	router.POST("/somePost", posting)
    	router.PUT("/somePut", putting)
    	router.DELETE("/someDelete", deleting)
    	router.PATCH("/somePatch", patching)
    	router.HEAD("/someHead", head)
    	router.OPTIONS("/someOptions", options)
    
    	// By default it serves on :8080 unless a
    	// PORT environment variable was defined.
    	router.Run()
    	// router.Run(":3000") for a hard coded port
    }
    
    
    • 绑定静态文件夹
    func main() {
    	router := gin.Default()
    	router.Static("/assets", "./assets")
    	router.StaticFS("/more_static", http.Dir("my_file_system"))
    	router.StaticFile("/favicon.ico", "./resources/favicon.ico")
    
    	// Listen and serve on 0.0.0.0:8080
    	router.Run(":8080")
    }
    
    • 参数作为URL
    package main
    
    import "github.com/gin-gonic/gin"
    
    type Person struct {
    	ID string `uri:"id" binding:"required,uuid"`
    	Name string `uri:"name" binding:"required"`
    }
    
    func main() {
    	route := gin.Default()
    	route.GET("/:name/:id", func(c *gin.Context) {
    		var person Person
    		if err := c.ShouldBindUri(&person); err != nil {
    			c.JSON(400, gin.H{"msg": err})
    			return
    		}
    		c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
    	})
    	route.Run(":8088")
    }
    
    • 正则绑定
    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func main() {
    	router := gin.Default()
    
    	router.GET("/user/*action", func(c *gin.Context) {
    		c.String(http.StatusOK, "Hello world")
    	})
    
    	router.Run(":8080")
    }
    

    2-3 获取请求参数

    • 获取GET请求参数
    func main() {
    	router := gin.Default()
    
    	// Query string parameters are parsed using the existing underlying request object.
    	// The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
    	router.GET("/welcome", func(c *gin.Context) {
    		firstname := c.DefaultQuery("firstname", "Guest")
    		lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
    
    		c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    	})
    	router.Run(":8080")
    }
    
    • 获取POST请求参数
    func main() {
    	router := gin.Default()
    
    	router.POST("/form_post", func(c *gin.Context) {
    		message := c.PostForm("message")
    		nick := c.DefaultPostForm("nick", "anonymous")
    
    		c.JSON(200, gin.H{
    			"status":  "posted",
    			"message": message,
    			"nick":    nick,
    		})
    	})
    	router.Run(":8080")
    }
    
    • 获取Body值
    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"io/ioutil"
    	"net/http"
    )
    
    func main() {
    	router := gin.Default()
    	
    	router.POST("/user/*action", func(c *gin.Context) {
    		bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
    		c.String(http.StatusOK, string(bodyBytes))
    	})
    	router.Run(":8080")
    }
    

    测试效果:

    #form-urlencode
    curl 'http://127.0.0.1:8080/user/adsadsad' -F "key=value"
    --------------------------88baf4eac8fd4ba1
    Content-Disposition: form-data; name="key"
    
    value
    --------------------------88baf4eac8fd4ba1--
    
    #form-data
    curl 'http://127.0.0.1:8080/user/adsadsad' -d "key=value"
    key=value
    
    • bind绑定结构体获取参数(Get&POST&POST_BODY)

    只需要在结构体上设置form标签即可

    package main
    
    import (
    	"fmt"
    	"log"
    	"time"
    
    	"github.com/gin-gonic/gin"
    )
    
    type Person struct {
    	Name     string    `form:"name"`
    	Address  string    `form:"address"`
    	Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
    }
    
    func main() {
    	route := gin.Default()
    	route.GET("/testing", startPage)
    	route.POST("/testing", startPage)
    	route.Run(":8085")
    }
    
    func startPage(c *gin.Context) {
    	var person Person
    	if c.ShouldBind(&person) == nil {
    		log.Println(person.Name)
    		log.Println(person.Address)
    		log.Println(person.Birthday)
    	}
    	c.String(200, fmt.Sprintf("%#v",person))
    }
    

    测试

    curl 'http://127.0.0.1:8085/testing?name=1&address=2&birthday=2006-01-02'
    
    main.Person{Name:"1", Address:"2", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}
    
    curl 'http://127.0.0.1:8085/testing' -d 'name=5566&address=3344&birthday=2006-01-02'
    main.Person{Name:"5566", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}%
    

    2-4 验证请求参数

    • 结构体binding验证

    gin默认使用validator.v8作为验证器。

    多种验证规则请参见:

    https://github.com/go-playground/validator/tree/v8

    https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Using_Validator_Tags

    注意点:gin必须使用 binding tag 来设置校验规则,而不是 validate。

    package main
    
    import (
    	"fmt"
    	"time"
    
    	"github.com/gin-gonic/gin"
    )
    
    type Person struct {
    	Name     string    `form:"name" binding:"required"`
    	Address  string    `form:"address"`
    	Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
    }
    
    func main() {
    	route := gin.Default()
    	route.GET("/testing", startPage)
    	route.Run(":8085")
    }
    
    func startPage(c *gin.Context) {
    	var person Person
    	if err:=c.ShouldBind(&person);err!=nil {
    		c.String(500,fmt.Sprint(err))
    	}
    	c.String(200, fmt.Sprintf("%#v",person))
    }
    

    测试

    curl 'http://127.0.0.1:8085/testing' -d 'name=&address=3344&birthday=2006-01-02&age=1'
    Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tag
    Key: 'Person.Name' Error:Field validation for 'Name' failed on the 'required' tagmain.Person{Age:1, Name:"", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}
    
    curl 'http://127.0.0.1:8085/testing' -d 'name=5566&address=3344&birthday=2006-01-02&age=1'
    Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tagmain.Person{Age:1, Name:"5566", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}
    
    • 自定义验证
    package main
    
    import (
    	"net/http"
    	"reflect"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"github.com/gin-gonic/gin/binding"
    	"gopkg.in/go-playground/validator.v8"
    )
    
    // Booking contains binded and validated data.
    type Booking struct {
    	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
    	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
    }
    
    func bookableDate(
    	v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
    	field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
    ) bool {
    	if date, ok := field.Interface().(time.Time); ok {
    		today := time.Now()
    		if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
    			return false
    		}
    	}
    	return true
    }
    
    func main() {
    	route := gin.Default()
    
    	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
    		v.RegisterValidation("bookabledate", bookableDate)
    	}
    
    	route.GET("/bookable", getBookable)
    	route.Run(":8085")
    }
    
    func getBookable(c *gin.Context) {
    	var b Booking
    	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
    		c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
    	} else {
    		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    	}
    }
    
    $ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
    {"message":"Booking dates are valid!"}
    
    $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
    {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
    
    • 升级v9验证,支持多语言错误信息

    注意v9验证的话,标签要使用 validate ,也是为了与v8的标签分开。

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"github.com/go-playground/locales/en"
    	"github.com/go-playground/locales/zh"
    	"github.com/go-playground/locales/zh_Hant_TW"
    	"github.com/go-playground/universal-translator"
    	"gopkg.in/go-playground/validator.v9"
    	zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
    	en_translations "gopkg.in/go-playground/validator.v9/translations/en"
    	zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw"
    )
    
    var (
    	Uni      *ut.UniversalTranslator
    	Validate *validator.Validate
    )
    
    type User struct {
    	Username string `form:"user_name" validate:"required"`
    	Tagline  string `form:"tag_line" validate:"required,lt=10"`
    	Tagline2 string `form:"tag_line2" validate:"required,gt=1"`
    }
    
    func main() {
    	en := en.New()
    	zh := zh.New()
    	zh_tw := zh_Hant_TW.New()
    	Uni = ut.New(en, zh, zh_tw)
    	Validate = validator.New()
    
    	route := gin.Default()
    	route.GET("/testing", startPage)
    	route.POST("/testing", startPage)
    	route.Run(":8085")
    }
    
    func startPage(c *gin.Context) {
    	//这部分应该放到中间件中
    	locale:=c.DefaultQuery("locale","zh")
    	trans, _ := Uni.GetTranslator(locale)
    	switch locale {
    	case "zh":
    		zh_translations.RegisterDefaultTranslations(Validate, trans)
    		break
    	case "en":
    		en_translations.RegisterDefaultTranslations(Validate, trans)
    		break
    	case "zh_tw":
    		zh_tw_translations.RegisterDefaultTranslations(Validate, trans)
    		break
    	default:
    		zh_translations.RegisterDefaultTranslations(Validate, trans)
    		break
    	}
    
    	//自定义错误内容
    	Validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {
    		return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
    	}, func(ut ut.Translator, fe validator.FieldError) string {
    		t, _ := ut.T("required", fe.Field())
    		return t
    	})
    
    	//这块应该放到公共验证方法中
    	user := User{}
    	c.ShouldBind(&user)
    	fmt.Println(user)
    	err := Validate.Struct(user)
    	if err != nil {
    		errs := err.(validator.ValidationErrors)
    		sliceErrs:=[]string{}
    		for _, e := range errs {
    			sliceErrs=append(sliceErrs,e.Translate(trans))
    		}
    		c.String(200,fmt.Sprintf("%#v",sliceErrs))
    	}
    	c.String(200, fmt.Sprintf("%#v",""))
    }
    

    测试

    http://127.0.0.1:8085/testing?user_name=1111111&tag_line=1111122222222222222222&tag_line=111&locale=zh
    []string{"Tagline长度必须小于10个字符", "Tagline2 must have a value!"}""
    
    http://127.0.0.1:8085/testing?user_name=1111111&tag_line=1111122222222222222222&tag_line=111&locale=zh
    []string{"Tagline must be less than 10 characters in length", "Tagline2 must have a value!"}""
    

    2-5 中间件

    • 使用gin中间件
    func Logger() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		t := time.Now()
    
    		// Set example variable
    		c.Set("example", "12345")
    
    		// before request
    
    		c.Next()
    
    		// after request
    		latency := time.Since(t)
    		log.Print(latency)
    
    		// access the status we are sending
    		status := c.Writer.Status()
    		log.Println(status)
    	}
    }
    
    func main() {
    	r := gin.New()
    	r.Use(Logger())
    
    	r.GET("/test", func(c *gin.Context) {
    		example := c.MustGet("example").(string)
    
    		// it would print: "12345"
    		log.Println(example)
    	})
    
    	// Listen and serve on 0.0.0.0:8080
    	r.Run(":8080")
    }
    
    • 自定义ip白名单中间件
    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"log"
    )
    
    func IPAuthMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		ipList:=[]string{
    			"192.168.1.1",
    		}
    		isMatched:=false
    		for _, host := range ipList {
    			if c.ClientIP() == host {
    				isMatched = true
    			}
    		}
    		if !isMatched{
    			c.String(200,fmt.Sprintf("%v, not in iplist", c.ClientIP()))
    			c.Abort()
    		}
    	}
    }
    
    func main() {
    	r := gin.New()
    	r.Use(IPAuthMiddleware())
    
    	r.GET("/test", func(c *gin.Context) {
    		example := c.MustGet("example").(string)
    		log.Println(example)
    	})
    
    	r.Run(":8080")
    }
    

    2-6 其他补充

    • 优雅关停
      确保在 go1.8+ 下编译
    // +build go1.8
    package main
    import (
    	"context"
    	"log"
    	"net/http"
    	"os"
    	"os/signal"
    	"syscall"
    	"time"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	router := gin.Default()
    	router.GET("/", func(c *gin.Context) {
    		time.Sleep(5 * time.Second)
    		c.String(http.StatusOK, "Welcome Gin Server")
    	})
    
    	srv := &http.Server{
    		Addr:    ":8080",
    		Handler: router,
    	}
    
    	go func() {
    		// service connections
    		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
    			log.Fatalf("listen: %s\n", err)
    		}
    	}()
    
    	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    	<-quit
    	log.Println("Shutdown Server ...")
    
    	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    	defer cancel()
    	if err := srv.Shutdown(ctx); err != nil {
    		log.Fatal("Server Shutdown:", err)
    	}
    	
    	select {
    	case <-ctx.Done():
    		log.Println("timeout of 5 seconds.")
    	}
    	log.Println("Server exiting")
    }
    

    Linux Signal及Golang中的信号处理
    https://blog.csdn.net/leonpengweicn/article/details/52131313

    • 模板渲染
      gin在html/template基础之上,进行的文件解析预处理。模块文件使用方法与之前相同。
      LoadHTMLGlob 可以解析一个文件夹的模板。
    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    func main() {
    	router := gin.Default()
    	//router.LoadHTMLFiles("templates/index.html",)
    	router.LoadHTMLGlob("templates/*")
    	router.GET("/index", func(c *gin.Context) {
    		c.HTML(http.StatusOK, "index.html", gin.H{
    			"title": "Main website",
    		})
    	})
    	router.Run(":8080")
    }
    

    index.html

    <html>
    <h1>
        {{ .title }}
    </h1>
    </html>
    

    template使用样例
    https://www.do1618.com/archives/893/go-template学习样例/
    https://www.calhoun.io/intro-to-templates-p3-functions/

    • Let’s Encrypt 自动证书下载
      无需配置任何证书即可自动下载证书,过期后自动续订。

    Let’s Encrypt原理
    在这里插入图片描述
    更多原理细节:
    https://www.cnblogs.com/esofar/p/9291685.html
    https://blog.csdn.net/canghaiguzhou/article/details/79945001

    gin使用方法超级简单:

    package main
    
    import (
    	"log"
    
    	"github.com/gin-gonic/autotls"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
    	r.GET("/ping", func(c *gin.Context) {
    		c.String(200, "pong")
    	})
    	log.Fatal(autotls.Run(r, "www.itpp.tk"))
    }
    

    测试

    https://www.itpp.tk/ping
    pong
    

    查看证书缓存

    [root@izrj998xcgvke9018fkvbvz autotls]# ls ~/.cache/golang-autocert/
    acme_account+key  www.itpp.tk
    

    福利彩蛋,免费域名注册:
    https://www.freenom.com/zh/index.html?lang=zh

    2-7 构建企业级脚手架

    代码地址:https://github.com/e421083458/gin_scaffold
    使用gin构建了企业级脚手架,代码简洁易读,可快速进行高效web开发。
    主要功能有:
    1、请求链路日志打印,涵盖mysql/redis/request_in/request_out
    2、接入validator.v9,支持多语言错误信息提示及自定义错误提示。
    3、借助golang_common,支持了多配置环境及log/redis/mysql/http.client

    现在开始

    go mod tidy
    
    • 运行脚本
    go run main.go
    
    ➜  gin_scaffold git:(master) ✗ go run main.go
    ------------------------------------------------------------------------
    [INFO]  config=./conf/dev/
    [INFO]  start loading resources.
    [INFO]  success loading resources.
    ------------------------------------------------------------------------
    [GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
    
    [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
    
    [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
     - using env:	export GIN_MODE=release
     - using code:	gin.SetMode(gin.ReleaseMode)
    
    [GIN-debug] GET    /demo/index               --> github.com/e421083458/gin_scaffold/controller.(*Demo).Index-fm (6 handlers)
    [GIN-debug] GET    /demo/bind                --> github.com/e421083458/gin_scaffold/controller.(*Demo).Bind-fm (6 handlers)
    [GIN-debug] GET    /demo/dao                 --> github.com/e421083458/gin_scaffold/controller.(*Demo).Dao-fm (6 handlers)
    [GIN-debug] GET    /demo/redis               --> github.com/e421083458/gin_scaffold/controller.(*Demo).Redis-fm (6 handlers)
     [INFO] HttpServerRun::8880
    
    • 测试mysql与请求链路
    curl 'http://127.0.0.1:8880/demo/dao?id=1'
    {
        "errno": 0,
        "errmsg": "",
        "data": "[{\"id\":1,\"area_name\":\"area_name\",\"city_id\":1,\"user_id\":2,\"update_at\":\"2019-06-15T00:00:00+08:00\",\"create_at\":\"2019-06-15T00:00:00+08:00\",\"delete_at\":\"2019-06-15T00:00:00+08:00\"}]",
        "trace_id": "c0a8fe445d05b9eeee780f9f5a8581b0"
    }
    
    查看链路日志(确认是不是一次请求查询,都带有相同trace_id):
    tail -f gin_scaffold.inf.log
    
    [INFO][2019-06-16T11:39:26.802][log.go:58] _com_request_in||method=GET||from=127.0.0.1||traceid=c0a8fe445d05b9eeee780f9f5a8581b0||cspanid=||uri=/demo/dao?id=1||args=map[]||body=||spanid=9dad47aa57e9d186
    [INFO][2019-06-16T11:39:26.802][log.go:58] _com_mysql_success||affected_row=1||traceid=c0a8fe445d05b9ee07b80f9f66cb39b0||spanid=9dad47aa1408d2ac||source=/Users/niuyufu/go/src/github.com/e421083458/gin_scaffold/dao/demo.go:24||proc_time=0.000000000||sql=SELECT * FROM `area`  WHERE (id = '1')||level=sql||current_time=2019-06-16 11:39:26||cspanid=
    [INFO][2019-06-16T11:39:26.802][log.go:58] _com_request_out||method=GET||args=map[]||proc_time=0.025019164||traceid=c0a8fe445d05b9eeee780f9f5a8581b0||spanid=9dad47aa57e9d186||uri=/demo/dao?id=1||from=127.0.0.1||response={\"errno\":0,\"errmsg\":\"\",\"data\":\"[{\\\"id\\\":1,\\\"area_name\\\":\\\"area_name\\\",\\\"city_id\\\":1,\\\"user_id\\\":2,\\\"update_at\\\":\\\"2019-06-15T00:00:00+08:00\\\",\\\"create_at\\\":\\\"2019-06-15T00:00:00+08:00\\\",\\\"delete_at\\\":\\\"2019-06-15T00:00:00+08:00\\\"}]\",\"trace_id\":\"c0a8fe445d05b9eeee780f9f5a8581b0\"}||cspanid=
    
    • 测试参数绑定与多语言验证
    curl 'http://127.0.0.1:8880/demo/bind?name=name&locale=zh'
    {
        "errno": 500,
        "errmsg": "Age为必填字段,Passwd为必填字段",
        "data": "",
        "trace_id": "c0a8fe445d05badae8c00f9fb62158b0"
    }
    
    curl 'http://127.0.0.1:8880/demo/bind?name=name&locale=en'
    {
        "errno": 500,
        "errmsg": "Age is a required field,Passwd is a required field",
        "data": "",
        "trace_id": "c0a8fe445d05bb4cd3b00f9f3a768bb0"
    }
    

    文件分层

    ├── README.md
    ├── conf   配置文件夹
    │   └── dev
    │       ├── base.toml
    │       ├── mysql_map.toml
    │       └── redis_map.toml
    ├── controller 控制器
    │   └── demo.go
    ├── dao DB数据访问层
    │   └── demo.go
    ├── dto  Bind结构体层
    │   └── demo.go
    ├── gin_scaffold.inf.log  info日志
    ├── gin_scaffold.wf.log warning日志
    ├── go.mod go module管理文件
    ├── go.sum
    ├── main.go
    ├── middleware 中间件层
    │   ├── panic.go
    │   ├── response.go
    │   ├── token_auth.go
    │   └── translation.go
    ├── public  公共文件
    │   ├── log.go
    │   ├── mysql.go
    │   └── validate.go
    ├── router  路由层
    │   ├── httpserver.go
    │   └── route.go
    └── tmpl
    

    引入轻量级golang类库,支持 mysql、redis、http.client、log、支持多级环境配置、支持链路日志打印

    go get -v github.com/e421083458/golang_common
    

    测试日志打印功能:

    package main
    
    import (
    	"github.com/e421083458/golang_common/lib"
    	"log"
    	"time"
    )
    
    func main() {
    	if err := lib.InitModule("./conf/dev/",[]string{"base","mysql","redis",}); err != nil {
    		log.Fatal(err)
    	}
    	defer lib.Destroy()
    
    	//todo sth
    	lib.Log.TagInfo(lib.NewTrace(), lib.DLTagUndefind, map[string]interface{}{"message": "todo sth"})
    	time.Sleep(time.Second)
    }
    

    类库更多功能细节请查看:
    https://github.com/e421083458/golang_common

    输出格式统一封装

    func ResponseError(c *gin.Context, code ResponseCode, err error) {
    	trace, _ := c.Get("trace")
    	traceContext, _ := trace.(*lib.TraceContext)
    	traceId := ""
    	if traceContext != nil {
    		traceId = traceContext.TraceId
    	}
    	resp := &Response{ErrorCode: code, ErrorMsg: err.Error(), Data: "", TraceId: traceId}
    	c.JSON(200, resp)
    	response, _ := json.Marshal(resp)
    	c.Set("response", string(response))
    	c.AbortWithError(200, err)
    }
    
    func ResponseSuccess(c *gin.Context, data interface{}) {
    	trace, _ := c.Get("trace")
    	traceContext, _ := trace.(*lib.TraceContext)
    	traceId := ""
    	if traceContext != nil {
    		traceId = traceContext.TraceId
    	}
    	resp := &Response{ErrorCode: SuccessCode, ErrorMsg: "", Data: data, TraceId: traceId}
    	c.JSON(200, resp)
    	response, _ := json.Marshal(resp)
    	c.Set("response", string(response))
    }
    

    定义中间件链路日志打印

    package middleware
    
    import (
    	"bytes"
    	"errors"
    	"fmt"
    	"github.com/e421083458/gin_scaffold/public"
    	"github.com/e421083458/golang_common/lib"
    	"github.com/gin-gonic/gin"
    	"io/ioutil"
    	"time"
    )
    //链路请求日志
    func RequestInLog(c *gin.Context) {
    	traceContext := lib.NewTrace()
    	if traceId := c.Request.Header.Get("com-header-rid"); traceId != "" {
    		traceContext.TraceId = traceId
    	}
    	if spanId := c.Request.Header.Get("com-header-spanid"); spanId != "" {
    		traceContext.SpanId = spanId
    	}
    	c.Set("startExecTime", time.Now())
    	c.Set("trace", traceContext)
    	bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
    	c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Write body back
    
    	lib.Log.TagInfo(traceContext, "_com_request_in", map[string]interface{}{
    		"uri":    c.Request.RequestURI,
    		"method": c.Request.Method,
    		"args":   c.Request.PostForm,
    		"body":   string(bodyBytes),
    		"from":   c.ClientIP(),
    	})
    }
    //链路输出日志
    func RequestOutLog(c *gin.Context) {
    	endExecTime := time.Now()
    	response, _ := c.Get("response")
    	st, _ := c.Get("startExecTime")
    	startExecTime, _ := st.(time.Time)
    	public.ComLogNotice(c, "_com_request_out", map[string]interface{}{
    		"uri":       c.Request.RequestURI,
    		"method":    c.Request.Method,
    		"args":      c.Request.PostForm,
    		"from":      c.ClientIP(),
    		"response":  response,
    		"proc_time": endExecTime.Sub(startExecTime).Seconds(),
    	})
    }
    
    func TokenAuthMiddleware() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		RequestInLog(c)
    		defer RequestOutLog(c)
    		isMatched := false
    		for _, host := range lib.GetStringSliceConf("base.http.allow_ip") {
    			if c.ClientIP() == host {
    				isMatched = true
    			}
    		}
    		if !isMatched{
    			ResponseError(c, InternalErrorCode, errors.New(fmt.Sprintf("%v, not in iplist", c.ClientIP())))
    			c.Abort()
    			return
    		}
    		c.Next()
    	}
    }
    

    请求数据绑定到结构体与校验

    dto/demo.go

    package dto
    
    import (
    	"errors"
    	"github.com/e421083458/gin_scaffold/public"
    	"github.com/gin-gonic/gin"
    	"github.com/go-playground/universal-translator"
    	"gopkg.in/go-playground/validator.v9"
    	"strings"
    )
    
    type InStruct struct {
    	Name   string `form:"name" validate:"required"`
    	Age    int64  `form:"age" validate:"required"`
    	Passwd string `form:"passwd" validate:"required"`
    }
    
    func (o *InStruct) BindingValidParams(c *gin.Context)  error{
    	if err:=c.ShouldBind(o);err!=nil{
    		return err
    	}
    	v:=c.Value("trans")
    	trans,ok := v.(ut.Translator)
    	if !ok{
    		trans, _ = public.Uni.GetTranslator("zh")
    	}
    	err := public.Validate.Struct(o)
    	if err != nil {
    		errs := err.(validator.ValidationErrors)
    		sliceErrs:=[]string{}
    		for _, e := range errs {
    			sliceErrs=append(sliceErrs,e.Translate(trans))
    		}
    		return errors.New(strings.Join(sliceErrs,","))
    	}
    	return nil
    }
    

    controller/demo.go

    func (demo *Demo) Bind(c *gin.Context) {
    	st:=&dto.InStruct{}
    	if err:=st.BindingValidParams(c);err!=nil{
    		middleware.ResponseError(c,500,err)
    		return
    	}
    	middleware.ResponseSuccess(c, "")
    	return
    }
    

    秤砣虽小压千斤

    3-1 用户管理系统设计

    在这里插入图片描述
    在这里插入图片描述

    功能点

    • 管理员登陆及退出
    • 用户列表 翻页
    • 增、删、改

    数据库

    • 用户表设计
    CREATE TABLE `user` (
     `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
     `name` varchar(255) NOT NULL DEFAULT '' COMMENT '姓名',
     `addr` varchar(255) NOT NULL DEFAULT '' COMMENT '住址',
     `age` smallint(4) NOT NULL DEFAULT '0' COMMENT '年龄',
     `birth` varchar(100) NOT NULL DEFAULT '2000-01-01 00:00:00' COMMENT '生日',
     `sex` smallint(4) NOT NULL DEFAULT '0' COMMENT '性别',
     `update_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '更新时间',
     `create_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '创建时间',
     PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8 COMMENT='用户表'
    

    后端准备

    前端准备

    3-2 实战开发

    • 开发接口
    [GIN-debug] POST   /api/login                --> github.com/e421083458/gin_scaffold/controller.(*Api).Login-fm (7 handlers)
    [GIN-debug] GET    /api/loginout             --> github.com/e421083458/gin_scaffold/controller.(*Api).LoginOut-fm (7 handlers)
    [GIN-debug] GET    /api/user/listpage        --> github.com/e421083458/gin_scaffold/controller.(*Api).ListPage-fm (8 handlers)
    [GIN-debug] GET    /api/user/add             --> github.com/e421083458/gin_scaffold/controller.(*Api).AddUser-fm (8 handlers)
    [GIN-debug] GET    /api/user/edit            --> github.com/e421083458/gin_scaffold/controller.(*Api).EditUser-fm (8 handlers)
    [GIN-debug] GET    /api/user/remove          --> github.com/e421083458/gin_scaffold/controller.(*Api).RemoveUser-fm (8 handlers)
    [GIN-debug] GET    /api/user/batchremove     --> github.com/e421083458/gin_scaffold/controller.(*Api).RemoveUser-fm (8 handlers)
    
    • 整合前端调试
    npm run dev
    

    修改 config/index.js文件,作接口代理转发

    dev: {
            env: require('./dev.env'),
            port: 8080,
            autoOpenBrowser: true,
            assetsSubDirectory: 'static',
            assetsPublicPath: '/',
            proxyTable: {
                "/api/": {
                    target:"http://127.0.0.1:8880/",
                    changeOrigin: true,
                    pathRewrite: {
                    }
                }
            },
            cssSourceMap: false
        }
    
    • 前后端部署与整合
      正式部署时我们一般考虑使用nginx 作转发。
        server {
            listen       8882;
            server_name  localhost;
            root /Users/niuyufu/WebstormProjects/vue-admin/dist;
            index  index.html index.htm index.php;
            location / {
                try_files $uri $uri/ /index.html?$args;
            }
            location /api/ {
                proxy_pass http://127.0.0.1:8880/api/;
            }
        }
    

    课程总结

    随堂笔记
    https://blog.csdn.net/e421083458/article/details/91994788
    gin入门实战 - 基础精髓 随堂代码
    https://github.com/e421083458/hello_gin
    golang基础代码类库
    https://github.com/e421083458/golang_common
    gin开发脚手架
    https://github.com/e421083458/gin_scaffold
    整合vue.js的admin后台
    https://github.com/e421083458/vue-admin

    展开全文
  • go语言gin框架教程

    2019-12-30 11:48:47
    Gin 是一个用 Go (Golang) 编写的 HTTP web 框架。 它是一个类似于 martini 但拥有更好性能的 API 框架, 由于 httprouter,速度提高了近 40 倍。如果你需要极好的性能,使用 Gin 吧。
  • go的gin框架使用(四):get请求

    千次阅读 2018-05-21 19:23:00
    比如我们get请求的试试输入参数,参数不存在,得有个兼容机制,使用DefaultQuery就能轻松解决我们在地址栏输入地址,不带参数,我们看一下结果我们看到firstname由于没值被替换成了test,如果有值的情况下则用参数值...

    比如我们get请求的试试输入参数,参数不存在,得有个兼容机制,使用DefaultQuery就能轻松解决
    我们在地址栏输入地址,不带参数,我们看一下结果
    我们看到firstname由于没值被替换成了test,如果有值的情况下则用参数值
    注意:当参数存在,但是空字符串时,并不会使用默认参数值

    展开全文
  • Gin使用

    2019-03-19 16:19:57
    Gin对net/http包做了封装,支持路由、中间件等特性,极大的方便对Http Server的开发。文章通过一个Test例子,来简要介绍。对于特别基础的部分,请阅读参考文章。 接口测试 Go中testing包为程序自测提供了便利。可以...
        
    这里的博客版本都不会被更新维护。查看最新的版本请移步:http://neojos.com

    Ginnet/http包做了封装,支持路由、中间件等特性,极大的方便对Http Server的开发。文章通过一个Test例子,来简要介绍。对于特别基础的部分,请阅读参考文章。

    接口测试

    Gotesting包为程序自测提供了便利。可以查阅之前写的博客Go test基础用法,对其内容,我还是挺满意的。

    使用Postman

    对于接口测试,很多情况都在使用Postman这样的工具。首先在本地启动服务,然后在Postman中配置请求的地址和参数、执行请求、查看结果。

    这种方式唯一让人不满意的地方在于:每次修改都需要重启服务。跟直接执行一次Test相比,明显多了一步。

    使用Test

    测试基类

    下面的代码作为接口测试的基类。

    TestMain中,我们为所有的测试用例指定通用的配置。之后在执行其他Test前,都会先执行TestMain中的代码。有效的避免了代码冗余。

    getRouter方法用于返回一个gin的实例。我们将服务的路由重新在Test中指定,并设置了中间件。

    testHttpResponse是我们请求处理的核心代码,发送请求,并保存响应到w

    //common_test.go
    
    func TestMain(m *testing.M) {
    
        //声明执行Test前的操作
        gin.SetMode(gin.TestMode)
        testutils.NewTestApp("../conf.test.toml")
    
        flag.Parse()
        os.Exit(m.Run())
    }
    
    //设置路由,获取框架Gin的实例
    func getRouter() *gin.Engine {
        router := gin.Default()
    
        //配置路由,这是我项目中的自定义配置
        router.Use(middleware.HeaderProcess())
        RouteAPI(router)
    
        return router
    }
    
    //统一处理请求返回结果
    func testHttpResponse(t *testing.T, r *gin.Engine, req *http.Request, f func(w *httptest.ResponseRecorder) error) {
        w := httptest.NewRecorder()
        r.ServeHTTP(w, req)
    
        if err := f(w); err != nil {
            t.Fatal(err)
        }
    }

    测试用例

    下面是具体的测试用例。我们构造了一个Json数据格式的POST请求,然后通过调用testHttpResponse方法来读取接口的响应数据。

    关于NewRequest方法,它参数body传递一个io.Reader接口类型。从源代码可以看出,实现了该接口的分别是:bytes.Bufferbytes.Readerstrings.Reader

    func TestWeChatRecharge(t *testing.T) {
        router := getRouter()
    
        //构造json的请求体
        params := map[string]interface{}{
            "open_id":     "olFg1s3gPcISnooRX9WSkX_E-cww",
            "device_type": "ANDROID",
        }
        jsonParams, _ := json.Marshal(params)
        readParams := bytes.NewReader(jsonParams)
        req, _ := http.NewRequest("POST", "/pay/wx/recharge", readParams)
        req.Header.Set("Content-type", "application/json")
    
        //发送请求
        testHttpResponse(t, router, req, func(w *httptest.ResponseRecorder) error {
            p, err := ioutil.ReadAll(w.Body)
            if err != nil {
                return err
            }
    
            t.Logf("%+v", string(p))
            return nil
        })
    }

    小结

    通过上述的步骤,我们实现了直接在Test中做接口测试。

    Middleware

    声明一个middleware函数,返回类型为type HandlerFunc func(*Context)

    func setUserStatus() gin.HandlerFunc {
        return func(c *gin.Context) {
            fmt.Println("set status")
        }
    }

    如果需要将函数应用于所有的请求,使用Use方法。比如统一的请求头转换、错误输出、日志打印等

    //Use adds middleware to the group
    router.Use(setUserStatus())

    下面是给具体的请求设置中间件。从这里可以看出,中间件处理函数和正常的业务处理函数类型是相同的。

    //Use a particular middleware
    articleRoutes.GET("/create", setUserStatus(), showArticleCreationPage)

    最后系统依次调用注册的handler完成请求处理:

    func (c *Context) Next() {
        c.index++
        for s := int8(len(c.handlers)); c.index < s; c.index++ {
            c.handlers[c.index](c)
        }
    }

    参考文章:

    1. Building Go Web Applications and Microservices Using Gin
    2. Test-driven Development of Go Web Applications with Gin
    展开全文
  • gin离线安装包

    2020-07-30 23:31:54
    $ go get gopkg.in/gin-gonic/gin.v1由于被墙,会下载失败,这里提供下载好的包。 Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活,容错方便等特点。其实对于...
  • Go语言web框架 gin

    万次阅读 2018-12-20 09:50:55
    Go语言web框架 GIN gin是go语言环境下的一个web框架, 它类似于Martini, 官方声称它比Martini有更好的性能, 比Martini快40倍, Ohhhh….看着不错的样子, 所以就想记录一下gin的学习. gin的github代码在这里: gin...
  • 文章目录Gin介绍安装快速开始路径参数构造参数post传参get+post混合形式构造Map格式 Gin介绍 Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活,容错方便等...
  • gin 入门教程

    千次阅读 2018-12-16 17:30:30
    周日花时间研究了gin框架的使用,基本上还算满意。 参考:https://blog.csdn.net/u010649766/article/details/79458004 代码参考:https://github.com/onebig32/gin-learn 教程代码:...
  • Gin框架一】Gin简介

    千次阅读 2019-06-18 20:08:49
    Gin是一个golang的微框架,封装比较优雅,API友好。具有快速灵活,容错方便等特点。Gin自身的net/http足够简单,性能也非常不错 Gin下载: https://github.com/gin-gonic/gin 英文文档:...
  • Gin Web框架简介

    万次阅读 2020-08-07 14:32:22
    翻译自: https://github.com/gin-gonic/gin/blob/develop/README.mdGin Web框架 Gin是用Golang实现的一种Web框架。基于 httprouter,它提供了类似martini但更好性能(路由性能约快40倍)的API服务. 如果你希望...
  • gin

    2020-05-06 18:56:22
    安装gin框架时出现链接超时的问题 描述 安装gin 框架时:go get github.com/gin-gonic/gin出现unrecognized import path "google.golang.org/protobuf/encoding/prototext": ...
  • Gin

    2020-04-18 08:42:31
    Gin快速入门 Gin文档 快速入门 package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) ...
  • Postgresql GIN索引

    2020-01-16 15:12:59
    GIN概念介绍: GIN是Generalized Inverted Index的缩写。就是所谓的倒排索引。它处理的数据类型的值不是原子的,而是由元素构成。我们称之为复合类型。如(‘hank’, ‘15:3 21:4’)中,表示hank在15:3和21:4这两个...
  • golang中的gin框架学习

    千次阅读 2019-03-29 21:00:24
    gin框架中常用方法 gin.H{ } 有这么一行c.JSON(200, gin.H{“message”: “use get method”})  这其中有一个gin.H{ },看样子,这像是一个结构体struct,查看gin框架的源码,声明如下:  所以,这只是一个map结构...
  • 1. 常见框架 1.1 框架排名 ...Gin 31k [Lite] Beego 22k Iris 16k Echo 15k [Lite] Revel 11k Martini 10k [×] buffalo 5k [Lite] 1.2 框架特性 GinGin 是一个用 Go (Golang) 编写的 web 框架。...
  • gin框架

    千次阅读 2018-12-24 09:54:50
    gin简介 gin是用Go语言写的后端web框架,简洁、轻量、支持高并发, 官方网站:https://gin-gonic.github.io/gin/ Github地址:https://github.com/gin-gonic/gin 下载 go get -u github.com/gin-gonic/gin 源码结构...
  • 我们已经了解了Golang的Gin框架。对于Webservice服务,restful风格几乎一统天下。Gin也天然的支持restful。下面就使用gin写一个简单的服务,麻雀虽小,五脏俱全。我们先以一个单文件开始,然后再逐步分解模块成包,...
  • postgresql 索引之 gin、btree_gin

    千次阅读 2020-08-05 15:56:15
    os: ubuntu 16.04 postgresql: 9.6.8 ip 规划 192.168.56.102 node2 postgresql help create index postgres=# \h create index Command: CREATE INDEX Description: define a new index ...CREATE [ UNIQUE ...
1 2 3 4 5 ... 20
收藏数 15,654
精华内容 6,261
关键字:

gin