前端一次性批量下载多个文件【多种方案及优缺点分析】
问题描述
最近项目中遇到一个需求,前端接收后端的urls(数组类型),循环批量下载日志文件。如果仅仅同步循环,通过a标签下载,只能下载urls中的最后一个文件,前面的文件会被取消掉,如图:
原因:由于安全限制,现代浏览器会阻止同时进行的多个下载,用户必须对每个下载都进行交互(点击或接受下载)。脚本试图启动超过一个的下载可能会被阻止。这是浏览器制定的安全策略,目前无法绕过。
// 你的 url 数组
let urls = [
'http://example.com/file1.jpg',
'http://example.com/file2.jpg',
// ...
];
function downloadFiles() {
urls.forEach((url, index) => {
// 创建新的 a 标签
var link = document.createElement('a');
link.href = url;
link.download = 'file' + (index + 1); // 文件名 如果 要文件原名可以通过url爬取或后端提供
link.style.display = 'none';
// 添加到 body,否则 Firefox 无法启动下载
document.body.appendChild(link);
link.click();
// 移除元素
document.body.removeChild(link);
});
}
</script>
方案一:使用**setTimeout**
实现文件逐个下载。
使用预定的时间间隔来防止浏览器阻止批量下载操作,模拟用户点击行为。
//setTimeout方法
function downloadFiles(urls) {
urls.forEach((url, i) => {
setTimeout(function () {
let link = document.createElement('a');
link.href = url;
link.setAttribute('download', ''); // 或者设置为具体的下载文件名
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}, i * 1000);
});
}
优点:
可以实现多个文件下载。
局限性:
你需要确保每个文件的下载时间都不超过你设定的时间间隔,并且如果你设定的时间间隔太大,那么整个下载过程可能会消耗较长的时间。如果设定时间间隔太小,网络快慢、文件大小等多种因素可能使文件无法全部下载。
如图,把网络调慢后下载会超时,导致没有全部下载完成。
方案二:使用 Blob 对象和 window.URL.createObjectURL
let urls = [
'http://example.com/file1.jpg',
'http://example.com/file2.jpg',
// ...
];
for (let i = 0; i < urls.length; i++) {
fetch(urls[i])
.then(response => response.blob()) // 获取文件数据流
.then(blob => {
const url = window.URL.createObjectURL(blob); // 生成文件在浏览器中的链接
const a = document.createElement('a');
a.href = url;
a.download = `file${i + 1}`; // 文件名
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url); // 清除文件链接
})
.catch(console.error);
}
解释:对于 urls 数组中的每个 URL,我们使用 fetch
API 从服务器获取文件数据,然后使用 response.blob()
将文件数据转化为 Blob 对象。接着使用 window.URL.createObjectURL
创建一个指向这个 Blob 对象的 URL,然后创建一个隐藏的 <a>
标签来进行下载。下载完成后移除 <a>
标签并使用 window.URL.revokeObjectURL
清除对 Blob 的引用,释放内存。
优点:
- 对于需要特殊认证或者其他复杂 HTTP headers 的文件下载请求更方便,因为
fetch
允许你自定义请求头。 - 如果你需要处理文件数据,例如将其转为 Blob 对象或者用 FileReader 进行读取等,
fetch
更方便。
局限性:
- 尽管我们在代码中自动点击了
<a>
标签来触发下载,但是在某些浏览器中如果下载操作过于频繁或者数量过多,仍可能被视为不安全的操作而被阻止。 - 对于大文件,
fetch
需要将整个文件数据读入内存,可能导致大量内存消耗。(建议使用Streams API等处理工具,避免一次性加载整个文件到内存,而是通过逐块处理文件数据) - 它只能用于同源的请求,或者 CORS 允许的请求。如果文件在别的域名且没有设定 CORS,fetch 无法获取文件。
方案三:使用iframe实现批量下载
urls.forEach((url, index) => {
// 创建一个新的 iframe 元素
let iframe = document.createElement('iframe');
// 将 iframe 的 'src' 属性设置为文件的 URL
iframe.src = url;
// 设置 iframe 的 'id' 以便稍后移除
iframe.id = 'download_iframe_' + index;
// 将 iframe 设置为隐藏
iframe.style.display = 'none';
// 将 iframe 添加到页面中
document.body.appendChild(iframe);
});
// 一段时间后移除这些 iframe
setTimeout(() => {
urls.forEach((url, index) => {
let iframe = document.getElementById('download_iframe_' + index);
document.body.removeChild(iframe);
});
}, 5000);
为什么iframe
可以批量下载,a标签不可以?
a标签是用来创建超链接的,当点击一个a标签时,浏览器通常会打开一个新页面加载链接的资源。如果这个资源是一个可下载文件,浏览器会启动文件下载。这种方式虽然对单个文件很有效,但是浏览器通常会限制同时打开的新页面数,因此对于多个文件的下载可能不那么有效。
iframe则实际上是在当前页面内嵌入了一个新的浏览器上下文,它可以独立于其它内容加载链接的资源。当在iframe中加载一个可下载文件时,浏览器会直接启动文件下载,而不会影响当前页面的其它内容。
优点:
- 对于大文件,iframe 可以直接触发下载,不需要将文件数据读入内存,对内存消耗较小。
- iframe 方法对文件的源没有限制。
局限性:
- 浏览器可能对大量
iframe
创建与下载行为进行限制,避免恶意下载。 - 对于需要自定义 HTTP headers(如认证信息)的请求,或者需要预处理文件数据的情况,iframe 不太方便。
还有其他方案,比如Promise.all()
异步下载文件等
总结:
由于浏览器的安全策略,批量下载可能会被浏览器视为恶意行为并被阻止。不同浏览器会有不同表现,一般会提示“阻止多个弹窗”或者“您是否允许一次性下载多个文件”,用户体验不够好。最佳的方案是通过服务器先将这些文件打包成一个 .zip
文件,然后让浏览器只下载这一个文件。
转载自:https://juejin.cn/post/7281208218175963136