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

java 批量下载将多个文件(minio中存储)压缩成一个zip包

我的需求是将minio中存储的文件按照查询条件查询出来统一压成一个zip包然后下载下来。

思路:针对这个需求,其实可以有多个思路,不过也大同小异,一般都是后端返回流文件前端再处理下载,也有少数是压缩成zip包之后直接给下载链接返回到前端,前端收到链接url直接window.open()进行下载,不过这种下载zip包的路径要确保是在网站下,否则访问不到,还有一个缺点就是文件没法删除,占用存储空间,后期需人为动作清理,选择哪种思路就可以看具体需求啦,我选择的是第一种思路,以下就针对第一种后端返回流方式进行具体介绍。

首先说第一种方法:将需要下载的文件找到,minio中有查询方法将文件转成inputStream,这里就不多说了,拿到一组InputStream,我们就可以写入一个zip包里了,创建临时zip路径,将流遍历写入文件,读取临时zip文件再写入response中的outputStream,最后删除临时文件。

前端处理方法最后统一介绍,后端核心代码如下:

public void downloadZip(String name, List<MediaFileEntity> filePaths,HttpServletResponse     response){File zipFile = compressedFileToZip(name,filePaths);ByteArrayOutputStream os = new ByteArrayOutputStream();try {FileInputStream ins = new FileInputStream(zipFile);WritableByteChannel writableByteChannel = Channels.newChannel(os);FileChannel fileChannel = ins.getChannel();fileChannel.transferTo(0, fileChannel.size(), writableByteChannel);fileChannel.close();response.setCharacterEncoding("UTF-8");name = URLEncoder.encode(name, "UTF-8");response.setContentType("application/octet-stream");response.addHeader("Content-Disposition", "attachment;filename=" + new String(name.getBytes("iso8859-1")));response.setContentLength(os.size());response.setHeader("filename", name);response.addHeader("Content-Length", "" + os.size());var outputstream = response.getOutputStream();os.writeTo(outputstream);os.flush();os.close();outputstream.flush();outputstream.close();writableByteChannel.close();if(zipFile.exists()){//删除临时文件zipFile.delete();}} catch (IOException e) {e.printStackTrace();} finally {try {os.close();} catch (IOException e) {e.printStackTrace();}}}/*** 构建临时zip文件**/public File compressedFileToZip(String name, List<MediaFileEntity> mediaFileEntityList) {String zipName = name.concat(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))).concat(".zip");//临时zip路径String fileZipPath = System.getProperty("user.dir").concat("/").concat(zipName);OutputStream os = null;ZipOutputStream zos = null;File file = new File(fileZipPath);try {if(!file.exists()){file.createNewFile();}os= new FileOutputStream(file);zos = new ZipOutputStream(os) ;for (MediaFileEntity entity:mediaFileEntityList) {zos.putNextEntry(new ZipEntry(entity.getFileName()));//minio 获取流InputStream ins = ossService.getObject(OssConfiguration.bucket,entity.getObjectKey());FileInputStream insf=convertToFileInputStream(ins);WritableByteChannel writableByteChannel = Channels.newChannel(zos);FileChannel fileChannel = insf.getChannel();fileChannel.transferTo(0, fileChannel.size(), writableByteChannel);zos.closeEntry();fileChannel.close();ins.close();}} catch (IOException e) {e.printStackTrace();}finally {if(zos != null){try {zos.close();} catch (IOException e) {e.printStackTrace();}}if(os != null){try {os.close();} catch (IOException e) {e.printStackTrace();}}}return file;}

第二种方法:安装hutool依赖,调用hutool包中的ZipUtil工具类中的zip方法进行下载,。此方法需要有3个参数,分别是OutoutStream,每个流对应的文件名字符串数组,文件的InputStream数组。

首先将需要下载的文件找到,拿到一组InputStream,也就是zip方法中的第3个参数,第一个参数顾名思义就是你想要输出的地方,我们是返回给前端所以就是response.getOutputStream(),第二个参数我们遍历文件时也可以拿到,废话不多说了,上代码看吧。

在项目下安装hutool依赖

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.5.7</version>
</dependency>
  /*** 下载多个文件转zip压缩包** @param mediaFileEntityList* @param response* @throws Exception*/public void dowloadToZip(List<MediaFileEntity> mediaFileEntityList, HttpServletResponse response) throws Exception {int i = 0;//如果有附件 进行zip处理if (mediaFileEntityList != null && mediaFileEntityList.size() > 0) {try {//被压缩文件流集合InputStream[] srcFiles = new InputStream[mediaFileEntityList.size()];//被压缩文件名称String[] srcFileNames = new String[mediaFileEntityList.size()];for (MediaFileEntity entity : mediaFileEntityList) {//以下代码为获取图片inputStreamInputStream ins = ossService.getObject(OssConfiguration.bucket,entity.getObjectKey());if (ins == null) {continue;}//塞入流数组中srcFiles[i] = ins;srcFileNames[i] = entity.getFileName();i++;}response.setCharacterEncoding("UTF-8");response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("下载.zip", "UTF-8"));//多个文件压缩成压缩包返回ZipUtil.zip(response.getOutputStream(), srcFileNames, srcFiles);} catch (IOException e) {e.printStackTrace();}}}

Controller这边可以直接写成没有返回值的接口,我的例子如下,仅供参考:

    @GetMapping("/{workspace_id}/fileDownList")@ApiOperation(value = "查询文件的下载地址")public void getFileStreamList(@PathVariable(name = "workspace_id") String workspaceId,@RequestParam(name = "ids") String ids, HttpServletResponse response) throws Exception {List<Integer> fileIds= Arrays.stream(ids.split(",")).collect(Collectors.toList()).stream().map(Integer::parseInt).collect(Collectors.toList());List<MediaFileEntity> mediaFileEntityList = fileService.getMediaListById(workspaceId, fileIds);
//        fileUtil.downloadZip("111",mediaFileEntityList,response);//第一种方法fileUtil.dowloadToZip(mediaFileEntityList,response);//第二种方法}

 到此,后端zip下载就完毕了,下面我们说说前端如何处理

网上查询前端处理大致都是如下,但是我自己使用的时候下载总是提示损坏,后找了一个工具类直接调用,就可以了,示例代码请求是get,如需调整,可根据情况自行调整

核心代码如下:

import { saveAs } from 'file-saver';    
const baseURL = (window as any).config.VITE_APP_BASE_API; //import.meta.env.VITE_APP_BASE_API;export default {
zip(url: string, name: string) {url = baseURL + url;axios({method: 'get',url: url,responseType: 'blob',headers: { Authorization: 'Bearer ' + getToken() },}).then(res => {const isBlob = blobValidate(res.data);if (isBlob) {const blob = new Blob([res.data], { type: 'application/zip' });this.saveAs(blob, name);} else {this.printErrMsg(res.data);}});},saveAs(text: any, name: string, opts?: any) {saveAs(text, name, opts);},async printErrMsg(data: any) {const resText = await data.text();const rspObj = JSON.parse(resText);const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];// ElMessage.error(errMsg);},blobValidate(data: any) {return data.type !== 'application/json';}
};

按钮绑定方法直接调用zip下载方法,传参为url和要导出zip的名称,示例如下:

import download from '@/plugins/download';function batchDownload(){ElMessage.success("文件下载中,请勿重复点击!");download.zip(`/media/api/v1/files/${workspaceId}/fileDownList?ids=${selectlist.value.join(",")}`,"MediaFiles"+new Date().toLocaleDateString()+".zip")}

相关文章:

java 批量下载将多个文件(minio中存储)压缩成一个zip包

我的需求是将minio中存储的文件按照查询条件查询出来统一压成一个zip包然后下载下来。 思路&#xff1a;针对这个需求&#xff0c;其实可以有多个思路&#xff0c;不过也大同小异&#xff0c;一般都是后端返回流文件前端再处理下载&#xff0c;也有少数是压缩成zip包之后直接给…...

nnUNet v2数据准备及格式转换 (二)

如果你曾经使用过nnUNet V1&#xff0c;那你一定明白数据集的命名是有严格要求的&#xff0c;必须按照特定的格式来进行命名才能正常使用。 这一节的学习需要有数据&#xff0c;如果你有自己的数据&#xff0c;可以拿自己的数据来实验&#xff0c;如果没有&#xff0c;可以用十…...

ant-vue1.78版监听a-modal遮罩层的滚动事件

监听a-modal遮罩层的滚动事件 我们开发过程中经常有遇到监听页面滚动的事件需求&#xff0c;去做一些下拉加载或者是下拉分页的需求&#xff0c;我们直接在vue的生命周期中去绑定事件监听非常的方便&#xff0c;但如果是弹框的遮罩层的滚动监听呢&#xff1f;页面的监听完全是…...

MATLAB中residue函数用法

目录 语法 说明 示例 求解具有实根的部分分式展开式 展开具有复数根和同次分子及分母的分式 展开分子次数高于分母次数的分式 residue函数的功能是部分分式展开&#xff08;部分分式分解&#xff09;。 语法 [r,p,k] residue(b,a) [b,a] residue(r,p,k) 说明 [r,p…...

攻防世界-Caesar

原题 解题思路 没出现什么特殊字符&#xff0c;可能是个移位密码。凯撒密码加密解密。偏移12位就行。...

嵌入式开发-lin总线介绍 一.概述

1.1lin总线定义和历史 LIN总线&#xff08;Local Interconnect Network&#xff09;是一种基于UART/SCI&#xff08;Universal Asynchronous Receiver-Transmitter/Serial Communication Interface&#xff09;的低成本串行通信协议。它主要用于汽车、家电、办公设备等多种领域…...

羊城杯-2023-Crypto

文章目录 Danger_RSA题目描述&#xff1a;题目分析&#xff1a; Easy_3L题目描述&#xff1a;题目分析&#xff1a; XOR贯穿始终题目描述&#xff1a;题目分析&#xff1a; MCeorpkpleer题目描述&#xff1a;题目分析&#xff1a; SigninCrypto题目描述&#xff1a;题目分析&am…...

RabbitMQ快速上手及讲解

前言&#xff1a;在介绍RabbitMQ之前&#xff0c;我们先来看下面一个场景&#xff1a; 1.1.1.1 异步处理 场景说明&#xff1a; 用户注册后&#xff0c;需要发注册邮件和注册短信&#xff0c;传统的做法有两种 1.串行的方式 (1)串行方式&#xff1a;将注册信息写入数据库后&a…...

使用多线程std::thread发挥多核计算优势(解答)

使用多线程std::thread发挥多核计算优势&#xff08;题目&#xff09; 单核无能为力 如果我们的电脑只有一个核&#xff0c;那么我们没有什么更好的办法可以让我们的程序更快。 因为这个作业限制了你修改算法函数。你唯一能做的就是利用你电脑的多核。 使用多线程 由于我们…...

MySQL分页查询详解:优化大数据集的LIMIT和OFFSET

最近在工作中&#xff0c;我们遇到了一个需求&#xff0c;甲方要求直接从数据库导出一个业务模块中所有使用中的工单信息。为了实现这一目标&#xff0c;我编写了一条SQL查询语句&#xff0c;并请求DBA协助导出数据。尽管工单数量并不多&#xff0c;只有3000多条&#xff0c;但…...

解构赋值、函数默认值

暂时性死区&#xff0c;TDZ&#xff08;Temporal Dead Zone&#xff09; var x 1 {let x x//此处声明了x&#xff0c;但是没有对x赋值&#xff0c;相当于在赋值之前引用x&#xff0c;所以会造成报错console.log(x)//报错x is not defined&#xff0c;暂时性死区&#xff0c;…...

【已解决】Mybatis 实现 Group By 动态分组查询

&#x1f389;工作中遇到这样一个需求场景&#xff1a;实现一个统计查询&#xff0c;要求可以根据用户在前端界面筛选的字段进行动态地分组统计。也就是说&#xff0c;后端在实现分组查询的时候&#xff0c;Group By 的字段是不确定的&#xff0c;可能是一个字段、多个字段或者…...

Android修改默认gradle路径

Android Studio每次新建项目&#xff0c;都会默认在C盘生成并下载gradle相关文件&#xff0c;由于C盘空间有限&#xff0c;没多久C盘就飘红了&#xff0c;于是就需要把gradle相关文件转移到其他盘 1、到C盘找到gradle文件 具体路径一般是&#xff1a;C:\Users\用户\ .gradle …...

原生JS+canvas实现炫酷背景

原生JScanvas实现炫酷背景 可以在需要的背景页使用 <!doctype html> <html> <head> <meta charset"utf-8"> <title>HTML5 Canvas矩阵粒子波浪背景动画特效</title> <style> html,body { height:100%; } body { …...

Linux学习之NAS服务器搭建

NAS是Network Attached Storage的缩写&#xff0c;也就是网络附属存储。可以使用自己已经不怎么使用的笔记本搭建一台NAS服务器。 fdisk -l可以看一下各个磁盘的状态。 可以看到有sda、sdb、sdc和sdd等四块硬盘。 lvs、vgs和pvs结合起来看&#xff0c;sdb和sdc没有被使用。 …...

分享码云上8个宝藏又有价值的开源图片编辑器

如果你需要高效地处理图片&#xff0c;那么这8款实用工具是可以尝试的&#xff01; 它们能够进行一键抠图、放大、拼接、转矢量图、图标自动生成以及等操作&#xff0c;让你的工作效率飞升&#xff01; 在Gitee这个最有价值的开源项目计划是Gitee综合评定出的优秀开源项目的展示…...

TCP Header都有啥?

分析&回答 源端口号&#xff08;Source Port&#xff09; &#xff1a;16位&#xff0c;标识主机上发起传送的应用程序&#xff1b; 目的端口&#xff08;Destonation Port&#xff09; &#xff1a;16位&#xff0c;标识主机上传送要到达的应用程序。 源端&#xff0c;目…...

无涯教程-Android - AutoCompleteTextView函数

AutoCompleteTextView是一个类似于EditText的视图&#xff0c;只是它在用户键入时自动显示补充数据。 AutoCompleteTextView - 属性 以下是与AutoCompleteTextView控件相关的重要属性。您可以查看Android官方文档以获取属性的完整列表以及可以在运行时更改这些属性的相关方法。…...

【Docker】 07-安装ElasticSearch、Kibana

安装ElasticSearch 1、拉取镜像 docker pull elasticsearch:6.4.2 2、运行 docker run -p 9200:9200 -p 9300:9300 --name es -d elasticsearch:6.4.2 启动会报错&#xff0c;按照下面流程修改 3、在宿主机中&#xff0c;修改配置sysctl.conf vim /etc/sysctl.conf 加入如下配…...

【数据结构篇】线性表1 --- 顺序表、链表 (万字详解!!)

前言&#xff1a;这篇博客我们重点讲 线性表中的顺序表、链表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列... 线性表在逻辑上是…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...