Go语言抽象自定义 Tag 及其处理方法

Go语言抽象自定义 Tag 及其处理方法


1、Go语言中Tag的定义

在Go语言中,结构体字段可以附带一个可选的字符串标签(Tag),该标签用于在运行时通过反射获取字段的元信息。标签是结构体字段后面紧跟着的一段以反引号包裹的字符串。Tag由一个或多个键值对组成,键值对中间由空格隔开。

type User struct {
    Name string `json:"name" db:"user_name"`
    Age  int    `json:"age" db:"user_age"`
}

在Go中,通过反射可以在运行时获取结构体字段的标签信息,用于动态地处理结构体。

type resume struct {
	Name string `json:"name" doc:"我的名字"`
}

func findDoc(stru interface{}) map[string]string {
	t := reflect.TypeOf(stru).Elem()
	doc := make(map[string]string)
	for i := 0; i < t.NumField(); i++ {
		doc[t.Field(i).Tag.Get("json")] = t.Field(i).Tag.Get("doc")
	}
	return doc
}

func main() {
	var stru resume
	doc := findDoc(&stru)
	fmt.Printf("name字段为:%s\n", doc["name"])
}

以上是一段示例,通过反射获取结构体的两个键值对的value并分别存放在Map的key、value中,最终输出为:

name字段为:我的名字

2、抽象自定义Tag及其处理方法的理解

上面讲到,Tag是可以动态获取的,而Tag本身就是支持自定义的(这也导致了Tag的编写不会做任何检查,就像注释一样,写错了不会有任何提示)。我们可以分别从开发者和使用者的角度来解读这个组件的核心:

从开发者的角度来看,假如需要新添一个对应Tag的处理器,我只需要编写新增的处理器具体的处理细节就可以了,我并不需要关心这个Tag的注册发现过程;

从使用者的角度来看,当我需要处理一个结构体变量中的所有Tag的时候,我只需要调用一个函数并传递这个变量就可以了,我并不需要关心这个变量中的Tag对应处理器的发现过程以及处理过程。

在这里插入图片描述


3、具体代码实现
3.1、处理器接口的编写

我们可以使用接口来定义处理器的规范,由于Tag相对字段纬度而言存在的,所以在处理器的入口我们需要两个参数,一个是该字段的所有信息,一个是该字段上需要处理的Tag的Value:

type TagProcess interface {
	Handle(interface{}, string)
}

由于字段类型的不同,我们用公共类型interface{}来接收。

3.2、存储结构的编写

接口的实现类放到后面再说,我们先讲处理器的注册发现。Tag和处理器应该是一对一的关系,所以我们可以用一个Map来存放,Tag就是string类型,处理器设为TagProcess即可:

type TagHandlerMap map[string]TagProcess

var TagHandlers TagHandlerMap = make(TagHandlerMap)
3.3、处理器实现类的编写

存储结构结构定义好之后就可以开始写实现类了,先看代码:

func init() {
  //注册Tag和对应的处理类
	RegisterTagProcessor("kmsx", KmsxProcessor{})
}

type KmsxProcessor struct {
}

func (processor KmsxProcessor) Handle(str reflect.Value, tag string) {
	fmt.Println(tag)
	fmt.Println(str, "处理中")
	fmt.Println("-------------------")
	str.SetString(str.String() + "已处理")
}

首先重写Handle函数,具体逻辑就是开发者需要做的事情了,可以看到这个函数拿到了字段的实例数据和Tag的Value。值得注意的是,由于传递的是指针,所以我们修改字段值,函数外的结构体变量的字段值也是会被修改的。

3.4、注册中心的编写

上面的init函数是被导入时go文件会优先执行的函数,我们可以利用init来实现处理器的注册。如上所说,存储器存储的是Tag和对应的处理器,由于实现了处理器接口,我们可以直接传递实现类。

// 为结构体注册tag处理方法
func RegisterTagProcessor(tag string, processor TagProcess) {
	if _, ok := TagHandlers[tag]; ok {
		fmt.Println(tag + "已经有处理方法!")
		return
	}

	//根据不同tag设置不同的处理方法
	TagHandlers[tag] = processor

	println(tag, "处理方法注册成功")
	println("-------------------")
}

注册也很简单,直接给Map赋值就可以了,Map是全局变量,需要获取处理器的时候直接获取就可以了。

3.5、全局处理器的编写

完成面向开发者部分的编写之后,我们开始编写面向使用者的部分。如上面所说,使用者只需要调用一个函数就可以根据Tag处理结构体变量,所以我们需要编写一个全局处理器来处理任意类型的结构体变量,先看代码:

