gin 文件上传和下载
文件上传 单文件 func main() { router := gin.Default() // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB) /
gin 文件上传和下载
发布时间:2023-10-11 (2023-10-11)

文件上传

单文件

func main() {
  router := gin.Default()
  // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
  // 单位是字节, << 是左移预算符号,等价于 8 * 2^20
  // gin对文件上传大小的默认值是32MB
  router.MaxMultipartMemory = 8 << 20  // 8 MiB
  router.POST("/upload", func(c *gin.Context) {
    // 单文件
    file, _ := c.FormFile("file")
    log.Println(file.Filename)

    dst := "./" + file.Filename
    // 上传文件至指定的完整文件路径
    c.SaveUploadedFile(file, dst)

    c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
  })
  router.Run(":8080")
}

服务端保存文件的几种方式

SaveUploadedFile

c.SaveUploadedFile(file, dst)  // 文件对象  文件路径,注意要从项目根路径开始写

Create+Copy

file.Open的第一个返回值就是我们讲文件对象中的那个文件(只读的),我们可以使用这个去直接读取文件内容

file, _ := c.FormFile("file")
log.Println(file.Filename)
// 读取文件中的数据,返回文件对象
fileRead, _ := file.Open()
dst := "./" + file.Filename
// 创建一个文件
out, err := os.Create(dst)
if err != nil {
  fmt.Println(err)
}
defer out.Close()
// 拷贝文件对象到out中
io.Copy(out, fileRead)

读取上传的文件

file, _ := c.FormFile("file")
// 读取文件中的数据,返回文件对象
fileRead, _ := file.Open()
data, _ := io.ReadAll(fileRead)
fmt.Println(string(data))

这里的玩法就很多了

例如我们可以基于文件中的内容,判断是否需要保存到服务器中

多文件上传

func main() {
  router := gin.Default()
  // 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
  router.MaxMultipartMemory = 8 << 20 // 8 MiB
  router.POST("/upload", func(c *gin.Context) {
    // Multipart form
    form, _ := c.MultipartForm()
    files := form.File["upload[]"]  // 注意这里名字不要对不上了

    for _, file := range files {
      log.Println(file.Filename)
      // 上传文件至指定目录
      c.SaveUploadedFile(file, "./"+file.Filename)
    }
    c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
  })
  router.Run(":8080")
}

文件下载

直接响应一个路径下的文件

c.File("uploads/12.png")

有些响应,比如图片,浏览器就会显示这个图片,而不是下载,所以我们需要使浏览器唤起下载行为

c.Header("Content-Type", "application/octet-stream")              // 表示是文件流,唤起浏览器下载,一般设置了这个,就要设置文件名
c.Header("Content-Disposition", "attachment; filename="+"牛逼.png") // 用来指定下载下来的文件名
c.Header("Content-Transfer-Encoding", "binary")                   // 表示传输过程中的编码形式,乱码问题可能就是因为它
c.File("uploads/12.png")

注意,文件下载浏览器可能会有缓存,这个要注意一下

解决办法就是加查询参数

前后端模式下的文件下载

如果是前后端模式下,后端就只需要响应一个文件数据

文件名和其他信息就写在请求头中

c.Header("fileName", "xxx.png")
c.Header("msg", "文件下载成功")
c.File("uploads/12.png")

前端写法

async downloadFile(row) {
   this.$http({
      method: 'post',
      url: 'file/upload',
      data:postData,
      responseType: "blob"
   }).then(res => {
      const _res = res.data
      let blob = new Blob([_res], {
            type: 'application/png'
          });
      let downloadElement = document.createElement("a");
      let href = window.URL.createObjectURL(blob); //创建下载的链接
      downloadElement.href = href;
      downloadElement.download = res.headers["fileName"]; //下载后文件名
      document.body.appendChild(downloadElement);
      downloadElement.click(); //点击下载
      document.body.removeChild(downloadElement); //下载完成移除元素
      window.URL.revokeObjectURL(href); //释放掉blob对象
    })}

前后端文件下载,中文乱码问题

后端

func Download(c *gin.Context) {

  filename := url.QueryEscape("国家机密.txt")
  // 可唤起浏览器下载
  c.Header("Content-Disposition", "attachment; filename*=utf-8''"+filename) //
  c.Header("fileName", filename)
  c.File("uploads/国家机密.txt")
}

前端

async download() {
    let res = await axios.get("/download", {headers: {responseType: "blob"}})
    if (res.status === 200) {
        let binaryData = [];
        binaryData.push(res.data);
        let url = window.URL.createObjectURL(new Blob(binaryData)); //表示一个指定的file对象或Blob对象

        let a = document.createElement("a");
        document.body.appendChild(a);

        // 转码文件的标题
        let filename = decodeURI(res.headers.filename)

        // 调起文件下载
        a.href = url;
        a.download = filename; //命名下载名称
        a.click(); //点击触发下载
        window.URL.revokeObjectURL(url);
    }
}