zoukankan      html  css  js  c++  java
  • Golang 反射简单应用--参数校验

    以下内容为个人学习总结,如果有不准确的地方,欢迎指出!

    说实话我之前用Python基本没怎么用过反射,估计在Golang里面也一样,在大多数应用和服务中并不常见。

    提到反射,就必须要提一下Golang反射的三大定律

    • 1 可以将interface{}类型转换为reflect类型。
    • 2 通过反射对象可以获取 interface{} 变量。
    • 3 值是否可以被更改,能被寻址。(概念不好理解,后面demo解释)

    原文

    • 1 Reflection goes from interface value to reflection object.
    • 2 Reflection goes from reflection object to interface value.
    • 3 To modify a reflection object, the value must be settable.
      三大定律原出处:
      https://blog.golang.org/laws-of-reflection

    反射的一般使用场景

    • 不确定预定类型的参数,需要根据参数的类型来执行不同的操作。

    当然也可以使用Assertion来判断类型,但是这种方式非常麻烦。

    // 通过断言判断类型
    func AssertionType(v interface{}){
    	switch v.(type){
    	case int, int16, int32, int64:
    		fmt.Printf("整数类型 %d 
    ", v)
    	case userInfo:
    		fmt.Printf("是 userInfo 类型 %v  ", v)
    		fmt.Printf("年龄 %d 
    ", v.(userInfo).Age)
    		// 断言调用方法
    		v.(userInfo).SayAge()
    	default:
    		fmt.Println("没有匹配到")
    	}
    }
    
    

    "但是我们如何处理其它类似[]float64、map[string][]string等类型呢?我们当然可以添加更多的测试分支,但是这些组合类型的数目基本是无穷的。还有如何处理类似url.Values这样的具名类型呢?即使类型分支可以识别出底层的基础类型是map[string][]string,但是它并不匹配url.Values类型,因为它们是两种不同的类型,而且switch类型分支也不可能包含每个类似url.Values的类型,这会导致对这些库的依赖。"--出自Go语言程序设计

    没有办法来检查未知类型的表示方式,我们被卡住了。这就是我们为何需要反射的原因。

    Golang中反射常用的方法

    • reflect.TypeOf 获取类型 以及相关信息(对应第一条定律)
    • reflect.ValueOf 获取数据的运行时的表示(对应第二条定律)

    其他基础方法(部分)

    • 结构体反射常用 假设user为实例化结构体
      • reflect.TypeOf(user).Field(int) // 获取 指定结构体下角标属性的类型
      • reflect.TypeOf(user).Field(int).Tag.Get("foo") // 获取指定下角标属性的结构体标签 内部再调用 Lookup方法
      • reflect.ValueOf(user).NumField() // 获取结构体属性数量
      • reflect.ValueOf(user).Kind() // 获取对应的类型
      • reflect.ValueOf(user).MethodByName("SayName").Call([]reflect.Value{reflect.ValueOf("测试")}) // 传参数调用方法
    • map slice获取获取 (n为map或者slice)
      • reflect.ValueOf(n).Len() // 获取长度

    一些关于反射的演示

    // 创建一个结构体
    type userInfo struct {
    	User     string `json:"user"foo:"test_tag"`
    	Avatar   string `json:"avatar"bar:""`
    	Age      int    `json:"age"`
    }
    // 定义两个方法
    func (u userInfo) SayName(name string){
    	fmt.Printf("用户名是 %s
    ", name)
    }
    // 方法
    func (u userInfo) SayAge(){
    	fmt.Printf("年龄是 %d
    ", u.Age)
    }
    
    func TestReflect(t *testing.T) {
    
    	// 实例化结构体
    	user := userInfo{User: "Nike", Age:18}
    
    	// 断言判断类型 功能不如反射强大
    	AssertionType(user)
    	AssertionType(1111)
    
    	typ := reflect.TypeOf(user) // 获取reflect的类型
    	t.Log(typ)
    
    	val := reflect.ValueOf(user) // 获取reflect的值
    	t.Log(val)
    
    	kd := val.Kind() // 获取到st对应的类别
    	t.Log(kd)
    
    	num := val.NumField() // 获取值字段的数量
    	t.Log(num)
    
    	// 通过反射调用方法
    	m1 := val.MethodByName("SayName")
    	//m1 := val.Method(0)
    	m1.Call([]reflect.Value{reflect.ValueOf("测试")}) // 传参数
    	// 私有方法不可反射调用  Java反射可以暴力调用私有方法
    	m2 := val.MethodByName("SayAge")
    	m2.Call([]reflect.Value{}) // 不传参数
    
    	tagVal := typ.Field(2) // 获取index为2的类型信息
    	val = val.Field(2)     // 获取index为2实例化后的值
    
    	t.Log(tagVal)
    	t.Log(val)
    	t.Log(tagVal.Type) // 输出结构体字段的类型
    	t.Log(tagVal.Name) // 输出结构体字段名称
    
    	// 获取结构体的tag 没有则为空 Get 实际就是调用的Lookup
    	t.Log(tagVal.Tag.Get("foo"))
    	// 返回两个值 第一个为tag值 第二个为bool值 true表示设置了此tag 无论是否为空字符串
    	t.Log(tagVal.Tag.Lookup("foo"))
    	t.Log(typ.Field(1).Tag.Lookup("bar"))     // 设置了tag为bar 但是为空字符串 依旧为true
    	t.Log(typ.Field(1).Tag.Lookup("any_tag")) // 没有设置此tag 就为false
    
    	// 必须使用地址 才可以修改原来的值 否则会panic (反射第三定律,值可以被修改)
    	modifyVal(&user)
    
    	t.Log(user)
    }
    
    // 通过反射修改值
    func modifyVal(user interface{}){
    
    	// 获取变量的指针
    	pVal := reflect.ValueOf(user) // 获取reflect的值
    
    	// 获取指针指向的变量
    	v := pVal.Elem()
    	// 找到并更新变量的值
    	v.FieldByName("User").SetString("Jack")
    
    }
    

    反射的简单应用

    最简单的例子,通过反射对比slice或者map是否相等

    规定Golang中slice,map,func不能用 == 比较
    https://stackoverflow.com/questions/37900696/why-cant-go-slice-be-used-as-keys-in-go-maps-pretty-much-the-same-way-arrays-ca
    https://golang.org/ref/spec#Comparison_operators

    sliceA := []int{1, 2, 3}
    sliceB := []int{1, 2, 3}
    //fmt.Println(sliceA == sliceB) // panic 
    // 可以用反射对比
    fmt.Println(reflect.DeepEqual(sliceA, sliceB)) 
    

    利用反射设计一个校验参数的方法

    主要参考 gin-vue-admin 参数验证 我自己添加了一个正则校验的方法。
    比如定义一个verify来验证结构体里面的方法是否合法

    // 存放验证规则的地方 一个字段可以有多个校验方法
    type Rules map[string][]string
    
    var (
    	UserInfoVerify = Rules{"Page": {Ge("1")}, "PageSize": {Le("50")}, "Name": {Regex(`^d{3}$`), NotEmpty()}}
    )
    
    type UserInfo struct {
    	Page     int    `json:"page"`
    	PageSize int    `json:"page_size"`
    	Name     string `json:"name"`
    }
    
    func TestVerify(t *testing.T) {
    
    	// 参数信息  可以和请求的参数信息绑定
    	u := UserInfo{Page: 1, PageSize: 30, Name: "234"} // 合法
    	//u := UserInfo{Page:1, PageSize:30, Name: "1234"} // Name非法
    
    	// 验证参数是否合法 verify 自定义的校验蚕农书
    	if err := verify(u, UserInfoVerify); err != nil {
    		t.Log(fmt.Sprintf("验证失败 %s 
    ", err))
    	} else {
    		t.Log("success")
    	}
    }
    
    // 核心函数
    func verify(st interface{}, roleMap Rules) (err error) {
    
    	// 限定 比较返回值为 以下几个
    	compareMap := map[string]bool{
    		"lt": true,
    		"le": true,
    		"eq": true,
    		"ne": true,
    		"ge": true,
    		"gt": true,
    	}
    
    	typ := reflect.TypeOf(st)
    	val := reflect.ValueOf(st)
    
    	// 判断待验证参数 是否是结构体 不是直接返回错误
    	if val.Kind() != reflect.Struct {
    		return errors.New("expect struct")
    	}
    
    	// 遍历结构体的所有字段
    	for i := 0; i < val.NumField(); i++ {
    
    		// 获取反射后的具体字段
    		tagVal := typ.Field(i)
    		val := val.Field(i)
    
    		// 判断此字段是否有校验规则 >0 则说明有
    		if len(roleMap[tagVal.Name]) > 0 {
    
    			// 循环此字段的校验规则(一个字段可以存在多个校验规则)  规则为 各个判断类型函数 返回值
    			for _, v := range roleMap[tagVal.Name] {
    				switch {
    				// 非空判断
    				case v == "notEmpty":
    					if isBlank(val) {
    						return errors.New(tagVal.Name + "值不能为空")
    					}
    				// 正则校验
    				case strings.Split(v, "=")[0] == "regex":
    					if !isRegexMatch(val.String(), v) {
    						return errors.New(tagVal.Name + "正则校验不合法" + v)
    					}
    
    				// 比较符判断 分割返回值里面的 = 符号  compareMap 确保输入的函数正确
    				case compareMap[strings.Split(v, "=")[0]]:
    					// 比较值    val 为反射字段后的值  v为 lt=1等校验值
    					if !compareVerify(val, v) {
    						return errors.New(tagVal.Name + "长度或值不在合法范围," + v)
    					}
    				default:
    					fmt.Println("检查 Rules 校验函数输入是否正确: " + v)
    				}
    			}
    		}
    	}
    	return nil
    }
    

    以上代码不完整,完整代码见GitHub地址

    反射应用参数校验代码地址

    原文地址见个人博客

    参数校验GitHub地址

  • 相关阅读:
    【Qt】splitter
    android 使用AsyncHttpClient框架上传文件以及使用HttpURLConnection下载文件
    poj 1879 Truck History
    在LinuxMint中对firefox进行手动安装flash插件
    王立平--include在Android中的应用
    【IPC进程间通讯之二】管道Pipe
    我组织类时无意间遵守了依赖倒置原则
    百度2016笔试(算法春招实习)
    制作翻转效果动画
    vim常用命令行备忘总结
  • 原文地址:https://www.cnblogs.com/CharmCode/p/14315474.html
Copyright © 2011-2022 走看看