参数获取与校验
Gin
框架中,可以通过 binding
包进行参数获取与校验。binding
是一个非常好用的反序列化库,可以将 HTTP
请求体中的 JSON、XML、FormData
等格式的数据和 URL
上路径参数等绑定到设定好的 Go
结构体指针上,而且还提供了 go-playground/validator
库进行简单的参数校验。
gin bind
方法分为 BindXXX
和 ShouldBindXXX
两种。 BindXXX
方法在参数校验错误时,会先自动向返回信息中写入 400
的错误码再返回错误由服务器处理。而 ShouldBindXXX
方法不会写入错误码,而是直接放回错误完全由服务器处理。
以下是我针对一般的 URL
路径参数和 FormData
的请求体格式进行参数解析的代码示例。
如果每个方法入参不同,大可以用这种临时结构体,减少代码复杂度。
// Get URL Params
body := struct {
UserID int64 `form:"user_id"
Token string `form:"token"
}{}
if err := c.ShouldBindQuery(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"errMsg": err.Error()})
return
}
// Get formData Params
body := struct {
UserID int64 `form:"user_id" json:"user_id"
Token string `form:"token" json:"token"
}{}
if err := c.ShouldBind(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"errMsg": err.Error()})
return
}
通过这种方式获取入参可以免掉一些参数校验的业务逻辑。这里通过 binding
包可以便捷的进行更多的入参校验。 binding
也是通过结构体中的 Tag
对字段进行校验,简单的代码示例如下:
ActionType int `form:"action_type" json:"action_type" binding:"required,oneof=1 2"`
该字段检测入参时有三种引发 BindXXX/ShouldBindXXX
方法返回错误的可能性
required
会校验该入参是否存在,如果不存在的话会返回错误oneof
会校验入参的值,如果不满足oneof
给出的值会返回错误int
如果入参无法转换为int
类型也会返回错误
实际上 binding
包能检测的入参类型更多,常见的有
string
email
:验证通用正则表达式验证电子邮箱e164
:验证国际 E.164 标准验证电话iso3166_1_alpha2
:验证ISO-3166-1
两字母标准验证国家代码uppercase
:只允许大写字母lowercase
:只允许小写字母contains
:包含指定子串alphanum
:只允许包含英文字母和数字alpha
:只允许包含英文字母endswith
:字符串以指定子串结尾startwith
:字符串以指定子串开始gt
:大于设定的长度gte
:大于等于设定的长度lt
:小于设定的长度lte
:小于等于设定的长度
int
oneof
:只能为预设的值gt
:大于设定的值gte
:大于等于设定的值lt
:小于设定的值lte
:小于等于设定的值max
:最大值min
:最小值ne
:不等于
当入参不满足输入条件时, BindXXX/ShouldBindXXX
方法会返回validator.ValidationErrors
错误类型,该类型实现了 error
接口。如果需要定义返回的错误信息,可以通过 switch (type)
语法处理错误。
深入该包中我们可以看到 type ValidationErrors []FieldError
,即该方法返回的是一个自定义类型的数组。深入探究 FieldError
类型:
// FieldError contains all functions to get error details
type FieldError interface {
Tag() string
ActualTag() string
Namespace() string
StructNamespace() string
Field() string
StructField() string
Value() interface{}
Param() string
Kind() reflect.Kind
Type() reflect.Type
Translate(ut ut.Translator) string
Error() string
}
可以看到该类型是一个接口,这里省略了方法的注释,主要注意到 Field
与 Tag
字段,Field
方法返回字段名, Tag
方法返回引发错误的 Tag
名。以下为我手动处理错误的方法:
func HandleBindError(err error) error {
switch err.(type) {
case validator.ValidationErrors:
firstErr := err.(validator.ValidationErrors)[0]
switch firstErr.Tag() {
case "required":
return ParamEmptyError
case "oneof":
return ParamUnknownActionTypeError
case "lte":
return ParamInputLengthExceededError
default:
fmt.Printf("%s\n", firstErr.Tag())
dyerr := UnknownError
dyerr.ErrMessage = firstErr.Error()
return dyerr
}
case *strconv.NumError:
return ParamInputTypeError
default:
fmt.Printf("%T\n", err)
dyerr := UnknownError
dyerr.ErrMessage = err.Error()
return dyerr
}
}
为已知错误自定义一个错误结构体,为未知错误输出错误日志并返回未知类型。在测试中可以不断校准。