更新README
This commit is contained in:
		
							
								
								
									
										113
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								README.md
									
									
									
									
									
								
							| @ -1,5 +1,7 @@ | ||||
| # Golang 运行时动态结构体 | ||||
|  | ||||
| ## 简介 | ||||
|  | ||||
| 主要提供在运行时动态生成结构体的能力, 同时支持合并多个已有结构体,生成一个新的结构体 | ||||
|  | ||||
| 主要功能如下: | ||||
| @ -11,53 +13,82 @@ | ||||
| * 修改已存在字段的类型以及Tag标签 | ||||
| * 读取动态结构体字段的Helper | ||||
| * 动态结构体的值, 解析到一个已定义的结构体中 | ||||
| * 懂动态结构体生成slice或者map实例 | ||||
| * 动态结构体生成slice或者map实例 | ||||
| * 支持嵌套的结构体 | ||||
| * 支持N维数组 | ||||
| * 支持嵌套的结构体数组 | ||||
|  | ||||
| ## 开发背景 | ||||
|  | ||||
| 开发个人网关项目时, 面临一个问题: **`如何验证网关请求参数的合法性?`** 在Golang生态中,比较知名的参数验证库是 [https://github.com/go-playground/validator](go-playground/validator/v10)。 其支持的参数验证方式如下: | ||||
|  | ||||
| - 单个值按照指定规则进行验证, 无上下文信息 | ||||
| - map数据验证, 无上下文信息, 可以看做是单个值的验证的批量版本 | ||||
| - 结构体数据验证, 支持上下文信息, 可以跨结构体字段进行关联验证 | ||||
|  | ||||
| 网关参数验证的需求背景如下: | ||||
|  | ||||
| - 基础的参数验证, 如取值范围、长度等 | ||||
| - 数据格式校验,如:IP地址、URL、邮箱、手机号等 | ||||
| - 数据关联校验,如:两个字段的值必须相等、A字段不存在,则B字段必须存在等 | ||||
| - ...... | ||||
|  | ||||
| 综合数据验证库提供的验证能力, 以及业务场景的复杂性, 需要使用结构体数据进行参数验证。那么, 此时则面临另外一个问题: 不同的网关接口是有不同的参数结构的, 如何在运行时动态生成结构体呢? 这就需要用到本文所介绍的动态结构体生成能力。 | ||||
|  | ||||
| ## 快速开始 | ||||
|  | ||||
| ```bash | ||||
| # 安装 | ||||
| go get -v -u github.com/liuxiaobopro/golang-dynamic-struct | ||||
| ``` | ||||
|  | ||||
| ## 使用示例 | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
|  | ||||
| 	"git.zhangdeman.cn/zhangdeman/dynamic-struct" | ||||
| 	dynamicstruct "git.zhangdeman.cn/zhangdeman/dynamic-struct" | ||||
| 	"github.com/go-playground/validator/v10" | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	instance := NewStruct(). | ||||
| 		AddField("Integer", "", 0, `json:"int"`, false). | ||||
| 		AddField("Text", "", "", `json:"someText"`, false). | ||||
| 		AddField("Float", "", 0.0, `json:"double"`, false). | ||||
| 		AddField("Boolean", "", false, "", false). | ||||
| 		AddField("Slice", "", []int{}, "", false). | ||||
| 		AddField("Anonymous", "", "", `json:"-"`, false). | ||||
| 		Build(). | ||||
| 		New() | ||||
| 	data := []byte(` | ||||
| { | ||||
|     "int": 123, | ||||
|     "someText": "example", | ||||
|     "double": 123.45, | ||||
|     "Boolean": true, | ||||
|     "Slice": [1, 2, 3], | ||||
|     "Anonymous": "avoid to read" | ||||
| } | ||||
| `) | ||||
|  | ||||
| 	err := json.Unmarshal(data, &instance) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	data, err = json.Marshal(instance) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println(string(data)) | ||||
| 	// Out: | ||||
| 	// {"int":123,"someText":"example","double":123.45,"Boolean":true,"Slice":[1,2,3]} | ||||
| 	// 动态生成一个结构体 | ||||
| 	ds := dynamicstruct.NewStruct() | ||||
| 	ds = ds.AddField("name", "", `json:"name" validate:"required"`) | ||||
| 	ds = ds.AddField("age", 0, `json:"age" validate:"gte=0,lte=130"`) | ||||
| 	ds = ds.AddField("email", "", `json:"email" validate:"required,email"`) | ||||
| 	// 生成结构体实例 | ||||
| 	user := ds.Build().New() | ||||
| 	userTestData := `{ | ||||
| 		"name": "张三", | ||||
| 		"age": 180, | ||||
| 		"email": "test@qq.com", | ||||
| 	}` | ||||
| 	validatorInstance = validator.New() | ||||
| 	validatorInstance.SetTagName("validate") | ||||
| 	err := validatorInstance.Struct(user) | ||||
| 	fmt.Println(err) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## 实现原理 | ||||
|  | ||||
| 无论任何语言, 但凡出现 **`动态`** 这一类字眼, 实现原理一定脱离不了 **`反射`** , 且 **`反射是核心实现技术`** , 本库中的实现原理也不例外, 也是基于反射进行实现的。核心逻辑是利用 **`reflect.New`** 这一泛实例化数据的机制进行实现. 确定原理方向, **`抽象的需求运行时动态生成不定结构的结构体`** 已经转化为具体功能诉求, **`如何利用reflect.New这一机制, 动态实例化结构体`** | ||||
|  | ||||
| ## 具体实现 | ||||
|  | ||||
| 核心就是一句代码: | ||||
|  | ||||
| ```go | ||||
| structFields := []reflect.StructField | ||||
| reflect.New(reflect.StructOf(structFields)) | ||||
| ``` | ||||
| 其中, **`structFields`** 是一个 **`一个结构体字段配置列表,其可配置的属性具体查看即可`**, **`reflect.StructOf`** 则是根据结构体字段配置反射出结构体类型, 利用 **`reflect.New`** 这一机制, 可以动态实例化一个 **`结构体`**。 | ||||
|  | ||||
| 核心逻辑就是这么简单, 但是, 动态生成结构体的过程中, 还需要考虑到很多其他的问题, 如: | ||||
|  | ||||
| - 如何动态生成嵌套的结构体 | ||||
| - 如何动态生成嵌套的结构体数组 | ||||
| - 如何动态生成N维数组 | ||||
| - 如何动态生成结构体数组 | ||||
|  | ||||
| 其实上述逻辑无非是基于最原始的基础逻辑, **`递归进行实现`** , 所以, 具体实现细节, 可以查看源码。 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user