golang之数据验证validator的实现

前言

在web应用中经常会遇到数据验证问题,普通的验证方法比较繁琐,这里介绍一个使用比较多的包validator

原理

将验证规则写在struct对字段tag里,再通过反射(reflect)获取struct的tag,实现数据验证。

安装

go get github.com/go-playground/validator/v10

示例

package main

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

type Users struct {
 Phone string `form:"phone" json:"phone" validate:"required"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {

 users := &Users{
 Phone:  "1326654487",
 Passwd:  "123",
 Code:   "123456",
 }
 validate := validator.New()
 err := validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err)//Key: 'Users.Passwd' Error:Field validation for 'Passwd' failed on the 'min' tag
 return
 }
 }
 return
}

验证规则

  • required :必填
  • email:验证字符串是email格式;例:“email”
  • url:这将验证字符串值包含有效的网址;例:“url”
  • max:字符串最大长度;例:“max=20”
  • min:字符串最小长度;例:“min=6”
  • excludesall:不能包含特殊字符;例:“excludesall=0x2C”//注意这里用十六进制表示。
  • len:字符长度必须等于n,或者数组、切片、map的len值为n,即包含的项目数;例:“len=6”
  • eq:数字等于n,或者或者数组、切片、map的len值为n,即包含的项目数;例:“eq=6”
  • ne:数字不等于n,或者或者数组、切片、map的len值不等于为n,即包含的项目数不为n,其和eq相反;例:“ne=6”
  • gt:数字大于n,或者或者数组、切片、map的len值大于n,即包含的项目数大于n;例:“gt=6”
  • gte:数字大于或等于n,或者或者数组、切片、map的len值大于或等于n,即包含的项目数大于或等于n;例:“gte=6”
  • lt:数字小于n,或者或者数组、切片、map的len值小于n,即包含的项目数小于n;例:“lt=6”
  • lte:数字小于或等于n,或者或者数组、切片、map的len值小于或等于n,即包含的项目数小于或等于n;例:“lte=6”

跨字段验证

如想实现比较输入密码和确认密码是否一致等类似场景

  • eqfield=Field: 必须等于 Field 的值;
  • nefield=Field: 必须不等于 Field 的值;
  • gtfield=Field: 必须大于 Field 的值;
  • gtefield=Field: 必须大于等于 Field 的值;
  • ltfield=Field: 必须小于 Field 的值;
  • ltefield=Field: 必须小于等于 Field 的值;
  • eqcsfield=Other.Field: 必须等于 struct Other 中 Field 的值;
  • necsfield=Other.Field: 必须不等于 struct Other 中 Field 的值;
  • gtcsfield=Other.Field: 必须大于 struct Other 中 Field 的值;
  • gtecsfield=Other.Field: 必须大于等于 struct Other 中 Field 的值;
  • ltcsfield=Other.Field: 必须小于 struct Other 中 Field 的值;
  • ltecsfield=Other.Field: 必须小于等于 struct Other 中 Field 的值;

示例

type UserReg struct {
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Repasswd string `form:"repasswd" json:"repasswd" validate:"required,max=20,min=6,eqfield=Passwd"`
}

示例验证了Passwd,和Repasswd值是否相等。如想了解更多类型,请参考文档 https://godoc.org/gopkg.in/go-playground/validator.v10

自定义验证类型

示例:

package main

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

type Users struct {
 Name string `form:"name" json:"name" validate:"required,CustomValidationErrors"`//包含自定义函数
 Age uint8 `form:"age" json:"age" validate:"required,gt=18"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {

 users := &Users{
 Name:  "admin",
 Age:  12,
 Passwd:  "123",
 Code:   "123456",
 }
 validate := validator.New()
 //注册自定义函数
 _=validate.RegisterValidation("CustomValidationErrors", CustomValidationErrors)
 err := validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err)//Key: 'Users.Name' Error:Field validation for 'Name' failed on the 'CustomValidationErrors' tag
 return
 }
 }
 return
}

func CustomValidationErrors(fl validator.FieldLevel) bool {
return fl.Field().String() != "admin"
}

翻译错误信息为中文

通过以上示例我们看到,validator默认的错误提示信息类似如下

Key: 'Users.Name' Error:Field validation for 'Name' failed on the 'CustomValidationErrors' tag

显然这并不是我们想要,如想翻译成中文,或其他语言怎么办?go-playground上提供了很好的解决方法。

先自行安装需要的两个包

https://github.com/go-playground/locales
https://github.com/go-playground/universal-translator

执行:

go get github.com/go-playground/universal-translator
go get github.com/go-playground/locales

示例:

package main

import (
 "fmt"
 "github.com/go-playground/locales/zh"
 ut "github.com/go-playground/universal-translator"
 "github.com/go-playground/validator/v10"
 zh_translations "github.com/go-playground/validator/v10/translations/zh"
)

