全书目录

第三十二篇:Gin 完全指南 —— 高速交警中枢宇宙

第九章:Binding 与 Validation —— 证件扫描仪和执法规则库

2 分钟 532 字 第 600 / 962 个阅读单元

这是 Gin 最常用、也最容易被误用的一块。

你可以把请求体绑定理解成:

  • Binding:把司机递来的纸质材料扫进系统
  • Validation:按交规检查材料是否合规
#1. 最常见的 JSON 绑定
go
type CreateTicketRequest struct {
    Plate       string `json:"plate" binding:"required"`
    Speed       int    `json:"speed" binding:"required"`
    SpeedLimit  int    `json:"speed_limit" binding:"required"`
    OfficerName string `json:"officer_name" binding:"required"`
}

func createTicket(c *gin.Context) {
    var req CreateTicketRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "处罚单信息不完整或格式错误",
            "detail": err.Error(),
        })
        return
    }

    c.JSON(http.StatusCreated, gin.H{
        "message": "处罚单已创建",
        "data":    req,
    })
}
#2. Bind*ShouldBind* 的区别,一定要吃透

这是 Gin 初学者的高频坑。

text
Bind / BindJSON / BindQuery ...
= 一旦绑定失败,Gin 直接帮你中断并写 400

ShouldBind / ShouldBindJSON / ShouldBindQuery ...
= 失败时把 error 返回给你,你自己决定怎么回

官方长期建议你真正写业务时优先理解并掌握 ShouldBind* 这套,因为它给你更完整的控制权。

#3. 为什么很多项目更偏爱 ShouldBind*

因为实际项目里,错误响应往往不是“裸 400 文本”这么简单,而是:

  • 统一 JSON 结构
  • 携带错误码
  • 携带 trace id
  • 对不同字段错误做定制提示

这时你就不想让框架先替你把头写死。

#4. Query、Path 也能绑定

Gin 不只会绑 JSON。

go
type TicketURI struct {
    ID string `uri:"id" binding:"required"`
}

func getTicket(c *gin.Context) {
    var uri TicketURI
    if err := c.ShouldBindUri(&uri); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"ticket_id": uri.ID})
}
#5. 一个非常实用的误区提醒

请求体默认是流。

也就是说,正常 ShouldBind 这类方法会消费 c.Request.Body
如果你先绑一次,再绑第二次,第二次大概率就 EOF 了。

text
第一次扫描证件
└── 原件已经被系统读走

第二次还想拿原件重扫
└── 发现已经没有了

如果你真要对同一个 body 尝试多次绑定,要用专门的 ShouldBindBodyWith 方案,而且它会带来额外开销。
所以一般原则是:

一个请求体,尽量只绑定一次。