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{
"#": {
{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 /# [post]
func (this *UserController) #() {
if js, err := this.LoadJson(); err != nil {
this.ReturnJson(4000, nil, "Json 解析失败")
} else {
var user models.User
if e := user.Validate(&user, js, "#"); 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, "登陆成功")
}
}
}