type Users struct {
 Name string `form:"name" json:"name" validate:"required"`
 Age uint8 `form:"age" json:"age" validate:"required,gt=18"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {
 users := &Users{
 Name:  "admin",
 Age:  12,
 Passwd:  "123",
 Code:   "123456",
 }
 uni := ut.New(zh.New())
 trans, _ := uni.GetTranslator("zh")
 validate := validator.New()
 //验证器注册翻译器
 err := zh_translations.RegisterDefaultTranslations(validate, trans)
 if err!=nil {
 fmt.Println(err)
 }
 err = validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err.Translate(trans))//Age必须大于18
 return
 }
 }

 return
}

输出:

Age必须大于18

至此我们发现大部分错误信息已经翻译成中文,但字段名(Age)还是没有翻译,为了将字段名翻译成中文,查了一些资料,https://www.jb51.net/article/197866.htm>,

照着做没有成功(可能有遗漏吧),最后还是翻看了一下源代码,在<https://github.com/go-playground/validator/blob/master/validator_instance.go,第137行

// RegisterTagNameFunc registers a function to get alternate names for StructFields.
//
// eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names:
//
// validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
//  name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
//  if name == "-" {
//   return ""
//  }
//  return name
// })

其实原理就是注册一个函数,将struct tag里添加的中文名 作为备用名。

package main

import (
 "fmt"
 "github.com/go-playground/locales/zh"
 ut "github.com/go-playground/universal-translator"
 "github.com/go-playground/validator/v10"
 zh_translations "github.com/go-playground/validator/v10/translations/zh"
 "reflect"
)

type Users struct {
 Name string `form:"name" json:"name" validate:"required" label:"用户名"`
 Age uint8 `form:"age" json:"age" validate:"required,gt=18" label:"年龄"`
 Passwd string `form:"passwd" json:"passwd" validate:"required,max=20,min=6"`
 Code string `form:"code" json:"code" validate:"required,len=6"`
}

func main() {
 users := &Users{
 Name:  "admin",
 Age:  12,
 Passwd:  "123",
 Code:   "123456",
 }
 uni := ut.New(zh.New())
 trans, _ := uni.GetTranslator("zh")
 validate := validator.New()
 //注册一个函数,获取struct tag里自定义的label作为字段名
 validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
 name:=fld.Tag.Get("label")
 return name
 })
 //注册翻译器
 err := zh_translations.RegisterDefaultTranslations(validate, trans)
 if err!=nil {
 fmt.Println(err)
 }
 err = validate.Struct(users)
 if err != nil {
 for _, err := range err.(validator.ValidationErrors) {
 fmt.Println(err.Translate(trans))//年龄必须大于18
 return
 }
 }

 return
}

输出结果:

年龄必须大于18

gin 内置的validator

gin已经支持go-playground / validator / v10进行验证。在此处查看有关标签用法的完整文档。

以下只提供了一个绑定ShouldBindWith示例,如需了解更多方法,进入这里

示例

package main

import (
 "fmt"
 "github.com/go-playground/locales/zh"
 ut "github.com/go-playground/universal-translator"
 "github.com/go-playground/validator/v10"
 "net/http"
 "reflect"
 "strings"
 "time"

 "github.com/gin-gonic/gin"
 "github.com/gin-gonic/gin/binding"
 zh_translations "github.com/go-playground/validator/v10/translations/zh"
)
var trans ut.Translator
// Booking contains binded and validated data.
type Booking struct {
 CheckIn time.Time `form:"check_in" json:"check_in" binding:"required,bookabledate" time_format:"2006-01-02" label:"输入时间"`
 CheckOut time.Time `form:"check_out" json:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02" label:"输出时间"`
}

var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
 date, ok := fl.Field().Interface().(time.Time)
 if ok {
 today := time.Now()
 if today.After(date) {
 return false
 }
 }
 return true
}

func main() {
 route := gin.Default()
 uni := ut.New(zh.New())
 trans, _ = uni.GetTranslator("zh")

 if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
 //注册翻译器
 _= zh_translations.RegisterDefaultTranslations(v, trans)
 //注册自定义函数
 _=v.RegisterValidation("bookabledate", bookableDate)

 //注册一个函数,获取struct tag里自定义的label作为字段名
 v.RegisterTagNameFunc(func(fld reflect.StructField) string {
 name:=fld.Tag.Get("label")
 return name
 })
 //根据提供的标记注册翻译
 v.RegisterTranslation("bookabledate", trans, func(ut ut.Translator) error {
 return ut.Add("bookabledate", "{0}不能早于当前时间或{1}格式错误!", true)
 }, func(ut ut.Translator, fe validator.FieldError) string {
 t, _ := ut.T("bookabledate", fe.Field(), fe.Field())
 return t
 })

 }
 route.GET("/bookable", getBookable)
 route.Run(":8085")
}

