Skip to content
On this page

上传和下载文件

上传文件

  • 服务端使用ctx.SaveUploadedFile保存文件到服务器
Go
// 通过name获取form file
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
// 获取上传的整个form
func (c *Context) MultipartForm() (*multipart.Form, error)
// 保存文件
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error

TIP

  • MaxMultipartMemory参数用于设置内存缓冲区的最大容量,也就是上传的数据超过该容量时,将会把文件流写入磁盘中,而不是一直保留在内存中。这是为了防止攻击者发送大量数据导致服务器内存耗尽,从而引发拒绝服务攻击(DoS)。
  • 8 << 20表示将数字8左移20位,即将数字8转换为字节为单位的容量值,得到的结果是8MB

上传单个文件

  • 解析上传单个文件参数使用ctx.FormFile,获取到的就是FormData中的file字段对应的文件

  • 获取FormData中其他字段使用ctx.PostForm

后端逻辑

Go
// 上传单个文件
func singleFileHandler(ctx *gin.Context) {
	name := ctx.PostForm("name")
	force := ctx.PostForm("force")
	file, err := ctx.FormFile("file")
	if err != nil {
		fmt.Println("ctx.FormFile err", err)
	}
	savePath := path.Join("uploads", file.Filename)
	fmt.Println(name, file.Filename, file.Size, "============")
	if err := ctx.SaveUploadedFile(file, savePath); err != nil {
		ctx.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "文件上传失败",
			"data": nil,
		})
		return
	}
	ctx.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "文件上传成功",
		"data": gin.H{"filepath": savePath, "name": name, "force": force},
	})
}

func main()  {
	router := gin.Default()
	//MaxMultipartMemory 参数用于设置内存缓冲区的最大容量,也就是上传的数据超过该容量时,将会把文件流写入磁盘中,而不是一直保留在内存中。这是为了防止攻击者发送大量数据导致服务器内存耗尽,从而引发拒绝服务攻击(DoS)。
	// 8 << 20 表示将数字 8 左移 20 位,即将数字 8 转换为字节为单位的容量值,得到的结果是 8 MB
	router.MaxMultipartMemory = 8 << 20 // 8M
	router.Static("/static", "assets")
	router.Static("/tpl", "templates")
	router.POST("/single_file", singleFileHandler)
	router.Run(":9100")
}

前端逻辑

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ajax</title>
</head>
<body>
<div class="upload-wrapper">
    <input id="ajax" type="file" multiple>
    <br>
    <br>
    <button id="btn-submit">提交</button>
</div>
<script>
    const btn = document.getElementById('btn-submit')
    btn.addEventListener('click', handleUpload)

    function handleUpload() {
        const inputFile = document.getElementById('ajax'),
            fileList = inputFile.files
        if (!fileList.length) {
            return alert('请选择文件')
        }
        /* 使用FormData对象,将上传对象存入FormData对象内 */
        const formData = new FormData()
        formData.append("name", "single file")
        formData.append("force", "true")
        for (let i = 0; i < fileList.length; i++) {
            formData.append('file', fileList[i])
        }
        const xhr = new XMLHttpRequest()
        xhr.open('POST', 'http://localhost:9100/single_file', true)
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4 && xhr.status === 200) {
                const obj = JSON.parse(xhr.responseText);
                console.log(obj);
            }
        }
        xhr.send(formData)
    }
</script>
</body>
</html>

如下图所示:

gin-file-upload

上传多个文件

  • 使用ctx.MultipartForm获取多个FormData数据

后端逻辑:

