假设我们有如下结构体:
type User struct {Id intName stringBio stringEmail string}
我们需要对结构体内的字段进行验证合法性:
Id的值在某一个范围内。
Name的长度在莫一个范围内。
Email格式正确。
我们可能会这么写:
user := User{Id: 0,Name: "superlongstring",Bio: "",Email: "foobar",}if user.Id < 1 && user.Id > 1000 {return false}if len(user.Name) < 2 && len(user.Name) > 10 {return false}if !validateEmail(user.Email) {return false}
这样的话代码比较冗余,而且如果结构体新加字段,还需要再修改验证函数再加一段if判断。这样代码比较冗余。我们可以借助golang的structTag来解决上述的问题:
type User struct {Id int `validate:"number,min=1,max=1000"`Name string `validate:"string,min=2,max=10"`Bio string `validate:"string"`Email string `validate:"email"`}
validate:”number,min=1,max=1000”就是structTag。
实现思路
我们定义一个接口Validator,定义一个方法Validate。再定义有具体意义的验证器例如StringValidator、NumberValidator、EmailValidator来实现接口Validator。
完整实例
package mainimport ("fmt""reflect""regexp""strings")const tagName = "validate"//邮箱验证正则var mailRe = regexp.MustCompile(`\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z`)//验证接口type Validator interface {Validate(interface{}) (bool, error)}type DefaultValidator struct {}func (v DefaultValidator) Validate(val interface{}) (bool, error) {return true, nil}type StringValidator struct {Min intMax int}func (v StringValidator) Validate(val interface{}) (bool, error) {l := len(val.(string))if l == 0 {return false, fmt.Errorf("cannot be blank")}if l < v.Min {return false, fmt.Errorf("should be at least %v chars long", v.Min)}if v.Max >= v.Min && l > v.Max {return false, fmt.Errorf("should be less than %v chars long", v.Max)}return true, nil}type NumberValidator struct {Min intMax int}func (v NumberValidator) Validate(val interface{}) (bool, error) {num := val.(int)if num < v.Min {return false, fmt.Errorf("should be greater than %v", v.Min)}if v.Max >= v.Min && num > v.Max {return false, fmt.Errorf("should be less than %v", v.Max)}return true, nil}type EmailValidator struct {}func (v EmailValidator) Validate(val interface{}) (bool, error) {if !mailRe.MatchString(val.(string)) {return false, fmt.Errorf("is not a valid email address")}return true, nil}func getValidatorFromTag(tag string) Validator {args := strings.Split(tag, ",")switch args[0] {case "number":validator := NumberValidator{}//将structTag中的min和max解析到结构体中fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)return validatorcase "string":validator := StringValidator{}fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)return validatorcase "email":return EmailValidator{}}return DefaultValidator{}}func validateStruct(s interface{}) []error {errs := []error{}v := reflect.ValueOf(s)for i := 0; i < v.NumField(); i++ {//利用反射获取structTagtag := v.Type().Field(i).Tag.Get(tagName)if tag == "" || tag == "-" {continue}validator := getValidatorFromTag(tag)valid, err := validator.Validate(v.Field(i).Interface())if !valid && err != nil {errs = append(errs, fmt.Errorf("%s %s", v.Type().Field(i).Name, err.Error()))}}return errs}type User struct {Id int `validate:"number,min=1,max=1000"`Name string `validate:"string,min=2,max=10"`Bio string `validate:"string"`Email string `validate:"email"`}func main() {user := User{Id: 0,Name: "superlongstring",Bio: "",Email: "foobar",}fmt.Println("Errors:")for i, err := range validateStruct(user) {fmt.Printf("\t%d. %s\n", i+1, err.Error())}}
其实github上已经有现成的验证包了govalidator,支持内置的验证tag和自定义验证tag:
package mainimport ("github.com/asaskevich/govalidator""fmt""strings")type Server struct {ID string `valid:"uuid,required"`Name string `valid:"machine_id"`HostIP string `valid:"ip"`MacAddress string `valid:"mac,required"`WebAddress string `valid:"url"`AdminEmail string `valid:"email"`}func main() {server := &Server{ID: "123e4567-e89b-12d3-a456-426655440000",Name: "IX01",HostIP: "127.0.0.1",MacAddress: "01:23:45:67:89:ab",WebAddress: "www.example.com",AdminEmail: "admin@exmaple.com",}//自定义tag验证函数govalidator.TagMap["machine_id"] = govalidator.Validator(func(str string) bool {return strings.HasPrefix(str, "IX")})if ok, err := govalidator.ValidateStruct(server); err != nil {panic(err)} else {fmt.Printf("OK: %v\n", ok)}}