func getBookable(c *gin.Context) {
 var b Booking
 if err := c.ShouldBindWith(&b, binding.Query); err == nil {
 c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
 } else {
 errs := err.(validator.ValidationErrors)

 fmt.Println(errs.Translate(trans))
 //for _, e := range errs {
 // // can translate each error one at a time.
 // fmt.Println(e.Translate(trans))
 //}
 c.JSON(http.StatusBadRequest, gin.H{"error": errs.Translate(trans)})
 }
}

运行程序,执行以下命令

$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-16"

结果:

{"error":{"Booking.输入时间":"输入时间不能早于当前时间或输入时间格式错误!","Booking.输出时间":"输出时间必须大于CheckIn"}}

查看以上结果我们发现翻译还是不太完美,如规则中有gtfield的情况,字段(CheckIn)并没有被翻译。所以通过struct添加label的方式并不能从根本上解决字段翻译问题。为了得到想要的结果,就需要将错误信息做单独处理再输出。

先定义翻译库

var BookingTrans =map[string]string{"CheckIn":"输入时间","CheckOut":"输出时间"}

再定义翻译函数

func TransTagName(libTans,err interface{}) interface{} {
 switch err.(type) {
 case validator.ValidationErrorsTranslations:
 var errs map[string]string
 errs = make(map[string]string,0)
 for k,v:=range err.(validator.ValidationErrorsTranslations){
 for key,value:=range libTans.(map[string]string) {
 v=strings.Replace(v,key,value,-1)
 }
 errs[k] = v
 }
 return errs
 case string:
 var errs string
 for key,value:=range libTans.(map[string]string) {
 errs=strings.Replace(errs,key,value,-1)
 }
 return errs
 default:
 return err
 }
}

将原来翻译错误信息的地方

errs.Translate(trans)

修改为

msg:=TransTagName(BookingTrans,errs.Translate(trans))
fmt.Println(msg)

结果

{"error":{"Booking.输入时间":"输入时间不能早于当前时间或输入时间格式错误!","Booking.输出时间":"输出时间必须大于输入时间"}}

小结:

1.gin 已经支持validator最新的v10。