func GlobalTagProcess[T any](data T) {
	if reflect.TypeOf(data).Kind() != reflect.Ptr {
		println("传递了非指针类型!")
		return
	}

	v := reflect.ValueOf(data)

	v = v.Elem()
	t := v.Type()

	//遍历每个字段
	for i := 0; i < v.NumField(); i++ {
		// 获取这个字段的tag
		tags := t.Field(i).Tag
		// 获取这个tag的所有key
		keys := GetAllKeys(tags)

		// 依次处理这个tag的所有key
		for _, key := range keys {
			handler, ok := TagHandlers[key]
			if ok {
        //tag有对应的处理器,传入该行数据和tag的value
				handler.Handle(v.Field(i), v.Type().Field(i).Tag.Get(key))
			} else {
				fmt.Println(key + "没有对应的处理器")
			}
		}
	}
}

思路很简单:

1、拿到传递进来的结构体变量;

2、获取结构体变量的实例数据和类型;

3、依次遍历每一个字段;

4、获取每行字段的所有Tag;

5、遍历该行的所有Tag,尝试从全局Map中获取对应的处理器并进行处理。

下面是我编写的获取单行所有Tag的Key的函数,但是存在一些问题,只做示例:

// GetAllKeys 从结构体标签中获取所有键
func GetAllKeys(tags reflect.StructTag) []string {
	keys := make(map[string]struct{})

	// 将结构体标签转换为字符串
	tagString := string(tags)

	// 按空格分隔标签字符串,得到每个键值对
	pairs := strings.Fields(tagString)
	for _, pair := range pairs {
		// 按冒号分隔键和值
		parts := strings.SplitN(pair, ":", 2)
		if len(parts) == 2 {
			// 提取键并加入到结果中
			key := strings.TrimSpace(parts[0])
			keys[key] = struct{}{}
		}
	}

	// 将 map 转换为切片
	result := make([]string, 0, len(keys))
	for key := range keys {
		result = append(result, key)
	}

	return result
}
3.6、测试数据
type User struct {
	Field1 string `kmsx:"type:json_key;field:field1,field2" abc:"123"`
	Field2 string `kmsx:"type:json_all"`
	Field3 string `kmsx:"type:string"`
}

func TestTagHandle(t *testing.T) {
	fmt.Println("tagHandle开始测试")

	user := &User{
		Field1: `{"field1":"a","field2":"b","field3":"c"}`,
		Field2: `{"field1":"a","field2":"b","field3":"c"}`,
		Field3: `{"string":"a","field2":"b","field3":"c"}`,
	}

	//传入结构体变量,执行对不同tag的处理
	fmt.Println("----------处理前----------")
	fmt.Println(user)
	GlobalTagProcess(user)
	fmt.Println("----------处理后----------")
	fmt.Println(user)
}
tagHandle开始测试
----------处理前----------
&{{"field1":"a","field2":"b","field3":"c"} {"field1":"a","field2":"b","field3":"c"} {"string":"a","field2":"b","field3":"c"}}
type:json_key;field:field1,field2
{"field1":"a","field2":"b","field3":"c"} 处理中
-------------------
abc没有对应的处理器
type:json_all
{"field1":"a","field2":"b","field3":"c"} 处理中
-------------------
type:string
{"string":"a","field2":"b","field3":"c"} 处理中
-------------------
----------处理后----------
&{{"field1":"a","field2":"b","field3":"c"}已处理 {"field1":"a","field2":"b","field3":"c"}已处理 {"string":"a","field2":"b","field3":"c"}已处理}

4、拓展

抽象自定义标签(Tag)及其处理方法在Go语言中具有很多潜在的用途,比如:

1、序列化和反序列化: 可以使用自定义标签来指定结构体字段在序列化为JSON、XML等格式时的名称,以及反序列化时从输入数据中读取字段的名称。这使得您可以控制数据的序列化和反序列化过程,使其与外部系统或服务的需求匹配。

2、ORM: 自定义标签可以用于指定结构体字段与数据库表列之间的映射关系。通过使用ORM库,可以根据这些标签自动创建数据库表、执行查询、插入和更新操作等,而无需手动编写SQL语句。

3、验证和校验: 可以使用自定义标签来指定结构体字段的验证规则和校验条件。例如,可以定义一个标签来指定字段的最小长度、最大长度、正则表达式等,然后在应用程序中执行输入数据的校验。

4、权限控制:自定义标签可以用于指定结构体字段的访问权限。例如,可以使用标签来标识哪些字段是敏感信息,哪些字段是公开信息,然后在应用程序中根据用户角色或权限级别来控制对这些字段的访问。

5、开源地址

https://gitee.com/DothmZark/goTagHandler

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值