当前位置: 首页 > news >正文

minio 文件批量下载

MinIO 批量下载功能说明

1. 功能描述

前端勾选多个对象文件后,一次性将这些对象从 MinIO 拉取并打包成 ZIP,通过浏览器直接下载。整体特性:

  • 支持跨桶批量下载(不同 bucket 的对象可同时下载)。
  • 服务端采用流式压缩边读边写,内存占用低,适合大文件与多文件场景。
  • 前端使用 XMLHttpRequest 的 onprogress 实时展示下载进度:
    • 若服务器返回响应体长度或提供总大小提示,显示确定进度百分比。
    • 否则显示不确定进度(动效)。

2. 时序图(时间序列图)

用户浏览器前端页面FileControllerMinioUtilMinIO服务器ZIP流1. 文件列表加载阶段打开批量下载页面1GET /file/listAllFile2listAllFile()3获取所有存储桶4返回桶列表5获取桶内文件6返回文件列表7为文件设置bucket信息8loop[遍历每个桶]返回完整文件列表9返回JSON响应10渲染文件列表112. 批量下载阶段选择文件并点击下载12验证选择,创建FormData13POST /file/batchDownload14batchDownload(objectNames, buckets, response)153. 服务端处理阶段统计所有文件总大小16设置响应头 X-Total-Bytes17创建ZIP输出流18下载文件流19返回文件数据20写入ZIP条目21flush()刷新数据22触发progress事件23更新进度条24loop[处理每个文件]关闭ZIP流25下载完成26响应完成274. 前端下载阶段创建Blob对象28生成下载链接29自动触发下载30清理资源31用户浏览器前端页面FileControllerMinioUtilMinIO服务器ZIP流

3. 关键代码与说明

3.1 后端控制器接口
// File: com/wangfugui/apprentice/controller/FileController.java
@ApiOperation("批量下载文件")
@PostMapping("/batchDownload")
public void batchDownload(@RequestParam String[] objectNames,@RequestParam String[] buckets,HttpServletResponse response) throws Exception {if (objectNames.length != buckets.length) {throw new IllegalArgumentException("文件对象名和存储桶数量不匹配");}minioUtil.batchDownload(objectNames, buckets, response);
}
  • 参数 objectNames[]buckets[] 按序对应,每个元素指向一个要下载的对象及其桶。
  • 直接将响应流交由 MinioUtil.batchDownload 写入 ZIP 内容。
3.2 服务层:流式打包与进度提示
// File: com/wangfugui/apprentice/common/util/MinioUtil.java
public void batchDownload(String[] objectNames, String[] buckets, HttpServletResponse response) throws Exception {// 统计总字节:以各对象原始大小之和作为估算(ZIP压缩后大小可能不同)long totalBytes = 0L;for (int i = 0; i < objectNames.length; i++) {try {totalBytes += minioClient.statObject(StatObjectArgs.builder().bucket(buckets[i]).object(objectNames[i]).build()).size();} catch (Exception e) {log.warn("统计对象大小失败,跳过: {}/{} - {}", buckets[i], objectNames[i], e.getMessage());}}response.setContentType("application/zip");response.setCharacterEncoding("UTF-8");response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("批量下载文件.zip", "UTF-8"));response.setHeader("X-Total-Bytes", String.valueOf(totalBytes)); // 前端估算进度的总大小提示response.setHeader("Cache-Control", "no-store");ServletOutputStream outputStream = response.getOutputStream();ZipOutputStream zipOut = new ZipOutputStream(outputStream);try {for (int i = 0; i < objectNames.length; i++) {String objectName = objectNames[i];String bucket = buckets[i];try (InputStream fileStream = download(bucket, objectName)) {String fileName = objectName.substring(objectName.lastIndexOf("/") + 1);zipOut.putNextEntry(new ZipEntry(fileName));byte[] buffer = new byte[8192];int bytesRead;while ((bytesRead = fileStream.read(buffer)) != -1) {zipOut.write(buffer, 0, bytesRead);zipOut.flush();response.flushBuffer(); // 关键:推动数据到浏览器,触发前端 onprogress}zipOut.closeEntry();log.info("成功添加文件到ZIP: {}/{}", bucket, objectName);} catch (Exception e) {log.error("添加文件到ZIP失败: {}/{}, 错误: {}", bucket, objectName, e.getMessage());// 忽略单文件错误,继续其他对象}}} finally {zipOut.close();outputStream.close();}
}

关键点说明:

  • 使用 ZipOutputStream 边读边写,避免大文件占用大量内存。
  • 通过 response.setHeader("X-Total-Bytes", ...) 提示前端总大小,提升无法 Content-Length 时的进度可用性。
  • 每次写入后 zipOut.flush()response.flushBuffer(),推动数据尽快到达浏览器,保证 onprogress 事件高频触发。
