使用一个go的集成框架时,遇到参数绑定的问题。大多数时候用的json,通过 POST 方法发送,而在直接使用 gin 时,对于 GET 中的 query 参数,则是直接通过 ShouldBindQuery 这样的方法来绑定。

而在框架中,为了统一解析,是通过读取定义的结构体中的 tag 来判断使用什么方法来解析参数的。通过 ShouldBindWith 方法,传入对应的绑定方法实现绑定。

type Param struct {
	Id int `json:"id" query:"id"`
}

仅添加json标签无效后,尝试加上query标签,但依旧没有绑定上参数值。定位到query绑定的方法中来:

// gin-gonic/gin@v1.10.0/binding/query.go

func (queryBinding) Bind(req *http.Request, obj any) error {
	values := req.URL.Query()
	if err := mapForm(obj, values); err != nil {
		return err
	}
	return validate(obj)
}

这里获取到了查询参数,继续进入 mapForm 方法中:

func mapForm(ptr any, form map[string][]string) error {
	return mapFormByTag(ptr, form, "form")
}

原来这里调用 mapFormByTag 进行绑定时,使用的是form 标签,于是加上form标签后,可以成功绑定参数:

type Param struct {
	Id int `json:"id" query:"id" form:"id"`
}

那如果只添加 form 标签呢?发现也是可以的。进入 form 方式的绑定方法中:

// gin-gonic/gin@v1.10.0/binding/form.go

func (formBinding) Bind(req *http.Request, obj any) error {
	if err := req.ParseForm(); err != nil {
		return err
	}
	if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
		return err
	}
	if err := mapForm(obj, req.Form); err != nil {
		return err
	}
	return validate(obj)
}

request 对象中的 ParseForm 方法:

func (r *Request) ParseForm() error {
	var err error
	if r.PostForm == nil {
		if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
			r.PostForm, err = parsePostForm(r)
		}
		if r.PostForm == nil {
			r.PostForm = make(url.Values)
		}
	}
	if r.Form == nil {
		if len(r.PostForm) > 0 {
			r.Form = make(url.Values)
			copyValues(r.Form, r.PostForm)
		}
		var newValues url.Values
		if r.URL != nil {
			var e error
            // 从url参数解析
			newValues, e = url.ParseQuery(r.URL.RawQuery)
			if err == nil {
				err = e
			}
		}
		if newValues == nil {
			newValues = make(url.Values)
		}
		if r.Form == nil {
			r.Form = newValues
		} else {
			copyValues(r.Form, newValues)
		}
	}
	return err
}

可以看到当表单解析为空时,也会从url的查询参数中解析,因此,也能绑定上。