go反射里,注解和函数成员调用的实例

本文仅对go反射结构体的注解和反射调用结构体的绑定函数为实例。

1. 通过注解来对结构体检查

对User的Username字段检查匹配正则^[\u4E00-\u9FA5a-zA-Z0-9_.]{0,40}$

package main

import (
	"errors"
	"fmt"
	"go/types"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var flagRange = []string{"range", "in", "regex", "int", "int32", "int64", "int8", "string", "char", "float", "float32", "float64", "decimal", "time.time", "func", "function"}

type User struct {
	Username string `validate:"regex,^[\u4E00-\u9FA5a-zA-Z0-9_.]{0,40}$"`
}

func Validate(input interface{}) (bool, string, error) {
	vType := reflect.TypeOf(input)
	vValue := reflect.ValueOf(input)
	valueStr := ""
L:
	for i := 0; i < vType.NumField(); i++ {
		tagValue := vType.Field(i).Tag.Get("validate")
		if whetherLowerCase(tagValue) {
			tagValue = strings.ToLower(tagValue)
		}

		// `validate:""` `validate:"-"` will be ignored
		if tagValue == "" || tagValue == "-" {
			continue
		}
		value := vValue.Field(i).Interface()
		tmp := strings.Split(tagValue, ",")
		flag := strings.ToLower(tmp[0])
		if !strings.Contains(strings.Join(flagRange, " "), flag) {
			return false,
				fmt.Sprintf("while validating field '%s',flag '%s' is not contained in the flagRange '%v'", vType.Field(i).Name, flag, flagRange),
				errors.New(fmt.Sprintf("while validating field '%s',flag '%s' is not contained in the flagRange '%v'", vType.Field(i).Name, flag, flagRange))
		}

		var rule string
		if len(tmp) > 1 {
			rule = strings.Join(tmp[1:], ",")
		}
		// regex validate
		// regex validate is used to replace superChecker tag
		// Name string `superChecker:"key"`  <==> Name string `validate:"regex,key"`
		if flag == "regex" {
			if whetherLowerCase(tagValue) {
				rule = strings.ToLower(rule)
			}
			valueStr = ToString(value)
			// rule is raw regex
			if strings.HasPrefix(rule, `^`) && strings.HasSuffix(rule, `$`) {
				re, er := regexp.Compile(rule)
				if er != nil {
					return false,
						fmt.Sprintf("while validating field '%s' regex '%s' throws an error '%s'", vType.Field(i).Name, strconv.QuoteToASCII(rule), er.Error()),
						errors.New(fmt.Sprintf("while validating field '%s' regex '%s' throws an error '%s'", vType.Field(i).Name, strconv.QuoteToASCII(rule), er.Error()))
				}
				ok := re.MatchString(valueStr)
				if !ok {
					return false,
						fmt.Sprintf("while validating field '%s' regex '%s' but got unmatched value '%s'", vType.Field(i).Name, strconv.QuoteToASCII(rule), valueStr),
						nil
				}
				continue L
			}
		}
	}
	return true,"success",nil
}

// whether to convert tag value to lowercase
func whetherLowerCase(tagValue string) bool {
	if strings.HasPrefix(tagValue, "regex") && strings.Contains(tagValue, "^") && strings.Contains(tagValue, "$") {
		return false
	}
	return true
}

// ToString Change arg to string
func ToString(arg interface{}, timeFormat ...string) string {
	if len(timeFormat) > 1 {
		panic(errors.New(fmt.Sprintf("timeFormat's length should be one")))
	}
	switch v := arg.(type) {
	case int:
		return strconv.Itoa(v)
	case int8:
		return strconv.FormatInt(int64(v), 10)
	case int16:
		return strconv.FormatInt(int64(v), 10)
	case int32:
		return strconv.FormatInt(int64(v), 10)
	case int64:
		return strconv.FormatInt(v, 10)
	case string:
		return v
	case float32:
		return strconv.FormatFloat(float64(v), 'f', -1, 32)
	case float64:
		return strconv.FormatFloat(v, 'f', -1, 64)
	case time.Time:
		if len(timeFormat) == 1 {
			return v.Format(timeFormat[0])
		}
		return v.Format("2006-01-02 15:04:05")
	case fmt.Stringer:
		return v.String()
	case types.Pointer:
		return "not for ptr,you might need &ptr"
	default:
		return ""
	}
}

func main() {
	u := User{Username:"ft123"}
	ok,msg, e :=Validate(u)
	if e!=nil{
		fmt.Println(e.Error())
		return
	}
	if !ok {
		fmt.Println(msg)
		return
	}
	fmt.Println(ok, msg)
}

true, success

2. 通过结构体的绑定函数检验

package main

import (
	"errors"
	"fmt"
	"reflect"
	"regexp"
	"strings"
)

type User struct {
	Username string `validate:"regex,^[\u4E00-\u9FA5a-zA-Z0-9_.]{0,40}$"`
}

func (u User) Validate() (bool, string, error) {
	re, e := regexp.Compile("^[\u4E00-\u9FA5a-zA-Z0-9_.]{0,40}$")
	if e != nil {
		return false, "fail becaues " + e.Error(), e
	}
	ok := re.MatchString(u.Username)
	if !ok {
		return ok, "username fails '^[\u4E00-\u9FA5a-zA-Z0-9_.]{0,40}$' got " + u.Username, nil
	}
	return ok, "success", nil
}

func ValidateMethods(input interface{}) (bool, string, error) {
	vType := reflect.TypeOf(input)
	vValue := reflect.ValueOf(input)
	var info string
	var methodName string

	var results []reflect.Value

	for i := 0; i < vType.NumMethod(); i++ {
		methodName = vType.Method(i).Name

		// UserValidate,UserSVValidate
		if strings.HasSuffix(methodName, "Validate"){
			// all cases will validate methods end with 'Validate' or 'SVValidate'
			results = vValue.Method(i).Call(nil)
			if len(results) != 3 {
				info = fmt.Sprintf("while validating method[%d],named '%s',illegal return values,want 3(bool,string,error) but got %d(%s)", i, methodName, len(results), valueListByType(results))
				return false, info, errors.New(info)
			}
			var er error
			ok, msg := results[0].Bool(), results[1].String()
			if results[2].IsNil() {
				er = nil
			} else {
				er = results[2].Interface().(error)
			}
			if ok {
				continue
			} else {
				return ok, msg, er
			}
		}
	}
	return true, "success", nil
}

// when input a []reflect.Value{false, 5, 'example'}
// returns 'bool,int,string'
func valueListByType(r []reflect.Value) string {
	typs := make([]string, 0)
	for _, v := range r {
		typs = append(typs, reflect.TypeOf(v.Interface()).String())
	}
	return strings.Join(typs, ",")
}

func main() {
	u := User{Username: "ft123"}
	ok, msg, e := ValidateMethods(u)
	if e != nil {
		fmt.Println(e.Error())
		return
	}
	if !ok {
		fmt.Println(msg)
		return
	}
	fmt.Println(ok, msg)
}

上述两例,分别简述了:
1.如何通过注解来对结构体字段验证
2.如果通过结构体绑定的方法验证

下面拓展为:
1.注解支持'regex','func','int','float', 'time.Time'
2.绑定方法支持通过参数,选择匹配需要验证的函数方法

实现工具superChecker地址为https://github.com/fwhezfwhez/SuperChecker

3.superChecker 验证匹配的函数

其中,被绑定函数方法需要包含字符串’Validate’或者’SVValidate’, 匹配字符串格式为SVB[flag1]SVS[flag2]SVS[flag3], 其中 执行 checker.ValidateMethods(dest, flag1, flag2, flag3…) 将会验证对应的方法,匹配规则是 HIT.
HIT: [1,2,3,4] hits [1,3,4,56] by [1,3,4]

type O struct {
	Username string
}

// v1
func (o O) OLengthValidate() (bool, string, error) {
	if o.Username > 5 && o.Username < 100 {
		return true, "success", nil
	}
	return false, "length should be between 5 and 100", nil
}

// v2
func (o O) OValidateSVBCreate() (bool, string, error) {
	if o.Username != "" {
		return true, "success", nil
	}
	return false, "length should be between 5 and 100", nil
}

// v3
func (o O) OValidateSVBUpdate() (bool, string, error) {
	if o.Username == "admin" {
		return false, "admin should not be updated", nil
	}
	return true, "success", nil
}

// v4
func (o O) OValidateSVBUpdateSVSCreate() (bool, string, error) {
	if o.Username == "admin" {
		return false, "admin should not be updated", nil
	}
	return true, "success", nil
}
func main() {
	...
	// o:=O{Username:"he"}
	o := O{Username: "hellworld"}
	// v1 will be validated
	ok, msg, e := checker.ValidateMethods(o)

	// v1,v2,v4 will be validated
	ok, msg, e = checker.ValidateMethods(o, "create")

	// v1,v3,v4 will be validated
	ok, msg, e := checker.ValidateMethods(o, "update")

	// v1,v2,v3,v4 will all be validated
	ok, msg, e := checker.ValidateMethods(o, "update", "create")

	if e != nil {
		handle(e)
	}
	fmt.Println(ok, msg)
}

4.superChecker验证多种注解

支持 int,float,time.time,regex,func,range,in

type Order struct {
	// TIME
	CreatedAt time.Time `validate:"time.time"`
	UpdatedAt string    `validate:"time.time,2006/01/02 15:04:05"`

	// INT
	Count    int `validate:"int,0:200"`
	MaxCount int `validate:"int,:200"`
	MinCount int `validate:"int,10:"`
	Count2   int `validate:"int64,0:200"`

	// FLOAT
	RewardRate    float64         `validate:"float,0:0.4"`
	MaxRewardRate float64         `validate:"float,:0.4"`
	MinRewradRate float64         `validate:"float,0:"`
	RewardRate2   float64         `validate:"float64,0:0.4"`
	RewardRate3   decimal.Decimal `validate:"decimal,0:0.4"`

	// REGEX
	OrderUsername  string `validate:"regex,^[\u4E00-\u9FA5a-zA-Z0-9_.]{0,40}$"`
	OrderUsername2 string `validate:"regex,username"`

	// RANGE,IN
	OrderStatus     int    `validate:"range,[1,2,3,4]"`
	OrderStatusName string `validate:"in,[unpaid,paid,closed]"`

	// FUNC, FUNCTION
	MailTypeCheckBox  string `validate:"func,inAndLength,lengthMoreThan3"`
	MailTypeCheckBox2 string `validate:"function,lengthLessThan3|inAndLength"`
}

func main() {
	order := Order{
		CreatedAt: time.Now(),
		UpdatedAt: time.Now().Format("2006/01/02 15:04:05"),

		Count:    200,
		MaxCount: 90,
		MinCount: 10,
		Count2:   100,

		RewardRate:    0.4,
		MaxRewardRate: 0.3,
		MinRewradRate: 0.1,
		RewardRate2:   0.1,
		RewardRate3:   decimal.NewFromFloat(0.4),

		OrderUsername:  "superCheckerValidate",
		OrderUsername2: "superCheckerValidate",

		OrderStatus:     3,
		OrderStatusName: "closed",

		MailTypeCheckBox:  "midMail",
		MailTypeCheckBox2: "midMail",
	}

	checker := superChecker.GetChecker()
	checker.AddFunc(func(in interface{}, fieldName string) (bool, string, error) {
		v := superChecker.ToString(in)
		maxLength := 7
		if len(v) > maxLength {
			return false, fmt.Sprintf("while validating field '%s', rule key '%s' over length,want %d ,but got %d", fieldName, "inAndLength", maxLength, len(v)), nil
		}
		vrange := []string{"midMail", "shenMail", "yundaMail"}
		for _, value := range vrange {
			if value == v {
				return true, "success", nil
			}
		}
		return false, fmt.Sprintf("while validating field '%s', rule key '%s',  value '%s' not in '%v'", fieldName, "inAndLength", v, vrange), nil
	}, "inAndLength")
	checker.AddFunc(func(in interface{}, fieldName string)(bool, string, error){
		v := superChecker.ToString(in)
		minLength := 3
		if len(v) < minLength {
			return false, fmt.Sprintf("while validating field '%s', rule key '%s' too short length,want %d ,but got %d", fieldName, "inAndLength", minLength, len(v)), nil
		}
		return true, "success", nil
	},"lengthmorethan3")

	checker.AddFunc(func(in interface{}, fieldName string)(bool, string, error){
		v := superChecker.ToString(in)
		maxLength := 3
		if len(v) > maxLength {
			return false, fmt.Sprintf("while validating field '%s', rule key '%s' too short length,want %d ,but got %d", fieldName, "inAndLength", maxLength, len(v)), nil
		}
		return true, "success", nil
	},"lengthlessthan3")

	ok, msg, er := checker.Validate(order)
	if er != nil {
		fmt.Println(fmt.Sprintf("got an error, '%s'", er.Error()))
		return
	}
	if !ok {
		fmt.Println(fmt.Sprintf("validate fail because of '%s'", msg))
		return
	}
	fmt.Println("success")
}

你可能感兴趣的:(go反射里,注解和函数成员调用的实例)