下载文件
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)
})