七叶笔记 » golang编程 » 2020年最新Gin框架中文文档

2020年最新Gin框架中文文档

最近在学习Gin框架。在学习的过程中,一直看英文文档,对于英语渣渣的我来说,很痛苦,就想着给他翻译过来,弄成中文文档,可以提高我们的学习下效率。网上翻译过来的文档有很多,不过都很久了,许多更新也没有处理,不是很完整。所以我就自己一边学英语、一边翻译了这篇中文文档,现在分享给你们,希望对你们有用。

备注:由于文档是我自己翻译,有错误欢迎指出。

文档已上传个人github:

无水印版本获取:关注公众号:Golang梦工厂,后台回复Gin,即可获取。

这里只贴部分文档,完整版按以上方式获取。

Gin Web 框架

Gin是用Go(Golang)编写的Web框架。它是一个类似于martini但拥有更好性能的API框架,由于httprouter,速度提高了40倍。如果您追求性能和高效的效率,您将会爱上Gin。

安装

在安装Gin包之前,你需要在你的电脑上安装Go环境并设置你的工作区。

  1. 首先需要安装Go(支持版本1.11+),然后使用以下Go命令安装Gin:
 $ go get -u github.com/gin-gonic/gin  
  1. 在你的代码中导入Gin包:
 import "github.com/gin-gonic/gin"  
  1. (可选)如果使用诸如http.StatusOK之类的常量,则需要引入net/http包:
 import "net/http"  

快速开始

 # 假设example.go 文件中包含以下代码
$ cat example.go  
 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 (for windows "localhost:8080")
}  
 # 运行example.go文件并在浏览器上访问0.0.0.0:8080/ping(windows访问:localhost:8080/ping)
$ go run example.go  

API 例子

您可以在Gin示例的仓库中找到许多现成的示例。

使用 GET, POST, PUT, PATCH, DELETE and OPTIONS

 func main() {
    //使用默认中间件(logger 和 recovery 中间件)创建 gin 路由
    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)
​
    // 默认在 8080 端口启动服务,除非定义了一个 PORT 的环境变量。.
    router.Run()
    //  router.Run(":3000") hardcode 端口号
}  

路由参数

 func main() {
    router := gin.Default()
​
    // 这个handler 将会匹配 /user/john 但不会匹配 /user/ 或者 /user
    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })
​
    // 但是, 这个将匹配 /user/john/ 以及 /user/john/send
    // 如果没有其他路由器匹配 /user/john, 它将重定向到 /user/john/
    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)
    })
​
    // 对于每个匹配的请求,上下文将保留路由定义
    router.POST("/user/:name/*action", func(c *gin.Context) {
        c.FullPath() == "/user/:name/*action" // true
    })
​
    router.Run(":8080")
}  

查询字符串参数

 func main() {
    router := gin.Default()
​
    // 查询字符串参数使用现有的底层 request 对象解析
    // 请求响应匹配的 URL:  /welcome?firstname=Jane&lastname=Doe
    router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname") // 这个是 c.Request.URL.Query().Get("lastname") 快捷写法
​
        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
    router.Run(":8080")
}  

Multipart/Urlencoded 表单

 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")
}  

其他示例:query+post 表单

 POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
​
name=manu&message=this_is_great
​  
 func main() {
    router := gin.Default()
​
    router.POST("/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", id, page, name, message)
    })
    router.Run(":8080")
}  

运行结果:

 id: 1234; page: 1; name: manu; message: this_is_great  

Map 作为查询字符串或 post表单 参数

 POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
Content-Type: application/x-www-form-urlencoded
​
names[first]=thinkerou&names[second]=tianou  
 func main() {
    router := gin.Default()
​
    router.POST("/post", func(c *gin.Context) {
​
        ids := c.QueryMap("ids")
        names := c.PostFormMap("names")
​
        fmt.Printf("ids: %v; names: %v", ids, names)
    })
    router.Run(":8080")
}  

运行结果:

 ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]  

上传文件

单个文件

参考 issue #774 与详细的示例代码: example code.

慎用 file.Filename , 参考 Content-Disposition on MDN 和 #1693

上传文件的文件名可以由用户自定义,所以可能包含非法字符串,为了安全起见,应该由服务端统一文件名规则。

 func main() {
    router := gin.Default()
    // 给表单限制上传大小 (默认是 32 MiB)
    router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // single file
        file, _ := c.FormFile("file")
        log.Println(file.Filename)
​
        // 上传文件到指定的路径
        c.SaveUploadedFile(file, dst)
​
        c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
    })
    router.Run(":8080")
}  