Go
// 上传多个文件
func multiFileHandler(ctx *gin.Context) {
	multipleName := ctx.PostForm("name")
	form, err := ctx.MultipartForm()
	if err != nil {
		fmt.Println("ctx.MultipartForm err =>>>", err)
	}
	files := form.File["file"]
	var paths []string
	for _, file := range files {
		savePath := path.Join("uploads", file.Filename)
		if err := ctx.SaveUploadedFile(file, savePath); err != nil {
			ctx.JSON(http.StatusOK, gin.H{
				"code": -1,
				"msg":  "文件上传失败",
				"data": nil,
			})
			return
		}
		paths = append(paths, savePath)
	}
	ctx.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "文件上传成功",
		"data": gin.H{
			"paths": paths,
			"name":  multipleName,
		},
	})
}

func main()  {
	router := gin.Default()
	//MaxMultipartMemory 参数用于设置内存缓冲区的最大容量,也就是上传的数据超过该容量时,将会把文件流写入磁盘中,而不是一直保留在内存中。这是为了防止攻击者发送大量数据导致服务器内存耗尽,从而引发拒绝服务攻击(DoS)。
	// 8 << 20 表示将数字 8 左移 20 位,即将数字 8 转换为字节为单位的容量值,得到的结果是 8 MB
	router.MaxMultipartMemory = 8 << 20 // 8M
	router.Static("/static", "assets")
	router.Static("/tpl", "templates")
	router.POST("/multi_file", multiFileHandler)
	router.Run(":9100")
}

前端逻辑

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ajax</title>
</head>
<body>
<div class="upload-wrapper">
    <input id="ajax" type="file" multiple>
    <br>
    <br>
    <button id="btn-submit">提交</button>
</div>
<script>
    const btn = document.getElementById('btn-submit')
    btn.addEventListener('click', handleUpload)

    function handleUpload() {
        const inputFile = document.getElementById('ajax'),
            fileList = inputFile.files
        if (!fileList.length) {
            return alert('请选择文件')
        }
        /* 使用FormData对象,将上传对象存入FormData对象内 */
        const formData = new FormData()
        formData.append("name", "single file")
        for (let i = 0; i < fileList.length; i++) {
            formData.append('file', fileList[i])
        }
        const xhr = new XMLHttpRequest()
        xhr.open('POST', 'http://localhost:9100/multi_file', true)
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4 && xhr.status === 200) {
                const obj = JSON.parse(xhr.responseText);
                console.log(obj);
            }
        }
        xhr.send(formData)
    }
</script>
</body>
</html>

如图所示:

gin-file-upload-multiple

文件下载

Go
// 设置响应问见是文件流模式,可以让浏览器主动下载(这个可以不设置)
ctx.Header("Content-Type", "application/octet-stream")
// 可以设置下载的文件名称(这个必须设置,不然下载的文件 没有后缀)
ctx.Header("Content-Disposition", "attachment; filename="+"文件名.后缀") 
// 响应下载路径
ctx.File(filePath)

TIP

  • ctx.Header("Content-Disposition", "attachment; filename="+"文件名.后缀"),这个请求头一定要设置,不然下载的文件没有后缀名,

  • 如下代码可实现返回前端文件,ctx.Header("Content-Type", "application/octet-stream")可以不设置

Go
ctx.Header(
    "Content-Disposition",
    fmt.Sprintf("attachment;filename=%s", filename)
) // 这个必须要有
filePath := path.Join("uploads", filename)
ctx.File(filePath) 
Go
package main

import (
	"fmt"
	"net/http"
	"path"

	"github.com/gin-gonic/gin"
)

func fileDownload(ctx *gin.Context) {
	filename := ctx.Query("filename")
	if len(filename) == 0 {
		ctx.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "参数错误",
			"data": nil,
		})
		return
	}
	// ctx.Header("Content-Type", "application/octet-stream") // 这个可以没有
	// 可以设置下载的文件名称
	ctx.Header("Content-Disposition", fmt.Sprintf("attachment;filename=%s", filename)) // 这个必须要有
	filePath := path.Join("uploads", filename)
	ctx.File(filePath)
}

func main() {
	router := gin.Default()
	router.GET("/file_download", fileDownload)
	router.Run(":9100")
}