一、数据绑定:
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等效方法.
- 这些方法属于MustBindWith的具体调用. 如果发生绑定错误, 则请求终止, 并触发
- Methods:
- Should bind:
- Methods:
ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
- Behavior:
- 这些方法属于ShouldBindWith的具体调用. 如果发生绑定错误, Gin 会返回错误并由开发者处理错误和请求.
- Methods:
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
进行验证
使用structTag
的binding
,如: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颜色 | |
字符串值包含有效的电子邮件 | ||
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,其他类型都将不能通过验证 |
邮箱格式,有效类型: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(¶ms); 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(¶ms)
// 验证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)
}
}
}