Package validator 实现了一个支持场景/国际化/自定义错误/自定义验证规则的 map[string]interface{} 元素批量验证器,意在支持各种框架的 model 层实现自动验证,亦可单独使用
- 该验证器是 逻辑验证器(float64(10)/int32(10)/"10" 均可被 intValidator 验证通过)而不是 强类型验证器
- 考虑到经过 encoding/json 解析后的数字类型均被解析为 float64,强类型验证器不太可用,故此设计
- 如果需要强类型验证,可以使用 funcValidator 自定义验证函数,或使用 AddValidator 自定义验证器
- 支持场景
- 支持国际化
- 支持批量验证
- 支持自定义验证器
- 支持自定义错误信息
go get github.com/goindow/validator
- 配合框架的 model 层实现自动验证
- 独立使用
package main import ( "github.com/goindow/validator" "fmt" ) func main() { user := map[string]interface{}{ // "username": "hyb", "password": "******", "gender": "male", "age": 17, "weight": "53kg", "email": "hyb76788424#163.com", } rules := validator.Rules{ "create": { { Attr: []string{"username", "password"}, Rule: "required" }, { Attr: "password", Rule: "regex", Pattern: `[A-Z]{1}\w{5,}`, Message: "密码必须由大写字母开头"}, { Attr: "gender", Rule: "in", Enum: []string{"0", "1"} }, { Attr: "age", Rule: "int", Min: 18 }, { Attr: "weight", Rule: "number", Symbol: 1 }, { Attr: "email", Rule: "email" }, }, "read": { { Attr: "id", Rule: "int", Symbol: 1 }, }, } if e := validator.New().Validate(rules, user, "create"); e != nil { // todo: handle errors for _, i := range e { for k, v := range i { fmt.Printf("%v => %v\n", k, v) } } // username => 不能为空 // password => 密码必须由大写字母开头 // gender => 只能是 [0、1] 中的一个 // age => 必须是不小于 18 的整数 // weight => 必须是数字 // email => 无效的 email } // todo: do something }
- validator.Rule struct 验证规则
- Attr interface{} 必选,待验证属性,单个属性 string,多个属性 []string,其他类型或未定义将 panic
- Rule string 必选,验证规则,即验证器,不存在的验证器或未定义将 panic
- Message string 可选,自定义错误信息
- Required bool 可选,可空限制,作用于除 requiredValidator 外的所有验证器,false(默认) - 有值验证/无值跳过,true - 有值验证/无值报错
- Symbol int64 可选,符号限制,作用于 numberValidator、integerValidator、decimalValidator,0(默认) - 正/负数,>0 - 正数(不包含0),<0 - 负数(不包含0)
- Max interface{} 可选,最大限制,作用于 stringValidator、numberValidator、integerValidator、decimalValidator
- Min interface{} 可选,最小限制,同 Max
- Enum []string 必选(inValidator),枚举限制,作用于 inValidator
- Func validator.F 必选(funcValidator),自定义验证函数,作用于 funcValidator
- Pattern string 必选(regexValidator),正则匹配模式,作用于 regexValidator
- validator.Scence string 场景
- validator.ScenceRules []validator.Rule 验证规则集 - 单一场景
- validator.Rules map[Scence]ScenceRules 验证规则集 - 所有场景
rules := validator.Rules{ // validator.Rules // validator.ScenceRules "create": { // validator.Scence { Attr: []string{"username", "password"}, Rule: "required" }, // validator.Rule { Attr: "password", Rule: "regex", Pattern: `[A-Z]{1}\w{5,}`, Message: "密码必须由大写字母开头"}, { Attr: "gender", Rule: "in", Enum: []string{"0", "1"} }, { Attr: "age", Rule: "int", Min: 18 }, { Attr: "weight", Rule: "number", Symbol: 1 }, { Attr: "email", Rule: "email" }, }, "read": { { Attr: "id", Rule: "int", Symbol: 1 }, }, }
- Lang(lang string) *validator
- 在 i18n 下,新建错误信息对应的语言文件,格式参考已有文件,包本身自带两种语言(zh_cn、en_us),默认语言为 zh_cn
// touch ./i18n/en_us.go v := validator.New().Lang("en_us")
- Rule.Message string
rules := validator.Rules{ "create": { { Attr: "password", Rule: "regex", Pattern: `[A-Z]{1}\w{5,}`, Message: "密码必须由大写字母开头"}, } }
- AddValidator(name string, customValidator F)
package main import ( "github.com/goindow/validator" "fmt" ) func main() { v := validator.New() // 自定义验证器,类型为 validator.F var oneValidator validator.F = func(attr string, rule validator.Rule, obj validator.M) validator.E { if _, ok := obj[attr]; !ok { return validator.E{attr: "not found"} } if obj[attr] != 1 { e := rule.Message if e == "" { e = "必须等于一" } return validator.E{attr: e} } return nil } // 挂载 v.AddValidator("one", oneValidator) // 使用 user := map[string]interface{}{ "name": "hyb", } rules := validator.Rules{ "someone": { {Attr: "name", Rule: "one"}, }, } e := v.Validate(rules, user, "someone") fmt.Println(e) // [map[name:必须等于一]] }
- funcValidator
- requiredValidator
- inValidator
- stringValidator
- integerValidator
- decimalValidator
- numberValidator
- booleanValidator
- ipValidator
- regexValidator
- emailValidator
- telValidator
- mobileValidator
- zipcodeValidator
- 使用 Rule.Func 定义的函数来验证本条规则,Rule.Func 的类型是 validator.F
- Rule.Rule string 必选 func
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
- Rule.Func validator.F 必选 使用 Rule.Func 来验证本条 Rule
rule := {Attr: "password", Rule: "func", Func: func(attr string, rule validator.Rule, obj validator.M) validator.E { if obj["password"] != obj["rpassword"] { return validator.E{attr: "两次输入不一致"} } return nil }}
- 必填
- Rule.Rule string 必选 required
rule := {Attr: []string{"username", "password"}, Rule: "required"}
- 枚举,被验证字段支持类型 int64、int32、int16、int8、int、float64、float32、string、bool
- Rule.Rule string 必选 in
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
- Rule.Enum []string 必选 被验证字段必须在 Rule.Enum 中
rule := {Attr: "gender", Rule: "in", Enum: {"male", "female", "unknown"}} // 默认,所有规则,有值验证,无值跳过 rule := {Attr: "gender", Rule: "in", Enum: {"male", "female", "unknown"}, Required: true} // 有值验证,无值报 required 错误
- 字符串
- Rule.Rule string 必选 string
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
- Rule.Max int 可选 被验证字段长度不能大于 Rule.Max
- Rule.Min in 可选 被验证字段长度不能小于 Rule.Min
rule := {Attr: "name", Rule: "string"} rule := {Attr: "name", Rule: "string", Min: 6} // utf8 字符数,即字符串长度,兼容中文 rule := {Attr: "name", Rule: "string", Min: 6, Max: 18} rule := {Attr: "name", Rule: "string", Min: 6, Max: 18, Required: true}
- 整数,被验证字段支持类型 int64、int32、int16、int8、int、float64、float32、string
- Rule.Rule string 必选 integer/int
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
- Rule.Symbol int64 可选 0(默认) - 正/负数,>0 - 正数(不包含0),<0 - 负数(不包含0)
- Rule.Max int 可选 被验证字段大小不能大于 Rule.Max
- Rule.Min int 可选 被验证字段大小不能小于 Rule.Min
// int 为 integer 的别名,都指向 integerValidator 验证器 rule := {Attr: "age", Rule: "int"} rule := {Attr: "age", Rule: "integer", Symobl: 1} // 正整数 rule := {Attr: "age", Rule: "integer", Min: 18} rule := {Attr: "age", Rule: "integer", Min: 18, Max: 18} // == 18 rule := {Attr: "age", Rule: "integer", Min: 18, Max: 35, Required: true} // float64(18)、float32(18)、"18" 都会被认为是整数
- 小数,被验证字段支持类型 float64、float32、string
- Rule.Rule string 必选 decimal/float
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
- Rule.Symbol int64 可选 0(默认) - 正/负数,>0 - 正数(不包含0),<0 - 负数(不包含0)
- Rule.Max int|float64 可选 被验证字段大小不能大于 Rule.Max
- Rule.Min int|float64 可选 被验证字段大小不能小于 Rule.Min
// float 为 decimal 的别名,都指向 decimalValidator 验证器 rule := {Attr: "field", Rule: "float"} rule := {Attr: "field", Rule: "decimal", Symobl: -1} // 负小数 rule := {Attr: "field", Rule: "decimal", Min: 2} rule := {Attr: "field", Rule: "decimal", Min: 3.14, Max: 3.14} // == 3.14 rule := {Attr: "field", Rule: "decimal", Min: 3, Max: 3.14, Required: true} // float64(18)、float32(18)、"18" 没有小数位会验证失败
- 数字,被验证字段支持类型 int64、int32、int16、int8、int、float64、float32、string
- Rule.Rule string 必选 number
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
- Rule.Symbol int64 可选 0(默认) - 正/负数,>0 - 正数(不包含0),<0 - 负数(不包含0)
- Rule.Max int|float64 可选 被验证字段大小不能大于 Rule.Max
- Rule.Min int|float64 可选 被验证字段大小不能小于 Rule.Min
rule := {Attr: "weight", Rule: "number"} rule := {Attr: "weight", Rule: "number", Symobl: 1} rule := {Attr: "weight", Rule: "number", Min: 45} rule := {Attr: "weight", Rule: "number", Min: 45, Max: 45} // == 45 rule := {Attr: "weight", Rule: "number", Min: 45, Max: 49.9, Required: true}
- 布尔,被验证字段支持类型 bool、string
- Rule.Rule string 必选 boolean/bool
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
// bool 为 boolean 的别名,都指向 booleanValidator 验证器 rule := {Attr: "admin", Rule: "bool"} rule := {Attr: "admin", Rule: "boolean"} // 布尔值[true、false]、字符串表示的布尔值["1"、"0"、"t"、"f"、"true"、"false"(忽略大小写)] 都会被认为是布尔
- ipv4/ipv6,被验证字段支持类型 string
- Rule.Rule string 必选 ip
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
rule := {Attr: "ip", Rule: "ip"}
- 正则,被验证字段支持类型 string
- Rule.Rule string 必选 regex
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
- Rule.Pattern string 必选 正则模式字符串
rule := {Attr: "password", Rule: "regex", Pattern: `[A-Z]{1}\w{5,}`},
- email,被验证字段支持类型 string
- Rule.Rule string 必选 email
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
rule := {Attr: "email", Rule: "email"} // pattern = `^[\w!#$%&'*+/=?^_` + "`" + `{|}~-]+(?:\.[\w!#$%&'*+/=?^_` + "`" + `{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[a-zA-Z0-9](?:[\w-]*[\w])?$`
- 中国大陆座机号,被验证字段支持类型 string
- Rule.Rule string 必选 tel
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
rule := {Attr: "tel", Rule: "tel"} // pattern = `^(0\d{2,3}(\-)?)?\d{7,8}$`
- 中国大陆手机号,被验证字段支持类型 string
- Rule.Rule string 必选 mobile
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
rule := {Attr: "mobile", Rule: "mobile"} // pattern = `^((\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][01356789]|[4][579]))\d{8}$`
- 中国大陆邮编,被验证字段支持类型 string
- Rule.Rule string 必选 zipcode
- Rule.Required bool 可选 false(默认) - 被验证字段有值验证/无值跳过,true - 被验证字段无值,验证失败,报 reqired 错误
rule := {Attr: "zipcode", Rule: "zipcode"} // pattern = `^[1-9]\d{5}$`
- 本例以 beego 框架为例,扩展其 model,实现自动验证,使用常见的 base model/controller 模式,为子类提供统一方法
package base import ( "encoding/json" "github.com/astaxie/beego" ) type JSON struct { Code int64 `json:"code"` Data interface{} `json:"data"` Errors interface{} `json:"errors"` } type BaseController struct { beego.Controller } func (this *BaseController) LoadJson() (beego.M, error){ var js beego.M return js, json.Unmarshal(this.Ctx.Input.RequestBody, &js) } func (this *BaseController) ReturnJson(code int64, data interface{}, e interface{}) { this.Data["json"] = &JSON{ Code: code, Data: data, Errors: e, } this.ServeJSON() }
package base import ( "reflect" "github.com/goindow/validator" ) // 为了避免每个子 model 文件都要写 import validator,在这里定义几个变量别名,供子 model 直接使用 type E = validator.E type M = validator.M type Rule = validator.Rule type Rules = validator.Rules type Scence = validator.Scence type BaseModel struct {} // 定义验证规则 func (this *BaseModel) Rules() Rules{ return nil } // 自动验证 func (this *BaseModel) Validate(ptrChildModel interface{}, js map[string]interface{}, scence Scence) []E { // 获取 ptrChildModel 的 Rules if rules := reflect.ValueOf(ptrChildModel).MethodByName("Rules").Call(make([]reflect.Value, 0))[0].Interface().(Rules); len(rules) != 0 { return validator.New().Validate(rules, js, scence) } return nil }
package models import ( "explore/base" ) type User struct { base.BaseModel Id int64 Username string Password string } func (this *User) Rules() base.Rules { return base.Rules{ "signup": { {Attr: []string{"username", "password", "rpassword"}, Rule: "required"}, {Attr: "username", Rule: "string"}, {Attr: "password", Rule: "regex", Pattern: `[a-zA-Z].\d{5,}`}, {Attr: "rpassword", Rule: "func", Func: func(attr string, rule base.Rule, obj base.M) base.E { if obj["password"] != obj["rpassword"] { return base.E{attr: "两次输入不一致"} } return nil }}, }, "signin": { {Attr: []string{"username", "password"}, Rule: "required"}, {Attr: "password", Rule: "func", Func: func(attr string, rule base.Rule, obj base.M) base.E { if obj["username"] != "admin" || obj["password"] != "admin" { return base.E{attr: "用户名或密码错误"} } return nil }}, }, } }
package controllers import ( "explore/base" "explore/models" ) type UserController struct { base.BaseController } // @router /signup [post] func (this *UserController) Signup() { if js, err := this.LoadJson(); err != nil { this.ReturnJson(4000, nil, "Json 解析失败") } else { var user models.User if e := user.Validate(&user, js, "signup"); len(e) != 0 { this.ReturnJson(3000, nil, e) } else { // todo: this.ReturnJson(2000, nil, "注册成功") } } } // @router /signin [post] func (this *UserController) Signin() { if js, err := this.LoadJson(); err != nil { this.ReturnJson(4000, nil, "Json 解析失败") } else { var user models.User if e := user.Validate(&user, js, "signin"); len(e) != 0 { this.ReturnJson(3001, nil, e) } else { // todo: this.ReturnJson(2000, nil, "登陆成功") } } }