概述

本文摘取网上文章汇总。Gin 是一个 golang 的微框架,封装比较优雅,相对于 echo,Gin 更适用于企业开发。

一 Gin 安装

1
go get -v -u github.com/gin-gonic/gin

二 路由使用

1. 默认中间件使用

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

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

func main() {
// gin.Default() 创建默认路由
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // 在 0.0.0.0:8080 上监听并服务
}

2. 无中间件的路由

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
// customer.go
package main

import (
"os"

"github.com/gin-gonic/gin"
"github.com/gogap/logrus"
)

func init() {
logrus.SetOutput(os.Stdout)
logrus.SetLevel(logrus.DebugLevel)
logrus.Debug("logrus init")
}

func middleware1(params string) gin.HandlerFunc {
return func(c *gin.Context) {
logrus.Debugf("middleware1:%s", params)
c.Next()
}
}

func middleware2(params string) gin.HandlerFunc {
return func(c *gin.Context) {
logrus.Debugf("middleware1:%s", params)
c.Next()
}
}

func pingEndPoint(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
}

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.Use(middleware1("comm"))

// 每个路由的中间件, 能添加任意数量的中间件
r.GET("/ping", middleware2("ping"), pingEndPoint)
r.GET("/ping1", pingEndPoint)

// 监听并服务于 0.0.0.0:8080
r.Run(":8080")
}

3. 动态路由

有时候我们需要动态的路由,如 /user/:id,通过调用的 url 来传入不同的 id .在 Gin 中很容易处理这种路由:

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
package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

func main() {
router := gin.Default()

// 注册一个动态路由
// 可以匹配 /user/joy
// 不能匹配 /user 和 /user/
router.GET("/user/:name", func(c *gin.Context) {
// 使用 c.Param(key) 获取 url 参数
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.Run(":8080")
}

4. 路由组

一些情况下,我们会有统一前缀的 url 的需求,典型的如 Api 接口版本号 /v1/something。Gin 可以使用 Group 方法统一归类到路由组中:

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
package main

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

func newEndPoint(name string) gin.HandlerFunc {
return func(c *gin.Context) {
c.String(200, "request[%s] ok!", name)
}
}

func main() {

loginEndpoint := newEndPoint("login")
submitEndpoint := newEndPoint("submit")
readEndpoint := newEndPoint("read")

router := gin.Default()

// 定义一个组前缀
// /v1/login 就会匹配到这个组
v1 := router.Group("/v1")
{
v1.GET("/login", loginEndpoint)
v1.GET("/submit", submitEndpoint)
v1.GET("/read", readEndpoint)
}

// 定义一个组前缀
// 不用花括号包起来也是可以的。上面那种只是看起来会统一一点。看你个人喜好
v2 := router.Group("/v2")
v2.GET("/login", loginEndpoint)
v2.GET("/submit", submitEndpoint)
v2.GET("/read", readEndpoint)

router.Run(":8080")
}

三 查询参数

1. URL 查询参数

假定一个 url 为 /welcome?firstname=Jane&lastname=Doe,我们想获取参数 firstname 的内容,可以使用c.Query方法。该方法始终返回一个 string 类型的数据。

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
package main

import (
"net/http"

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

func main() {
router := gin.Default()

// 注册路由和Handler
// url 为 /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
// 获取参数内容
// 获取的所有参数内容的类型都是 string
// 如果不存在,使用第二个当做默认内容
firstname := c.DefaultQuery("firstname", "Guest")
// 获取参数内容,没有则返回空字符串
lastname := c.Query("lastname")

c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}

2. 其他格式的数据

一些复杂的场景下,如用户直接 POST一段 json字符串到应用中,我们需要获取原始数据,这时需要用 c.GetRawData来获取原始字节。

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
package main

import (
"os"

"github.com/gin-gonic/gin"
"github.com/gogap/logrus"
)

func init() {
logrus.SetOutput(os.Stdout)
logrus.SetLevel(logrus.DebugLevel)
logrus.Debug("logrus init")
}

func main() {
router := gin.Default()

router.POST("/post", func(c *gin.Context) {
// 获取原始字节
d, err := c.GetRawData()
if err != nil {
logrus.Fatalln(err)
}
logrus.Println(string(d))
c.String(200, "ok")
})
router.Run(":8080")
}

测试:

1
curl -v -X POST http://localhost:8080/post -H 'content-type: application/json' -d '{ "name": "joy","address":"beijing" }'

四 数据绑定

Gin 提供了非常方便的数据绑定功能,可以将用户传来的参数自动跟我们定义的结构体绑定在一起。

1. 只绑定 URL 查询参数

使用 c.ShouldBindQuery方法,可以自动绑定 Url 查询参数到 struct

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
package main

import (
"os"

"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
)

// 定义一个 Person 结构体,用来绑定 url query
type Person struct {
Name string `form:"name"` // 使用成员变量标签定义对应的参数名
Address string `form:"address"`
}

func init() {
logrus.SetOutput(os.Stdout)
logrus.SetLevel(logrus.DebugLevel)
logrus.Debug("logrus init")
}

func main() {
route := gin.Default()
// 任何 HTTP 方法
route.Any("/bind", startPage)
route.Run(":8080")
}

func startPage(c *gin.Context) {
var person Person
// 将 url 查询参数和person绑定在一起
if c.ShouldBindQuery(&person) == nil {
logrus.Info("====== Only Bind By Query String ======")
logrus.Info(person.Name)
logrus.Info(person.Address)
}
c.String(200, "Success")
}

测试

1
curl -v -X POST "http://localhost:8080/bind?name=joy&address=beijing" 

2. 绑定 URL 查询参数和 POST 参数

使用 c.ShouldBind方法,可以将参数自动绑定到 struct.该方法是会检查 Url 查询字符串和 POST 的数据,而且会根据 content-type类型,优先匹配JSON或者 XML,之后才是 Form. 有关详情查阅 这里

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
package main

import (
"log"
"time"

"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
)

// 定义一个 Person 结构体,用来绑定数据
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()
// middleware 与 endpoint 处理函数是等价的,只是 endpoint 不需要 c.Next() 调用
route.Any("/bind", startPage, lastMiddleware)
route.Run(":8080")
}

func lastMiddleware(c *gin.Context) {
logrus.Info("last middleware")
}

func startPage(c *gin.Context) {
var person Person
// 绑定到 person
if c.ShouldBind(&person) == nil {
log.Println(person.Name)
log.Println(person.Address)
log.Println(person.Birthday)
}

c.String(200, "Success")
c.Next()
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 此时获取 body 中内容进行绑定
curl -v -X POST "http://localhost:8080/bind?name=joy&address=beijing" -H 'content-type: application/json' -d '{ "name": "joy1","address":"beijing1" }'

# 获取 url 参数内容进行绑定
curl -v -X GET "http://localhost:8080/bind?name=joy&address=beijing"

# 能获取到 url 参数内容并进行绑定
curl -v -X POST "http://localhost:8080/bind?name=joy&address=beijing"

# 能获取到 url 参数内容并进行绑定
curl -v -X POST "http://localhost:8080/bind?name=joy&address=beijing" -d ''

# 无法获取到 url 参数自动进行绑定
curl -v -X POST "http://localhost:8080/bind?name=joy&address=beijing" -H 'content-type: application/json'
curl -v -X POST "http://localhost:8080/bind?name=joy&address=beijing" -H 'content-type: application/json' -d ''

五 生产环境运行

变量更改 Gin 运行模式为发布模式

设置环境变量: export GIN_MODE=release
在代码中设置: gin.SetMode(gin.ReleaseMode)

链接