Gin 快速上手

使用 Go 搭建 Web 后端,最简单的就是使用原生的 net/http 库了,下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"net/http"
)

func greet(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World!")
}

func main() {
http.HandleFunc("/", greet)
http.ListenAndServe(":8080", nil)
}

运行代码之后,便会在8080端口处监听,接收客户端的请求,然后返回响应,访问 http://localhost:8080 即可以访问到对应的内容。

对于简单的服务器,使用标准库提供的函数就已经可以解决了,如果更加复杂一点,使用标准库处理就显得力不从心了,这时候我们可以使用网上提供的框架,比如 Gin

Gin 使用起来特别方便(真的很方便!),并且支持中间件,异常处理,路由组等等,下面用代码演示一下如何使用吧。

快速入门

首先我们需要下载gin,我们要保证设置了 GO111MODULE=on,然后进行下载

1
2
go mod init gin-demo
go get -u github.com/gin-gonic/gin

下面是一个简单的web服务,我们使用 gin.Default() 创建一个默认的路由引擎,然后使用 GET 方式注册一个路由,负责接受来自客户端的 GET 请求,并且返回一个 JSON 数据给客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "github.com/gin-gonic/gin"

func main() {
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

使用curl进行测试结果如下,符合预期

1
2
$ curl http://localhost:8080/ping 
{"message":"pong"}

使用HTTP方法

1
2
3
4
5
6
7
8
9
10
// 使用默认中间件(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)

使用路由组

使用 Group 方法进行创建,路由组可以使得路由结构更加清晰

1
2
3
4
5
6
7
8
9
router := gin.Default()

// 简单的路由组: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}

返回内容

返回JSON内容

gin.Context 提供了 JSON 方法, 我们可以直接使用, 第一个参数为返回的状态码,第二个参数为返回的数据,gin.H 其实就是 map[string]interface{} 的简写,最后会被序列化返回给客户端。

1
2
3
4
5
6
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})

返回HTML内容

首先需要加载 HTML 模板文件,然后使用 c.HTML 方法将数据注入到指定的模板文件中

1
2
3
4
5
6
7
router := gin.Default()
router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "Posts",
})
})

模板文件 posts/index.tmpl

1
2
3
4
5
6
7
{{ define "posts/index.tmpl" }}
<html><h1>
{{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}

返回字符串

String方法即可

1
2
3
router.GET("String", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World")
})

获取查询参数

对于简单的 query 参数,使用 GetQueryDefaultQuery 方法就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
router.GET("/query", func(c *gin.Context) {
q, exist := c.GetQuery("q")
if exist {
c.JSON(200, gin.H{
"data": q,
})
} else {
c.JSON(200, gin.H{
"code": 400,
"message": "q is not exist",
})
}
})
1
2
3
4
5
$ curl http://localhost:8080/query
{"code":400,"message":"q is not exist"}

$ curl http://localhost:8080/query?q=hello
{"data":"hello"}

不过如果需要获取到的 query 参数是数组或者 map,那么就需要使用其他的方式了,比如数组需要使用GetQueryArray or QueryArrayGetQueryMap or QueryMap

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
r.GET("/query_array", func(c *gin.Context) {
q, exist := c.GetQueryArray("q")
if exist {
c.JSON(200, gin.H{
"data": q,
})
} else {
c.JSON(200, gin.H{
"code": 400,
"message": "q is not exist",
})
}
})

r.GET("/query_map", func(c *gin.Context) {
q, exist := c.GetQueryMap("q")
if exist {
c.JSON(200, gin.H{
"data": q,
})
} else {
c.JSON(200, gin.H{
"code": 400,
"message": "q is not exist",
})
}
})
1
2
3
4
5
$ curl "http://localhost:8080/query_array?q=1&q=2"
{"data":["1","2"]}

$ curl "http://localhost:8080/query_map?q[1]=1&q[2]=2"
{"data":{"1":"1","2":"2"}}

获取表单内容

1
2
3
4
5
6
7
8
9
10
router.POST("/form", func(c *gin.Context) {
name := c.PostForm("name") // or GetPostForm
list := c.PostFormArray("list") // or GetPostFormArray
m := c.PostFormMap("map") // or GetPostFormMap
c.JSON(http.StatusOK, gin.H{
"name": name,
"list": list,
"map": m,
})
})
1
2
$ curl  --form name=test --form list=1 --form list=2 --form map[1]=1 --form map[2]=2 http://localhost:8080/form
{"list":["1","2"],"map":{"1":"1","2":"2"},"name":"test"}

文件上传

单文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
r.POST("/upload/single", func(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
fmt.Println(file.Filename)

// 上传文件至指定目录
c.SaveUploadedFile(file, dst)

c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "upload success",
})
})

多文件

1
2
3
4
5
6
7
8
9
10
11
router.POST("/upload/multiple", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["upload[]"]

for _, file := range files {
log.Println(file.Filename)

// 上传文件至指定目录
c.SaveUploadedFile(file, dst)
}

使用中间件

中间件可以对经过一个路由的请求进行预处理,比如说鉴权,记录日志等等, gin 中创建一个中间件十分简单,我们只需要返回一个 gin.Handler 的类型就好了。下面就创建了一个简单的中间件来计算路由花费的时间

1
2
3
4
5
6
7
8
func Cost() gin.HandlerFunc{
return func(c *gin.Context) {
start := time.Now()
// 一定要调用
c.Next()
fmt.Println("total time: ", time.Now().Sub(start))
}
}

使用中间件有多种方式,一种是全局中间件,一种是路由组中间件,一种单个路由中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
router := gin.New() // 没有使用任何中间件的路由引擎
// 整个路由
router.Use(Cost())

// 路由组
group := r.Group("/api")
group.Use(Cost())
{
group.GET("/greet", greet)
group.POST("/form", form)
}
// 单个路由,不会影响其他路由
router.POST("/middleware", Cost(), controller)

绑定参数至结构体

在业务中,我们经常需要将用户发送过来的请求绑定到一个特定的结构体中,这样使用起来更加方便,gin 提供了大量的绑定方法,下面以 ShouldBind 为例讲解如何使用:

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
// 首先声明需要绑定参数的结构体
type LoginReq struct {
// form 参数指定需要绑定的表单内容
// binding:"required" 表示不能非空,否则绑定的时候返回错误
// 如果该字段非必须,可以去掉该tag
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}

r.POST("/login", func(c *gin.Context) {
var form LoginReq

if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusOK, gin.H{
"code": -1,
"message": err.Error(),
})
return
}
fmt.Printf("%#v\n", form)
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": form,
})
})

gin 还有很多方面的特性,这里只是简单的介绍了如何去使用,更多的使用方法可以参考文档,个人之前也用过写了一个小项目


生活杂笔,学习杂记,偶尔随便写写东西。

作者

Edgar

发布于

2021-07-22

更新于

2021-12-21

许可协议

评论