跳到主要内容

参数绑定与验证

问题

Go Web 框架中如何进行请求参数绑定和验证?

答案

Gin 绑定方式

// 绑定 JSON Body
type CreateOrderReq struct {
ProductID int `json:"product_id" binding:"required"`
Quantity int `json:"quantity" binding:"required,min=1,max=100"`
Address string `json:"address" binding:"required"`
}

func createOrder(c *gin.Context) {
var req CreateOrderReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
}

// 绑定 Query 参数:/users?page=1&size=10
type ListReq struct {
Page int `form:"page" binding:"gte=1"`
Size int `form:"size" binding:"gte=1,lte=100"`
Name string `form:"name"`
}

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

// 绑定路由参数
type UserURI struct {
ID int `uri:"id" binding:"required"`
}

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

// 绑定 Header
type AuthHeader struct {
Token string `header:"Authorization" binding:"required"`
}

常用 Validator Tag

Tag示例说明
requiredbinding:"required"必填
min / maxbinding:"min=1,max=100"数值范围 / 字符串长度
lenbinding:"len=11"精确长度
emailbinding:"email"邮箱格式
oneofbinding:"oneof=male female"枚举
gt / gte / lt / ltebinding:"gte=0"比较
divebinding:"dive,required"验证切片每个元素
urlbinding:"url"URL 格式

自定义验证器

import "github.com/go-playground/validator/v10"

// 自定义验证函数
func validPhone(fl validator.FieldLevel) bool {
phone := fl.Field().String()
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, phone)
return matched
}

// 注册到 Gin
func init() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("phone", validPhone)
}
}

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

自定义错误信息

默认错误信息对用户不友好,需要翻译:

func translateError(err error) map[string]string {
errs := make(map[string]string)
var ve validator.ValidationErrors
if errors.As(err, &ve) {
for _, e := range ve {
field := e.Field()
switch e.Tag() {
case "required":
errs[field] = field + " 不能为空"
case "email":
errs[field] = "邮箱格式不正确"
case "min":
errs[field] = field + " 不能小于 " + e.Param()
default:
errs[field] = field + " 验证失败"
}
}
}
return errs
}

func createUser(c *gin.Context) {
var req CreateUserReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"errors": translateError(err)})
return
}
}

常见面试问题

Q1: ShouldBindBind 的区别?

答案

方法绑定失败行为
ShouldBindJSON返回 error,由开发者处理
BindJSON自动返回 400,调用 c.AbortWithError

推荐用 ShouldBind 系列,灵活控制错误响应格式。

Q2: 如何验证嵌套结构体?

答案

type Address struct {
City string `json:"city" binding:"required"`
Street string `json:"street" binding:"required"`
}

type CreateUserReq struct {
Name string `json:"name" binding:"required"`
Address Address `json:"address" binding:"required"` // 自动递归验证
}

validator 会自动递归验证嵌套结构体。切片元素用 dive tag:

type OrderReq struct {
Items []Item `json:"items" binding:"required,min=1,dive"` // 验证每个 Item
}

相关链接