表单参数

表单参数通常通过 POST 请求提交,有两种格式:application/x-www-form-urlencodedmultipart/form-data。Gin 都支持。

获取表单字段

使用 PostForm() 获取表单字段:

r.POST("/login", func(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")
    
    c.JSON(200, gin.H{
        "username": username,
        "password": password,
    })
})

获取带默认值的表单字段

DefaultPostForm() 设置默认值:

r.POST("/submit", func(c *gin.Context) {
    name := c.DefaultPostForm("name", "Anonymous")
    email := c.DefaultPostForm("email", "")
    
    c.JSON(200, gin.H{
        "name":  name,
        "email": email,
    })
})

检查字段是否存在

GetPostForm() 可以区分空值和不存在:

r.POST("/submit", func(c *gin.Context) {
    email, exists := c.GetPostForm("email")
    
    if !exists {
        c.JSON(400, gin.H{"error": "缺少 email 字段"})
        return
    }

    c.JSON(200, gin.H{"email": email})
})

获取所有表单字段

PostFormMap() 返回所有表单字段:

r.POST("/submit", func(c *gin.Context) {
    form := c.PostFormMap()
()
    
    for key, values := range form {
        for _, value := range values {
            fmt.Printf("%s = %s\n", key, value)
        }
    }

    c.JSON(200, form)
})

获取数组字段

表单可以有同名字段:

r.POST("/submit", func(c *gin.Context) {
    tags := c.PostFormArray("tags")
    c.JSON(200, gin.H{"tags": tags})
})

HTML 表单:

<form method="POST" action="/submit">
    <input type="text" name="tags" value="go">
    <input type="text" name="tags" value="web">
    <input type="text" name="tags" value="api">
    <button type="submit">提交</button>
</form>

文件上传

multipart/form-data 用于文件上传:

r.POST("/upload", func(c *gin.Context) {
    file, err := c.FormFile("file")
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{
        "filename": file.Filename,
        "size":     file.Size,
        "header":   file.Header,
    })
})

保存上传的文件

import "os"

r.POST("/upload", func(c *gin.Context) {
    file, err := c.FormFile("file")
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    dst := "./uploads/" + file.Filename
    err = c.SaveUploadedFile(file, dst)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{
        "message":  "文件上传成功",
        "filename": file.Filename,
    })
})

多文件上传

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

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

    c.JSON(200, gin.H{
        "message": "文件上传成功",
        "count":   len(files),
    })
})

登录表单示例

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

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
    }

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

注册表单示例

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"`
}

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

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

混合表单和文件

r.POST("/profile", func(c *gin.Context) {
    name := c.PostForm("name")
    
    avatar, err := c.FormFile("avatar")
    if err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

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

    c.JSON(200, gin.H{
        "name":    name,
        "avatar":  avatar.Filename,
        "message": "个人资料更新成功",
    })
})

完整示例

package main

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

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

    r.POST("/login", login)
    r.POST("/register", register)
    r.POST("/upload", upload)

    r.Run(":8080")
}

func login(c *gin.Context) {
    username := c.PostForm("username")
    password := c.PostForm("password")

    if username == "" || password == "" {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "用户名和密码不能为空",
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "message": "登录成功",
        "username": username,
    })
}

func register(c *gin.Context) {
    username := c.PostForm("username")
    email := c.PostForm("email")
    password := c.PostForm("password")

    if username == "" || email == "" || password == "" {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "所有字段都必须填写",
        })
        return
    }

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

func upload(c *gin.Context) {
    file, err := c.FormFile("file")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "请选择要上传的文件",
        })
        return
    }

    if file.Size > 10*1024*1024 {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": "文件大小不能超过 10MB",
        })
        return
    }

    dst := "./uploads/" + file.Filename
    err = c.SaveUploadedFile(file, dst)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "error": "文件保存失败",
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "message":  "文件上传成功",
        "filename": file.Filename,
        "size":     file.Size,
    })
}

小结

这一章学习了:

  • PostForm() 获取表单字段
  • DefaultPostForm() 设置默认值
  • FormFile() 处理文件上传
  • SaveUploadedFile() 保存上传的文件
  • 绑定表单到结构体