1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ## 前言 哈喽,大家好,我是asong。最近在学习Gin框架。在学习的过程 中,一直看英文文档,对于英语渣渣的我来说,很痛苦,就想着给 他翻译过来,弄成中文文档,可以提高我们的学习下效率。网上翻 译过来的文档有很多,不过都很旧了,许多更新也没有处理,不是 很完整。所以我就自己一边学英语、一边翻译了这篇中文文档,现 在分享给你们,希望对你们有用。备注:由于文档是我自己翻译, 有错误欢迎指出。文档已上传个人 github:https://github.com/sunsong2020/Golang_Dream/tree/master/Gin/Doc 无水印版本获取:关注公众号:Golang梦工厂,后台回复Gin,即可获取。 |
备注: 这里只贴出部分重要文档 ,完整版PDF获取请按以上方式进行获取。
2020Gin框架中文文档@[toc]
安装
在安装Gin包之前,你需要在你的电脑上安装Go环境并设置你的工作区。
- 首先需要安装Go(支持版本1.11+),然后使用以下Go命令安装Gin:
1 | $ go get -u github.com/gin-gonic/gin |
- 在你的代码中导入Gin包:
1 | import "github.com/gin-gonic/gin" |
- (可选)如果使用诸如
http.StatusOK 之类的常量,则需要引入net/http 包:
1 | import "net/http" |
快速开始
1 2 | # 假设example.go 文件中包含以下代码 $ cat example.go |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package main import "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") } |
API 例子
您可以在Gin示例的仓库中找到许多现成的示例。
使用 GET, POST, PUT, PATCH, DELETE and OPTIONS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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 端口号 } |
路由参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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") } |
查询字符串参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 表单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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 表单
1 2 3 4 | POST /post?id=1234&page=1 HTTP/1.1 Content-Type: application/x-www-form-urlencoded name=manu&message=this_is_great |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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") } |
运行结果:
1 | id: 1234; page: 1; name: manu; message: this_is_great |
Map 作为查询字符串或 post表单 参数
1 2 3 4 | POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1 Content-Type: application/x-www-form-urlencoded names[first]=thinkerou&names[second]=tianou |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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") } |
运行结果:
1 | ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou] |
上传文件
单个文件
参考 issue #774 与详细的示例代码: example code.
慎用 file.Filename , 参考 Content-Disposition on MDN 和 #1693
上传文件的文件名可以由用户自定义,所以可能包含非法字符串,为了安全起见,应该由服务端统一文件名规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 测试:
1 2 3 | curl -X POST http://localhost:8080/upload \\ -F "file=@/Users/appleboy/test.zip" \\ -H "Content-Type: multipart/form-data" |
多个文件
参考详细示例:example code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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 测试:
1 2 3 4 | curl -X POST http://localhost:8080/upload \\ -F "upload[]=@/Users/appleboy/test1.zip" \\ -F "upload[]=@/Users/appleboy/test2.zip" \\ -H "Content-Type: multipart/form-data" |
路由分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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
使用:
1 | r := gin.New() |
代替
1 2 | // 默认已经连接了 Logger and Recovery 中间件 r := gin.Default() |
使用中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | 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") } |
如何写入日志文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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") } |
自定义日志格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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\"\ ", 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 | ::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进行着色。
没有为日志着色:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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") } |
为日志着色:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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,需要设置为
此外,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 。
你还可以给字段指定特定规则的修饰符,如果一个字段用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | // 绑定为 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") } |
请求示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ curl -v -X POST \\ http://localhost:8080/loginJSON \\ -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"} |
跳过验证:
当使用上面的
# 运行example.go文件并在浏览器上访问0.0.0.0:8080/ping(windows访问:localhost:8080/ping)
$ go run example.go
公众号:Golang梦工厂
Asong是一名Golang开发工程师,专注于Golang相关技术:Golang面试、Beego、Gin、Mysql、Linux、网络、操作系统等,致力于Golang开发。欢迎关注公众号:Golang梦工厂。一起学习,一起进步。
获取文档方式:直接公众号后台回复:Gin,即可获取最新Gin中文文档。作者asong定期维护。
同时文档上传个人github:https://github.com/sunsong2020/Golang_Dream/Gin/Doc,自行下载,能给个Star就更好了!!!