// Package validator ... // // Description : validator ... // // Author : go_developer@163.com<白茶清欢> // // Date : 2024-04-29 10:51 package validator import ( "bytes" "encoding/json" "fmt" "git.zhangdeman.cn/gateway/validator/define" "git.zhangdeman.cn/zhangdeman/consts" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "strings" ) // RunForStruct 验证结构体 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:18 2024/4/29 func RunForStruct(sourceData any, ruleList []*define.FieldRule) (map[string]any, error) { if nil == sourceData { return map[string]any{}, nil } byteData, _ := json.Marshal(sourceData) var ( sourceMapData map[string]any err error ) d := json.NewDecoder(bytes.NewReader(byteData)) d.UseNumber() if err = d.Decode(&sourceMapData); nil != err { return nil, err } if err = Run(sourceMapData, ruleList); nil != err { return nil, err } return sourceMapData, nil } // Run 运行参数验证 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:13 2024/4/29 func Run(sourceData map[string]any, ruleList []*define.FieldRule) error { byteData, err := RunWithResult(sourceData, ruleList) if nil != err { return err } sourceData = make(map[string]any) d := json.NewDecoder(bytes.NewReader(byteData)) d.UseNumber() if err := d.Decode(&sourceData); nil != err { return err } return nil } // RunWithResult 运行并返回结果 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:42 2024/6/12 func RunWithResult(sourceData map[string]any, ruleList []*define.FieldRule) ([]byte, error) { if nil == sourceData { sourceData = make(map[string]any) } byteData, _ := json.Marshal(sourceData) for _, itemRule := range ruleList { if len(itemRule.Path) == 0 { // 未指定验证数据位置 continue } // 通过有条件必传, 填充 is_required checkRuleConditionRequiredRule(byteData, itemRule) inputFieldVal := gjson.GetBytes(byteData, itemRule.Path) if formatRule, err := validate(byteData, inputFieldVal, itemRule); nil != err { return nil, err } else { if !itemRule.DisableRewrite { // 更新数据 byteData, _ = sjson.SetBytes(byteData, itemRule.Path, formatRule) } } } return byteData, nil } // checkRuleConditionRequiredRule 校验有条件必传 // // Author : go_developer@163.com<白茶清欢> // // Date : 21:10 2024/5/16 func checkRuleConditionRequiredRule(sourceData []byte, rule *define.FieldRule) { if rule.IsRequired { // 本身已经必传, 无所谓有条件无条件 return } // 验证有条件必传 if len(rule.RequiredConditionGroup) > 0 { for _, itemRuleGroup := range rule.RequiredConditionGroup { isRequired := true for _, itemFieldRule := range itemRuleGroup { dependFieldStatus := getDataStatus(gjson.GetBytes(sourceData, itemFieldRule.DependOnField), itemFieldRule.DependOnFieldType) isRequired = isRequired && inArray(itemFieldRule.DependOnFieldStatus, dependFieldStatus) } if isRequired { rule.IsRequired = true // 非必传参数, 命中有条件必传 break } } } } // getDataStatus 获取数据状态 // // Author : go_developer@163.com<白茶清欢> // // Date : 15:59 2024/4/29 func getDataStatus(val gjson.Result, dataType string) string { if !val.Exists() { return consts.DataStatusNotFound } switch dataType { case consts.DataTypeString: if len(val.String()) == 0 { return consts.DataStatusIsEmpty } case consts.DataTypeFloat: fallthrough case consts.DataTypeInt: if val.Float() == 0 { return consts.DataStatusIsZero } default: if strings.HasPrefix(dataType, "[]") { // 数组 if len(val.Array()) == 0 { return consts.DataStatusIsEmpty } } else if strings.HasPrefix(dataType, "map") { // 对象 if len(val.Map()) == 0 { return consts.DataStatusIsEmpty } } } return "" } // formatInputVal ... // // Author : go_developer@163.com<白茶清欢> // // Date : 18:02 2024/5/2 func formatInputVal(val gjson.Result, rule *define.FieldRule) (any, error) { inputVal := val.Value() if nil == inputVal { if rule.IsRequired { return nil, fmt.Errorf("%v : data is required, but get nil", rule.Path) } if rule.DisableAutoConvert { inputVal = rule.DefaultValue } else { inputVal = strings.TrimSpace(rule.DefaultValue) } } else { if inputValStr, ok := inputVal.(string); ok { if !rule.DisableAutoTrimSpace { // 自动去空格 inputVal = strings.TrimSpace(inputValStr) } else { inputVal = inputValStr } } } return inputVal, nil } // inArray ... // // Author : go_developer@163.com<白茶清欢> // // Date : 21:00 2024/5/16 func inArray(enumList []string, val string) bool { for _, item := range enumList { if strings.ToUpper(val) == strings.ToUpper(item) { return true } } return false } // validate 验证字段 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:32 2024/4/29 func validate(sourceData []byte, val gjson.Result, rule *define.FieldRule) (any, error) { var ( err error inputVal any ) if !val.Exists() { if rule.IsRequired { return nil, fmt.Errorf("%v : field is required, but not found", rule.Path) } inputVal = rule.DefaultValue } else { if inputVal, err = formatInputVal(val, rule); nil != err { return nil, err } } return handleData(inputVal, rule) } // handleData 处理数据 // // Author : go_developer@163.com<白茶清欢> // // Date : 14:43 2024/4/29 func handleData(inputVal any, rule *define.FieldRule) (any, error) { switch rule.Type { case consts.DataTypeAny: // 任意类型 return inputVal, nil case consts.DataTypeFloat: // float数据 return handleFloat(inputVal, rule) case consts.DataTypeInt: // int类型 return handleInt(inputVal, rule) case consts.DataTypeUint: return handleUint(inputVal, rule) case consts.DataTypeString: // 字符串处理 return handleString(inputVal, rule) case consts.DataTypeBool: return handleBool(inputVal, rule) case consts.DataTypeMapStrFloat, consts.DataTypeMapStrBool, consts.DataTypeMapStrInt, consts.DataTypeMapStrUint: // 一律按照 map[string]float64处理 return handleMapStringFloat(inputVal, rule) case consts.DataTypeMapStrAny: // 对象结构 return handleMapStringAny(inputVal, rule) case consts.DataTypeMapStrStr: // 对象结构 return handleMapStringString(inputVal, rule) case consts.DataTypeMapStrSlice: // map列表 return handleMapStringSlice(inputVal, rule) case consts.DataTypeMapAnyAny: // 任意类型map return handleMapAnyAny(inputVal, rule) case consts.DataTypeSliceInt, consts.DataTypeSliceIntWithChar: // int数组处理 return handleSliceInt(inputVal, rule) case consts.DataTypeSliceUint, consts.DataTypeSliceUintWithChar: // uint数组处理 return handleSliceUint(inputVal, rule) case consts.DataTypeSliceFloat, consts.DataTypeSliceFloatWithChar: // float数组处理 return handleSliceFloat(inputVal, rule) case consts.DataTypeSliceBool, consts.DataTypeSliceBoolWithChar: // bool数组 return handleSliceBool(inputVal, rule) case consts.DataTypeSliceSlice: return handleSliceSlice(inputVal, rule) case consts.DataTypeSliceMapAnyAny: // map 列表 return handleSliceMapAny(inputVal, rule) case consts.DataTypeSliceMapStringAny: return handleSliceMapString(inputVal, rule) } return nil, fmt.Errorf("%v : data type [%v] is not support", rule.Path, rule.Type) }