模型绑定和验证
Gin 使用 go-playground/validator.v8 进行验证。 在 这里 查看标签使用的完整文档。
- 类型- Must bind
- 方法 -
Bind,BindJSON,BindQuery - 行为 - 这些方法在底层使用
MustBindWith。如果存在绑定错误, 请求通过c.AbortWithError(400, err).SetType(ErrorTypeBind)被终止。 这组响应的状态吗被设置成 400 ,并将Content-Type头设置成text/plain; charset=utf-8。注意: 如果你尝试在这个之后去设置响应码,它会发出一个警告[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422。 如果你希望更好的控制行为, 请考虑使用ShouldBind等效的方法。
- 方法 -
- 类型- Should bind
- 方法 -
ShouldBind,ShouldBindJSON,ShouldBindQuery - 行为 - 这些方法在底层使用
ShouldBindWith。 如果存在绑定错误,这个错误会被返回, 需要开发者去处理相应的请求和错误。
- 方法 -
使用 Bind 系列方法时, Gin 会尝试通过 Content-Type 推断出绑定器,如果你明确你要绑定内容,可以使用 MustBindWith 或 ShouldBindWith 。
你也可以指定必填的字段。如果一个字段使用 binding:"required" 修饰,并且被绑定到一个空值的时候,将会返回一个错误。
// 从 JSON 绑定type Login struct {User string `form:"user" json:"user" binding:"required"`// password的长度必须为10Password string `form:"password" json:"password" binding:"required,len = 10"`}func main() {router := gin.Default()// 绑定 JSON 的示例 ({"user": "manu", "password": "123"})router.POST("/loginJSON", func(c *gin.Context) {var json Loginif err := c.ShouldBindJSON(&json); err == nil {if json.User == "manu" && json.Password == "123" {c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})} else {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})}} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// 一个 HTML 表单绑定的示例 (user=manu&password=123)router.POST("/loginForm", func(c *gin.Context) {var form Login// 这个将通过 content-type 头去推断绑定器使用哪个依赖。if err := c.ShouldBind(&form); err == nil {if form.User == "manu" && form.Password == "123" {c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})} else {c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})}} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}})// 监听并服务于 0.0.0.0:8080router.Run(":8080")}
跳过验证
当在命令行上使用 curl 运行上面的示例时,它会返回一个错误。因为示例给 Password 绑定了 binding:"required" 。如果 Password 使用 binding:"-" ,然后再次运行上面的示例,它将不会返回错误。
自定义验证器
可以注册自定义验证器。 参见 示例代码 。
package mainimport ("net/http""reflect""time""github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""gopkg.in/go-playground/validator.v8")type Booking struct {CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`}func bookableDate(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,) bool {if date, ok := field.Interface().(time.Time); ok {today := time.Now()if today.Year() > date.Year() || today.YearDay() > date.YearDay() {return false}}return true}func main() {route := gin.Default()if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("bookabledate", bookableDate)}route.GET("/bookable", getBookable)route.Run(":8085")}func getBookable(c *gin.Context) {var b Bookingif err := c.ShouldBindWith(&b, binding.Query); err == nil {c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}}
结构级别的验证 可以像这样注册。
查看 示例 struct-lvl-validation 学习更多。
只绑定查询字符串
ShouldBindQuery 函数只绑定查询参数而不绑定 post 数据。查看 详细信息.
package mainimport ("log""github.com/gin-gonic/gin")type Person struct {Name string `form:"name"`Address string `form:"address"`}func main() {route := gin.Default()route.Any("/testing", startPage)route.Run(":8085")}func startPage(c *gin.Context) {var person Personif c.ShouldBindQuery(&person) == nil {log.Println("====== Only Bind By Query String ======")log.Println(person.Name)log.Println(person.Address)}c.String(200, "Success")}
绑定查询字符串或 post 数据
查看 详细信息.
package mainimport "log"import "github.com/gin-gonic/gin"import "time"type Person struct {Name string `form:"name"`Address string `form:"address"`Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`}func main() {route := gin.Default()route.GET("/testing", startPage)route.Run(":8085")}func startPage(c *gin.Context) {var person Person// 如果是 `GET`, 只使用 `Form` 绑定引擎 (`query`) 。// 如果 `POST`, 首先检查 `content-type` 为 `JSON` 或 `XML`, 然后使用 `Form` (`form-data`)// 需要注意的是,如果同时给了json与form,`form:"name" json:"name"`,会以json为准// 在这里查看更多信息 https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48if c.ShouldBind(&person) == nil {log.Println(person.Name)log.Println(person.Address)log.Println(person.Birthday)}c.String(200, "Success")}
绑定uri
https://github.com/gin-gonic/gin/issues/846
package mainimport "github.com/gin-gonic/gin"type Person struct {ID string `uri:"id" binding:"required,uuid"`Name string `uri:"name" binding:"required"`}func main() {route := gin.Default()route.GET("/:name/:id", func(c *gin.Context) {var person Personif err := c.ShouldBindUri(&person); err != nil {c.JSON(400, gin.H{"msg": err})return}c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})})route.Run(":8088")}ok ----localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3notok ----localhost:8088/thinkerou/not-uuid
Multipart/Urlencoded绑定
type ProfileForm struct {Name string `form:"name" binding:"required"`Avatar *multipart.FileHeader `form:"avatar" binding:"required"`// or for multiple files// Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`}func main() {router := gin.Default()router.POST("/profile", func(c *gin.Context) {var form ProfileFormif err := c.ShouldBind(&form); err != nil {c.String(http.StatusBadRequest, "bad request")return}err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)if err != nil {c.String(http.StatusInternalServerError, "unknown error")return}c.String(http.StatusOK, "ok")})router.Run(":8080")}
备注:这里使用,如下的结构体是会报错的
type CreateNewsReqDto struct {NewsId int64 `form:"newsId" json:"newsId"` // 新闻IDFile *multipart.Form `form:"file" json:"file" binding:"required"` //上传得文件}
看源码https://github.com/gin-gonic/gin/blob/master/binding/multipart_form_mapping.go
只支持multipart.FileHeader 或 *multipart.FileHeader的绑定
ype multipartRequest http.Requestvar _ setter = (*multipartRequest)(nil)// TrySet tries to set a value by the multipart request with the binding a form filefunc (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) {if files := r.MultipartForm.File[key]; len(files) != 0 {return setByMultipartFormFile(value, field, files)}return setByForm(value, field, r.MultipartForm.Value, key, opt)}func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {switch value.Kind() {case reflect.Ptr:switch value.Interface().(type) {case *multipart.FileHeader:value.Set(reflect.ValueOf(files[0]))return true, nil}case reflect.Struct:switch value.Interface().(type) {case multipart.FileHeader:value.Set(reflect.ValueOf(*files[0]))return true, nil}case reflect.Slice:slice := reflect.MakeSlice(value.Type(), len(files), len(files))isSetted, err = setArrayOfMultipartFormFiles(slice, field, files)if err != nil || !isSetted {return isSetted, err}value.Set(slice)return true, nilcase reflect.Array:return setArrayOfMultipartFormFiles(value, field, files)}return false, errors.New("unsupported field type for multipart.FileHeader")}func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {if value.Len() != len(files) {return false, errors.New("unsupported len of array for []*multipart.FileHeader")}for i := range files {setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])if err != nil || !setted {return setted, err}}return true, nil}
Bind Header
package mainimport ("fmt""github.com/gin-gonic/gin")type testHeader struct {Rate int `header:"Rate"`Domain string `header:"Domain"`}func main() {r := gin.Default()r.GET("/", func(c *gin.Context) {h := testHeader{}if err := c.ShouldBindHeader(&h); err != nil {c.JSON(200, err)}fmt.Printf("%#v\n", h)c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain})})r.Run()// client// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/// output// {"Domain":"music","Rate":300}}