2.validator数据验证顺序struct字段从上往下,单个字段规则(binding:"gt=0,lt=2`),先左后右。

参考:

https://github.com/go-playground/validator

https://github.com/gin-gonic/gin

https://gitissue.com/issues/5d06a73965d56f73569b825f

https://segmentfault.com/a/1190000022527284

到此这篇关于golang之数据验证validator的实现的文章就介绍到这了,更多相关golang 数据验证validator内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • golang validator参数校验的实现

    今天在改后台页面,参数校验错误时输出全是英文,使用着很难看懂到底时什么错了 故而决定去做i18n前端国际化. 改的时候踩了很多坑,故而记录一下,顺便记录以下查问题的方式. 效果 从原来的Title is required变为标题为必填字段 完成后的代码: 这里主要定义了初始化了一个中文的trans和Validate的变量,并对其做初始化 初始化主要做了以下事情: 注册了TagName函数 // RegisterTagNameFunc registers a function to get alt

  • golang之数据校验的实现代码示例

    目前大都是使用 validator 安装 go get gopkg.in/go-playground/validator.v9 原理 当然只能通过反射来实现了,之前写过一篇反射的文章 golang之反射和断言 ,里面有写到怎么通过反射获取struct tag. 读取struct tag之后就是对里面的标识符进行识别,然后进行验证了.具体可以去看源码. demo 简单使用: package main import ( "fmt" "gopkg.in/go-playground/

  • golang常用库之字段参数验证库-validator使用详解

    golang常用库:gorilla/mux-http路由库使用 golang常用库:配置文件解析库-viper使用 golang常用库:操作数据库的orm框架-gorm基本使用 golang常用库:字段参数验证库-validator使用 一.背景 在平常开发中,特别是在web应用开发中,为了验证输入字段的合法性,都会做一些验证操作.比如对用户提交的表单字段进行验证,或者对请求的API接口字段进行验证,验证字段的合法性,保证输入字段值的安全,防止用户的恶意请求. 一般的做法是用正则表达式,一个字段

  • golang之数据验证validator的实现

    前言 在web应用中经常会遇到数据验证问题,普通的验证方法比较繁琐,这里介绍一个使用比较多的包validator. 原理 将验证规则写在struct对字段tag里,再通过反射(reflect)获取struct的tag,实现数据验证. 安装 go get github.com/go-playground/validator/v10 示例 package main import ( "fmt" "github.com/go-playground/validator/v10&quo

  • Go gRPC服务proto数据验证进阶教程

    前言 上篇介绍了go-grpc-middleware的grpc_zap.grpc_auth和grpc_recovery使用,本篇将介绍grpc_validator,它可以对gRPC数据的输入和输出进行验证. 创建proto文件,添加验证规则 这里使用第三方插件go-proto-validators自动生成验证规则. go get github.com/mwitkow/go-proto-validators 1.新建simple.proto文件 syntax = "proto3"; pa

  • JQuery 简便实现页面元素数据验证功能

    ASP.NET提供了丰富的数据验证控件,但这个验证功能都必须运用在服务器控件中:在复合验证数据时使用也不是很方便(虽然拖放控件很方便,但拖放数增多和设置相关属性就变得很麻烦的事情).为了更方便灵活地实现验证功能,于是采用了JQuery编写了一个简单验证组件,在使用时只需要简单的描述一下验证规则,并不需要写一句JavaScript就可以实现具备ASP.NET验证控件的功能(当然页面要引入相关JavaScript文件). 功能目标        通过简单的自定义属性值描述具体的验证功能,当Form提

  • jQuery EasyUI常用数据验证汇总

    easyui 的validatebox()提供了自定义验证的方法,为此我把一些常用的数据验证汇总了一下,代码如下 $.extend($.fn.validatebox.defaults.rules, { CHS: { validator: function (value, param) { return /^[\u0391-\uFFE5]+$/.test(value); }, message: '请输入汉字' }, ZIP: { validator: function (value, param)

  • jquery插件bootstrapValidator数据验证详解

    因为项目需要数据验证,看bootstrapValidator 还不错,就上手一直,完美兼容,话不多说. bootstrap:能够增加兼容性的强大框架. 需要引用css: bootstrap.min.css bootstrapValidator.min.css js: jquery-1.10.2.min.js bootstrap.min.js bootstrapValidator.min.js (下载实例) 以上这些都是必须的. 先上个简单的例子,只要导入相应的文件可以直接运行: <!DOCTYP

  • 详解SpringMVC学习系列(6) 之 数据验证

    在系列(4).(5)中我们展示了如何绑定数据,绑定完数据之后如何确保我们得到的数据的正确性?这就是我们本篇要说的内容 -> 数据验证. 这里我们采用Hibernate-validator来进行验证,Hibernate-validator实现了JSR-303验证框架支持注解风格的验证.首先我们要到http://hibernate.org/validator/下载需要的jar包,这里以4.3.1.Final作为演示,解压后把hibernate-validator-4.3.1.Final.jar.jb

  • Yii2框架数据验证操作实例详解

    本文实例讲述了Yii2框架数据验证操作.分享给大家供大家参考,具体如下: 一.场景 什么情况下需要使用场景呢?当一个模型需要在不同情境中使用时,若不同情境下需要的数据表字段和数据验证规则有所不同,则需要定义多个场景来区分不同使用情境.例如,用户注册的时候需要填写email,登录的时候则不需要,这时就需要定义两个不同场景加以区分. 默认情况下模型的场景是由rules()方法申明的验证规则中使用到的场景决定的,也可以通过覆盖scenarios()方法来更具体地定义模型的所有场景,例如: public

  • js中自定义react数据验证组件实例详解

    我们在做前端表单提交时,经常会遇到要对表单中的数据进行校验的问题.如果用户提交的数据不合法,例如格式不正确.非数字类型.超过最大长度.是否必填项.最大值和最小值等等,我们需要在相应的地方给出提示信息.如果用户修正了数据,我们还要将提示信息隐藏起来. 有一些现成的插件可以让你非常方便地实现这一功能,如果你使用的是knockout框架,那么你可以借助于Knockout-Validation这一插件.使用起来很简单,例如我下面的这一段代码: ko.validation.locale('zh-CN');

  • 浅析JS中什么是自定义react数据验证组件

    我们在做前端表单提交时,经常会遇到要对表单中的数据进行校验的问题.如果用户提交的数据不合法,例如格式不正确.非数字类型.超过最大长度.是否必填项.最大值和最小值等等,我们需要在相应的地方给出提示信息.如果用户修正了数据,我们还要将提示信息隐藏起来. 有一些现成的插件可以让你非常方便地实现这一功能,如果你使用的是knockout框架,那么你可以借助于Knockout-Validation这一插件.使用起来很简单,例如我下面的这一段代码: ko.validation.locale('zh-CN');

  • laravel 数据验证规则详解

    如下所示: return [ 'accepted' => '必须为yes,on,1,true', 'active_url' => '是否是一个合法的url,基于PHP的checkdnsrr函数,因此也可以用来验证邮箱地址是否存在', 'after:date' => '验证字段必须是给定日期后的值,比如required|date|after:tomorrow,通过PHP函数strtotime来验证', 'after_or_equal:date' => '大于等于', 'alpha'

随机推荐