From b5a1909eb0e83788f2dab6b606a384fd83234fdb Mon Sep 17 00:00:00 2001 From: Huangzj Date: Thu, 7 Jan 2021 09:23:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(Go-StudyExample):=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=202021/01/07?= =?UTF-8?q?=EF=BC=9A=E6=96=B0=E5=A2=9Ecopier=E6=BA=90=E7=A0=81=E9=98=85?= =?UTF-8?q?=E8=AF=BB=E5=92=8C=E5=8D=95=E6=B5=8B=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SourceAnalysisAndTool/Readme.md | 5 + SourceAnalysisAndTool/jinzhu/copier/Guardfile | 3 + SourceAnalysisAndTool/jinzhu/copier/License | 20 + SourceAnalysisAndTool/jinzhu/copier/README.md | 130 ++++++ SourceAnalysisAndTool/jinzhu/copier/copier.go | 398 ++++++++++++++++++ SourceAnalysisAndTool/jinzhu/copier/go.mod | 3 + example/copier/Copier_test.go | 6 + readme.md | 2 + 8 files changed, 567 insertions(+) create mode 100644 SourceAnalysisAndTool/Readme.md create mode 100644 SourceAnalysisAndTool/jinzhu/copier/Guardfile create mode 100644 SourceAnalysisAndTool/jinzhu/copier/License create mode 100644 SourceAnalysisAndTool/jinzhu/copier/README.md create mode 100644 SourceAnalysisAndTool/jinzhu/copier/copier.go create mode 100644 SourceAnalysisAndTool/jinzhu/copier/go.mod diff --git a/SourceAnalysisAndTool/Readme.md b/SourceAnalysisAndTool/Readme.md new file mode 100644 index 0000000..d53c86e --- /dev/null +++ b/SourceAnalysisAndTool/Readme.md @@ -0,0 +1,5 @@ +validate.V8 源码分析文章:https://blog.csdn.net/qq_34326321/article/details/111030128 + +container源码分析文章:https://blog.csdn.net/qq_34326321/article/details/110804660 + +copier源码分析文章:https://blog.csdn.net/qq_34326321/article/details/112177087 diff --git a/SourceAnalysisAndTool/jinzhu/copier/Guardfile b/SourceAnalysisAndTool/jinzhu/copier/Guardfile new file mode 100644 index 0000000..0b860b0 --- /dev/null +++ b/SourceAnalysisAndTool/jinzhu/copier/Guardfile @@ -0,0 +1,3 @@ +guard 'gotest' do + watch(%r{\.go$}) +end diff --git a/SourceAnalysisAndTool/jinzhu/copier/License b/SourceAnalysisAndTool/jinzhu/copier/License new file mode 100644 index 0000000..e2dc538 --- /dev/null +++ b/SourceAnalysisAndTool/jinzhu/copier/License @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Jinzhu + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/SourceAnalysisAndTool/jinzhu/copier/README.md b/SourceAnalysisAndTool/jinzhu/copier/README.md new file mode 100644 index 0000000..1c725e6 --- /dev/null +++ b/SourceAnalysisAndTool/jinzhu/copier/README.md @@ -0,0 +1,130 @@ +# Copier + + I am a copier, I copy everything from one to another + +[![test status](https://github.com/jinzhu/copier/workflows/tests/badge.svg?branch=master "test status")](https://github.com/jinzhu/copier/actions) + +## Features + +* Copy from field to field with same name +* Copy from method to field with same name +* Copy from field to method with same name +* Copy from slice to slice +* Copy from struct to slice +* Copy from map to map +* Enforce copying a field with a tag +* Ignore a field with a tag + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/jinzhu/copier" +) + +type User struct { + Name string + Role string + Age int32 + + // Explicitly ignored in the destination struct. + Salary int +} + +func (user *User) DoubleAge() int32 { + return 2 * user.Age +} + +// Tags in the destination Struct provide instructions to copier.Copy to ignore +// or enforce copying and to panic or return an error if a field was not copied. +type Employee struct { + // Tell copier.Copy to panic if this field is not copied. + Name string `copier:"must"` + + // Tell copier.Copy to return an error if this field is not copied. + Age int32 `copier:"must,nopanic"` + + // Tell copier.Copy to explicitly ignore copying this field. + Salary int `copier:"-"` + + DoubleAge int32 + EmployeId int64 + SuperRule string +} + +func (employee *Employee) Role(role string) { + employee.SuperRule = "Super " + role +} + +func main() { + var ( + user = User{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 200000} + users = []User{{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 100000}, {Name: "jinzhu 2", Age: 30, Role: "Dev", Salary: 60000}} + employee = Employee{Salary: 150000} + employees = []Employee{} + ) + + copier.Copy(&employee, &user) + + fmt.Printf("%#v \n", employee) + // Employee{ + // Name: "Jinzhu", // Copy from field + // Age: 18, // Copy from field + // Salary:150000, // Copying explicitly ignored + // DoubleAge: 36, // Copy from method + // EmployeeId: 0, // Ignored + // SuperRule: "Super Admin", // Copy to method + // } + + // Copy struct to slice + copier.Copy(&employees, &user) + + fmt.Printf("%#v \n", employees) + // []Employee{ + // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"} + // } + + // Copy slice to slice + employees = []Employee{} + copier.Copy(&employees, &users) + + fmt.Printf("%#v \n", employees) + // []Employee{ + // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRule: "Super Admin"}, + // {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeId: 0, SuperRule: "Super Dev"}, + // } + + // Copy map to map + map1 := map[int]int{3: 6, 4: 8} + map2 := map[int32]int8{} + copier.Copy(&map2, map1) + + fmt.Printf("%#v \n", map2) + // map[int32]int8{3:6, 4:8} +} +``` + +### Copy with Option + +```go +copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true}) +``` + +## Contributing + +You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do. + +# Author + +**jinzhu** + +* +* +* + +## License + +Released under the [MIT License](https://github.com/jinzhu/copier/blob/master/License). diff --git a/SourceAnalysisAndTool/jinzhu/copier/copier.go b/SourceAnalysisAndTool/jinzhu/copier/copier.go new file mode 100644 index 0000000..ec0c0ff --- /dev/null +++ b/SourceAnalysisAndTool/jinzhu/copier/copier.go @@ -0,0 +1,398 @@ +package copier + +import ( + "database/sql" + "errors" + "fmt" + "reflect" + "strings" +) + +// These flags define options for tag handling +const ( + // Denotes that a destination field must be copied to. If copying fails then a panic will ensue. + tagMust uint8 = 1 << iota + + // Denotes that the program should not panic when the must flag is on and + // value is not copied. The program will return an error instead. + tagNoPanic + + // Ignore a destation field from being copied to. + tagIgnore + + // Denotes that the value as been copied + hasCopied +) + +// Copy copy things +func Copy(toValue interface{}, fromValue interface{}) (err error) { + return copy(toValue, fromValue, false) +} + +type Option struct { + IgnoreEmpty bool +} + +// CopyWithOption copy with option +func CopyWithOption(toValue interface{}, fromValue interface{}, option Option) (err error) { + return copy(toValue, fromValue, option.IgnoreEmpty) +} + +func copy(toValue interface{}, fromValue interface{}, ignoreEmpty bool) (err error) { + var ( + isSlice bool + amount = 1 + from = indirect(reflect.ValueOf(fromValue)) //read note 判断是指针还是结构体类型来返回对应的参数的值 + to = indirect(reflect.ValueOf(toValue)) //read note 判断是指针还是结构体类型来返回对应的参数的值 + ) + + //read note 不可寻址的数据类型可以参考:https://github.com/hyper0x/Golang_Puzzlers/blob/master/src/puzzlers/article15/q1/demo35.go + if !to.CanAddr() { + return errors.New("copy to value is unaddressable") + } + + // Return is from value is invalid + if !from.IsValid() { + return + } + + //read note 返回参数的具体类型 + fromType := indirectType(from.Type()) + toType := indirectType(to.Type()) + + // Just set it if possible to assign + // And need to do copy anyway if the type is struct + //read note 判断是否非结构体并且可以直接赋值 + if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) { + to.Set(from) + return + } + + //read note from和to都是map + if fromType.Kind() == reflect.Map && toType.Kind() == reflect.Map { + //read note 判断map的key的结构类型是否可以转换,因为这边已经判断是map,所以直接通过 .key来获取,不担心是否报错 + if !fromType.Key().ConvertibleTo(toType.Key()) { + return + } + //read note 判断要转换的Map是否为空,为空则进行初始化 + if to.IsNil() { + to.Set(reflect.MakeMapWithSize(toType, from.Len())) + } + //read note 遍历Map的所有key + for _, k := range from.MapKeys() { + //read note 根据to的key类型创建一个新的key + toKey := indirect(reflect.New(toType.Key())) + //read note 设置key的值 + if !set(toKey, k) { + continue + } + + //read note 设置value值 + toValue := indirect(reflect.New(toType.Elem())) + if !set(toValue, from.MapIndex(k)) { + //read note 对嵌套的结构体进行copy + err = Copy(toValue.Addr().Interface(), from.MapIndex(k).Interface()) + if err != nil { + continue + } + } + to.SetMapIndex(toKey, toValue) + } + } + + //read note 只要有一个数据结构不是结构体直接返回? + if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { + return + } + + //read note 切片处理:设置切片的长度 + + //read note 如果Result是Slice类型 + if to.Kind() == reflect.Slice { + //read note 设置isSlice + isSlice = true + //read note amount初始是1,如果被复制的对象也是数组,则这边修改成数组长度,否则1表示只处理结构体 + if from.Kind() == reflect.Slice { + amount = from.Len() + } + } + + //read note 循环被复制的切片的长度 + for i := 0; i < amount; i++ { + var dest, source reflect.Value + + //read note Result的结果是数组 + if isSlice { + // source + // read note 如果Origin的类型是数组,需要根据index进行获取结构体 + if from.Kind() == reflect.Slice { + source = indirect(from.Index(i)) + } else { + // read note 如果Origin的类型是结构体,直接获取该结构体 + source = indirect(from) + } + // dest + dest = indirect(reflect.New(toType).Elem()) + } else { + //read note Result的结果不是数组,直接获取结构体 + source = indirect(from) + dest = indirect(to) + } + + // Get tag options + //read note 获取tag的所有标签 + tagBitFlags := map[string]uint8{} + if dest.IsValid() { + //read note 根据结构体type获取所有的Field对应的tag标签 + tagBitFlags = getBitFlags(toType) + } + + // check source + + //read note 如果是非零值 + if source.IsValid() { + //read note 获取结构体的所有Field的信息(数组) + fromTypeFields := deepFields(fromType) + // fmt.Printf("%#v", fromTypeFields) + // Copy from field to field or method + + //todo 循环所有的Field处理 + for _, field := range fromTypeFields { + name := field.Name + + // Get bit flags for field + //read note 根据name获取tag数据 + fieldFlags, _ := tagBitFlags[name] + + // Check if we should ignore copying + //read note ignore标签对结构体的影响处理 + if (fieldFlags & tagIgnore) != 0 { + continue + } + + //read note Origin的方法和Result字段同名的处理 + + if fromField := source.FieldByName(name); fromField.IsValid() && !shouldIgnore(fromField, ignoreEmpty) { + // has field + + //read note 根据名称获取Result结构体的字段. + if toField := dest.FieldByName(name); toField.IsValid() { + if toField.CanSet() { + if !set(toField, fromField) { + if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil { + return err + } + } else { + //read note 赋值完成,设置对应的标识 + if fieldFlags != 0 { + // Note that a copy was made + + //read note 设置复制标识 + tagBitFlags[name] = fieldFlags | hasCopied + } + } + } + } else { + // try to set to method + var toMethod reflect.Value + //read note 通过名称找到对应的Method + if dest.CanAddr() { + toMethod = dest.Addr().MethodByName(name) + } else { + toMethod = dest.MethodByName(name) + } + //read note 【被转换对象的方法调用】的校验还比较严格,这边可以看出来只能有一个字段,并且字段类型要对应上 + if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { + //read note 调用声明的方法 + toMethod.Call([]reflect.Value{fromField}) + } + } + } + } + + //read note Result方法 与Origin字段同名的处理 + + // Copy from method to field + //read note 处理目标结构体的方法,目标结构体的方法要和被复制结构体的字段名一致,就是这边控制的 + for _, field := range deepFields(toType) { + name := field.Name + + //read note 根据Result的字段,获取Origin同名的方法 + var fromMethod reflect.Value + if source.CanAddr() { + fromMethod = source.Addr().MethodByName(name) + } else { + fromMethod = source.MethodByName(name) + } + + //read note 如果方法符合规则,没有入参,有一个出参,则进行对应方法的调用处理 + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, ignoreEmpty) { + if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() { + values := fromMethod.Call([]reflect.Value{}) + if len(values) >= 1 { + //read note 进行字段的设值 + set(toField, values[0]) + } + } + } + } + } + + //read note 转换结果Result是切片的处理:分成两种情况,被复制的是 结构体指针 和 结构体 + if isSlice { + if dest.Addr().Type().AssignableTo(to.Type().Elem()) { + to.Set(reflect.Append(to, dest.Addr())) + } else if dest.Type().AssignableTo(to.Type().Elem()) { + to.Set(reflect.Append(to, dest)) + } + } + //read note 这边是不是会有一个问题,就是err是不是会被覆盖,前面的字段有错误,最后一个没有错误则会覆盖之前的error + err = checkBitFlags(tagBitFlags) + } + return +} + +func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool { + if !ignoreEmpty { + return false + } + + return v.IsZero() +} + +//read note 根据结构体类型,获取结构体对应的Field切片,注意这边的【Anonymous】表示的匿名变量,匿名变量的Field这边需要特殊处理 +func deepFields(reflectType reflect.Type) []reflect.StructField { + var fields []reflect.StructField + + //read note 判断是不是结构体,只能对结构体进行处理 + if reflectType = indirectType(reflectType); reflectType.Kind() == reflect.Struct { + //read note 循环处理对应的所有Field + for i := 0; i < reflectType.NumField(); i++ { + v := reflectType.Field(i) + //read note 对【嵌入(匿名)字段】结构体 的所有结构体进行添加 + if v.Anonymous { + fields = append(fields, deepFields(v.Type)...) + } else { + fields = append(fields, v) + } + } + } + + return fields +} + +func indirect(reflectValue reflect.Value) reflect.Value { + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + return reflectValue +} + +func indirectType(reflectType reflect.Type) reflect.Type { + for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice { + reflectType = reflectType.Elem() + } + return reflectType +} + +func set(to, from reflect.Value) bool { + + //read note IsValid返回是否非零值的结果.所以这边的处理是针对非零值 + if from.IsValid() { + + //read note 前置条件:处理to的类型,如果from为空,则直接设置空值返回 + + // to是指针类型特殊处理 + if to.Kind() == reflect.Ptr { + // set `to` to nil if from is nil + //read note 如果from是空,则直接设置to为零值返回 + if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) + return true + } else if to.IsNil() { + //read note 如果to是nil且不满足上面的from为nil的条件,这个时候要给to设置默认值 + to.Set(reflect.New(to.Type().Elem())) + } + //read note 指针的转换处理 + to = to.Elem() + } + + //read note from和to类型的转换处理,这边当from是ptr类型的时候,会调用set进行递归处理 + + //read note 如果类型可以进行转换,则要设置对应的值(具体什么类型可以转换需要看一下源码,这里不多赘述) + if from.Type().ConvertibleTo(to.Type()) { + to.Set(from.Convert(to.Type())) + } else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok { + //read note sql.Scanner 这个不知道具体是干嘛的. + err := scanner.Scan(from.Interface()) + if err != nil { + return false + } + } else if from.Kind() == reflect.Ptr { + //read note from是指针类型,处理成结构体进行赋值(相当于递归再往下走) + return set(to, from.Elem()) + } else { + //read note 其他不能转换的直接返回false + return false + } + } + + //read note 零值直接返回true,零值不处理 + return true +} + +// parseTags Parses struct tags and returns uint8 bit flags. +func parseTags(tag string) (flags uint8) { + for _, t := range strings.Split(tag, ",") { + switch t { + case "-": + flags = tagIgnore + return + case "must": + flags = flags | tagMust + case "nopanic": + flags = flags | tagNoPanic + } + } + return +} + +// getBitFlags Parses struct tags for bit flags. +func getBitFlags(toType reflect.Type) map[string]uint8 { + //read note 存储的结构是 FieldName->tag对应的二进制数据(tag标签转换成程序标识) + flags := map[string]uint8{} + //read note 根据结构体的类型获取对应的Field切片 + toTypeFields := deepFields(toType) + + // Get a list dest of tags + //read note 循环Field切片,获取切片对应的tag数据 + for _, field := range toTypeFields { + tags := field.Tag.Get("copier") //tag标签是【copier】 + if tags != "" { + //read note tag标签转换成程序处理标识(这边也是使用二进制的处理方式) + flags[field.Name] = parseTags(tags) + } + } + return flags +} + +// checkBitFlags Checks flags for error or panic conditions. +func checkBitFlags(flagsList map[string]uint8) (err error) { + // Check flag conditions were met + //read note 循环map(FieldName->tag对应的二进制数据) + for name, flags := range flagsList { + //read note 如果字段没有被复制 + if flags&hasCopied == 0 { + switch { + case flags&tagMust != 0 && flags&tagNoPanic != 0: + //read note 处理1:返回错误信息 + err = fmt.Errorf("Field %s has must tag but was not copied", name) + return + case flags&(tagMust) != 0: + //read note 处理2:直接报错 + panic(fmt.Sprintf("Field %s has must tag but was not copied", name)) + } + } + } + return +} diff --git a/SourceAnalysisAndTool/jinzhu/copier/go.mod b/SourceAnalysisAndTool/jinzhu/copier/go.mod new file mode 100644 index 0000000..531422d --- /dev/null +++ b/SourceAnalysisAndTool/jinzhu/copier/go.mod @@ -0,0 +1,3 @@ +module github.com/jinzhu/copier + +go 1.15 diff --git a/example/copier/Copier_test.go b/example/copier/Copier_test.go index dee3c52..4d7908f 100644 --- a/example/copier/Copier_test.go +++ b/example/copier/Copier_test.go @@ -54,4 +54,10 @@ func TestCopier(t *testing.T) { //不同的结构体数组的转换 _ = copier.Copy(&employees, &users) fmt.Println(fmt.Sprintf("%#v\n", employee)) + + i := 10 + var j int + err := copier.Copy(&i, &j) + + fmt.Println(err, j) } diff --git a/readme.md b/readme.md index 35e605a..9387146 100644 --- a/readme.md +++ b/readme.md @@ -49,6 +49,8 @@ json与struct之间转换处理工具使用示例 2020/12/31:添加validator.v8源码解析和使用示例 +2021/01/07:新增copier源码阅读和单测代码 + # mod vendor模式加载包 通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到