// 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 }