curl 测试:

 curl -X POST  \
  -F "file=@/Users/appleboy/test.zip" \
  -H "Content-Type: multipart/form-data"  

多个文件

参考详细示例:example code.

 func main() {
    router := gin.Default()
    // 给表单限制上传大小 (default is 32 MiB)
    router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // 多文件
        form, _ := c.MultipartForm()
        files := form.File["upload[]"]
​
        for _, file := range files {
            log.Println(file.Filename)
​
            //上传文件到指定的路径
            c.SaveUploadedFile(file, dst)
        }
        c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
    })
    router.Run(":8080")
}  

curl 测试:

 curl -X POST  \
  -F "upload[]=@/Users/appleboy/test1.zip" \
  -F "upload[]=@/Users/appleboy/test2.zip" \
  -H "Content-Type: multipart/form-data"  

路由分组

 func main() {
    router := gin.Default()
​
    // Simple group: v1
    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }
​
    // Simple group: v2
    v2 := router.Group("/v2")
    {
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
    }
​
    router.Run(":8080")
}  

默认的没有中间件的空白 Gin

使用:

 r := gin.New()  

代替

 // 默认已经连接了 Logger and Recovery 中间件
r := gin.Default()  

使用中间件

 func main() {
    // 创建一个默认的没有任何中间件的路由
    r := gin.New()
​
    // 全局中间件
    // Logger 中间件将写日志到 gin.DefaultWriter 即使你设置 GIN_MODE=release.
    // 默认设置 gin.DefaultWriter = os.Stdout
    r.Use(gin.Logger())
​
    // Recovery 中间件从任何 panic 恢复,如果出现 panic,它会写一个 500 错误。
    r.Use(gin.Recovery())
​
    // 对于每个路由中间件,您可以根据需要添加任意数量
    r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
​
    // 授权组
    // authorized := r.Group("/", AuthRequired())
    // 也可以这样
    authorized := r.Group("/")
    // 每个组的中间件! 在这个实例中,我们只需要在 "authorized" 组中
    // 使用自定义创建的 AuthRequired() 中间件
    authorized.Use(AuthRequired())
    {
        authorized.POST("/login", loginEndpoint)
        authorized.POST("/submit", submitEndpoint)
        authorized.POST("/read", readEndpoint)
​
        // 嵌套组
        testing := authorized.Group("testing")
        testing.GET("/analytics", analyticsEndpoint)
    }
​
    // 监听并服务于 0.0.0.0:8080
    r.Run(":8080")
}  

如何写入日志文件

 func main() {
    // 禁用控制台颜色,当你将日志写入到文件的时候,你不需要控制台颜色
    gin.DisableConsoleColor()
​
    // 写入日志文件
    f, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(f)
​
    // 如果你需要同时写入日志文件和控制台上显示,使用下面代码
    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
​
    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
​
    router.Run(":8080")
}  

自定义日志格式

 func main() {
    router := gin.New()
​
    // LoggerWithFormatter 中间件会将日志写入 gin.DefaultWriter
    // 默认 gin.DefaultWriter = os.Stdout
    router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
​
        // 你的自定义格式
        return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
                param.ClientIP,
                param.TimeStamp.Format(time.RFC1123),
                param.Method,
                param.Path,
                param.Request.Proto,
                param.StatusCode,
                param.Latency,
                param.Request.UserAgent(),
                param.ErrorMessage,
        )
    }))
    router.Use(gin.Recovery())
​
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
​
    router.Run(":8080")
}  

样本输出:

 ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "  

控制日志输出颜色(Controlling Log output coloring)

默认,控制台上输出的日志应根据检测到的TTY进行着色。

没有为日志着色:

 func main() {
    // 禁用日志的颜色
    gin.DisableConsoleColor()
    
    // 使用默认中间件创建一个 gin路由:
    // logger 与 recovery (crash-free) 中间件
    router := gin.Default()
    
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
    
    router.Run(":8080")
}  

为日志着色:

 func main() {
    //记录日志的颜色
    gin.ForceConsoleColor()
    
    // 使用默认中间件创建一个 gin路由:
    // logger 与 recovery (crash-free) 中间件
    router := gin.Default()
    
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
    
    router.Run(":8080")
}  

