Golang 验证 struct 字段的数据格式

来源:互联网 发布:风险矩阵法的作用 编辑:程序博客网 时间:2024/06/03 21:45

假设我们有如下结构体:

type User struct {    Id    int        Name  string     Bio   string     Email 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。

以下是具体实现:

package mainimport (    "fmt"    "reflect"    "regexp"    "strings")const tagName = "validate"// 邮箱验证正则var mailRe = regexp.MustCompile(`\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z`)// Validator 验证接口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 int    Max 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 int    Max 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 validator    case "string":        validator := StringValidator{}        fmt.Sscanf(strings.Join(args[1:], ","), "min=%d,max=%d", &validator.Min, &validator.Max)        return validator    case "email":        return EmailValidator{}    }    return DefaultValidator{}}func validateStruct(s interface{}) []error {    errs := []error{}    v := reflect.ValueOf(s)    for i := 0; i < v.NumField(); i++ {        // 利用反射获取structTag        tag := 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}// User user structtype 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())    }}

当然也可以直接使用一些第三方的库来实现,推荐
gopkg.in/go-playground/validator.v9
/github.com/asaskevich/govalidator