Go-Gin中优雅的实现参数校验,自定义错误消息提示

发布于:2024-04-03 ⋅ 阅读:(121) ⋅ 点赞:(0)

问题描述

在参数校验的时候我们一般会基于"github.com/go-playground/validator/v10"这个库给结构体加标签实现校验参数,当参数校验错误的时候,他的提示一般是英文的,怎么自定义参数错误提示呢?跟着我一步步来

注册校验器

package initialize

import (
	"ToDoList/global"
	"ToDoList/model/request"
	"ToDoList/util/validate"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	v10 "github.com/go-playground/validator/v10"
	zhTranslations "github.com/go-playground/validator/v10/translations/zh"
)

func RegisterValidator[T interface{}](fn v10.StructLevelFunc, model T) {
	translator := zh.New()
	universalTransator := ut.New(translator, translator)
	global.GVA_TRANS, _ = universalTransator.GetTranslator("zh")

	if v, ok := binding.Validator.Engine().(*v10.Validate); ok {
		v.RegisterStructValidation(fn, model)
		_ = zhTranslations.RegisterDefaultTranslations(v, global.GVA_TRANS)
	}

}

type _Validator struct{}

func (receiver _Validator) InitValidator() {
	RegisterValidator[request.Login](validate.UserLoginValidate.Validation, request.Login{})
}

var Validator = new(_Validator)


func (receiver *ginEngine) InitEngine() *gin.Engine {
	r := gin.Default()
	// 这里进行注册校验器!!
	Validator.InitValidator()
	return r
}

定义接口

我们定义一个接口规范,利于代码维护

package validate

import "github.com/go-playground/validator/v10"

type Validation interface {
	CreateValidate() validator.StructLevelFunc
}

定义具体参数校验的方法

ReportError函数第四个参数Param将作为错误时消息提示。

package validate

import (
	"ToDoList/model/request"
	"github.com/go-playground/validator/v10"
	"reflect"
)

type userLoginValidate struct{}

func (receiver userLoginValidate) CreateValidate() validator.StructLevelFunc {
	return func(sl validator.StructLevel) {
		user := sl.Current().Interface().(request.Login)
		StructNotEmpty(sl, user, []string{})
		if !VerifyPassword(8, 16, user.Password) {
			sl.ReportError(reflect.ValueOf(user.Password), "", "", "", "密码要出现数字、大写字母、小写字母、特殊字符(.@$!%*#_~?&^),至少包含其中2种且长度在8-16之间")
		}
	}
}

var UserLoginValidate = new(userLoginValidate)


定义公共函数

1.定义一个翻译的函数

func CustomErrMessage(err error) string {
	var result string
	errors := err.(validator.ValidationErrors)
	for _, err := range errors {
		if err.Param() != "" {
			result += err.Param() + ","
		}
	}
	return result
}

2.判断结构体字段是否为空

// 结构体字段判断是否为空
func StructNotEmpty(sl validator.StructLevel, st interface{}, omitKeys []string) {
	t := reflect.TypeOf(st)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	if t.Kind() != reflect.Struct {
		fmt.Println("必须传入结构体才能判断是否字段为空")
		return
	}
	v := reflect.ValueOf(st) //获取reflect.Type类
	for i := 0; i < v.NumField(); i++ {
		if !slices.Contains(omitKeys, t.Field(i).Name) {
			label := t.Field(i).Tag.Get("label")
			value := v.Field(i).Interface().(string)
			if len(value) == 0 {
				sl.ReportError(t.Field(i), "", "", "", fmt.Sprintf("%s不能为空", label))
			}
		}
	}
}

结构体标签

label标签是结构体字段的中文翻译,以便于当结构体字段为空时StructNotEmpty函数可以进行翻译。

type Login struct {
	Username  string `json:"username" binding:"required,min=4,max=16" label:"用户名"` // 用户名
	Password  string `json:"password" binding:"required,min=8,max=16" label:"密码"`  // 密码
	Captcha   string `json:"captcha" binding:"required" label:"验证码"`               // 验证码
	CaptchaId string `json:"captchaId" binding:"required" label:"验证码ID"`           // 验证码ID
}

返回结果

func (receiver *userApi) Login(c *gin.Context) {
	// 1.绑定用户表单数据
	var loginForm request.Login
	// 2.检验用户登录表单
	if err := c.ShouldBindJSON(&loginForm); err != nil {
		response.Response.FailWithMessage(validate.CustomErrMessage(err), c)
		return
	}
}

结果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

  1. 先在Gin中注册验证器
  2. 定义校验函数并通过sl validator.StructLevel调用sl.ReportError传入param参数作为自定义错误
  3. 然后绑定结构体 var loginForm request.Login if err := c.ShouldBindJSON(&loginForm);
  4. 当绑定失败的时候把err传给CustomErrMessage函数CustomErrMessage函数,将会把param的值作为错误结果
  5. 然后将CustomErrMessage函数返回结果给客户端即可