Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Gin参数校验

Gin框架中参数校验优化详解

作者:Coder慌

这篇文章主要为大家详细介绍了Gin框架中参数校验优化的相关知识,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以了解下

原始方式

gin使用的是 github.com/go-playground/validator 该组件进行入参校验,如下是gin中常用的参数校验方式:

type AccountCreateForm struct {
    Id       uint64 `json:"id"`
    Name     string `json:"name" binding:"required,max=16"` // 使用required和max限制入参为必填项与最大长度不能超过16字符
    Username string `json:"username" binding:"required"`
    Password string `json:"password"`
}

该种方式有如下几个不好使的地方:

错误提示不友好,如果不做任何处理,默认参数校验不通过会返回如下错误提示

不支持正则表达式

不支持自定义错误描述

改进

自定义validatorx(validator扩展工具包)

注册翻译器,新增对校验错误进行转译方法

package validatorx
import (
	"mayfly-go/pkg/utils/stringx"
	"mayfly-go/pkg/utils/structx"
	"reflect"
	"strings"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	zh_trans "github.com/go-playground/validator/v10/translations/zh"
)
const CustomMsgTagName = "msg"
var (
    trans ut.Translator
)
func Init() {
    // 获取gin的校验器
    validate, ok := binding.Validator.Engine().(*validator.Validate)
    if !ok {
        return
    }
    // 修改返回字段key的格式
    validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
        // 如果存在校验错误提示消息,则使用字段名,后续需要通过该字段名获取相应错误消息
        if _, ok := fld.Tag.Lookup(CustomMsgTagName); ok {
            return fld.Name
        }
        name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
        if name == "-" {
            return ""
        }
        return name
    })
    // 注册翻译器
    zh := zh.New()
    uni := ut.New(zh, zh)
    trans, _ = uni.GetTranslator("zh")
    // 注册翻译器
    zh_trans.RegisterDefaultTranslations(validate, trans)
    // 注册自定义正则表达式校验器
    validate.RegisterValidation(CustomPatternTagName, patternValidFunc)
    // 注册自定义正则校验规则
    RegisterCustomPatterns()
}
// Translate 翻译错误信息
func Translate(data any, err error) map[string][]string {
    var result = make(map[string][]string)
    errors := err.(validator.ValidationErrors)
    for _, err := range errors {
        fieldName := err.Field()
        // 判断该字段是否设置了自定义的错误描述信息,存在则使用自定义错误信息进行提示
        if field, ok := structx.IndirectType(reflect.TypeOf(data)).FieldByName(fieldName); ok {
            if errMsg, ok := field.Tag.Lookup(CustomMsgTagName); ok {
                customMsg := getCustomErrMsg(err.Tag(), errMsg)
                if customMsg != "" {
                    result[fieldName] = append(result[fieldName], customMsg)
                    continue
                }
            }
        }
        // 如果是自定义正则校验规则,则使用自定义的错误描述信息
        if err.Tag() == CustomPatternTagName {
            result[fieldName] = append(result[fieldName], fieldName+patternErrMsg[err.Param()])
            continue
        }
        result[fieldName] = append(result[fieldName], err.Translate(trans))
    }
    return result
}
// 获取自定义的错误提示消息
//
// @param validTag 校验标签,如required等
// @param customMsg 自定义错误消息
func getCustomErrMsg(validTag, customMsg string) string {
    // 解析 msg:"required=用户名不能为空,min=用户名长度不能小于8位"
    msgs := strings.Split(customMsg, ",")
    for _, msg := range msgs {
        tagAndMsg := strings.Split(stringx.Trim(msg), "=")
        if len(tagAndMsg) > 1 && validTag == stringx.Trim(tagAndMsg[0]) {
            // 获取valid tag对应的错误消息
            return stringx.Trim(tagAndMsg[1])
        }
    }
    return customMsg
}
// Translate 翻译错误信息为字符串
func Translate2Str(data any, err error) string {
    res := Translate(data, err)
    errMsgs := make([]string, 0)
    for _, v := range res {
        errMsgs = append(errMsgs, v...)
    }
    return strings.Join(errMsgs, ", ")
}

自定义正则表达式校验方式

