diff --git a/go.mod b/go.mod index 6ffce81..ab84572 100644 --- a/go.mod +++ b/go.mod @@ -8,16 +8,18 @@ require ( git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240130062251-a87a97b0e8d4 git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240110090803-399e964daa0c github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 + github.com/stretchr/testify v1.8.4 ) require ( git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240104123641-b3f23974e5d6 // indirect git.zhangdeman.cn/zhangdeman/util v0.0.0-20231227095334-7eb5cdbf9253 // indirect github.com/BurntSushi/toml v1.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mozillazg/go-pinyin v0.20.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 74f2e60..8f4228b 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,7 @@ -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20230815040024-2b12dd51d19b h1:C7KftnLh7dOqzNRs5dn/9yqMDvuqMn5RCglvV6bY758= -git.zhangdeman.cn/zhangdeman/consts v0.0.0-20230815040024-2b12dd51d19b/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240104123641-b3f23974e5d6 h1:ytpXTP3oxp480BAZQoOzqlBP4XP73NcpMplZ1/fA1lQ= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20240104123641-b3f23974e5d6/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= -git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20231224145141-489e31b07a71 h1:nvVSH+Ju8EmoPiPHTae5lxHo4kDjROYChs19Yayz+NY= -git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20231224145141-489e31b07a71/go.mod h1:SrtvrQRdzt+8KfYzvosH++gWxo2ShPTzR1m3VQ6uX7U= -git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20231229032122-804bc9822704 h1:y/hXa0Ez+u0ka+QLv1abiCRy+rlJlnNdaH4H/eI6ZJ0= -git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20231229032122-804bc9822704/go.mod h1:SrtvrQRdzt+8KfYzvosH++gWxo2ShPTzR1m3VQ6uX7U= git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240130062251-a87a97b0e8d4 h1:93JYY8JLbFcrlq37q/uKyxs2r2e3modsjvfSbnZQ/UI= git.zhangdeman.cn/zhangdeman/easymap v0.0.0-20240130062251-a87a97b0e8d4/go.mod h1:SrtvrQRdzt+8KfYzvosH++gWxo2ShPTzR1m3VQ6uX7U= -git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20231224125439-01f39b6ea08d h1:TV0BCQQewBEtLsv3i9gXkxLFd5A5bWBTiNd3D/I5o4Q= -git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20231224125439-01f39b6ea08d/go.mod h1:w7kG4zyTJ1uPFaTWhze+OQuaUBINT2XnDxpyiM6ctc0= git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240110090803-399e964daa0c h1:k7VCn9GfRGTilvdF/TcTFVMDBfKLe3VeGAtMTiDSnS0= git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20240110090803-399e964daa0c/go.mod h1:w7kG4zyTJ1uPFaTWhze+OQuaUBINT2XnDxpyiM6ctc0= git.zhangdeman.cn/zhangdeman/util v0.0.0-20231227095334-7eb5cdbf9253 h1:GO3oZa5a2sqwAzGcLDJtQzmshSWRmoP7IDS8bwFqvC4= diff --git a/tool/define/diff.go b/tool/define/diff.go new file mode 100644 index 0000000..e8a969c --- /dev/null +++ b/tool/define/diff.go @@ -0,0 +1,245 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-03-08 11:04 +package define + +import ( + "fmt" + "git.zhangdeman.cn/zhangdeman/util" + "git.zhangdeman.cn/zhangdeman/wrapper" + "reflect" + "strings" +) + +// DiffOption 做数据对比时的选项 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:12 2024/3/8 +type DiffOption struct { + StrictMode bool `json:"strict_mode"` // 采用严格模式: 1 != 1.0 , 采用非严格模式 1 == 1.0 + AllowStringNumber bool `json:"allow_string_number"` // 是否允许字符串数字, 在非严格模式下, 若允许, 则 1 == "1" , 不允许, 则 1 != "1" + IgnoreNotFoundField bool `json:"ignore_not_found_field"` // 忽略不存在的字段 + IgnoreEmptyString bool `json:"ignore_empty_string"` // 忽略空字符串, 若输入值为空字符串, 则不做比较, 认为两个值相同 + IgnoreZeroNumber bool `json:"ignore_zero_number"` // 忽略置为0的数字, 若输入的数据为数字类型, 则不做比较, 认为两个值相同 + IgnoreNil bool `json:"ignore_nil"` // 忽略 nil 值, 若输入值为NIL , 则不做比较, 认为两个值相同 + CustomDiffFuncTable map[string]CustomDiffFunc `json:"-"` // 外部自定义的字段是否相同的比较函数, 会优先使用外部输入的比较函数 +} + +// NewDiffOption ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 12:06 2024/3/8 +func NewDiffOption() *DiffOption { + return &DiffOption{ + StrictMode: false, + IgnoreNotFoundField: false, + IgnoreEmptyString: false, + IgnoreZeroNumber: false, + IgnoreNil: false, + CustomDiffFuncTable: make(map[string]CustomDiffFunc), + } +} + +// CustomDiffFunc 自定义字段对比方法 +// +// 输入分别如下: +// +// field : 要对比的字段 +// +// inputVal : 输入的原始数据 +// +// storageVal : 当前存储的数据 +// +// option : 对比时的额外选项 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:06 2024/3/8 +type CustomDiffFunc func(field string, inputVal wrapper.Map, storageVal wrapper.Map, option *DiffOption) *DiffResult + +// DiffResult 对比结果 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:10 2024/3/8 +type DiffResult struct { + Field string `json:"field"` // 字段名 + OldVal interface{} `json:"old_val"` // 当前field在storageVal中的值 + NewVal interface{} `json:"new_val"` // 当前field在inputVal中的值 + IsSame bool `json:"is_same"` // 两个值是否相同 + DiffReason string `json:"diff_reason"` // 两个值不同的原因 + Err error `json:"err"` // 对比过程中是否出现异常 +} + +const ( + DiffReasonTypeNotMatch = "TYPE_NOT_MATCH" // 类型不匹配 + DiffReasonValueNotMatch = "VALUE_NOT_MATCH" // 数据值不匹配 + DiffReasonInputFieldNotFound = "INPUT_FIELD_NOT_FOUND" // 输入数据中不存在相关字段 + DiffReasonStorageFieldNotFound = "STORAGE_FIELD_NOT_FOUND" // 存储数据中不存在相关字段 +) + +var ( + // 当前仅支持基础类型的比较,不支持slice/map/struct等复杂类型的比较 + supportValueTypeTable = map[reflect.Kind]interface{}{ + reflect.Bool: true, + reflect.Int: true, + reflect.Int8: true, + reflect.Int16: true, + reflect.Int32: true, + reflect.Int64: true, + reflect.Uint: true, + reflect.Uint8: true, + reflect.Uint16: true, + reflect.Uint32: true, + reflect.Uint64: true, + reflect.Float32: true, + reflect.Float64: true, + reflect.String: true, + // reflect.Ptr: true, + } +) + +// IsSupportValueType ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 11:23 2024/3/8 +func IsSupportValueType(kind reflect.Kind) bool { + if _, exist := supportValueTypeTable[kind]; exist { + return true + } + return false +} + +// DefaultDiffFunc 默认的diff函数 +// +// Author : zhangdeman001@ke.com<张德满> +// +// Date : 12:05 2024/3/8 +func DefaultDiffFunc(field string, inputVal wrapper.Map, storageVal wrapper.Map, option *DiffOption) *DiffResult { + if nil == option { + option = NewDiffOption() + } + result := &DiffResult{ + Field: field, + OldVal: nil, + NewVal: nil, + IsSame: true, + DiffReason: "", + Err: nil, + } + var ( + inputFieldVal interface{} + inputFieldValExist bool + storageFieldVal interface{} + storageFieldValExist bool + ) + + inputFieldVal, inputFieldValExist = inputVal.Get(field) + storageFieldVal, storageFieldValExist = storageVal.Get(field) + // 字段在输入数据和存储数据中均不存在 + if !inputFieldValExist && !storageFieldValExist { + // 输入和存储都没这个字段 + return result + } + + // 判断输入字段是否存在 + if !inputFieldValExist { + if option.IgnoreNotFoundField { + // 忽略不存在的字段 + return result + } + // 由于前置逻辑保证了, 输入不存在相关字段, 则现存数据一定存在相关字段 + result.IsSame = false + result.DiffReason = DiffReasonInputFieldNotFound + result.OldVal = storageFieldVal + return result + } + // 判断存储字段是否存在 + if !storageFieldValExist { + result.IsSame = false + result.DiffReason = DiffReasonStorageFieldNotFound + result.NewVal = inputFieldVal + return result + } + // 校验类型 + inputFieldValType := reflect.TypeOf(inputFieldVal) + storageFieldValType := reflect.TypeOf(storageFieldVal) + if inputFieldValType.Kind() == reflect.Ptr { + inputReflect := reflect.ValueOf(inputFieldVal) + inputFieldValType = inputReflect.Type() + inputFieldVal = inputReflect.Interface() + } + if storageFieldValType.Kind() == reflect.Ptr { + storageReflect := reflect.ValueOf(storageFieldValType) + storageFieldValType = storageReflect.Type() + storageFieldVal = storageReflect.Interface() + } + result.NewVal = inputFieldVal + result.OldVal = storageFieldVal + if inputFieldValType.Kind() != storageFieldValType.Kind() && option.StrictMode { + // 严格模式下, 类型不相同 + result.IsSame = false + result.DiffReason = DiffReasonTypeNotMatch + return result + } + // 类型相同, 或者非严格模式下不校验类型 + if option.StrictMode { + // 严格模式 + if inputFieldVal != storageFieldVal { + result.IsSame = false + result.DiffReason = DiffReasonValueNotMatch + return result + } + // return result + } + // 非严格模式 + // 存储值尝试转 float64 + inputValStr := fmt.Sprintf("%v", inputFieldVal) + storageValStr := fmt.Sprintf("%v", storageFieldVal) + if inputValStr == storageValStr { + return result + } + if option.AllowStringNumber { + // 允许字符串数字 + var ( + storageFloat64 float64 + inputFloat64 float64 + ) + if err := util.ConvertAssign(&storageFloat64, storageValStr); nil == err { + if err := util.ConvertAssign(&inputFloat64, inputValStr); nil == err { + if storageFloat64 == inputFloat64 { + return result + } + } + } + } + + // 浮点型数字. 去小数部分最右侧的0 + if inputFieldValType.Kind() == reflect.Float64 || inputFieldValType.Kind() == reflect.Float32 { + inputValStrArr := strings.Split(inputValStr, ".") + if len(inputValStrArr) == 2 { + inputValStrArr[1] = strings.TrimRight(inputValStrArr[1], "0") + inputValStr = strings.Join(inputValStrArr, ".") + } + } + if storageFieldValType.Kind() == reflect.Float64 || storageFieldValType.Kind() == reflect.Float32 { + storageValStrArr := strings.Split(storageValStr, ".") + if len(storageValStrArr) == 2 { + storageValStrArr[1] = strings.TrimRight(storageValStrArr[1], "0") + storageValStr = strings.Join(storageValStrArr, ".") + } + } + if inputValStr != storageValStr { + result.IsSame = false + result.DiffReason = DiffReasonValueNotMatch + return result + } + return result +} diff --git a/tool/define/diff_test.go b/tool/define/diff_test.go new file mode 100644 index 0000000..d805e90 --- /dev/null +++ b/tool/define/diff_test.go @@ -0,0 +1,49 @@ +// Package define ... +// +// Description : define ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-03-08 15:24 +package define + +import ( + "git.zhangdeman.cn/zhangdeman/wrapper" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestDefaultDiffFunc(t *testing.T) { + var ( + num1 float64 = 1.0 + num2 float32 = 1.00 + num3 string = "1" + num4 string = "1.00" + ) + input := wrapper.EasyMap(map[string]interface{}{ + "num": num1, + "num3": num3, + "num4": num4, + "num5": num1, + }) + storage := wrapper.EasyMap(map[string]interface{}{ + "num": num2, + "num3": num2, + "num4": num2, + "num5": num1, + }) + diffOption := NewDiffOption() + diffOption.StrictMode = true + res := DefaultDiffFunc("num", input, storage, diffOption) + assert.EqualValues(t, false, res.IsSame, "严格模式下, float32与float64不相等") + res = DefaultDiffFunc("num5", input, storage, diffOption) + assert.EqualValues(t, true, res.IsSame, "严格模式下, float32与float32相等") + diffOption.StrictMode = false + res = DefaultDiffFunc("num", input, storage, diffOption) + assert.EqualValues(t, true, res.IsSame, "非严格模式下, float32与float64相等") + res = DefaultDiffFunc("num3", input, storage, diffOption) + assert.EqualValues(t, true, res.IsSame, "非严格模式下, float32与string相等") + diffOption.AllowStringNumber = true + res = DefaultDiffFunc("num4", input, storage, diffOption) + assert.EqualValues(t, true, res.IsSame, "非严格模式下, float32与string相等") +} diff --git a/tool/diff.go b/tool/diff.go new file mode 100644 index 0000000..3d2783a --- /dev/null +++ b/tool/diff.go @@ -0,0 +1,52 @@ +// Package tool ... +// +// Description : 对比两个数据是否相同 + 构建不同数据的前后值 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2024-03-08 11:03 +package tool + +import ( + "git.zhangdeman.cn/zhangdeman/wrapper" + "git.zhangdeman.cn/zhangdeman/wrapper/tool/define" +) + +var ( + Diff = &diff{} +) + +type diff struct { +} + +// Compare 比较两个数据源的指定字段 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:53 2024/3/8 +func (d *diff) Compare(fieldList []string, input wrapper.Map, storage wrapper.Map, option *define.DiffOption) map[string]*define.DiffResult { + if nil == option { + option = define.NewDiffOption() + } + res := make(map[string]*define.DiffResult) + for _, itemField := range fieldList { + res[itemField] = d.CompareSingle(itemField, input, storage, option) + } + return res +} + +// CompareSingle 比较一个字段 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:57 2024/3/8 +func (d *diff) CompareSingle(field string, input wrapper.Map, storage wrapper.Map, option *define.DiffOption) *define.DiffResult { + if nil == option { + option = define.NewDiffOption() + } + if compareFunc, exist := option.CustomDiffFuncTable[field]; exist && nil != compareFunc { + return compareFunc(field, input, storage, option) + } else { + return define.DefaultDiffFunc(field, input, storage, option) + } +}