基础类型, 无嵌套匿名结构体生成, 待解决寻址问题

This commit is contained in:
白茶清欢 2025-03-18 18:43:45 +08:00
parent 393af3c4c3
commit 6b5bfaf666
5 changed files with 338 additions and 10 deletions

View File

@ -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:"规则验证不通过时, 报错的信息"` // 规则验证不通过时, 报错的信息
}

33
go.mod
View File

@ -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

57
go.sum
View File

@ -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=

190
validate.go Normal file
View File

@ -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, " ")
}

48
validate_test.go Normal file
View File

@ -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)
}