[go-每日一库] golang validator常规参数校验及自定义规则校验

1.validator简介

validator是一个开源的验证器包,可以快速校验输入信息是否符合自定规则。源码地址: https://github.com/go-playground/validator

本地开发安装库:

go get github.com/go-playground/validator

2.常用示例

例如我们使用golang的gin框架进行web server的开发,对于传来的json参数进行校验,这个是必不可少的,只要是传过来的参数,就不可信。

validate的tag校验类型见官方文档-参考。

话不多说,直接上代码示例:

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"net/http"
)

type User struct {
	Username string   `json:"username" validate:"required,min=4,max=20"`     // 必填字段,限制长度
	Password string   `json:"password" validate:"required"` 			     // 必填字段
	Email    string   `json:"email" validate:"required,email"`			     // 限于email格式
	Phone    string   `json:"phone" validate:"omitempty,numeric"`		     // 限于数字型
	Hobby	 []string `json:"hobby" validate:"omitempty"` 				     // 空时忽略
	Age 	 int      `json:"age" validate:"omitempty,gt=18,lt=100"`		 // 限制大小
	Gender   string	  `json:"gender" validate:"omitempty,oneof=male female"` // 限于男女
}

func main()  {
	router := gin.Default()

	router.POST("/login", login)

	_ = router.Run(":8080")
}

func login(ctx *gin.Context)  {
	var user User
	err := ctx.ShouldBindJSON(&user)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
		return
	}
	// repeat parse use below
	//_ = ctx.ShouldBindWith(&user, binding.JSON)

	validate := validator.New()
	err = validate.Struct(user)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"msg": err.Error()})
		return
	}

	ctx.JSON(http.StatusOK, gin.H{"msg": "success"})
}

启动server,postman测试,可以看到哪些字段因为某个校验不能通过校验,导致error, 是不是很友好呢,不过建议返回前台校验参数失败即可,具体校验详情作为日志输出到server的log文件中。

3.进阶示例-自定义字段或验证方法

3.1 自定义规则

代码源自: http://liuqh.icu/2021/05/30/go/gin/11-validate/

package main
import (
	"fmt"
	"github.com/go-playground/validator/v10"
)
// 验证pre
type CustomParam struct {
	Pre string `validate:"pre=go_"`
}
func main()  {
	// 实例化验证器
	validate = validator.New()
	// 注册自定义标签
	_ = validate.RegisterValidation("pre", ValidatePre)
	cusParam := CustomParam{
		Pre: "php_",
	}
	err := validate.Struct(cusParam)
	fmt.Println(err)
}
// 自定义验证规则
func ValidatePre(fl validator.FieldLevel) bool {
	return fl.Field().String() == "go_"
}

3.2 自定义规则在gin中使用

代码源自: http://liuqh.icu/2021/05/30/go/gin/11-validate/
有自定义的validator,tag的类型设为binding。

package main
import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
	"net/http"
	"time"
)
// 定义结构体
type User struct {
	Name string `form:"name" binding:"required,min=3,max=5" `
	BirthDate time.Time `form:"date" binding:"required,birth" time_format:"2006-01-02"`
}

// 运行程序
func main()  {
	engine := gin.Default()
  // 注册自定义验证标签:birth
	if validate,ok  := binding.Validator.Engine().(*validator.Validate);ok {
		validate.RegisterValidation("birth",checkBirthDate)
	}
  // 接收请求
	engine.GET("/valid", func(context *gin.Context) {
		var user User
    // 集成验证
		err := context.ShouldBindQuery(&user)
		if err != nil {
			context.JSON(http.StatusBadRequest,gin.H{"error":err.Error()})
			return
		}
		context.JSON(http.StatusOK,gin.H{"msg":"success"})
	})
	_ = engine.Run()
}
// 检测生日
func checkBirthDate(fl validator.FieldLevel) bool {
	t,ok := fl.Field().Interface().(time.Time)
	if ok {
		// 当前时间应该大于生日时间
		if time.Now().After(t) {
			return true
		}
	}
	return false
}

4.binding && validator

见参考文档:Gin请求参数校验

5.示例-用户名与密码校验

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
	"reflect"
	"regexp"
)


func main()  {
	u := User{
		Username: "IamJames",
		Password: "1234567A$888",
	}
	validate := validator.New()
	_ = validate.RegisterValidation("minLen", minLen)
	_ = validate.RegisterValidation("verifyPwd", verifyPwd)
	err := validate.Struct(u)
	if err != nil {
		errInfo := processErr(u, err)
		fmt.Printf("error: %v\n", err)
		fmt.Printf("errorInfo: %v\n", errInfo)
		return
	}
	fmt.Println("validate pass...")
}

type User struct {
	Username string `json:"username,omitempty" validate:"required,minLen" field_error_info:"用户名最少6个字符"`
	Password string `json:"password,omitempty" validate:"required,verifyPwd" field_error_info:"密码应包含数字、大/小写字母、特殊字符中的3种, 且至少8个字符"`
}

func minLen(f validator.FieldLevel) bool {
	val := f.Field().String()
	if len(val) < 6 {
		return false
	}
	return true
}

func verifyPwd(f validator.FieldLevel) bool {
	val := f.Field().String()
	if len(val) < 8 || len(val) > 20 { // length需要通过验证
		fmt.Println("pwd length error")
		return false
	}

	pwdPattern := `^[0-9a-zA-Z!@#$%^&*~-_+]{8,20}$`
	reg, err := regexp.Compile(pwdPattern) // filter exclude chars
	if err != nil {
		return false
	}

	match := reg.MatchString(val)
	if !match {
		fmt.Println("not match error.")
		return false
	}

	var cnt int = 0 // 满足3中以上即可通过验证
	patternList := []string{ // 数字、大小写字母、特殊字符
		`[0-9]+`,
		`[a-z]+`,
		`[A-Z]+`,
		`[!@#$%^&*~-_+]+`,
	}
	for _, pattern := range patternList {
		match, _ = regexp.MatchString(pattern, val)
		if match {
			cnt++
		}
	}
	if cnt < 3 {
		fmt.Println("pwd should include at least 3 types.")
		return false
	}
	return true
}

func processErr(u interface{},err error) string {
	if err == nil {  //如果为nil 说明校验通过
		return ""
	}

	invalid, ok := err.(*validator.InvalidValidationError)   //如果是输入参数无效,则直接返回输入参数错误
	if ok {
		return "输入参数错误:" + invalid.Error()
	}
	validationErrs := err.(validator.ValidationErrors)   //断言是ValidationErrors
	for _, validationErr := range validationErrs {
		fieldName := validationErr.Field()              //获取是哪个字段不符合格式
		field, ok := reflect.TypeOf(u).FieldByName(fieldName)  //通过反射获取filed
		if ok {
			errorInfo := field.Tag.Get("field_error_info")  //获取field对应的reg_error_info tag值
			return fieldName + ": " + errorInfo  //返回错误
		}else {
			return "缺失field_error_info"
		}
	}
	return ""
}

参考文档

posted on 2022-06-23 10:28  进击的davis  阅读(5154)  评论(0编辑  收藏  举报

导航