package httpx import ( "encoding/json" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/validator/v10" ) // bindingValidator 与 gin 同源: 校验 struct 的 binding tag var bindingValidator = validator.New(validator.WithRequiredStructEnabled()) func init() { bindingValidator.SetTagName("binding") // 关闭 gin 内置校验 (ShouldBindQuery/Header/JSON 内部各自 validate, 会使 json 字段的 // required 在 query 阶段误报)。校验统一由 Parse 末尾的 bindingValidator 收口。 binding.Validator = nil } // Parse 按统一顺序绑定请求参数: uri → query → header → json body。 // 各来源只做绑定不做校验, 全部绑定完后统一跑 binding 校验。 // (修复: gin 的 ShouldBindQuery 内部即执行校验, 会使 json 字段的 required 在 query 阶段误报) func Parse(c *gin.Context, v any) error { if err := parseUri(c, v); err != nil { return err } if err := parseQuery(c, v); err != nil { return err } if err := parseHeader(c, v); err != nil { return err } if err := parseJSON(c, v); err != nil { return err } return validate(c, v) } func parseUri(c *gin.Context, v any) error { if _, ok := c.Params.Get("id"); ok { return c.ShouldBindUri(v) } return nil } func parseQuery(c *gin.Context, v any) error { return c.ShouldBindQuery(v) } func parseHeader(c *gin.Context, v any) error { return c.ShouldBindHeader(v) } // parseJSON 直接解码 body, 不触发 gin 内部校验 (校验统一在 Parse 末尾) func parseJSON(c *gin.Context, v any) error { if c.Request == nil || c.Request.Body == nil || c.Request.ContentLength == 0 { return nil } return json.NewDecoder(c.Request.Body).Decode(v) } // ParseXML 显式绑定 XML 请求体到 v(空请求体直接返回 nil)。 // 自 go-hua/httpx 并入:Parse 主流程收口 uri/query/header/json 四种来源, // XML 报文(如第三方回调)按需显式调用本函数。 func ParseXML(c *gin.Context, v any) error { if c.Request.ContentLength == 0 { return nil } return c.ShouldBindXML(v) } // Validator 请求结构体可实现此接口做自定义校验, 在 binding 校验之前调用 type Validator interface { Validate() error } func validate(c *gin.Context, v any) error { if vv, ok := v.(Validator); ok { if err := vv.Validate(); err != nil { return err } } return bindingValidator.Struct(v) }