大文件下载解决方案
传统文件下载方案
1、window.open & window.location.href
基本使用
window.open(url);
window.location.href = url
特点:
- 只支持 get 请求,不支持 post
- 浏览器会根据 header 的 content-type 来判断是下载文件还是预览文件
- 不支持 header 添加请求表头的形式来鉴权
- 不支持自定义 filename
2、a 标签
基本使用
// 两种方式
// 方式 1
<a href="/url.png" download="filename"></a>
// 方式 2
const a = document.createElement('a')
a.href = 'url'
a.download = 'filename'
// 使用target="_blank"时,添加rel="noopener noreferrer" 堵住钓鱼安全漏洞 防止新页面window指向之前的页面
a.rel = "noopener noreferrer";
a.style.display = 'none'
document.body.appendChild(a)
a.click()
a.remove()
特点:
对于浏览器不能识别的文件类型,会自动转换下载模式,否则默认自动打开文件,可以手动添加 download
属性,启用下载模式。
总结:
- 仅适用于 同源 URL 或 blob 、data 协议起作用
- 不能携带 header 所以不支持添加请求表头的形式来鉴权
- 只支持 get 不支持 post
- 不会预览,直接下载文件
- 支持修改 filename
3、API 请求
本质是先获取 blob
对象,再通过 URL.createObjectURL
获取 dowload url。所以会有两个下载过程。
基本使用
axios({
url,
responseType: 'blob',
headers: {
Authorization: getToken()
}
}).then((res) => {
if (res && res.status === 200 && res.data) {
const { data, headers } = res
// 从文件中获取文件名称
let fileName
if (headers['content-disposition']) {
fileName = headers['content-disposition'].split(';')[1].split('=')[1]
} else {
fileName = data.fileName
}
const blob = new Blob([data], { type: headers['content-type'] })
const downUrl = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = downUrl
a.download = fileName
a.style.display = 'none'
document.body.appendChild(a)
a.click()
a.remove()
}
})
特点:
- 支持设置 header 携带鉴权信息
- 支持修改 filename
- 支持跨域,支持post
- 下载完成再响应,对于大文件下载不友好,用户无响应等待时间较长
API 请求加强:fetch+streamsaver (适用于大文件下载)
fetch 和 axios 的区别就是 fetch 是响应之后就返回信息,而 axios 是在文件流下载完成之后再返回。
但是 fetch 返回之后并不会立马调起浏览器下载窗口,所以需要用到一个第三方插件 streamsaver
,在接口响应之后就能立马调起浏览器下载窗口,显示下载进度。
基本使用
1、需要借助 streamsaver
,安装 streamsaver
,npm install streamsaver
2、代码如下
import streamSaver from 'streamsaver'
function download(url: string, filename = '') {
fetch(url, {
method: 'get',
headers: {
Authorization: getToken() as string
}
}).then((res) => {
if (res.status !== 200) {
ElMessage.error('文件错误,下载失败')
return
}
const readableStream = res.body
const fileStream = streamSaver.createWriteStream(filename, {
size: Number(res.headers.get('content-length'))
})
if (window.WritableStream && readableStream?.pipeTo) {
return readableStream?.pipeTo(fileStream).then((res) => {
console.log('done writeen')
})
}
window.writer = fileStream.getWriter()
const reader = res.body?.getReader()
const pump = () =>
reader
?.read()
.then((res) =>
res.done
? window.writer.close()
: window.reader.write(res.value).then(pump)
)
pump()
})
return
}
特点:
- 当接口有响应就会弹窗显示下载过程,对大文件下载友好
- 可以设置 header 携带鉴权信息
- 支持修改 filename
- 支持跨域,支持post
参考:
转载自:https://juejin.cn/post/7269433344894681129