3.3 前端下载与进度条
<!-- File: src/main/resources/static/batch-download.html (片段) -->
<div class="section download-section" id="downloadSection" style="display: none;"><h3>下载进度</h3><div class="progress-bar" id="downloadProgressBar"><div class="progress-fill" id="downloadProgressFill"></div></div><div class="progress-text" id="downloadProgressText">0%</div><!-- 确定/不确定进度两种模式:- 有总长度或总大小提示时显示百分比- 否则显示不确定动画 --><style>.progress-bar.indeterminate .progress-fill { width: 30%; position: absolute; left: -30%; animation: indeterminate-move 1.2s linear infinite; }@keyframes indeterminate-move { 0% { left: -30%; } 100% { left: 100%; } }</style>
</div><script>
// 发送下载请求并实时刷新进度
const xhr = new XMLHttpRequest();
xhr.open('POST', '/file/batchDownload', true);
xhr.responseType = 'blob';let totalHint = 0; // 后端给的总大小提示
xhr.onreadystatechange = function () {if (xhr.readyState === 2) { // HEADERS_RECEIVEDconst headerVal = xhr.getResponseHeader('X-Total-Bytes');totalHint = headerVal ? parseInt(headerVal, 10) : 0;}
};xhr.onprogress = function (e) {if (e.lengthComputable) {setIndeterminate(false);setDownloadProgress(Math.floor((e.loaded / e.total) * 100));return;}if (totalHint > 0) {setIndeterminate(false);setDownloadProgress(Math.min(Math.floor((e.loaded / totalHint) * 100), 99));} else {setIndeterminate(true);}
};xhr.onload = function () {setIndeterminate(false);setDownloadProgress(100);if (xhr.status >= 200 && xhr.status < 300) {const url = window.URL.createObjectURL(xhr.response);const a = document.createElement('a');a.href = url;a.download = '批量下载文件.zip';document.body.appendChild(a);a.click();window.URL.revokeObjectURL(url);document.body.removeChild(a);}
};function setDownloadProgress(p) {const fill = document.getElementById('downloadProgressFill');const text = document.getElementById('downloadProgressText');fill.style.width = Math.max(0, Math.min(100, p)) + '%';text.textContent = Math.max(0, Math.min(100, p)) + '%';
}
function setIndeterminate(on) {const bar = document.getElementById('downloadProgressBar');const text = document.getElementById('downloadProgressText');if (on) { bar.classList.add('indeterminate'); text.textContent = '正在下载...'; }else { bar.classList.remove('indeterminate'); }
}
</script>

要点说明:

  • 采用 XMLHttpRequest(而非 fetch)以便使用 onprogress 事件。
  • 优先使用浏览器提供的 lengthComputable,否则退化为基于 X-Total-Bytes 的估算。
  • 使用两种进度模式:确定百分比与不确定动效,提升不同后端/网络环境下的体验一致性。

4. 小结与建议

  • 生产环境中建议:
    • 若对象较多,可限制每次最大文件数或总字节阈值,避免超大 ZIP 影响响应时延或者内存不足报错。
    • 若需更精确进度:服务端可在每写入 N 字节时通过 SSE/WebSocket 推送“已写原始字节数”,前端以此计算更准确进度。

文章转载自:

http://5B49NUgK.drndL.cn
http://OhjclmIV.drndL.cn
http://C5S1M6QJ.drndL.cn
http://RFwvm0HS.drndL.cn
http://da4GtsRS.drndL.cn
http://A0FVyDaL.drndL.cn
http://MrN1QlDi.drndL.cn
http://fze0EY0Z.drndL.cn
http://7ieiNMOY.drndL.cn
http://7fHvRmSy.drndL.cn
http://y8PW7dPS.drndL.cn
http://b7wPg4OU.drndL.cn
http://rXek84Yd.drndL.cn
http://8GGUhjn4.drndL.cn
http://9j0dCPwg.drndL.cn
http://tZaNkv6I.drndL.cn
http://1OLA2dQt.drndL.cn
http://JliVatVO.drndL.cn
http://lkyf1vmP.drndL.cn
http://aJ5MhW8H.drndL.cn
http://0Sl9HozY.drndL.cn
http://J8COwg5P.drndL.cn
http://VPJrHjov.drndL.cn
http://tcAuvG1U.drndL.cn
http://v0YlLgxk.drndL.cn
http://aCK5cwjP.drndL.cn
http://hsdTzoXM.drndL.cn
http://Q8AAvED8.drndL.cn
http://ihrjMwiv.drndL.cn
http://wneOMrlc.drndL.cn
http://www.dtcms.com/a/375656.html

相关文章:

  • 【算法专题训练】19、哈希表
  • AJAX入门-URL、参数查询、案例查询
  • 安装ultralytics
  • Eino ChatModel 组件指南摘要
  • 腾讯codebuddy-cli重磅上线-国内首家支持全形态AI编程工具!
  • 基于PCL(Point Cloud Library)的点云高效处理方法
  • UVa1302/LA2417 Gnome Tetravex
  • STC Link1D电脑端口无法识别之升级固件
  • 【C++】LLVM-mingw + VSCode:Windows 开发攻略
  • SRM系统有哪些核心功能?企业该如何科学选型?
  • LINUX99 centos8:网络 yum配置;shell:while [ $i -ne 5 ];do let i++ done
  • 【陇剑杯2025】密码复现(部分)
  • 漫谈《数字图像处理》之图像自适应阈值处理
  • Melon: 基于marker基因的三代宏基因组分类和定量软件
  • 水题记录1.7
  • JVM 执行引擎详解!
  • lua中 string.match返回值
  • 2025-安装集成环境XAMPP
  • 整体设计 之 绪 思维导图引擎 :思维价值链分层评估的 思维引导和提示词导航 之 引 认知系统 之6之 序 认知元架构 之1(豆包助手 之3)
  • 【教学类-07-10】20250909中3班破译电话号码(手写数字版、撕贴版、头像剪贴底纹版、抄写填空版)
  • 【初阶数据结构】算法复杂度
  • PowerBI 的双隐藏,我在QuickBI 里也找到了
  • AI赋能训诂学:解码古籍智能新纪元
  • 微服务雪崩问题与系统性防御方案
  • css3之grid布局
  • git config --global user.name指令报错时的解决方案
  • 三维仿真软件中渲染层面的孔洞优化方法调研
  • Linux学习-ARM汇编指令
  • 微软依旧稳定发挥,Windows 最新更新性能「开倒车」
  • 预录车辆号牌提示系统——车牌检测系统