Skip to content

Commit ef3d193

Browse files
authored
feat: hertz binding refactor (#795)
1 parent bb362b8 commit ef3d193

File tree

1 file changed

+285
-11
lines changed

1 file changed

+285
-11
lines changed

content/zh/docs/hertz/tutorials/basic-feature/binding-and-validate.md

Lines changed: 285 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func main() {
2727

2828
...
2929

30-
// Bind
30+
// Bind 只做参数绑定
3131
req = Test{}
3232
err = ctx.Bind(&req)
3333

@@ -41,6 +41,22 @@ func main() {
4141
...
4242
}
4343
```
44+
### 全部 API
45+
> hertz version >= v0.7.0
46+
47+
| API | 说明 |
48+
|:--------------------|:----------------------------------------------------------------------------------------------------------------------------------------|
49+
| ctx.BindAndValidate | 利用下述的 go-tag 进行参数绑定,并在绑定成功后做一次参数校验(如果有校验 tag 的话) |
50+
| ctx.Bind | 同 "BindAndValidate" 但是不做参数校验 |
51+
| ctx.BindQuery | 绑定所有 Query 参数,相当于给每一个 field 声明一个 'query' tag,适用于没写 tag 的场景 |
52+
| ctx.BindHeader | 绑定所有 Header 参数,相当于给每一个 field 声明一个 'header' tag,适用于没写 tag 的场景 |
53+
| ctx.BindPath | 绑定所有 Path 参数,相当于给每一个 field 声明一个 'path' tag,适用于没写 tag 的场景 |
54+
| ctx.BindForm | 绑定所有 Form 参数,相当于给每一个 field 声明一个 'form' tag,需要 Content-Type 为: `application/x-www-form-urlencoded`/`multipart/form-data`, 适用于没写 tag 的场景 |
55+
| ctx.BindJSON | 绑定 JSON Body,调用 json.Unmarshal() 进行反序列化,需要 Body 为 `application/json` 格式 |
56+
| ctx.BindProtobuf | 绑定 Protobuf Body,调用 proto.Unmarshal() 进行反序列化,需要 Body 为 `application/x-protobuf` 格式 |
57+
| ctx.BindByContentType | 根据 Content-Type 来自动选择绑定的方法,其中 "GET" 请求会调用 BindQuery(), 带有 Body 的请求会根据 Content-Type 自动选择 |
58+
| ctx.Validate | 进行参数校验,需要校验 tag 配合使用(默认校验 tag: vd) |
59+
4460

4561
## 支持的 tag 及参数绑定优先级
4662

@@ -76,18 +92,172 @@ path > form > query > cookie > header > json > raw_body
7692

7793
``` go
7894
type TagRequiredReq struct {
79-
// 当 JSON 中没有 hertz 字段时,会返回 required 错误:binding: expr_path=hertz, cause=missing required parameter
95+
// 当 JSON 中没有 hertz 字段时,会返回 required 错误
8096
Hertz string `json:"hertz,required"`
81-
// 当 query 和 JSON 中同时没有 kitex 字段时,会返回 required 错误:binding: expr_path=hertz, cause=missing required parameter"
97+
// 当 query 和 JSON 中同时没有 kitex 字段时,会返回 required 错误
8298
Kitex string `query:"kitex,required" json:"kitex,required" `
8399
}
84100
```
85101

86-
## 常见用法
102+
## 常用配置
103+
> hertz 在 v0.7.0 版本对参数绑定&校验进行重构,重构后配置的行为发生变更,下面将分别介绍<br>
104+
> 如果还想使用之前的绑定器,目前已把其实现放到了 hertz-contrib 下,可通过自定义 binder 引入
105+
### 自定义 binder
106+
> hertz version >= v0.7.0 支持
107+
108+
需要实现 Binder 接口,并通过配置方式注入到 hertz engine
109+
```go
110+
type Binder interface {
111+
Name() string // 绑定器的名字
112+
// 下面为各种绑定方法
113+
Bind(*protocol.Request, interface{}, param.Params) error
114+
BindAndValidate(*protocol.Request, interface{}, param.Params) error
115+
BindQuery(*protocol.Request, interface{}) error
116+
BindHeader(*protocol.Request, interface{}) error
117+
BindPath(*protocol.Request, interface{}, param.Params) error
118+
BindForm(*protocol.Request, interface{}) error
119+
BindJSON(*protocol.Request, interface{}) error
120+
BindProtobuf(*protocol.Request, interface{}) error
121+
}
122+
```
123+
注入
124+
```go
125+
126+
func main() {
127+
// 通过配置的方式注入自定义 binder
128+
h := server.New(server.WithCustomBinder(&mockBinder{}))
129+
...
130+
h.Spin()
131+
}
132+
133+
134+
type mockBinder struct{}
135+
136+
func (m *mockBinder) Name() string {
137+
return "test binder"
138+
}
139+
140+
func (m *mockBinder) Bind(request *protocol.Request, i interface{}, params param.Params) error {
141+
return nil
142+
}
143+
144+
func (m *mockBinder) BindAndValidate(request *protocol.Request, i interface{}, params param.Params) error {
145+
return fmt.Errorf("test binder")
146+
}
147+
148+
func (m *mockBinder) BindQuery(request *protocol.Request, i interface{}) error {
149+
return nil
150+
}
151+
152+
func (m *mockBinder) BindHeader(request *protocol.Request, i interface{}) error {
153+
return nil
154+
}
155+
156+
func (m *mockBinder) BindPath(request *protocol.Request, i interface{}, params param.Params) error {
157+
return nil
158+
}
159+
160+
func (m *mockBinder) BindForm(request *protocol.Request, i interface{}) error {
161+
return nil
162+
}
163+
164+
func (m *mockBinder) BindJSON(request *protocol.Request, i interface{}) error {
165+
return nil
166+
}
167+
168+
func (m *mockBinder) BindProtobuf(request *protocol.Request, i interface{}) error {
169+
return nil
170+
}
171+
172+
```
173+
174+
目前已拓展的绑定器:
175+
* bytedance/go-tagexpr: https://github.com/hertz-contrib/binding/tree/main/go_tagexpr
176+
177+
### 自定义 validator
178+
> hertz version >= v0.7.0 支持
179+
180+
需要实现 Validator 接口,并通过配置方式注入到 hertz engine
181+
```go
182+
type StructValidator interface {
183+
ValidateStruct(interface{}) error // 校验函数
184+
Engine() interface{} // 返回底层的 Validator
185+
ValidateTag() string // 校验的 tag, 声明校验器使用的 tag
186+
}
187+
```
188+
注入
189+
```go
190+
191+
func main() {
192+
// 通过配置的方式注入自定义 binder
193+
h := server.New(server.WithCustomValidator(&mockValidator{}))
194+
...
195+
h.Spin()
196+
}
197+
198+
type mockValidator struct{}
199+
200+
func (m *mockValidator) ValidateStruct(interface{}) error {
201+
return fmt.Errorf("test mock validator")
202+
}
203+
204+
func (m *mockValidator) Engine() interface{} {
205+
return nil
206+
}
207+
208+
func (m *mockValidator) ValidateTag() string {
209+
return "vt"
210+
}
211+
212+
```
213+
目前已拓展的校验器:
214+
* go-playground/validator: https://github.com/hertz-contrib/binding/tree/main/go_playground
87215

88216
### 自定义 bind 和 validate 的 Error
89217

90-
绑定参数发生错误和参数校验失败的时候,用户可以自定义的 Error([demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_error) ),使用方法如下:
218+
绑定参数发生错误和参数校验失败的时候,用户可以自定义 Error 的内容,使用方法如下:<br>
219+
**hertz version >= v0.7.0**
220+
> 暂不支持自定义 bind error
221+
222+
自定义错误 error:
223+
```go
224+
package main
225+
import (
226+
"github.com/cloudwego/hertz/pkg/app/server/binding"
227+
"github.com/cloudwego/hertz/pkg/app/server"
228+
)
229+
230+
type ValidateError struct {
231+
ErrType, FailField, Msg string
232+
}
233+
234+
// Error implements error interface.
235+
func (e *ValidateError) Error() string {
236+
if e.Msg != "" {
237+
return e.ErrType + ": expr_path=" + e.FailField + ", cause=" + e.Msg
238+
}
239+
return e.ErrType + ": expr_path=" + e.FailField + ", cause=invalid"
240+
}
241+
242+
func main() {
243+
validateConfig := &binding.ValidateConfig{}
244+
validateConfig.SetValidatorErrorFactory(func(failField, msg string) error {
245+
err := ValidateError{
246+
ErrType: "validateErr",
247+
FailField: "[validateFailField]: " + failField,
248+
Msg: "[validateErrMsg]: " + msg,
249+
}
250+
251+
return &err
252+
})
253+
h := server.New(server.WithValidateConfig(validateConfig))
254+
...
255+
h.Spin()
256+
}
257+
```
258+
259+
**hertz version < v0.7.0**<br>
260+
[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_error)
91261

92262
```go
93263
import "github.com/cloudwego/hertz/pkg/app/server/binding"
@@ -142,8 +312,48 @@ func init() {
142312
```
143313

144314
### 自定义类型解析
315+
在参数绑定的时候, 针对某些特殊类型, 默认行为无法满足需求, 可使用自定义类型解析来解决, 使用方法如下:<br>
316+
**hertz version >= v0.7.0**<br>
317+
```go
318+
package main
319+
320+
import (
321+
"github.com/cloudwego/hertz/pkg/app/server/binding"
322+
"github.com/cloudwego/hertz/pkg/app/server"
323+
)
324+
325+
type Nested struct {
326+
B string
327+
C string
328+
}
329+
330+
type TestBind struct {
331+
A Nested `query:"a,required"`
332+
}
145333

146-
在参数绑定的时候,所有的 request 参数都是 `string` 或者 `[]string`;当有一些 field 的类型为非基础类型或者无法直接通过 `string` 转换,则可以自定义类型解析([demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_type_resolve) )。使用方法如下:
334+
func main() {
335+
bindConfig := &binding.BindConfig{}
336+
// v0.7.0 重构后,在原基础上增加了请求 Request 内容以及路由参数,可方便用户更加灵活的自定义类型解析
337+
bindConfig.MustRegTypeUnmarshal(reflect.TypeOf(Nested{}), func(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {
338+
if text == "" {
339+
return reflect.ValueOf(Nested{}), nil
340+
}
341+
val := Nested{
342+
B: text[:5],
343+
C: text[5:],
344+
}
345+
// 此外,也可以利用 req, params 来获取其他参数进行参数绑定
346+
return reflect.ValueOf(val), nil
347+
})
348+
h := server.New(server.WithBindConfig(bindConfig))
349+
350+
...
351+
h.Spin()
352+
}
353+
```
354+
**hertz version < v0.7.0**<br>
355+
356+
[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_type_resolve)
147357

148358
```go
149359
import "github.com/cloudwego/hertz/pkg/app/server/binding"
@@ -172,9 +382,39 @@ func init() {
172382
```
173383

174384
### 自定义验证函数
385+
可以通过注册自定义验证函数,在'vd'注解中实现复杂的验证逻辑:<br>
386+
**hertz version >= v0.7.0**<br>
387+
```go
388+
package main
175389

176-
可以通过注册自定义验证函数,在'vd'注解中实现复杂的验证逻辑([demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_validate_func) ),使用方法如下:
390+
import (
391+
"github.com/cloudwego/hertz/pkg/app/server/binding"
392+
"github.com/cloudwego/hertz/pkg/app/server"
393+
)
177394

395+
func main() {
396+
type Req struct {
397+
A int `query:"a" vd:"test($)"`
398+
}
399+
validateConfig := &binding.ValidateConfig{}
400+
validateConfig.MustRegValidateFunc("test", func(args ...interface{}) error {
401+
if len(args) != 1 {
402+
return fmt.Errorf("the args must be one")
403+
}
404+
s, _ := args[0].(string)
405+
if s == "123" {
406+
return fmt.Errorf("the args can not be 123")
407+
}
408+
return nil
409+
})
410+
h := server.New(server.WithValidateConfig(validateConfig))
411+
...
412+
h.Spin()
413+
}
414+
```
415+
416+
**hertz version < v0.7.0**<br>
417+
[demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/custom_validate_func)
178418
```go
179419
import "github.com/cloudwego/hertz/pkg/app/server/binding"
180420

@@ -194,8 +434,25 @@ func init() {
194434

195435
### 配置 looseZero
196436

197-
在一些场景下,前端有时候传来的信息只有 key 没有 value,这会导致绑定数值类型的时候,会报错 `cause=parameter type does not match binding data`
198-
这时需要配置 looseZero 模式([demo](https://github.com/cloudwego/hertz-examples/tree/main/binding/loose_zero) ),使用方法如下:
437+
在一些场景下,前端有时候传来的信息只有 key 没有 value,这会导致绑定数值类型的时候报错;这时需要配置 looseZero 模式,使用方法如下:
438+
**hertz version >= v0.7.0**<br>
439+
```go
440+
package main
441+
442+
import (
443+
"github.com/cloudwego/hertz/pkg/app/server/binding"
444+
"github.com/cloudwego/hertz/pkg/app/server"
445+
)
446+
447+
func main() {
448+
bindConfig := binding.NewBindConfig()
449+
bindConfig.LooseZeroMode = true
450+
h := server.New(server.WithBindConfig(bindConfig))
451+
...
452+
h.Spin()
453+
}
454+
```
455+
**hertz version < v0.7.0**<br>
199456

200457
```go
201458
import "github.com/cloudwego/hertz/pkg/app/server/binding"
@@ -207,9 +464,24 @@ func init() {
207464
```
208465

209466
### 配置其他 json unmarshal 库
467+
在绑定参数的时候,如果请求体为 json,会进行一次 json 的 unmarshal,如果用户需要使用特定的 json 库可以自己配置(hertz 默认使用开源 json 库 [sonic](https://github.com/bytedance/sonic) )。使用方法如下:<br>
468+
**hertz version >= v0.7.0**<br>
469+
```go
470+
import (
471+
"github.com/cloudwego/hertz/pkg/app/server/binding"
472+
"github.com/cloudwego/hertz/pkg/app/server"
473+
)
210474

211-
在绑定参数的时候,如果请求体为 json,会进行一次 json 的 unmarshal,如果用户需要使用特定的 json 库可以自己配置(hertz 默认使用开源 json 库 [sonic](https://github.com/bytedance/sonic) )。使用方法如下:
212-
475+
func main() {
476+
bindConfig := binding.NewBindConfig()
477+
bindConfig.UseStdJSONUnmarshaler() // 使用标准库, hertz 默认使用 sonic 作为 json 序列化/反序列化器
478+
//bindConfig.UseThirdPartyJSONUnmarshaler(sonic.Unmarshal) // 使用 sonic 作为 json 序列化/反序列化器
479+
h := server.New(server.WithBindConfig(bindConfig))
480+
...
481+
h.Spin()
482+
}
483+
```
484+
**hertz version < v0.7.0**<br>
213485
```go
214486
import "github.com/cloudwego/hertz/pkg/app/server/binding"
215487

@@ -226,6 +498,7 @@ func init() {
226498
```
227499

228500
### 设置默认值
501+
> 重构前后使用方式都一样
229502
230503
参数支持 "default" tag 进行默认值的配置,使用方法如下:
231504

@@ -237,6 +510,7 @@ type UserInfoResponse struct {
237510
```
238511

239512
### 绑定文件
513+
> 重构前后使用方式一样,IDL 场景不支持文件绑定
240514
241515
参数绑定支持绑定文件,使用方法如下:
242516

0 commit comments

Comments
 (0)