package validatorx
import (
	"mayfly-go/pkg/global"
	"regexp"
	"github.com/go-playground/validator/v10"
)
const CustomPatternTagName = "pattern"
var (
	regexpMap     map[string]*regexp.Regexp  // key:正则表达式名称   value:正则表达式
	patternErrMsg map[string]string         // key:正则表达式名称   value:校验不通过时的错误消息提示
)
// 注册自定义正则表达式校验规则
func RegisterCustomPatterns() {
	// 账号用户名校验,使用该种方式可以复用正则表达式以及错误提示
    // 使用方式如:Username string `json:"username" binding:"pattern=account_username"`
	RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允许输入5-20位大小写字母、数字、下划线")
}
// 注册自定义正则表达式
func RegisterPattern(patternName string, regexpStr string, errMsg string) {
	if regexpMap == nil {
		regexpMap = make(map[string]*regexp.Regexp, 0)
		patternErrMsg = make(map[string]string)
	}
	regexpMap[patternName] = regexp.MustCompile(regexpStr)
	patternErrMsg[patternName] = errMsg
}
// 自定义正则表达式校验器函数
func patternValidFunc(f validator.FieldLevel) bool {
	reg := regexpMap[f.Param()]
	if reg == nil {
		global.Log.Warnf("%s的正则校验规则不存在!", f.Param())
		return false
	}
	return reg.MatchString(f.Field().String())
}

错误转译

对入参进行校验,检验不通过时将错误进行转译,转译为汉字或自定义的错误描述等。

// 绑定并校验请求结构体参数
func BindJsonAndValid[T any](g *gin.Context, data T) T {
    if err := g.ShouldBindJSON(data); err != nil {
        // 统一recover处理
        panic(ConvBindValidationError(data, err))
    } else {
        return data
    }
}
// 绑定请求体中的json至form结构体,并拷贝至另一结构体
func BindJsonAndCopyTo[T any](g *gin.Context, form any, toStruct T) T {
	BindJsonAndValid(g, form)
	structx.Copy(toStruct, form)
	return toStruct
}
// 转译参数校验错误,并将参数校验错误为业务异常错误(统一recover处理)
func ConvBindValidationError(data any, err error) error {
    if e, ok := err.(validator.ValidationErrors); ok {
        // 调用validatorx.Translate2Str方法进行校验错误转译
        return biz.NewBizErrCode(403, validatorx.Translate2Str(data, e))
    }
    return err
}
// 返回失败结果集
func ErrorRes(g *gin.Context, err any) {
    switch t := err.(type) {
    case biz.BizError:
        g.JSON(http.StatusOK, model.Error(t))
    case error:
        g.JSON(http.StatusOK, model.ServerError())
        global.Log.Errorf("%s\n%s", t.Error(), string(debug.Stack()))
    case string:
        g.JSON(http.StatusOK, model.ServerError())
        global.Log.Errorf("%s\n%s", t, string(debug.Stack()))
    default:
        global.Log.Error(t)
    }
}

初始化校验器

项目启动时,在合适的时机初始化校验器

// 参数校验器初始化、如错误提示中文转译、注册自定义校验器等
validatorx.Init()

统一使用方式

入参字段tag绑定

type AccountCreateForm struct {
    Id       uint64 `json:"id"`
    // msg tag里对应的required max即为binding里的校验类型
    Name     string `json:"name" binding:"required,max=16" msg:"required=姓名不能为空,max=姓名最大长度不能超过16位"`
    // account_name为validatorx.RegisterPattern("account_username", "^[a-zA-Z0-9_]{5,20}$", "只允许输入5-20位大小写字母、数字、下划线")
    Username string `json:"username" binding:"pattern=account_username"`
    Password string `json:"password" binding:"required"`
}
form := &form.AccountCreateForm{}
// 校验不通过会自行panic统一recover处理
var account *entity.Account = ginx.BindJsonAndCopyTo(rc.GinCtx, form, new(entity.Account)) 

效果

更多代码详见:gitee.com/objs/mayfly-go一个web版 linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres)、redis(单机 哨兵 集群)、mongo统一管理操作平台

到此这篇关于Gin框架中参数校验优化详解的文章就介绍到这了,更多相关Gin参数校验内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文