golang学习之gin(五):数据绑定及验证

blmius
• 阅读 3768

一、数据绑定:

1. 数据绑定介绍:

Gin提供了两类绑定方法:

  • Must bind:
    • Methods:
      • Bind, BindJSON, BindXML, BindQuery, BindYAML
    • Behavior:
      • 这些方法属于MustBindWith的具体调用. 如果发生绑定错误, 则请求终止, 并触发 c.AbortWithError(400, err).SetType(ErrorTypeBind)响应状态码被设置为 400 并且Content-Type被设置为text/plain; charset=utf-8. 如果您在此之后尝试设置响应状态码, Gin会输出日志[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422. 如果您希望更好地控制绑定, 考虑使用ShouldBind等效方法.
  • Should bind:
    • Methods:
      • ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
    • Behavior:
      • 这些方法属于ShouldBindWith的具体调用. 如果发生绑定错误, Gin 会返回错误并由开发者处理错误和请求.

2. 数据绑定–Should bind:

2.1 ShouldBind:

.
├── chapter05
│   └── bind_form.go
├── main.go
├── static
├── template
│   ├── chapter05
│   │   └── user_add.html 
  • ./main.go
package main

import (
    "gin_project/chapter05"
    "github.com/gin-gonic/gin"
)

func main() {
    engine := gin.Default()
    // 注册模板
    engine.LoadHTMLGlob("template/**/*")
    // 注册静态文件
    engine.Static("./static", "static")

    // 注册路由
    engine.GET("/to_bindform", chapter05.ToBindform)
    engine.POST("/push_bindform", chapter05.DoBindform)

    engine.GET("/push_bindquery", chapter05.DoBindquery)    // http://localhost:9000/push_bindquery?name=zhangsan&age=110&addr=%E5%8C%97%E4%BA%AC
    engine.POST("/push_bindajax", chapter05.DoBindajax)

    engine.Run(":9000")
} 
  • ./chapter05/bind_form.go
package chapter05

import (
    "fmt"
    "net/http"
    "github.com/gin-gonic/gin"
)

type User struct {
    Name string `form:"name" json:"name"`
    Age  int    `form:"age" json:"age"`
    Addr string `form:"addr" json:"addr"`
}

func ToBindform(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "chapter05/user_add.html", nil)
}

// bind form data
func DoBindform(ctx *gin.Context) {
    var user User
    err := ctx.ShouldBind(&user)
    fmt.Println(user)
    if err != nil {
        ctx.String(http.StatusNotFound, "绑定form失败")
    } else {
        ctx.String(http.StatusOK, "绑定form成功")
    }
}

// bind Query data
func DoBindquery(ctx *gin.Context) {
    var user User
    err := ctx.ShouldBind(&user)
    fmt.Println(user)
    if err != nil {
        ctx.String(http.StatusNotFound, "绑定query失败")
    } else {
        ctx.String(http.StatusOK, "绑定query成功")
    }
}

// bind Ajax data
func DoBindajax(ctx *gin.Context) {
    var user User
    err := ctx.ShouldBind(&user)
    fmt.Println(user)
    if err != nil {
        ctx.String(http.StatusNotFound, "绑定ajax失败")
    } else {
        ctx.String(http.StatusOK, "绑定ajax成功")
    }
} 
  • ./template/chapter05/user_add.html
{{ define "chapter05/user_add.html" }}
<!DOCTYPE html>
<html lang="zh">
<head>
    <title>post请求练习</title>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <style> .userForm {
            width: 480px;
            height: 360px;
            margin: 20px 200px;
        }
        input {
            margin: 5px 0;

        } </style>
</head>
<body>
    <div class="userForm">
        <h2>bind form data</h2>
        <form action="/push_bindform" method="post">
            <p>姓名:<input type="text" name="name" id="name"></p>
            <p>年龄:<input type="text" name="age" id="age"></p>
            <p>地址:<input type="text" name="addr" id="addr"></p>
            <p><input type="submit" value="提交form"></p>
            <p><input type="button" value="提交ajax" id="sub-btn"></p>
        </form>
    </div>

    <script> // 使用ajax提交json
        var btn = document.querySelector("#sub-btn")
        btn.onclick = function(ev) {
            var name = document.getElementById("name").value;
            var age = document.getElementById("age").value;
            var addr = document.getElementById("addr").value;
            console.log(name, age, addr);
            $.ajax({
                url:"/push_bindajax",
                type:"POST",
                data: JSON.stringify({
                    "name":name,
                    "age":Number(age),
                    "addr":addr
                }),
                contentType: "application/json",
                dataType: "json",
                success:function (data) {
                    alert(data["code"]);
                    alert(data["msg"]);
                },
                fail:function (data) {
                    console.log(data);
                }
            })

        } </script>
