diff --git a/define.go b/define.go index 4c23580..b9bd623 100644 --- a/define.go +++ b/define.go @@ -13,22 +13,24 @@ import ( const ( TagErrMsg = "err" // json结构体中, 错误信息 tag 字段 - TagRule = "validator" // json结构体中, 验证规则 rule 字段 + TagValidate = "validator" // json结构体中, 验证规则 rule 字段 TagDefaultValue = "d" // json结构体中默认值标签 ) // Rule 规则定义 type Rule struct { - Tag string `json:"tag" dc:"规则标签,如:required, email, phone, etc."` // 规则标签,如:required, email, phone, etc. - Args []string `json:"args" dc:"规则参数"` // 规则参数,如: Tag=min, Args=[1], 则会转化成验证规则 min=1 - Errmsg string `json:"errmsg" dc:"规则验证不通过时, 报错的信息"` // 规则验证不通过时, 报错的信息 + Tag string `json:"tag" dc:"规则标签,如:required, email, phone, etc."` // 规则标签,如:required, email, phone, etc. + Args []string `json:"args" dc:"规则参数"` // 规则参数,如: Tag=min, Args=[1], 则会转化成验证规则 min=1 } // StructField 结构体字段定义 type StructField struct { - JsonTag string `json:"json_tag" dc:"字段对外输出的json标签"` // 结构体字段名称 - Type consts.DataType `json:"type" dc:"字段的数据类型"` // 字段的数据类型 - Required bool `json:"required" dc:"是否必传"` // 对应验证规则的 required 属性, 在非必传的情况下, 字段不存在, 不会进行规则验证, 字段存在, 才会进行验证, Required == false 时, 会在 RuleList 检测是否有required 规则 - RuleList []Rule `json:"rule_list" dc:"字段规则列表"` // 字段规则列表 - DefaultValue string `json:"default_value" dc:"字段默认值"` // 字段默认值, 仅对非必传字段生效, 统一用字符串类型, 后面会转换成 FieldType 类型 + JsonTag string `json:"json_tag" dc:"字段对外输出的json标签"` // 结构体字段名称, 不单独设置, 从 TargetPath 解析 + Type consts.DataType `json:"type" dc:"字段的数据类型"` // 字段的数据类型 + Required bool `json:"required" dc:"是否必传"` // 对应验证规则的 required 属性, 在非必传的情况下, 字段不存在, 不会进行规则验证, 字段存在, 才会进行验证, Required == false 时, 会在 RuleList 检测是否有required 规则 + RuleList []Rule `json:"rule_list" dc:"字段规则列表"` // 字段规则列表 + DefaultValue string `json:"default_value" dc:"字段默认值"` // 字段默认值, 仅对非必传字段生效, 统一用字符串类型, 后面会转换成 FieldType 类型 + SourcePath string `json:"source_path" dc:"读取数据的数据源路径"` // 读取数据的数据源路径 + TargetPath string `json:"target_path" dc:"数据设置哪一个路径"` // 目标数据路径 + Errmsg string `json:"errmsg" dc:"规则验证不通过时, 报错的信息"` // 规则验证不通过时, 报错的信息 } diff --git a/go.mod b/go.mod index afaf1d5..9acd167 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,35 @@ module git.zhangdeman.cn/gateway/validate go 1.24.1 -require git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250227040546-863c03f34bb8 // indirect +require ( + git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250227040546-863c03f34bb8 + git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20241205105007-b8c8c9d4338c + git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250302133417-c1588abcb436 + github.com/tidwall/gjson v1.18.0 +) + +require ( + git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect + git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd // indirect + git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.25.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mozillazg/go-pinyin v0.20.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace git.zhangdeman.cn/zhangdeman/wrapper => ../wrapper diff --git a/go.sum b/go.sum index 4b6536c..5364df8 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,59 @@ git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250227040546-863c03f34bb8 h1:VEifPc+vkpEQoX9rj7zxmT1m+IA81XjOxe7+Z1aqWNM= git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250227040546-863c03f34bb8/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k= +git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20241205105007-b8c8c9d4338c h1:y8WLSdY8dBkKmazLt/XokW/LGz3hbdvNUdCUnsWXpRw= +git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20241205105007-b8c8c9d4338c/go.mod h1:GzCzTobrpI6J94Cluj9gjN/o5ZdNEyrXQcfGfj05ohs= +git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y= +git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0/go.mod h1:VHb9qmhaPDAQDcS6vUiDCamYjZ4R5lD1XtVsh55KsMI= +git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd h1:q7GG14qgXKB4MEXQFOe7/UYebsqMfPaSX80TcPdOosI= +git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd/go.mod h1:+D6uPSljwHywjVY5WSBY4TRVMj26TN5f5cFGEYMldjs= +git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e h1:Q973S6CcWr1ICZhFI1STFOJ+KUImCl2BaIXm6YppBqI= +git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e/go.mod h1:VpPjBlwz8U+OxZuxzHQBv1aEEZ3pStH6bZvT21ADEbI= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= +github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= +github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mozillazg/go-pinyin v0.20.0 h1:BtR3DsxpApHfKReaPO1fCqF4pThRwH9uwvXzm+GnMFQ= +github.com/mozillazg/go-pinyin v0.20.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/validate.go b/validate.go new file mode 100644 index 0000000..12e4a00 --- /dev/null +++ b/validate.go @@ -0,0 +1,190 @@ +// Package validate ... +// +// Description : validate ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-03-18 15:10 +package validate + +import ( + "encoding/json" + "errors" + "fmt" + "git.zhangdeman.cn/zhangdeman/consts" + "git.zhangdeman.cn/zhangdeman/json_filter/gjson_hack" + "git.zhangdeman.cn/zhangdeman/serialize" + "git.zhangdeman.cn/zhangdeman/wrapper" + "github.com/go-playground/validator/v10" + "github.com/tidwall/gjson" + "strings" +) + +var validatorInstance = validator.New() + +// Run 执行参数验证 +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 15:12 2025/3/18 +func Run(sourceData []byte, fieldList []StructField) ([]byte, error) { + handleInstance := &handle{ + sourceData: sourceData, + fieldList: fieldList, + dynamicStruct: wrapper.NewDynamic(), + } + return handleInstance.Run() +} + +type handle struct { + sourceData []byte + fieldList []StructField + dynamicStruct *wrapper.DynamicStruct +} + +// Run 执行验证 +func (h *handle) Run() ([]byte, error) { + for _, field := range h.fieldList { + if len(field.Errmsg) == 0 { + field.Errmsg = field.JsonTag + " : 参数校验不通过" + } + required, hasRequired := h.checkRequired(field) + field.Required = required + if field.Required && !hasRequired { + if nil == field.RuleList { + field.RuleList = make([]Rule, 0) + } + field.RuleList = append(field.RuleList, Rule{ + Tag: consts.ValidatorRuleCommonRequired.String(), + Args: nil, + }) + } + sourceValue, err := h.getSourceDataValue(field) + if nil != err { + return nil, err + } + if nil == sourceValue { + // 没出现异常, 但是value为nil, 视作参数不存在处理 + continue + } + // fieldName := wrapper.String(field.JsonTag).SnakeCaseToCamel() + h.dynamicStruct.AddAny(field.JsonTag, h.generateTag(field), "dynamic_struct", sourceValue) + } + val := h.dynamicStruct.ToStructDefaultValue() + if err := serialize.JSON.Transition(h.dynamicStruct.MapData(), &val); nil != err { + return nil, err + } + targetByte, _ := json.Marshal(val) + err := validatorInstance.Struct(val) + return targetByte, err +} + +// checkRequired 格式化必传参数 +// 返回值1, 是否必传 +// 返回值2, 校验规则中是否存在必传校验 +func (h *handle) checkRequired(field StructField) (bool, bool) { + required := field.Required + isHasRequiredRule := false + for _, rule := range field.RuleList { + if rule.Tag == consts.ValidatorRuleCommonRequired.String() { + isHasRequiredRule = true + break + } + } + if isHasRequiredRule { + required = true + } + if required && !isHasRequiredRule { + // 必传, 但是没有必传校验规则 + return true, true + } + return true, false +} + +// getSourceDataValue 获取源数据值 +func (h *handle) getSourceDataValue(field StructField) (any, error) { + sourceValue := gjson.GetBytes(h.sourceData, field.SourcePath) + if !sourceValue.Exists() { + if field.Required { + return nil, errors.New(field.SourcePath + " is required") + } + if len(field.DefaultValue) > 0 { + // 非必传, 且设置了默认值, 在数据源不存在时, 这只默认值 + sourceValue = gjson.Result{ + Type: gjson.String, + Raw: field.DefaultValue, + Str: field.DefaultValue, + Num: 0, + Index: 0, + Indexes: nil, + } + } else { + // 非必传, 且没有默认值, 就当做数据不存在 + return nil, nil + } + } + switch field.Type { + case consts.DataTypeInt: // Int类型 + fallthrough + case consts.DataTypeIntPtr: // Uint类型 + val, err := gjson_hack.Int(sourceValue) + return val, err + case consts.DataTypeUint: + fallthrough + case consts.DataTypeUintPtr: // Uint类型 + val, err := gjson_hack.Uint(sourceValue) + return val, err + case consts.DataTypeFloat: + fallthrough + case consts.DataTypeFloatPtr: // Float类型 + val, err := gjson_hack.Float64(sourceValue) + return val, err + case consts.DataTypeString: // String类型 + return sourceValue.String(), nil + case consts.DataTypeBool: // Bool类型 + return sourceValue.Bool(), nil + case consts.DataTypeSliceFloat: // Float slice + val, err := gjson_hack.SliceFloat(sourceValue) + return val, err + case consts.DataTypeSliceInt: // Int slice + val, err := gjson_hack.SliceInt(sourceValue) + return val, err + case consts.DataTypeSliceUint: // Uint slice + val, err := gjson_hack.SliceUint(sourceValue) + return val, err + case consts.DataTypeSliceString: // String slice + val, err := gjson_hack.SliceString(sourceValue) + return val, err + case consts.DataTypeSliceBool: // Bool slice + val, err := gjson_hack.SliceBool(sourceValue) + return val, err + case consts.DataTypeMapStrAny: // Bool slice + val, err := gjson_hack.MapStrAny[any](sourceValue) + return val, err + } + return sourceValue.Value(), nil +} + +// 生成结构体的tag标签 +func (h *handle) generateTag(field StructField) string { + tagList := []string{ + fmt.Sprintf(`json:"%s"`, field.JsonTag), // json tag + } + if len(field.Errmsg) == 0 { + field.Errmsg = field.JsonTag + ": 参数校验不通过" + } + tagList = append(tagList, fmt.Sprintf(`%s:"%s"`, TagErrMsg, field.Errmsg)) // 错误信息tag + validateRuleList := []string{} + for _, itemRule := range field.RuleList { + if len(itemRule.Args) == 0 { + validateRuleList = append(validateRuleList, itemRule.Tag) + } else { + validateRuleList = append(validateRuleList, fmt.Sprintf("%s=%s", itemRule.Tag, strings.Join(itemRule.Args, " "))) + } + } + // 验证规则tag + tagList = append(tagList, fmt.Sprintf(`%s:"%s"`, TagValidate, strings.Join(validateRuleList, ","))) + // 默认值 + tagList = append(tagList, fmt.Sprintf(`%s:"%s"`, TagDefaultValue, field.DefaultValue)) + return strings.Join(tagList, " ") +} diff --git a/validate_test.go b/validate_test.go new file mode 100644 index 0000000..65ce148 --- /dev/null +++ b/validate_test.go @@ -0,0 +1,48 @@ +// Package validate ... +// +// Description : validate ... +// +// Author : go_developer@163.com<白茶清欢> +// +// Date : 2025-03-18 17:36 +package validate + +import ( + "encoding/json" + "fmt" + "git.zhangdeman.cn/zhangdeman/consts" + "testing" +) + +// TestRun_Simple_Data 无嵌套、无复杂数据类型的处理 +func TestRun_Simple_Data(t *testing.T) { + testMap := map[string]any{ + "age": 180, + "height": 179.5, + "name": "baicha", + } + sourceByteData, _ := json.Marshal(testMap) + fieldList := []StructField{ + { + JsonTag: "age", + Type: consts.DataTypeInt, + Required: false, + RuleList: []Rule{ + { + Tag: "min", + Args: []string{"1"}, + }, + { + Tag: "max", + Args: []string{"20"}, + }, + }, + DefaultValue: "", + SourcePath: "age", + TargetPath: "user_age", + Errmsg: "年龄必须在[1,20]之间", + }, + } + _, err := Run(sourceByteData, fieldList) + fmt.Println(err) +}