参数获取与校验
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 包能检测的入参类型更多,常见的有
stringemail:验证通用正则表达式验证电子邮箱e164:验证国际 E.164 标准验证电话iso3166_1_alpha2:验证ISO-3166-1两字母标准验证国家代码uppercase:只允许大写字母lowercase:只允许小写字母contains:包含指定子串alphanum:只允许包含英文字母和数字alpha:只允许包含英文字母endswith:字符串以指定子串结尾startwith:字符串以指定子串开始gt:大于设定的长度gte:大于等于设定的长度lt:小于设定的长度lte:小于等于设定的长度
intoneof:只能为预设的值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
}
}
为已知错误自定义一个错误结构体,为未知错误输出错误日志并返回未知类型。在测试中可以不断校准。