分享

Go语言Gin框架:数据绑定与校验详解

 F2967527 2024-05-27 发布于天津

一、基本示例

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 mainimport ( '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')
// 定义一个全局翻译器Tvar 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:///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()}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多