完成基础版本基于运行时的动态结构体生成 + 参数验证

This commit is contained in:
白茶清欢 2025-03-19 15:53:57 +08:00
parent 6b5bfaf666
commit db7779e488
5 changed files with 111 additions and 24 deletions

55
format.go Normal file
View File

@ -0,0 +1,55 @@
// Package validate ...
//
// Description : validate ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-03-19 15:50
package validate
import (
"errors"
"fmt"
"github.com/go-playground/validator/v10"
"reflect"
"strings"
)
// GetValidateErr 格式化验证结果错误信息
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 15:51 2025/3/19
func GetValidateErr(obj any, rawErr error) error {
if nil == rawErr {
return nil
}
if nil == obj {
return rawErr
}
var (
ok bool
validationErrs validator.ValidationErrors
errString []string
field reflect.StructField
)
if ok = errors.As(rawErr, &validationErrs); !ok {
return rawErr
}
objType := reflect.TypeOf(obj)
if objType.Kind() == reflect.Ptr {
objType = objType.Elem()
}
for _, validationErr := range validationErrs {
if field, ok = objType.FieldByName(validationErr.Field()); ok {
if e := field.Tag.Get(TagErrMsg); e != "" {
errString = append(errString, fmt.Sprintf("%s: %s", field.Tag.Get("json"), e))
continue
} else {
errString = append(errString, fmt.Sprintf("%s: %v", field.Tag.Get("json"), validationErr.Value()))
}
}
errString = append(errString, validationErr.Error())
}
return errors.New(strings.Join(errString, "\n"))
}

8
go.mod
View File

