Post

Gin框架常用参数绑定

简介

本文总结使用Golang日常开发Restful API时,常用的参数绑定方法。详情请查看官方文档

GET

使用form标签绑定参数,配合binding标签进行额外的绑定配置。

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
type SearchQuery struct {
	Keyword  string `form:"keyword" binding:"required"` // `form:"..."` 对应 URL 中的 ?keyword=xxx
	Page     int    `form:"page"    binding:"required"`
	PageSize int    `form:"page_size" binding:"required,gte=1,lte=100"`
}

func main() {

	// 创建一个默认的 Gin 引擎
	r := gin.Default()

	r.GET("/search", func(c *gin.Context) {
		var req SearchQuery

		// 方式一:显式调用 c.ShouldBindQuery(&req)
		if err := c.ShouldBindQuery(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		// 方式二:使用 c.ShouldBind(&req) 也行,它会根据请求方法和 Content-Type 先尝试 Query 解析
		// if err := c.ShouldBind(&req); err != nil {
		//     c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		//     return
		// }

		// 如果没有报错,则可以使用 req.Keyword, req.Page, req.PageSize
		c.JSON(http.StatusOK, gin.H{
			"keyword":   req.Keyword,
			"page":      req.Page,
			"page_size": req.PageSize,
		})
	})

	// 启动 HTTP Server
	if err := r.Run(":8080"); err != nil {
		log.Fatal("Failed to run gin server: ", err)
	}
}

POST

使用json标签绑定参数,配合binding标签进行额外的绑定配置。

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
type BindingRequest struct {
	Name   string `json:"name" binding:"required"`               // 字符串:必填
	Age    int    `json:"age"  binding:"required,gte=0,lte=150"` // 数值:必填,范围 0~150
	Active bool   `json:"active"`                                // 布尔:非必填
	Gender string `json:"gender" binding:"required,oneof=male female other"`
	// 枚举值:只能是 male / female / other
}

func main() {

	// 创建一个默认的 Gin 引擎
	r := gin.Default()

	// 常见参数绑定
	r.POST("/binding", func(c *gin.Context) {
		var req BindingRequest

		// 这里用 ShouldBindJSON 为例,实际可根据请求类型选 ShouldBindQuery/ShouldBindForm 等
		if err := c.ShouldBindJSON(&req); err != nil {
			// 参数无效则返回 400
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		// 校验通过,可使用 req.Name、req.Age、req.Active、req.Gender 做后续处理
		c.JSON(http.StatusOK, gin.H{
			"message": "ok",
		})
	})

	// 启动 HTTP Server
	if err := r.Run(":8080"); err != nil {
		log.Fatal("Failed to run gin server: ", err)
	}
}

日期与时间绑定

这里有个大坑,常用标签是:time_format还有time_utc, 官方的示例用法

但是这两个标签只有在form表单的请求才有效,也就是普通GET请求,和application/x-www-form-urlencoded格式的POST请求。

而且,这两个标签对标准的POST请求JSON解析绑定是不生效的!

时至今日,官方也没有解决这个标签行为不一致的问题。详见ISSUE

在日常开发中,如果前端要传递2006-01-02这种格式的字符串日期,并且后端要比较的话(绑定到golangtime类型),只有两种方法:

  • 遵循官方,接口采用GET请求或application/x-www-form-urlencoded格式的POST请求
  • 自己创建一个自定义的time类型,重写UnmarshalJSON方法,略复杂,参考这篇文章

还有一种方案,就是让前端传递标准RFC3339 格式2006-01-02T15:04:05Z07:00),只传递日期,其他值置0,但还要加上时区。这个方案需要前端额外的工作量,但是,只要跑通,也是一劳永逸的。

示例值: 2025-01-13T00:00:00+08:00

unix时间绑定

time_format标签提供了原生对unix时间戳的支持,同样,只支持普通GET请求,和application/x-www-form-urlencoded格式的POST请求。注意,当time_format:"unix"time_utc标签将会失效。因为unix时间戳本身就包含时区信息。

参考代码

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
type DateRequest struct {
	// time_format 指定解析模板, 必须是 2006-01-02 这种 Go 的时间模板写法
	//Birthday time.Time `json:"birthday" binding:"required" time_format:"2006-01-02"`
	Birthday time.Time `form:"birthday" binding:"required" time_format:"2006-01-02" time_utc:"1"`

	UnixTime time.Time `form:"unixTime" time_format:"unix"` // 前端传 1672531200(已包含时区)
}

func main() {

	// 创建一个默认的 Gin 引擎
	r := gin.Default()

	// 日期与时间绑定
	r.POST("/date", dateBinding())
	r.GET("/date", dateBinding())

	// 启动 HTTP Server
	if err := r.Run(":8080"); err != nil {
		log.Fatal("Failed to run gin server: ", err)
	}
}

func dateBinding() func(c *gin.Context) {
	return func(c *gin.Context) {
		var req DateRequest
		if err := c.ShouldBind(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		// 这里的 req.Birthday 默认会以附加上系统本地时区,如果想设置为UTC,请使用time_utc:"1"标签
		c.JSON(http.StatusOK, req)
	}
}
This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.