</body>
</html>
{{ end }} 

2.2 ShouldBindWith

可以使用显式绑定声明绑定 multipart form:

ctx.ShouldBindWith(&form, binding.Form) 

或者简单地使用 ShouldBind 方法自动绑定

2.3 ShouldBindQuery等

ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML等函数只绑定对应格式的参数

2.4 ShouldBindUri:绑定 Uri:

package main

import (
    "gin_project/chapter05"
    "github.com/gin-gonic/gin"
)

func main() {
    engine := gin.Default()
    // 注册模板
    engine.LoadHTMLGlob("template/**/*")
    // 注册静态文件
    engine.Static("./static", "static")

    // 注册路由
    engine.GET("/push_bindURI/:name/:age/:addr", chapter05.DoBindURI)    // http://localhost:9000/push_bindURI/zhangsan/19/%E5%8C%97%E4%BA%AC

    engine.Run(":9000")
} 
package chapter05

import (
    "fmt"
    "net/http"
    "github.com/gin-gonic/gin"
)

type User struct {
    Name string `form:"name" json:"name" uri:"name"`
    Age  int    `form:"age" json:"age" uri:"age"`
    Addr string `form:"addr" json:"addr" uri:"addr"`
}
...

// bind URI data
func DoBindURI(ctx *gin.Context) {
    var user User
    err := ctx.ShouldBindUri(&user)
    fmt.Println(user)
    if err != nil {
        ctx.String(http.StatusNotFound, "绑定uri失败")
    } else {
        ctx.String(http.StatusOK, "绑定uri成功")
    }
} 

3. 数据绑定–Must bind

3.1 Bind

可以绑定Form、QueryString、Json等

和ShouldBind的区别在于,ShouldBind没有绑定成功不报错,就是空值,Bind会报错

3.2 BindQuery等

BindJSON, BindXML, BindQuery, BindYAML等函数只绑定对应格式的参数

二、数据验证:

1. 使用:

go-playground/validator.v8进行验证
使用structTagbinding,如:binding:"required"
如果没有空值或者类型不匹配就会报错,重定向到400 (Bad Request)
错误信息:Key: 'Article.Title' Error:Field validation for 'Title' failed on the 'required' tag

./
├── chapter05
│   └── valid_data.go
├── main.go
├── static
├── template
│   ├── chapter05
│   │   └── valid_data.html 
  • ./main.go
package main

import (
    "gin_project/chapter05"
    "github.com/gin-gonic/gin"
)

func main() {
    engine := gin.Default()
    // 注册模板
    engine.LoadHTMLGlob("template/**/*")
    // 注册静态文件
    engine.Static("./static", "static")

    // 注册路由
    engine.GET("/to_valid", chapter05.ToValidData)
    engine.POST("/do_valid", chapter05.DoValidData)

    engine.Run(":9000")
} 
  • ./chapter05/valid_data.go
package chapter05

import (
    "fmt"
    "net/http"
    "github.com/gin-gonic/gin"
)

type Article struct {
    Id      int    `form:"-"`
    Title   string `form:"title" binding:"required"`
    Content string `form:"content"`
    Desc    string `form:"desc"`
}

func ToValidData(ctx *gin.Context) {
    ctx.HTML(http.StatusOK, "chapter05/valid_data.html", nil)
}

func DoValidData(ctx *gin.Context) {
    var article Article
    err := ctx.ShouldBind(&article)
    if err != nil {
        fmt.Println(err)
        ctx.JSON(http.StatusBadRequest, gin.H{
            "msg": "参数错误",
        })
    }
    fmt.Println(article)

    ctx.String(http.StatusOK, "成功")
} 
  • ./template/chapter05/valid_data.html
{{ define "chapter05/valid_data.html" }}
<!DOCTYPE html>
<html lang="cn">
<head>
    <title>数据验证</title>
    <style> .form {
            width: 300px;
            height: 300px;
            margin: 100px auto;
        } </style>
