238 lines
6.8 KiB
Go
238 lines
6.8 KiB
Go
// Package validate ...
|
|
//
|
|
// Description : validate ...
|
|
//
|
|
// Author : go_developer@163.com<白茶清欢>
|
|
//
|
|
// Date : 2025-03-18 15:10
|
|
package validate
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"git.zhangdeman.cn/zhangdeman/consts"
|
|
dynamicStructGenerate "git.zhangdeman.cn/zhangdeman/dynamic-struct"
|
|
filter "git.zhangdeman.cn/zhangdeman/json_filter"
|
|
"git.zhangdeman.cn/zhangdeman/serialize"
|
|
"github.com/creasty/defaults"
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/tidwall/gjson"
|
|
"strings"
|
|
)
|
|
|
|
var validatorInstance *validator.Validate
|
|
|
|
func init() {
|
|
validatorInstance = validator.New()
|
|
validatorInstance.SetTagName(TagValidate)
|
|
}
|
|
|
|
// Run 执行参数验证
|
|
//
|
|
// Author : go_developer@163.com<白茶清欢>
|
|
//
|
|
// Date : 15:12 2025/3/18
|
|
func Run(sourceData []byte, fieldList []StructField) (*handle, error) {
|
|
filterRuleList := make([]filter.MapRule, 0)
|
|
for _, item := range fieldList {
|
|
filterRuleList = append(filterRuleList, filter.MapRule{
|
|
SourcePath: item.SourcePath,
|
|
TargetPath: item.TargetPath,
|
|
Required: item.Required,
|
|
DataType: item.Type,
|
|
DefaultValue: item.DefaultValue,
|
|
})
|
|
}
|
|
// source_path => target_path数据转换
|
|
filterInstance := filter.NewFilter(string(sourceData), filterRuleList)
|
|
if err := filterInstance.Deal(); nil != err {
|
|
return nil, err
|
|
}
|
|
// 格式化sourceData
|
|
handleInstance := &handle{
|
|
sourceData: filterInstance.Byte(),
|
|
fieldList: fieldList,
|
|
parentFieldTable: map[string]bool{},
|
|
formatVal: filterInstance.String(),
|
|
}
|
|
tagTable := map[string]string{}
|
|
for _, item := range fieldList {
|
|
tagTable[item.TargetPath] = handleInstance.generateTag(item)
|
|
// 检测当前字段是否是某一个字段的父级字段
|
|
for _, field := range fieldList {
|
|
if field.TargetPath == item.TargetPath {
|
|
continue
|
|
}
|
|
// 当前字段以itemTarget开头
|
|
if strings.HasPrefix(field.TargetPath, item.TargetPath) {
|
|
// item.TargetPath是父级字段
|
|
handleInstance.parentFieldTable[item.TargetPath] = true
|
|
}
|
|
}
|
|
}
|
|
handleInstance.dynamicStruct = dynamicStructGenerate.NewStruct(tagTable)
|
|
|
|
if err := handleInstance.Run(); nil != err {
|
|
return nil, err
|
|
}
|
|
return handleInstance, nil
|
|
}
|
|
|
|
type handle struct {
|
|
sourceData []byte
|
|
fieldList []StructField
|
|
dynamicStruct dynamicStructGenerate.IBuilder
|
|
formatVal string
|
|
parentFieldTable map[string]bool // 父级字段路径表
|
|
res any // 处理结果
|
|
}
|
|
|
|
// Run 执行验证
|
|
func (h *handle) Run() error {
|
|
if len(h.fieldList) == 0 {
|
|
return nil
|
|
}
|
|
for _, field := range h.fieldList {
|
|
if len(field.Errmsg) == 0 {
|
|
field.Errmsg = field.JsonTag + " : 参数校验不通过"
|
|
}
|
|
required, hasRequired := h.checkRequired(field)
|
|
field.Required = required
|
|
if h.parentFieldTable[field.TargetPath] {
|
|
// 中间层级字段, 无需额外处理, 验一下必传就行
|
|
if !gjson.GetBytes(h.sourceData, field.SourcePath).Exists() && field.Required {
|
|
return errors.New(field.TargetPath + " : 数据源路径数据不存在 => " + field.SourcePath)
|
|
}
|
|
continue
|
|
}
|
|
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,
|
|
})
|
|
}
|
|
// 支持嵌套结构体
|
|
fieldTag := h.generateTag(field)
|
|
// 这里需要设置为对应类型的零值就行, 此处传入值的目的只是为了确认数据类型
|
|
path := field.TargetPath
|
|
if path == "" {
|
|
path = field.JsonTag
|
|
}
|
|
h.dynamicStruct.AddField(path, "", consts.GetDataTypeDefaultValue(field.Type), fieldTag, false)
|
|
}
|
|
val := h.dynamicStruct.Build().New()
|
|
if err := serialize.JSON.UnmarshalWithNumber([]byte(h.formatVal), &val); nil != err {
|
|
return err
|
|
}
|
|
if err := defaults.Set(val); nil != err {
|
|
// 默认值设置失败
|
|
return err
|
|
}
|
|
if err := validatorInstance.Struct(val); nil != err {
|
|
return GetValidateErr(val, err, TagErrMsg)
|
|
}
|
|
h.res = val // 记录结果
|
|
return nil
|
|
}
|
|
|
|
// Result 获取结果
|
|
//
|
|
// Author : go_developer@163.com<白茶清欢>
|
|
//
|
|
// Date : 10:50 2025/4/28
|
|
func (h *handle) Result() any {
|
|
return h.res
|
|
}
|
|
|
|
// Marshal 获取序列化之后的结果
|
|
func (h *handle) Marshal(marshalType string) ([]byte, error) {
|
|
if marshalType == "" {
|
|
marshalType = "json" // 默认按照json序列化
|
|
}
|
|
return serialize.Wrapper.Marshal(marshalType, h.Result())
|
|
}
|
|
|
|
// Map 直接获取map结果
|
|
func (h *handle) Map(marshalType string) (map[string]any, error) {
|
|
if marshalType == "" {
|
|
marshalType = "json" // 默认按照json序列化
|
|
}
|
|
var res map[string]any
|
|
err := h.Transform(marshalType, &res)
|
|
if nil != err {
|
|
return res, err
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// Transform 数据转换
|
|
func (h *handle) Transform(targetType string, receiver any) error {
|
|
if targetType == "" {
|
|
targetType = "json" // 默认按照json序列化
|
|
}
|
|
byteData, err := serialize.Wrapper.Marshal(targetType, h.Result())
|
|
if nil != err {
|
|
return err
|
|
}
|
|
return serialize.Wrapper.Unmarshal(targetType, byteData, receiver)
|
|
}
|
|
|
|
// 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 {
|
|
if !isHasRequiredRule {
|
|
// 必传, 但是没有必传校验规则
|
|
return true, true
|
|
}
|
|
return true, false
|
|
}
|
|
return false, isHasRequiredRule
|
|
}
|
|
|
|
// 生成结构体的tag标签
|
|
func (h *handle) generateTag(field StructField) string {
|
|
tagList := []string{
|
|
fmt.Sprintf(`json:"%s"`, field.JsonTag), // json tag
|
|
fmt.Sprintf(`xml:"%s"`, field.JsonTag), // xml tag
|
|
fmt.Sprintf(`yaml:"%s"`, field.JsonTag), // yaml tag
|
|
fmt.Sprintf(`toml:"%s"`, field.JsonTag), // toml tag
|
|
fmt.Sprintf(`ini:"%s"`, field.JsonTag), // ini 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, ",")))
|
|
// 默认值
|
|
if field.DefaultValue == "-" && (field.Type == consts.DataTypeString || field.Type == consts.DataTypeStringPtr) {
|
|
field.DefaultValue = ""
|
|
}
|
|
tagList = append(tagList, fmt.Sprintf(`%s:"%s"`, TagDefaultValue, field.DefaultValue))
|
|
return strings.Join(tagList, " ")
|
|
}
|