模型绑定和验证

若要将请求主体绑定到结构体中,请使用模型绑定,目前支持JSON、XML、YAML和标准表单值(foo=bar&boo=baz)的绑定。

Gin 使用go-playground/validator.v8验证参数,点击此处查看完整文档here

需要在绑定的字段上设置tag,比如,绑定格式为json,需要设置为 json:”fieldname”

此外,Gin提供了两种绑定方法:

  • 类型 – Must bind方法 – Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader行为 – 这些方法底层使用MustBindWith,如果存在绑定错误,请求将被以下指令中止 c.AbortWithError(400, err).SetType(ErrorTypeBind),响应状态代码会被设置为400,请求头Content-Type被设置为text/plain; charset=utf-8。注意,如果你试图在此之后设置响应代码,将会发出一个警告 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422,如果你希望更好地控制行为,请使用ShouldBind相关的方法。
  • 类型 – Should bind方法 – ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindHeader。行为 – 这些方法底层使用 ShouldBindWith,如果存在绑定错误,则返回错误,开发人员可以正确处理请求和错误。当我们使用绑定方法时,Gin会根据Content-Type推断出使用哪种绑定器,如果你确定你绑定的是什么,你可以使用MustBindWith或者BindingWith。

你还可以给字段指定特定规则的修饰符,如果一个字段用binding:”required”修饰,并且在绑定时该字段的值为空,那么将返回一个错误。

 // 绑定为 JSON
type Login struct {
    User     string `form:"user" json:"user" xml:"user"  binding:"required"`
    Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
​
func main() {
    router := gin.Default()
​
    // JSON 绑定示例 ({"user": "manu", "password": "123"})
    router.POST("/loginJSON", func(c *gin.Context) {
        var json Login
        if err := c.ShouldBindJSON(&json); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        
        if json.User != "manu" || json.Password != "123" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        } 
        
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    })
​
    // XML 绑定示例 (
    //  <?xml version="1.0" encoding="UTF-8"?>
    //  <root>
    //      <user>user</user>
    //      <password>123</password>
    //  </root>)
    router.POST("/loginXML", func(c *gin.Context) {
        var xml Login
        if err := c.ShouldBindXML(&xml); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        
        if xml.User != "manu" || xml.Password != "123" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        } 
        
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    })
​
    // 绑定HTML表单的示例 (user=manu&password=123)
    router.POST("/loginForm", func(c *gin.Context) {
        var form Login
        //这个将通过 content-type 头去推断绑定器使用哪个依赖。
        if err := c.ShouldBind(&form); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        
        if form.User != "manu" || form.Password != "123" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        } 
        
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    })
​
    // 监听并服务于 0.0.0.0:8080
    router.Run(":8080")
}  

请求示例:

 $ curl -v -X POST \
   \
  -H 'content-type: application/json' \
  -d '{ "user": "manu" }'
> POST /loginJSON HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> content-type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Date: Fri, 04 Aug 2017 03:51:31 GMT
< Content-Length: 100
<
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}  

跳过验证:

当使用上面的curl命令运行上面的示例时,返回错误,因为示例中Password字段使用了binding:”required”,如果我们使用binding:”-“,那么它就不会报错。

自定义验证器

也可以注册自定义验证器。请参阅示例代码:example code

 package main
​
import (
    "net/http"
    "time"
​
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "gopkg.in/go-playground/validator.v10"
)
​
// 预订包含绑定和验证的数据
type Booking struct {
    CheckIn  time.Time `form:"check_in" binding:"required" time_format:"2006-01-02"`
    CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
​
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
    date, ok := fl.Field().Interface().(time.Time)
    if ok {
        today := time.Now()
        if today.After(date) {
            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✓_out=2018-04-17"
{"message":"Booking dates are valid!"}
​
$ curl "localhost:8085/bookable?check_in=2018-03-10✓_out=2018-03-09"
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}  

Struct level validations 也可以以这种方式被注册。请参阅struct-lvl-validation示例以了解更多信息。

总结

翻译这篇中文文档,我收获很大,把所有的例子也都自己实现了一遍,对Gin框架的了解又更深了一点。Gin当前是Go语言环境下最受欢迎的WEB框架。推荐你们学习WEB框架直接上手Gin。

最近在忙着毕业的事情,没有怎么更新文章,马上就忙完了,打个预告,下一篇准备更新一篇爬虫文章,敬请期待呦!!!

相关文章