</head>
<body>
    <div class="form">
        <form action="/do_valid" method="post">
            <p>title: <input type="text" name="title"></p>
            <p>content: <input type="text" name="content"></p>
            <p>desc: <input type="text" name="desc"></p>
            <p><input type="submit" name="btn" value="提交"></p>
        </form>
    </div>
</body>
</html>
{{ end }} 

2. 其他验证器:

官方文档:https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags

注意:
多个验证器之间用英文输入法下的逗号(,)隔开, 并且是按照验证器的顺序执行的
如果希望在参数中包含逗号(即excludesall =,), 则需要使用UTF-8十六进制表示形式0x2C例如: validate:"excludesall=0x2C"

验证器 说明 示例
- 忽略字段 binding:"-"
required 必填字段 binding:“required”
min 最小长度 binding:“min=10”
max 最大长度 binding:“max=10”
structonly 如果有嵌套,可以决定只验证结构体上的 binding:“structonly”
omitempty 省略空,如果为空,则不会继续验证该字段上其他的规则,只有不为空才会继续验证其他的
len 长度 binding:“len=10”
eq 等于 binding:“eq=10”
ne 不等于 binding:“ne=10”
gt 大于 binding:“gt=10”
gte 大于等于 binding:“gte=10”
lt 小于 binding:“lt=10”
lte 小于等于 binding:“lte=10”
eqfield 等于其他字段的值 Password string binding:"eqfield=ConfirmPassword"
nefield 不等于其他字段的值
eqcsfield 类似eqfield,它会验证相对于顶层结构提供的字段 binding:"eqcsfield = InnerStructField.Field
necsfield
gtfield 大于其他字段的值
gtefield
gtcsfield
gtecsfield
ltfield 小于其他字段的值
ltefield
ltcsfield
ltecsfield
alpha 字符串值仅包含字母字符
alphanum 字符串值仅包含字母数字字符
numeric 字符串值包含基本数字值。基本不包括指数等…
hexadecimal 字符串值包含有效的十六进制
hexcolor 验证字符串值包含有效的十六进制颜色, 包括井号(#)
rgb 字符串值包含有效的rgb颜色
rgba 字符串值包含有效的rgba颜色
HSL 字符串值包含有效的hsl颜色
hsla 字符串值包含有效的hsla颜色
email 字符串值包含有效的电子邮件
url 字符串值包含有效的网址,必须包含http://等
uri 字符串值包含有效的uri. 它将接受golang请求uri接受的任何uri
base64 字符串值包含有效的base64值
contains 字符串值包含子字符串值, contains=@
containsany 包含所有,containsany =!@#?
containsrune 字符串值包含提供的符号 containsrune = @
excludes 字符串值不包含子字符串值,excludes = @
excludeall 排除所有
excluderune 字符串值不包含提供的符号,excluderune = @
isbn 国际标准书号,验证字符串值包含有效的isbn10或isbn13值
isbn10 国际标准书号10, 验证字符串值包含有效的isbn10值
isbn13 国际标准书号13, 字符串值包含有效的isbn13值
uuid 字符串值包含有效的UUID
uuid3 字符串值包含有效的版本3 UUID
uuid4 字符串值包含有效的版本5 UUID
uuid5 字符串值包含有效的版本5 UUID
ascii 字符串值仅包含ASCII字符. 注意:如果字符串为空, 则验证为true
asciiprint 字符串值仅包含可打印的ASCII字符. 注意: 如果字符串为空,则验证为true
multibyte 字符串值包含一个或多个多字节字符。注意:如果字符串为空,则验证为true
datauri 字符串值包含有效的DataURI。注意:这还将验证数据部分是有效的base64
latitude 纬度,字符串值包含有效的纬度
longitude 经度,字符串值包含有效的经度
ssn 字符串值包含有效的美国社会安全号码
ip 字符串值包含有效的IP地址
ipv4 字符串值包含有效的v4 IP地址
ipv6 字符串值包含有效的v6 IP地址
cidr 字符串值包含有效的CIDR地址
cidrv4 字符串值包含有效的CIDR地址
cidrv6 字符串值包含有效的v6 CIDR地址
tcp_addr 字符串值包含有效的可解析TCP地址
dive 嵌套验证
name [][]string  `binding:"gt=0,dive,len=1,dive,required"`
// gt = 0将应用于[]
// len = 1将应用于[] string
//必填项将应用于字符串

name [][]string  `binding:"gt=0,dive,dive,required"`
// gt = 0 将应用于[]
// []string 将保留验证
//必填项将应用于字符串 

注意:gt、gte、lt、lte等都可以用于时间的比较,后面不需要跟值,直接binding:“gt”,表示大于当前utc时间

3. 自定义验证器:

自定义验证器

3.1 安装:

go get github.com/go-playground/validator

3.2 定义验证器:

注意:必须为validator.Func类型

var Len6Valid validator.Func = func(fl validator.FieldLevel) bool {
    data := fl.Field().Interface().(string)
    if len(data) > 6 {
        fmt.Println("false")
        return false
    }else {
        fmt.Println("true")
        return true
    }
} 

3.3 注册验证器:

在路由匹配前,main中即可

if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
   v.RegisterValidation("len_valid", valid.Len6Valid)
} 