@ -4,14 +4,17 @@ go 1.24.1
require (
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250227040546-863c03f34bb8
git.zhangdeman.cn/zhangdeman/dynamic-struct v0.0.0-20250319072714-eab2a7abde63
git.zhangdeman.cn/zhangdeman/json_filter v0.0.0-20241205105007-b8c8c9d4338c
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250302133417-c1588abcb436
github.com/go-playground/validator/v10 v10.25.0
github.com/tidwall/gjson v1.18.0
github.com/tidwall/sjson v1.2.5
)
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
@ -19,7 +22,6 @@ require (
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
@ -33,4 +35,4 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace git.zhangdeman.cn/zhangdeman/wrapper => ../wrapper
replace git.zhangdeman.cn/zhangdeman/dynamic-struct => ../dynamic-struct

7
go.sum
View File

@ -8,6 +8,8 @@ git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd h1:q7G
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=
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250302133417-c1588abcb436 h1:SM4zc54W2wmM72+4pMNQ8iS371H6lj4J8rj8KJKf7pw=
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250302133417-c1588abcb436/go.mod h1:YJ1FlvFgkfAHkxkt3l5rKKUqEpQkNMbCFDzDmgteEU8=
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=
@ -18,6 +20,8 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
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/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
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=
@ -38,6 +42,7 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b
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.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
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=
@ -45,6 +50,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
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=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
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=

View File

@ -12,15 +12,22 @@ import (
"errors"
"fmt"
"git.zhangdeman.cn/zhangdeman/consts"
dynamicStructGenerate "git.zhangdeman.cn/zhangdeman/dynamic-struct"
"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"
"github.com/tidwall/sjson"
"strings"
)
var validatorInstance = validator.New()
var validatorInstance *validator.Validate
func init() {
validatorInstance = validator.New()
validatorInstance.SetTagName(TagValidate)
}
// Run 执行参数验证
//
@ -31,7 +38,7 @@ func Run(sourceData []byte, fieldList []StructField) ([]byte, error) {
handleInstance := &handle{
sourceData: sourceData,
fieldList: fieldList,
dynamicStruct: wrapper.NewDynamic(),
dynamicStruct: dynamicStructGenerate.NewStruct(),
}
return handleInstance.Run()
}
@ -39,7 +46,8 @@ func Run(sourceData []byte, fieldList []StructField) ([]byte, error) {
type handle struct {
sourceData []byte
fieldList []StructField
dynamicStruct *wrapper.DynamicStruct
dynamicStruct dynamicStructGenerate.Builder
formatVal string
}
// Run 执行验证
@ -67,16 +75,20 @@ func (h *handle) Run() ([]byte, error) {
// 没出现异常, 但是value为nil, 视作参数不存在处理
continue
}
// fieldName := wrapper.String(field.JsonTag).SnakeCaseToCamel()
h.dynamicStruct.AddAny(field.JsonTag, h.generateTag(field), "dynamic_struct", sourceValue)
fieldName := wrapper.String(field.JsonTag).SnakeCaseToCamel()
fieldTag := h.generateTag(field)
h.dynamicStruct.AddField(fieldName, "", sourceValue, fieldTag, false)
}
val := h.dynamicStruct.ToStructDefaultValue()
if err := serialize.JSON.Transition(h.dynamicStruct.MapData(), &val); nil != err {
val := h.dynamicStruct.Build().New()
if err := serialize.JSON.UnmarshalWithNumber([]byte(h.formatVal), &val); nil != err {
return nil, err
}
if err := validatorInstance.Struct(val); nil != err {
return nil, GetValidateErr(val, err)
}
targetByte, _ := json.Marshal(val)
err := validatorInstance.Struct(val)
return targetByte, err
return targetByte, nil
}
// checkRequired 格式化必传参数
@ -123,46 +135,57 @@ func (h *handle) getSourceDataValue(field StructField) (any, error) {
return nil, nil
}
}
var (
val any
err error
)
defer func() {
if nil == err && nil != val {
// 更新到格式化之后的数据结构中
h.formatVal, err = sjson.Set(h.formatVal, field.TargetPath, val)
}
}()
switch field.Type {
case consts.DataTypeInt: // Int类型
fallthrough
case consts.DataTypeIntPtr: // Uint类型
val, err := gjson_hack.Int(sourceValue)
val, err = gjson_hack.Int(sourceValue)
return val, err
case consts.DataTypeUint:
fallthrough
case consts.DataTypeUintPtr: // Uint类型
val, err := gjson_hack.Uint(sourceValue)
val, err = gjson_hack.Uint(sourceValue)
return val, err
case consts.DataTypeFloat:
fallthrough
case consts.DataTypeFloatPtr: // Float类型
val, err := gjson_hack.Float64(sourceValue)
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)
val, err = gjson_hack.SliceFloat(sourceValue)
return val, err
case consts.DataTypeSliceInt: // Int slice
val, err := gjson_hack.SliceInt(sourceValue)
val, err = gjson_hack.SliceInt(sourceValue)
return val, err
case consts.DataTypeSliceUint: // Uint slice
val, err := gjson_hack.SliceUint(sourceValue)
val, err = gjson_hack.SliceUint(sourceValue)
return val, err
case consts.DataTypeSliceString: // String slice
val, err := gjson_hack.SliceString(sourceValue)
val, err = gjson_hack.SliceString(sourceValue)
return val, err
case consts.DataTypeSliceBool: // Bool slice
val, err := gjson_hack.SliceBool(sourceValue)
val, err = gjson_hack.SliceBool(sourceValue)
return val, err
case consts.DataTypeMapStrAny: // Bool slice
val, err := gjson_hack.MapStrAny[any](sourceValue)
val, err = gjson_hack.MapStrAny[any](sourceValue)
return val, err
}
return sourceValue.Value(), nil
val = sourceValue.Value()
return val, nil
}
// 生成结构体的tag标签

View File

@ -17,14 +17,14 @@ import (
// TestRun_Simple_Data 无嵌套、无复杂数据类型的处理
func TestRun_Simple_Data(t *testing.T) {
testMap := map[string]any{
"age": 180,
"age": 18,
"height": 179.5,
"name": "baicha",
}
sourceByteData, _ := json.Marshal(testMap)
fieldList := []StructField{
{
JsonTag: "age",
JsonTag: "user_age",
Type: consts.DataTypeInt,
Required: false,
RuleList: []Rule{