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

 更新时间:2023年08月24日 08:37:13   作者: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参数校验内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang中文字符串截取函数实现原理

    Golang中文字符串截取函数实现原理

    在golang中可以通过切片截取一个数组或字符串,但是当截取的字符串是中文时,可能会出现问题,下面我们来自定义个函数解决Golang中文字符串截取问题
    2018-03-03
  • 一文详解Go语言中Mutex互斥锁

    一文详解Go语言中Mutex互斥锁

    Golang中的Mutex互斥锁是一种常用的并发控制机制,用于保护共享资源的访问,在本文中,我们将深入探讨Mutex互斥锁的原理、日常使用、锁结构以及运行机制,需要的朋友可以参考下
    2023-12-12
  • Go语言基础学习之数组的使用详解

    Go语言基础学习之数组的使用详解

    数组相必大家都很熟悉,各大语言也都有数组的身影。Go 语言也提供了数组类型的数据结构。本文就来通过一些简单的示例带大家了解一下Go语言中数组的使用,希望对大家有所帮助
    2022-12-12
  • Golang基于泛化调用与Nacos实现Dubbo代理

    Golang基于泛化调用与Nacos实现Dubbo代理

    这篇文章主要为大家详细介绍了Golang如何基于泛化调用与Nacos实现Dubbo代理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-04-04
  • golang简单位运算示例

    golang简单位运算示例

    这篇文章主要介绍了golang简单位运算,包括位移运算、取反及位与位或等运算,需要的朋友可以参考下
    2016-07-07
  • Go语言之使用pprof工具查找goroutine(协程)泄漏

    Go语言之使用pprof工具查找goroutine(协程)泄漏

    这篇文章主要介绍了Go语言之使用pprof工具查找goroutine(协程)泄漏,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Go语言RPC Authorization进行简单ip安全验证的方法

    Go语言RPC Authorization进行简单ip安全验证的方法

    这篇文章主要介绍了Go语言RPC Authorization进行简单ip安全验证的方法,实例分析了Go语言进行ip验证的技巧,需要的朋友可以参考下
    2015-03-03
  • Golang开发库的集合及作用说明

    Golang开发库的集合及作用说明

    这篇文章主要为大家介绍了Golang开发golang库的集合及简单的作用说明,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-11-11
  • go使用Gin框架利用阿里云实现短信验证码功能

    go使用Gin框架利用阿里云实现短信验证码功能

    这篇文章主要介绍了go使用Gin框架利用阿里云实现短信验证码,使用json配置文件及配置文件解析,编写路由controller层,本文通过代码给大家介绍的非常详细,需要的朋友可以参考下
    2021-08-08
  • 浅析Go语言中的逃逸分析

    浅析Go语言中的逃逸分析

    在Go语言中,内存分配和逃逸分析是至关重要的概念,对于理解代码的性能和内存使用情况至关重要,本文将深入探讨Go语言中的内存分配原理以及逃逸分析的作用,希望对大家有所帮助
    2024-04-04

最新评论