表单绑定

表单绑定可以处理 application/x-www-form-urlencodedmultipart/form-data 格式的数据。

基本用法

type LoginForm struct {
    Username string `form:"username" binding:"required"`
    Password string `form:"password" binding:"required"`
}

r.POST("/login", func(c *gin.Context) {
    var form LoginForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{
        "username": form.Username,
        "message":  "登录成功",
    })
})

请求:

curl -X POST http://localhost:8080/login \
  -d "username=alice&password=secret"

表单标签

type User struct {
    Name     string `form:"name"`
    Email    string `form:"email"`
    Age      int    `form:"age"`
    Active   bool   `form:"active"`
}

文件上传绑定

type UploadForm struct {
    Name  string                `form:"name"`
    File  *multipart.FileHeader `form:"file" binding:"required"`
}

r.POST("/upload", func(c *gin.Context) {
    var form UploadForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    dst := "./uploads/" + form.File.Filename
    c.SaveUploadedFile(form.File, dst)

    c.JSON(200, gin.H{
        "name":     form.Name,
        "filename": form.File.Filename,
        "size":     form.File.Size,
    })
})

多文件上传

type MultiUploadForm struct {
    Name  string                  `form:"name"`
    Files []*multipart.FileHeader `form:"files" binding:"required"`
}

r.POST("/upload", func(c *gin.Context) {
    var form MultiUploadForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    for _, file := range form.Files {
        dst := "./uploads/" + file.Filename
        c.SaveUploadedFile(file, dst)
    }

    c.JSON(200, gin.H{
        "name":  form.Name,
        "count": len(form.Files),
    })
})

数组字段

type FilterForm struct {
    Tags []string `form:"tags"`
}

r.POST("/filter", func(c *gin.Context) {
    var form FilterForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{"tags": form.Tags})
})

请求:

curl -X POST http://localhost:8080/filter \
  -d "tags=go&tags=web&tags=api"

完整示例

package main

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

type LoginForm struct {
    Username string `form:"username" binding:"required,min=3"`
    Password string `form:"password" binding:"required,min=6"`
}

type RegisterForm struct {
    Username string `form:"username" binding:"required,min=3,max=20"`
    Email    string `form:"email" binding:"required,email"`
    Password string `form:"password" binding:"required,min=6"`
}

type ProfileForm struct {
    Name   string                `form:"name"`
    Bio    string                `form:"bio"`
    Avatar *multipart.FileHeader `form:"avatar"`
}

func main() {
    r := gin.Default()

    r.POST("/login", login)
    r.POST("/register", register)
    r.POST("/profile", updateProfile)

    r.Run(":8080")
}

func login(c *gin.Context) {
    var form LoginForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    if form.Username == "admin" && form.Password == "password123" {
        c.JSON(http.StatusOK, gin.H{
            "message": "登录成功",
            "token":   "xxx",
        })
    } else {
        c.JSON(http.StatusUnauthorized, gin.H{
            "error": "用户名或密码错误",
        })
    }
}

func register(c *gin.Context) {
    var form RegisterForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, gin.H{
        "message":  "注册成功",
        "username": form.Username,
        "email":    form.Email,
    })
}

func updateProfile(c *gin.Context) {
    var form ProfileForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    response := gin.H{
        "name": form.Name,
        "bio":  form.Bio,
    }

    if form.Avatar != nil {
        dst := "./uploads/" + form.Avatar.Filename
        c.SaveUploadedFile(form.Avatar, dst)
        response["avatar"] = form.Avatar.Filename
    }

    c.JSON(http.StatusOK, response)
}

小结

这一章学习了:

  • 表单绑定的基本用法
  • 文件上传绑定
  • 多文件上传
  • 数组字段处理