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环境并设置你的工作区。

  1. 首先需要安装Go(支持版本1.11+),然后使用以下Go命令安装Gin:
1
$ go get -u github.com/gin-gonic/gin
  1. 在你的代码中导入Gin包:
1
import "github.com/gin-gonic/gin"
  1. (可选)如果使用诸如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,需要设置为 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"修饰,并且在绑定时该字段的值为空,那么将返回一个错误。

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

跳过验证:

当使用上面的curl命令运行上面的示例时,返回错误,因为示例中Password字段使用了binding:"required",如果我们使用binding:"-",那么它就不会报错。
# 运行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就更好了!!!

公众号图片