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的singleflight防止缓存击穿的方法

    使用Golang的singleflight防止缓存击穿的方法

    这篇文章主要介绍了使用Golang的singleflight防止缓存击穿的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • 快速解决Golang Map 并发读写安全的问题

    快速解决Golang Map 并发读写安全的问题

    这篇文章主要介绍了快速解决Golang Map 并发读写安全的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang实现文件传输功能

    Golang实现文件传输功能

    这篇文章主要为大家详细介绍了Golang实现文件传输功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-07-07
  • go日志库中的logrus

    go日志库中的logrus

    这篇文章主要介绍了go日志库中的logrus主要包括go日志库logrus的安装和使用,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • go程序员日常开发效率神器汇总

    go程序员日常开发效率神器汇总

    这篇文章主要介绍了go程序员开发效率神器包含了go常用开发工具,go调试工具,go常用网站,golang常用库,需要的朋友可以参考下
    2022-11-11
  • 用Go语言标准库实现Web服务之项目介绍

    用Go语言标准库实现Web服务之项目介绍

    从本节开始将从后端到前端一步一步实现一个Go语言Web服务,后端除了MySQL驱动,全部使用Go语言标准库来实现一个小型项目,本篇将简单的介绍一下项目开发要准备的流程,感兴趣的同学可以阅读一下
    2023-05-05
  • GO语言实现日志切割的示例详解

    GO语言实现日志切割的示例详解

    日志记录对程序排查问题比较关键,所以本文将选择Logrus和lumberjack两个库进行日志切割以及记录调用源,感兴趣的小伙伴可以了解一下
    2023-07-07
  • Golang连接并操作PostgreSQL数据库基本操作

    Golang连接并操作PostgreSQL数据库基本操作

    PostgreSQL是常见的免费的大型关系型数据库,具有丰富的数据类型,也是软件项目常用的数据库之一,下面这篇文章主要给大家介绍了关于Golang连接并操作PostgreSQL数据库基本操作的相关资料,需要的朋友可以参考下
    2022-09-09
  • Golang异常处理之优雅地控制和处理异常

    Golang异常处理之优雅地控制和处理异常

    在Golang中,异常处理是非常重要的一部分,能够有效地控制和处理代码中的异常情况。通过Golang的异常处理机制,可以优雅地捕获和处理异常,保障代码的可靠性和稳定性。同时,Golang还提供了丰富的工具和API,帮助开发者更加轻松地进行异常处理
    2023-04-04
  • Go库text与template包使用示例详解

    Go库text与template包使用示例详解

    这篇文章主要为大家介绍了Go库text与template包使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12

最新评论