在web开发中一个不可避免的环节就是对请求参数进行校验,通常我们会在代码中定义与请求参数相对应的模型(结构体),借助模型绑定快捷地解析请求中的参数,例如 gin 框架中经常使用validator
库对参数进行校验。validator
库的具体使用可以参考官网:validator
基本示例
-
main.go
package main import ( "github.com/gin-gonic/gin" "net/http" ) type Login struct { RequestData string `form:"requestData" json:"requestData" binding:"-"` } func main() { r := gin.Default() r.GET("/hello", func(c *gin.Context) { var login Login // ShouldBind()会根据请求的Content-Type自行选择绑定器 if err := c.ShouldBind(&login); err == nil { c.JSON(http.StatusOK, gin.H{ "RequestData": login.RequestData, }) } else { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } }) err := r.Run(":8081") if err != nil { return } }
// http://127.0.0.1:8081/hello { "requestData": "" } // http://127.0.0.1:8081/hello?requestData=wanna { "requestData": "wanna" }
请求参数默认值是开发中的常见需求,如果前端没有传相关参数,我们可以自己设置一个默认值。Gin 框架提供了方法,例如:
DefaultQuery
,DefaultPostForm
等,还可以在逻辑中自己判断补充,但是这未免也太麻烦了,请求参数的类型是各种各样的,场景太多了,我们可不可以在校验参数的时候设置默认值呢?validator
库没有直接提供设置默认值的方式,如果没有传,那么参数默认为定义类型的零值,例如:数据类型 对应零值 string 空字符串 int 0 float 0.0 boolean false 上述示例中,我没有传
requestData
的值,所以返回的是空字符串。
使用反射设置默认值
我们可以使用反射获取struct
字段中的 tag
,然后设置默认值
- util.go
// 设置默认值的通用函数
func setDefaults(obj interface{}) {
v := reflect.ValueOf(obj).Elem()
setDefaultsRecursive(v)
}
func setDefaultsRecursive(v reflect.Value) {
// 处理指针类型
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// 处理结构体类型
if v.Kind() == reflect.Struct {
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
// 处理嵌套结构体
if value.Kind() == reflect.Struct && field.Anonymous {
setDefaultsRecursive(value)
continue
}
// 处理字段默认值
if tagValue, ok := field.Tag.Lookup("default"); ok && value.CanSet() {
if value.IsZero() {
switch value.Kind() {
case reflect.String:
value.SetString(tagValue)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if intValue, err := strconv.ParseInt(tagValue, 10, 64); err == nil {
value.SetInt(intValue)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if uintValue, err := strconv.ParseUint(tagValue, 10, 64); err == nil {
value.SetUint(uintValue)
}
case reflect.Float32, reflect.Float64:
if floatValue, err := strconv.ParseFloat(tagValue, 64); err == nil {
value.SetFloat(floatValue)
}
case reflect.Bool:
if boolValue, err := strconv.ParseBool(tagValue); err == nil {
value.SetBool(boolValue)
}
// 这里可以添加更多类型处理
}
}
}
}
}
}
// BindWithDefaults 自定义绑定器以处理默认值
func BindWithDefaults(c *gin.Context, obj interface{}) error {
if err := c.ShouldBind(obj); err != nil {
return err
}
setDefaults(obj)
return nil
}
- 修改示例代码
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Login struct {
// 添加 default tag 设置默认值
RequestData string `form:"requestData" json:"requestData" binding:"-" default:"default"`
}
func main() {
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
var login Login
// 修改为自定义的绑定器
if err := BindWithDefaults(c, &login); err == nil {
c.JSON(http.StatusOK, gin.H{
"RequestData": login.RequestData,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
err := r.Run(":8081")
if err != nil {
return
}
}
// http://127.0.0.1:8081/hello
{
"requestData": "default"
}
// http://127.0.0.1:8081/hello?requestData=wanna
{
"requestData": "wanna"
}