303 lines
8.5 KiB
Go
303 lines
8.5 KiB
Go
// 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, receiver any) error {
|
|
if nil == sourceData {
|
|
return 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 err
|
|
}
|
|
return Run(sourceMapData, ruleList, receiver)
|
|
}
|
|
|
|
// Run 运行参数验证
|
|
//
|
|
// Author : go_developer@163.com<白茶清欢>
|
|
//
|
|
// Date : 14:13 2024/4/29
|
|
func Run(sourceData map[string]any, ruleList []*define.FieldRule, receiver any) error {
|
|
for _, itemRule := range ruleList {
|
|
itemRule.DefaultValue = nil
|
|
}
|
|
byteData, err := RunWithResult(sourceData, ruleList)
|
|
if nil != err {
|
|
return err
|
|
}
|
|
if nil != receiver {
|
|
d := json.NewDecoder(bytes.NewReader(byteData))
|
|
d.UseNumber()
|
|
if err := d.Decode(&receiver); 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)
|
|
resultByteData, _ := 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 formatValue, err := validate(byteData, inputFieldVal, itemRule); nil != err {
|
|
return nil, err
|
|
} else {
|
|
if !itemRule.DisableRewrite {
|
|
// 更新数据
|
|
resultByteData, _ = sjson.SetBytes(resultByteData, itemRule.Path, formatValue)
|
|
}
|
|
}
|
|
}
|
|
return resultByteData, 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
|
|
}
|
|
} else if strings.HasPrefix(dataType, "*") {
|
|
// 指针类型
|
|
if nil == val.Value() {
|
|
return consts.DataStatusIsNil
|
|
}
|
|
}
|
|
}
|
|
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 {
|
|
if nil == rule.DefaultValue {
|
|
return nil, nil
|
|
}
|
|
if strVal, ok := rule.DefaultValue.(string); ok {
|
|
inputVal = strings.TrimSpace(strVal)
|
|
} else {
|
|
inputVal = 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() || nil == val.Value() {
|
|
if rule.IsRequired {
|
|
return nil, fmt.Errorf("%v : field is required, but not found", rule.Path)
|
|
}
|
|
if strings.HasSuffix(rule.Type, "_ptr") {
|
|
// 指针类型数据, 无需验证
|
|
return nil, nil
|
|
}
|
|
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) {
|
|
rule.Type = strings.ToLower(rule.Type)
|
|
// 处理真实的map和序列化之后的map
|
|
if strings.HasPrefix(rule.Type, "map") {
|
|
if strings.HasSuffix(rule.Type, "_marshal") {
|
|
rule.MapConfig = &define.MapConfig{Mode: consts.DataMapModelMarshal}
|
|
} else {
|
|
rule.MapConfig = &define.MapConfig{Mode: consts.DataMapModelReal}
|
|
}
|
|
}
|
|
|
|
switch rule.Type {
|
|
case consts.DataTypeAny: // 任意类型
|
|
return inputVal, nil
|
|
case consts.DataTypeFloat, consts.DataTypeFloatPtr: // float数据
|
|
return handleFloat(inputVal, rule)
|
|
case consts.DataTypeInt, consts.DataTypeIntPtr: // int类型
|
|
return handleInt(inputVal, rule)
|
|
case consts.DataTypeUint, consts.DataTypeUintPtr:
|
|
return handleUint(inputVal, rule)
|
|
case consts.DataTypeString, consts.DataTypeStringPtr: // 字符串处理
|
|
return handleString(inputVal, rule)
|
|
case consts.DataTypeBool, consts.DataTypeBoolPtr:
|
|
return handleBool(inputVal, rule)
|
|
case consts.DataTypeMapStrFloat, consts.DataTypeMapStrFloatWithMarshal,
|
|
consts.DataTypeMapStrBool, consts.DataTypeMapStrBoolWithMarshal,
|
|
consts.DataTypeMapStrInt, consts.DataTypeMapStrIntWithMarshal,
|
|
consts.DataTypeMapStrUint, consts.DataTypeMapStrUintWithMarshal:
|
|
// 一律按照 map[string]float64处理
|
|
return handleMapStringFloat(inputVal, rule)
|
|
case consts.DataTypeMapStrAny, consts.DataTypeMapStrAnyWithMarshal: // 对象结构
|
|
return handleMapStringAny(inputVal, rule)
|
|
case consts.DataTypeMapStrStr, consts.DataTypeMapStrStrWithMarshal: // 对象结构
|
|
return handleMapStringString(inputVal, rule)
|
|
case consts.DataTypeMapStrSlice, consts.DataTypeMapStrSliceWithMarshal: // map列表
|
|
return handleMapStringSlice(inputVal, rule)
|
|
case consts.DataTypeMapAnyAny, consts.DataTypeMapAnyAnyWithMarshal: // 任意类型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)
|
|
}
|