一、背景
在服务的API接口层面,我们常常需要验证参数的有效性。 Golang中,大部分参数校验场景实际上是先将数据Bind到结构体,然后校验其字段值。
一般地,校验结构体字段值有如下两种实现方式。
email
、max:100
等形式 思考:有没有一种方式,即简单易用(少写代码),又能满足各种复杂的校验场景?
答案是:有!结构体标签表达式 go-tagexpr 的出现,为我们提供了兼得鱼和熊掌的第三种选择。
二、认识 go-tagexpr
go-tagexpr 允许Gopher们在 struct tag 写表达式代码,并通过高性能的解释器计算其结果。
安装
go get -u github.com/bytedance/go-tagexpr
下面使用一个小示例,演示含有枚举、比较、字段关联的较复杂场景。
示例代码
import (
"fmt"
tagexpr "github.com/bytedance/go-tagexpr"
)
func ExampleTagexpr() {
vm := tagexpr.New("te")
type Meteorology struct {
Season string `te:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
Weather string `te:"$!='snowing' || (Season)$=='winter'"`
Temperature int `te:"{range:$>=-10 && $<38}{alarm:sprintf('Uncomfortable temperature: %v',$)}"`
}
m := &Meteorology{
Season: "summer",
Weather: "snowing",
Temperature: 40,
}
r := vm.MustRun(m)
fmt.Println(r.Eval("Season"))
fmt.Println(r.Eval("Weather"))
fmt.Println(r.Eval("Temperature@range"))
fmt.Println(r.Eval("Temperature@alarm"))
// Output:
// true
// false
// false
// Uncomfortable temperature: 40
}
代码诠释:
$
表示当前字段值,(Season)$
表示 Season 字段的值
type Meteorology struct { Season string `te:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"` Weather string `te:"$!='snowing' || (Season)$=='winter'"` Temperature int `te:"{range:$>=-10 && $<38}{alarm:sprintf('Uncomfortable temperature: %v',$)}"` } m := &Meteorology{ Season: "summer", Weather: "snowing", Temperature: 40, }
$=='spring'||$=='summer'||$=='autumn'||$=='winter'
)的值。因字段值 summer 在穷举列表中,故表达式结果为“true”
r.Eval("Season")
$!='snowing' || (Season)$=='winter'
的值。因字段值为 snowing 且 Season 为 summer,故表达式结果为“false”
r.Eval("Weather")
range
表达式 $>=-10 && $<38
的值。因字段值为 40,超出给出的范围,所以结果为“false”
r.Eval("Temperature@range")
alarm
表达式 sprintf('Uncomfortable temperature: %v',$)
的值。这是一个调用内部函数的表达式,它打印并返回字符串,结果为“Uncomfortable temperature: 40”
r.Eval("Temperature@alarm")
获取更多关于 go-expr 结构体标签表达式的语法知识 -> 查看这里
二、使用Validator校验
Validator 是有 go-expr 包提供的一个采用结构体标签表达式的参数校验组件。
主要特性
msg
且结果为字符串的表达式作为错误信息安装
go get -u github.com/bytedance/go-tagexpr
我们基于前面示例稍作修改,来演示如何使用validator校验结构体字段的有效性。
示例代码
import (
"fmt"
"github.com/bytedance/go-tagexpr/validator"
)
func ExampleValidator() {
vd := validator.New("vd")
type Meteorology struct {
Season string `vd:"$=='spring'||$=='summer'||$=='autumn'||$=='winter'"`
Weather string `vd:"$!='snowing' || (Season)$=='winter'"`
Temperature int `vd:"{@:$>=-10 && $<38}{msg:sprintf('Uncomfortable temperature: %v',$)}"`
Contact string `vd:"email($)"`
}
m := &Meteorology{
Season: "summer",
Weather: "rain",
Temperature: 40,
Contact: "henrylee2cn@gmail.com",
}
err := vd.Validate(m)
if err != nil {
fmt.Println(err)
}
// Output:
// Uncomfortable temperature: 40
}
代码诠释:
注册自己的校验函数
可能你已注意到 email($)
这个表达式,它是默认注册的一个函数表达式,用于验证邮箱的有效性。其实我们也可以定义自己通用的函数表达式,以便较少标签中的代码量,增加代码复用性。
下面以 email 函数的实现为例,演示如何注册自己的校验函数:
var pattern = "^([A-Za-z0-9_\\-\\.\u4e00-\u9fa5])+\\@([A-Za-z0-9_\\-\\.])+\\.([A-Za-z]{2,8})$"
emailRegexp := regexp.MustCompile(pattern)
validator.RegValidateFunc("email", func(args ...interface{}) bool {
if len(args) != 1 {
return false
}
s, ok := args[0].(string)
if !ok {
return false
}
return emailRegexp.MatchString(s)
}, true)
其中,validator.RegValidateFunc 的定义如下:
func RegValidateFunc(funcName string, fn func(args ...interface{}) bool, force ...bool) error
RegValidateFunc的force可选参数,表示是否强制覆盖已经注册了的同名函数。
**结论:**validator的使用方法非常简单、灵活且具有良好的扩展性,能够轻松满足各种复杂的验证场景。
获取更多关于 validator 校验器的语法知识 -> 查看这里
(adsbygoogle = window.adsbygoogle || []).push({});