3.4 结构体中使用:

这里必须binding中,而且名称为前面注册的字符串名称

type Article struct {
    Id int `form:"id"`
    Title string `form:"title" binding:"required,len_valid"`
    Desc string `form:"desc" binding:"required,len_valid"`
} 

4. beego中的验证器:

4.1 安装

go get github.com/astaxie/beego/validation

4.2 验证方法:

方法 说明
Required 不为空,即各个类型要求不为其零值
Min(min int) 最小值,有效类型:int,其他类型都将不能通过验证
Max(max int) 最大值,有效类型:int,其他类型都将不能通过验证
Range(min, max int) 数值的范围,有效类型:int,他类型都将不能通过验证
MinSize(min int) 最小长度,有效类型:string slice,其他类型都将不能通过验证
MaxSize(max int) 最大长度,有效类型:string slice,其他类型都将不能通过验证
Length(length int) 指定长度,有效类型:string slice,其他类型都将不能通过验证
Alpha alpha字符,有效类型:string,其他类型都将不能通过验证
Numeric 数字,有效类型:string,其他类型都将不能通过验证
AlphaNumeric alpha 字符或数字,有效类型:string,其他类型都将不能通过验证
Match(pattern string) 正则匹配,有效类型:string,其他类型都将被转成字符串再匹配(fmt.Sprintf(“%v”, obj).Match)
AlphaDash alpha 字符或数字或横杠 -_,有效类型:string,其他类型都将不能通过验证
Email 邮箱格式,有效类型:string,其他类型都将不能通过验证
IP IP 格式,目前只支持 IPv4 格式验证,有效类型:string,其他类型都将不能通过验证
Base64 base64 编码,有效类型:string,其他类型都将不能通过验证
Mobile 手机号,有效类型:string,其他类型都将不能通过验证
Tel 固定电话号,有效类型:string,其他类型都将不能通过验证
Phone 手机号或固定电话号,有效类型:string,其他类型都将不能通过验证
ZipCode 邮政编码,有效类型:string,其他类型都将不能通过验证

4.3 通过 StructTag校验数据:

  • 验证函数写在 “valid” tag 的标签里
  • 各个验证规则之间用分号 “;” 分隔,分号后面可以有空格
  • 参数用括号 “()” 括起来,多个参数之间用逗号 “,” 分开,逗号后面可以有空格
  • 正则函数(Match)的匹配模式用两斜杠 “/” 括起来
  • 各个函数的结果的 key 值为字段名.验证函数名
type LoginParams struct {
      Name string valid:"Required"
      Age int    valid:"Required;MinSize(2)"
      Addr string    valid:"Required"
 }

func (l *LoginController) Post()  {
    valid := validation.Validation{}
    // 解析到结构体
    params := LoginParams{}
    if err := l.ParseForm(&params); err != nil {
        //handle error
        return
    }

      //重写错误信息:validation.SetDefaultMessage(map)
      var messages = map[string]string{
        "Required": "不能为空",
        "MinSize":  "最短长度为 %d",
        "Length":   "长度必须为 %d",
        "Numeric":  "必须是有效的数字",
        "Email":    "必须是有效的电子邮件地址",
        "Mobile":   "必须是有效的手机号码",
      }
      validation.SetDefaultMessage(messages)

      // 校验
      b, err := valid.Valid(&params)
      // 验证StructTag 是否正确
      if err != nil {
          fmt.Println(err)
      }
      if !b {   
          // 验证没通过,则b为false
          for _, err := range valid.Errors {
              fmt.Println(err.Key, err.Message)
              message := err.Key + err.Message
              l.Ctx.WriteString(message)
          }
      }
} 
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这