Chapter 03

请求处理与绑定

把 JSON、查询串、URI、表单一键映射到 Go 结构体,并用 binding tag 完成声明式校验,处理文件上传。

3.1 绑定相关名词

绑定(Binding)
把 HTTP 请求中的数据(JSON body、query、form、uri、header)反序列化并填充到 Go 结构体的过程。
struct tag
结构体字段后的反引号元数据,如 `json:"name" binding:"required"`,告诉绑定器字段名与校验规则。
ShouldBind 系列
绑定失败返回 error 由你处理(不自动响应),推荐使用:ShouldBindJSONShouldBindQueryShouldBindUri 等。
MustBind / Bind 系列
绑定失败自动写 400 并终止,灵活性低,一般不用。
validator.v10
Gin 内置的校验引擎(go-playground/validator),通过 binding tag 声明 required/email/min/max 等规则。
Content-Type 推断
ShouldBind 会根据请求头自动选 JSON/XML/Form 绑定器;显式方法(ShouldBindJSON)则强制格式。
multipart/form-data
用于文件上传的表单编码,Gin 用 c.FormFile / c.MultipartForm 读取。

3.2 绑定 JSON Body

type CreateUserReq struct {
    Name  string `json:"name" binding:"required,min=2,max=32"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"gte=0,lte=150"`
}

func createUser(c *gin.Context) {
    var req CreateUserReq
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    // req 已经过校验,可以安全使用
    c.JSON(201, gin.H{"created": req.Name})
}
$ curl -X POST localhost:8080/users \
    -H "Content-Type: application/json" \
    -d '{"name":"Al","email":"bad","age":20}'
# {"error":"Key: 'CreateUserReq.Email' Error:Field validation for 'Email' failed on the 'email' tag"}

3.3 绑定 Query 与 Uri

// 分页查询:?page=2&size=20&sort=created_at
type PageQuery struct {
    Page int    `form:"page,default=1" binding:"gte=1"`
    Size int    `form:"size,default=10" binding:"gte=1,lte=100"`
    Sort string `form:"sort"`
}

func list(c *gin.Context) {
    var q PageQuery
    if err := c.ShouldBindQuery(&q); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    c.JSON(200, q)
}

// 绑定路径参数:/users/:id
type UserUri struct {
    ID uint `uri:"id" binding:"required"`
}
func getUser(c *gin.Context) {
    var u UserUri
    if err := c.ShouldBindUri(&u); err != nil {
        c.JSON(400, gin.H{"error": "invalid id"})
        return
    }
    c.JSON(200, gin.H{"id": u.ID})
}
tag 对应关系

JSON body 用 json:;query/form 用 form:;路径参数用 uri:;请求头用 header:。同一个结构体可以混用多个 tag 供不同绑定方法复用。

3.4 常用 binding 校验规则

规则含义示例
required非零值必填binding:"required"
email合法邮箱binding:"email"
min/max字符串长度或数值范围binding:"min=6,max=20"
gte/lte大于等于 / 小于等于binding:"gte=0,lte=150"
oneof枚举值之一binding:"oneof=male female"
len固定长度binding:"len=11"
eqfield与另一字段相等binding:"eqfield=Password"
url/uuid合法 URL / UUIDbinding:"url"

3.5 表单与文件上传

// 普通表单字段 + 单文件
func upload(c *gin.Context) {
    name := c.PostForm("name")              // 表单字段
    file, err := c.FormFile("file")         // *multipart.FileHeader
    if err != nil {
        c.JSON(400, gin.H{"error": "file required"})
        return
    }
    // 保存到磁盘
    dst := "./uploads/" + file.Filename
    if err := c.SaveUploadedFile(file, dst); err != nil {
        c.JSON(500, gin.H{"error": "save failed"})
        return
    }
    c.JSON(200, gin.H{"name": name, "size": file.Size})
}

// 多文件
func uploadMulti(c *gin.Context) {
    form, _ := c.MultipartForm()
    files := form.File["files"]
    for _, f := range files {
        c.SaveUploadedFile(f, "./uploads/"+f.Filename)
    }
    c.JSON(200, gin.H{"count": len(files)})
}
限制上传体积

默认内存缓冲 32 MiB。调大或调小用 r.MaxMultipartMemory = 8 << 20(8 MiB)。超大文件应流式落盘,切勿全部读进内存。生产还要校验扩展名/MIME、随机化文件名防路径穿越。

3.6 自定义验证器

validator 支持注册自定义规则。例如校验手机号:

import (
    "regexp"
    "github.com/gin-gonic/gin/binding"
    "github.com/go-playground/validator/v10"
)

var phoneRe = regexp.MustCompile(`^1[3-9]\d{9}$`)

var mobile validator.Func = func(fl validator.FieldLevel) bool {
    return phoneRe.MatchString(fl.Field().String())
}

func initValidator() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("mobile", mobile)
    }
}

// 使用:
type Req struct {
    Phone string `json:"phone" binding:"required,mobile"`
}

3.7 绑定策略小结