一、基本示例
1、validator介绍
在web开发中一个不可避免的环节就是对请求参数进行校验,通常我们会在代码中定义与请求参数相对应的模型(结构体)
借助模型绑定快捷地解析请求中的参数,例如gin框架中的Bind和ShouldBind系列方法
本文就以gin框架的请求参数校验为例,介绍一些validator库的实用技巧
2、Json数据解析和绑定
package main
import (
'github.com/gin-gonic/gin'
'net/http'
)
// 定义接收数据的结构体
type Login struct {
// binding:'required'修饰的字段,若接收为空值,则报错,是必须字段
User string `form:'username' json:'user' uri:'user' xml:'user' binding:'required'`
Pssword string `form:'password' json:'password' uri:'password' xml:'password' binding:'required'`
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// JSON绑定
r.POST('loginJSON', testHandler)
r.Run(':8000')
}
func testHandler(c *gin.Context) {
// 声明接收的变量
var json Login
// 将request的body中的数据,自动按照json格式解析到结构体
if err := c.ShouldBindJSON(&json); err != nil {
// 返回错误信息
// gin.H封装了生成json数据的工具
c.JSON(http.StatusBadRequest, gin.H{'error': err.Error()})
return
}
// 判断用户名密码是否正确
if json.User != 'root' || json.Pssword != 'root' {
c.JSON(http.StatusBadRequest, gin.H{'status': '304'})
return
}
c.JSON(http.StatusOK, gin.H{'status': '200'})
}
测试效果
http://localhost:8000/loginJSON/
3、表单数据解析和绑定
package main
import (
'net/http'
'github.com/gin-gonic/gin'
)
// 定义接收数据的结构体
type Login struct {
// binding:'required'修饰的字段,若接收为空值,则报错,是必须字段
User string `form:'username' json:'user' uri:'user' xml:'user' binding:'required'`
Pssword string `form:'password' json:'password' uri:'password' xml:'password' binding:'required'`
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// JSON绑定
r.POST('/loginForm', testHandler)
r.Run(':8000')
}
func testHandler(c *gin.Context) {
// 声明接收的变量
var form Login
// Bind()默认解析并绑定form格式
// 根据请求头中content-type自动推断
if err := c.Bind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{'error': err.Error()})
return
}
// 判断用户名密码是否正确
if form.User != 'root' || form.Pssword != 'root' {
c.JSON(http.StatusBadRequest, gin.H{'status': '304'})
return
}
c.JSON(http.StatusOK, gin.H{'status': '200'})
}
login.html
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<meta http-equiv='X-UA-Compatible' content='ie=edge'>
<title>Document</title>
</head>
<body>
<form action='http://localhost:8000/loginForm' method='post' enctype='application/x-www-form-urlencoded'>
用户名<input type='text' name='username'><br>
密码<input type='password' name='password'>
<input type='submit' value='提交'>
</form>
</body>
</html>
4、URL数据解析和绑定
package main
import (
'github.com/gin-gonic/gin'
'net/http'
)
// 定义接收数据的结构体
type Login struct {
// binding:'required'修饰的字段,若接收为空值,则报错,是必须字段
User string `form:'username' json:'user' uri:'user' xml:'user' binding:'required'`
Pssword string `form:'password' json:'password' uri:'password' xml:'password' binding:'required'`
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// JSON绑定
r.GET('/:user/:password', testHandler)
r.Run(':8000')
}
func testHandler(c *gin.Context) {
// 声明接收的变量
var login Login
// Bind()默认解析并绑定form格式
// 根据请求头中content-type自动推断
if err := c.ShouldBindUri(&login); err != nil {
c.JSON(http.StatusBadRequest, gin.H{'error': err.Error()})
return
}
// 判断用户名密码是否正确
if login.User != 'root' || login.Pssword != 'root' {
c.JSON(http.StatusBadRequest, gin.H{'status': '304'})
return
}
c.JSON(http.StatusOK, gin.H{'status': '200'})
}
效果演示
二、结构体验证
用gin框架的数据验证,可以不用解析数据,减少if else 会简洁许多
1、main.go
package main
import (
'fmt'
'time'
'github.com/gin-gonic/gin'
)
//Person ..
type Person struct {
//不能为空并且大于10
Age int `form:'age' binding:'required,gt=10'`
Name string `form:'name' binding:'required'`
Birthday time.Time `form:'birthday' time_format:'2006-01-02' time_utc:'1'`
}
func main() {
r := gin.Default()
r.GET('/5lmh', func(c *gin.Context) {
var person Person
if err := c.ShouldBind(&person); err != nil {
c.String(500, fmt.Sprint(err))
return
}
c.String(200, fmt.Sprintf('%#v', person))
})
r.Run()
}
2、测试
所有参数正常
缺少age字段
三、翻译校验错误提示信息
validator
库本身是支持国际化的,借助相应的语言包可以实现校验错误提示信息的自动翻译。
下面的示例代码演示了如何将错误提示信息翻译成中文,翻译成其他语言的方法类似
package main
import (
'fmt'
'net/http'
'github.com/gin-gonic/gin'
'github.com/gin-gonic/gin/binding'
'github.com/go-playground/locales/en'
'github.com/go-playground/locales/zh'
ut 'github.com/go-playground/universal-translator'
'github.com/go-playground/validator/v10'
enTranslations 'github.com/go-playground/validator/v10/translations/en'
zhTranslations 'github.com/go-playground/validator/v10/translations/zh'
)
// 定义一个全局翻译器T
var trans ut.Translator
// InitTrans 初始化翻译器
func InitTrans(locale string) (err error) {
// 修改gin框架中的Validator引擎属性,实现自定制
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
zhT := zh.New() // 中文翻译器
enT := en.New() // 英文翻译器
// 第一个参数是备用(fallback)的语言环境
// 后面的参数是应该支持的语言环境(支持多个)
// uni := ut.New(zhT, zhT) 也是可以的
uni := ut.New(enT, zhT, enT)
// locale 通常取决于 http 请求头的 'Accept-Language'
var ok bool
// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf('uni.GetTranslator(%s) failed', locale)
}
// 注册翻译器
switch locale {
case 'en':
err = enTranslations.RegisterDefaultTranslations(v, trans)
case 'zh':
err = zhTranslations.RegisterDefaultTranslations(v, trans)
default:
err = enTranslations.RegisterDefaultTranslations(v, trans)
}
return
}
return
}
type SignUpParam struct {
Age uint8 `json:'age' binding:'gte=1,lte=130'`
Name string `json:'name' binding:'required'`
Email string `json:'email' binding:'required,email'`
Password string `json:'password' binding:'required'`
RePassword string `json:'re_password' binding:'required,eqfield=Password'`
}
func main() {
if err := InitTrans('zh'); err != nil {
fmt.Printf('init trans failed, err:%v\n', err)
return
}
r := gin.Default()
r.POST('/signup', func(c *gin.Context) {
var u SignUpParam
if err := c.ShouldBind(&u); err != nil {
// 获取validator.ValidationErrors类型的errors
errs, ok := err.(validator.ValidationErrors)
if !ok {
// 非validator.ValidationErrors类型错误直接返回
c.JSON(http.StatusOK, gin.H{
'msg': err.Error(),
})
return
}
// validator.ValidationErrors类型错误则进行翻译
c.JSON(http.StatusOK, gin.H{
'msg':errs.Translate(trans),
})
return
}
// 保存入库等具体业务逻辑代码...
c.JSON(http.StatusOK, 'success')
})
_ = r.Run(':8999')
}
同样的请求再来一次
curl -H 'Content-type: application/json' -X POST -d '{'name':'q1mi','age':18,'email':'123.com'}' http://127.0.0.1:8999/signup
这一次的输出结果如下
{'msg':{'SignUpParam.Email':'Email必须是一个有效的邮箱','SignUpParam.Password':'Password为必填字段','SignUpParam.RePassword':'RePassword为必填字段'}}
四、自定义验证
package main
import (
'net/http'
'reflect'
'github.com/gin-gonic/gin'
'github.com/gin-gonic/gin/binding'
'gopkg.in/go-playground/validator.v8'
)
/*
对绑定解析到结构体上的参数,自定义验证功能
比如我们要对 name 字段做校验,要不能为空,并且不等于 admin ,类似这种需求,就无法 binding 现成的方法
需要我们自己验证方法才能实现 官网示例(https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Custom_Functions)
这里需要下载引入下 gopkg.in/go-playground/validator.v8
*/
type Person struct {
Age int `form:'age' binding:'required,gt=10'`
// 2、在参数 binding 上使用自定义的校验方法函数注册时候的名称
Name string `form:'name' binding:'NotNullAndAdmin'`
Address string `form:'address' binding:'required'`
}
// 1、自定义的校验方法
func nameNotNullAndAdmin(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value, field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string) bool {
if value, ok := field.Interface().(string); ok {
// 字段不能为空,并且不等于 admin
return value != '' && !('5lmh' == value)
}
return true
}
func main() {
r := gin.Default()
// 3、将我们自定义的校验方法注册到 validator中
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
v.RegisterValidation('NotNullAndAdmin', nameNotNullAndAdmin)
}
/*
curl -X GET 'http://127.0.0.1:8080/testing?name=&age=12&address=beijing'
curl -X GET 'http://127.0.0.1:8080/testing?name=lmh&age=12&address=beijing'
curl -X GET 'http://127.0.0.1:8080/testing?name=adz&age=12&address=beijing'
*/
r.GET('/5lmh', func(c *gin.Context) {
var person Person
if e := c.ShouldBind(&person); e == nil {
c.String(http.StatusOK, '%v', person)
} else {
c.String(http.StatusOK, 'person bind err:%v', e.Error())
}
})
r.Run()
}
联系客服