跳转至

Data Binding

对 Web 开发来说,数据绑定无疑是非常重要的,它可以自动从请求上下文中获取数据然后反序列化到对象中,大大的简化了数据提取,让你专注于真正的业务开发。web.Context 提供了一个 Bind 方法来实现数据绑定,它本质上是通过调用注册在 web.Server 上的 Binder 对象来实现的。web 包中已经实现了一个了非常强大的 Binder,并且默认就会注册到 web.Server 上,所以你不需要任何配置就可以使用。

user := &User{}
err := ctx.Bind(user)

非常简单,不是吗?接下来我们详细了解下内置 Binder 的原理与使用场景。

Bind JSON

当请求的 Context-Typeapplication/json 时,Binder 会把请求内容用 JSON 方式进行反序列化。标准库中的 JSON 反序列化规则完全适用于这里。

Bind XML

当请求的 Context-Typeapplication/xml 时,Binder 会把请求内容用 XML 方式进行反序列化。标准库中的 XML 反序列化规则完全适用于这里。

Bind parameters

除了 JSON 和 XML 之外,其它数据绑定需要依赖 bind 标签,它的格式为:[name],[name=type1],[name=type2]...,其中有效的类型为 path/query/form/cookie/header,当 type 省略时,相当于同时指定了 query/form 类型。

让我们结合一个简单实例来解释下。

type TestUser struct {
    Name string `json:"name" xml:"name" bind:"name,name1=path,name2=cookie"`
}

上面 Name 字段上定义的 bind 标签表示此字段可以绑定到 query 或 form 的 name 参数,或者 path 的 name1 参数,或者 cookie 的 name2 参数,所以下面这三个请求都可以成功绑定:

Method URL Route Query Body Cookie
GET /user /user name=foobar
POST /user /user name=foobar
GET /user/foobar /user/:name1
GET /user /user name2=foobar

Bind file

对于上传的文件,内置的 Binder 提供了更为丰富的绑定策略。假设客户端上传一个头像图片,对应的表单字段名为 avatar,我们希望能自动把文件信息绑定到对象上,只需要定义下面的 struct 即可。

type Avatar struct {
    Data []byte `bind:"avatar=file"`
    Name string `bind:"avatar=file.name"`
    Size int32  `bind:"avatar=file.size"`
}

Custom type support

内置的 Binder 支持几乎所有基本类型以及 time.Time 和 time.Duration 类型,如果需要支持自定义类型,只需要让类型实现 web.Unmarshaler 接口即可。

// Unmarshaler is the interface implemented by types that can unmarshal a param to themselves.
type Unmarshaler interface {
    // Unmarshal converts a param value to type.
    Unmarshal(param interface{}) error
}

下面的 FailMode 自定义类型演示了如何实现此接口。

type FailMode int

func (f *FailMode) Unmarshal(i interface{}) error {
    if s, ok := i.(string); ok {
        switch strings.ToLower(strings.TrimSpace(s)) {
        case "over":
            *f = FailOver
            return nil
        case "try":
            *f = FailTry
            return nil
        case "fast", "":
            *f = FailFast
            return nil
        }
    }
    return errors.Format("can't convert %v to FailMode", i)
}

Custom binder

Binder 是一个接口,它只有一个 Bind 方法,所以如果你不喜欢 auxo 中默认的实现,完全可以自己实现一个来替换它。

type YourBinder struct {}

// Bind implements `web.Binder` interface.
func (b *YourBinder) Bind(ctx Context, i interface{}) (err error) {
    // your code...
}

func main() {
    s := web.Auto()
    s.Binder = &YourBinder{}
}

Data Validation

你可能已经发现 Context.Bind 方法其实还有一个布尔型的可选参数 validate,这个参数可以让你在成功绑定数据后自动执行数据校验。

err := ctx.Bind(i, true)

Binder 不同,创建 web.Server 实例时默认并没有注册任何验证器,所以你必须先设置 Validator 才能使用数据校验,否则将会触发一个运行时错误。auxo 中已经提供了一个完善的 Validator 实现,它就在 data/valid 包中,你可以这样来使用它:

s := web.Auto()
s.Validator = &valid.Validator{Tag: "valid"}

auxo 中内置验证器和验证规则的详细使用说明请查看 valid 包的相关文档。