Skip to content

下载文件

location.href / window.open

适用:

  • get 请求
  • 单文件下载

缺点:

  • 浏览器支持的文件会直接打开而非下载
js
window.location.href = url

window.open(url, '_blank', 'noopener, noreferrer');

链接地址下载

js
const link = document.createElement('a')
const url = `url下载链接地址`
link.href = url
link.setAttribute('download', '文件名') // 同域才生效
document.body.appendChild(link)
link.click()
document.body.removeChild(link)

链接地址下载

js
function downloadFile(url,filename) {
  fetch(url)
      .then(response => response.blob())
      .then(blob => {
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = filename;
        link.style.display = 'none';
        document.body.appendChild(link);
        link.click();
        URL.revokeObjectURL(link.href); // 释放URL对象
        document.body.removeChild(link);
      })
      .catch(error => {
        console.error('下载失败:', error);
      });
},
html
<a href="url下载链接地址" download="文件名"></a>

文件流下载

服务端代码 以 Nodejs 的 fastify 框架为例:

js
const fastify = require('fastify')({ logger: true })
const cors = require('@fastify/cors')
const fs = require('fs')
const path = require('path')

fastify.register(cors, {
  // put your options here
})

// 设置允许下载的文件所在的目录
const downloadDir = path.join(__dirname, 'downloads')

fastify.post('/download/:filename', async (request, reply) => {
  const { filename } = request.params
  const filePath = path.join(downloadDir, filename)

  try {
    // 检查文件是否存在
    await fs.promises.access(filePath, fs.constants.F_OK)

    reply.header('access-control-expose-headers', 'Content-Disposition')
    reply.header('Content-Disposition', `attachment; filename=${filename}`)
    reply.type('application/octet-stream')

    // 创建文件读取流并将其通过管道传输到响应中
    const fileStream = fs.createReadStream(filePath)
    return reply.send(fileStream)
  } catch (error) {
    reply.code(404).send({ error: '文件不存在' })
  }
})

const start = async () => {
  try {
    await fastify.listen({ port: 3000 })
    console.log(`服务器运行在 http://localhost:3000`)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}

start()

⚠️ 注意:

允许浏览器访问 Content-Disposition 头,在 CORS 默认情况下只暴露出了 CORS-safelisted response header 包括:

  • Cache - Control
  • Content - Language
  • Content - Length
  • Content - Type
  • Expires
  • Last - Modified
  • Pragma

通过 Access-Control-Expose-Headers 添加期望暴露的其他请求头。

客户端代码

js
const req = new XMLHttpRequest()
  req.open('POST', 'http://localhost:3000/download/123.txt', true)
  req.responseType = 'blob'
  req.setRequestHeader('Content-Type', 'application/json')
  req.onload = function () {
  const content = req.getResponseHeader('Content-Disposition')
  // 文件名最好用后端返的Content-disposition
  // 需要后端设置 Access-Control-Expose-Headers: Content-disposition 使得浏览器将该字段暴露给前端
  // 获取文件名的处理需要看后端的 Content-Disposition 字段如何设置的
  const name = content && content.split('filename=')[1]
  const fileName = decodeURIComponent(name)

  const link = document.createElement('a')
  link.href = window.URL.createObjectURL(new Blob([req.response])) // 创建下载的链接
  link.download = fileName || '默认文件名' // 下载后文件名
  link.style.display = 'none'
  document.body.appendChild(link)
  link.click() // 点击下载
  window.URL.revokeObjectURL(link.href) // 释放掉blob对象
  document.body.removeChild(link) // 下载完成移除元素
}
req.send(JSON.stringify(params))
js
axios({
  method: 'post',
  url: 'http://localhost:3000/download/123.txt',
  responseType: 'blob',
})
  .then(function (resp) {
    const fileName = resp.headers['content-disposition'].split('filename=')[1]

    const link = document.createElement('a')
    link.href = window.URL.createObjectURL(new Blob([resp.data])) // 创建下载的链接
    link.download = fileName || '默认文件名' // 下载后文件名
    link.style.display = 'none'
    document.body.appendChild(link)
    link.click() // 点击下载
    window.URL.revokeObjectURL(link.href) // 释放掉blob对象
    document.body.removeChild(link) // 下载完成移除元素
  })
  .catch((e) => {
    console.log(e)
  })