Java列表导出时将附件信息压缩成一个zip
一:使用场景
在最近的工作当中遇到了一个需求,在列表导出时,不仅需要将列表信息导出为excel文件,同时也需要将列表每一条数据所对应的附件信息放在同一个文件夹当中,并且压缩成一个zip响应给浏览器。首先后端需要写两个接口,一个负责导出excel列表,另一个负责生成zip并且响应给前端浏览器,此时我们只研究生成zip的接口。
二:生成zip思路分析
- 定义response
- 通过for循环获取到附件的流文件
- 生成本地临时文件,并且进行压缩
- 获取到输出的压缩流文件,将其传递给response
- 删除本地临时文件,并且关闭流文件
三:代码实现
3.1 Controller控制层
@ApiOperation("导出附件")@PostMapping("/exportAnnex")public void exportAnnex(HttpServletResponse response, @RequestBody ContentManage param) {contentManageService.exportAnnex(param, response);}
3.2 service层
/*** 导出附件信息** @param param 查询参数* @param response 响应*/void exportAnnex(ContentManage param, HttpServletResponse response);
3.3 service实现类
/*** 导出附件信息** @param param 查询参数* @param response 响应*/@Override@Asyncpublic void exportAnnex(ContentManage param, HttpServletResponse response) {try {//筛选出文件模式的列表List<ContentManage> releaseModeContentManageList = new ArrayList<>();String fileName = "导出的压缩包名称";// 设置response的Headerresponse.setCharacterEncoding("UTF-8");//Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存//attachment表示以附件方式下载 inline表示在线打开 "Content-Disposition: inline; filename=文件名.mp3"// filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".zip" + ";filename*=utf-8''" + URLEncoder.encode(fileName + ".zip", "UTF-8"));//设置响应格式,已文件流的方式返回给前端。response.setContentType("application/octet-stream;charset=utf-8");OutputStream stream = response.getOutputStream();ByteArrayOutputStream outputStream = new ByteArrayOutputStream();ZipOutputStream zos = new ZipOutputStream(outputStream);for (ContentManage contentManage : releaseModeContentManageList) {String filename = "附件名称";String folderName = "目录名称";File file = new File(folderName);boolean mkdirs = file.mkdirs();//此处的inputStream是获取各自业务的流文件InputStream inputStream = obsClientHelper.downloadInputStream(attachmentUrl);if (inputStream == null) {throw new RuntimeException("OBS文件下载失败");}File attachmentFile = new File(folderName, filename);FileOutputStream fos = new FileOutputStream(attachmentFile);try {byte[] buffer = new byte[1024];int length = 0;while (-1 != (length = inputStream.read(buffer, 0, buffer.length))) {fos.write(buffer, 0, length);}File temp = new File(file.getAbsolutePath());CompressUtil.doCompressFile(temp, zos, "");//要想删除文件夹下的所有内容,必须关闭流inputStream.close();fos.close();//这种方式删除path下的文件时,若path路径下的文件读写流没有关闭,则删除不了;FileUtils.deleteDirectory(file);} catch (IOException e) {log.error("附件打包压缩异常:{}", e.getMessage());}}IOUtils.write(outputStream.toByteArray(), stream);zos.close();stream.flush();stream.close();} catch (Exception e) {throw new RuntimeException("压缩异常");}}
3.4 CompressUtil类
package com.byd.ghy.phylogeny.utils;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;/*** @author fhey* @date 2023-05-11 20:48:28* @description: 压缩工具类*/
@Component
@Slf4j
public class CompressUtil {/*** 将文件打包到zip并创建文件** @param sourceFilePath* @param zipFilePath* @throws IOException*/public static void createLocalCompressFile(String sourceFilePath, String zipFilePath) throws IOException {createLocalCompressFile(sourceFilePath, zipFilePath, null);}/*** 将文件打包到zip并创建文件** @param sourceFilePath* @param zipFilePath* @param zipName* @throws IOException*/public static void createLocalCompressFile(String sourceFilePath, String zipFilePath, String zipName) throws IOException {File sourceFile = new File(sourceFilePath);if (!sourceFile.exists()) {throw new RuntimeException(sourceFilePath + "不存在!");}if (StringUtils.isBlank(zipName)) {zipName = sourceFile.getName();}File zipFile = createNewFile(zipFilePath + File.separator + zipName + ".zip");try (FileOutputStream fileOutputStream = new FileOutputStream(zipFile)) {compressFile(sourceFile, fileOutputStream);}}/*** 获取压缩文件流** @param sourceFilePath* @return ByteArrayOutputStream* @throws IOException*/public static OutputStream compressFile(String sourceFilePath, OutputStream outputStream) throws IOException {File sourceFile = new File(sourceFilePath);if (!sourceFile.exists()) {throw new RuntimeException(sourceFilePath + "不存在!");}return compressFile(sourceFile, outputStream);}/*** 获取压缩文件流** @param sourceFile* @return ByteArrayOutputStream* @throws IOException*/private static OutputStream compressFile(File sourceFile, OutputStream outputStream) throws IOException {try (CheckedOutputStream checkedOutputStream = new CheckedOutputStream(outputStream, new CRC32());ZipOutputStream zipOutputStream = new ZipOutputStream(checkedOutputStream)) {doCompressFile(sourceFile, zipOutputStream, StringUtils.EMPTY);return outputStream;}}/*** 处理目录下的文件** @param sourceFile* @param zipOutputStream* @param zipFilePath* @throws IOException*/public static void doCompressFile(File sourceFile, ZipOutputStream zipOutputStream, String zipFilePath) throws IOException {// 如果文件是隐藏的,不进行压缩if (sourceFile.isHidden()) {return;}if (sourceFile.isDirectory()) {//如果是文件夹handDirectory(sourceFile, zipOutputStream, zipFilePath);} else {//如果是文件就添加到压缩包中try (FileInputStream fileInputStream = new FileInputStream(sourceFile)) {//String fileName = zipFilePath + File.separator + sourceFile.getName();String fileName = zipFilePath + sourceFile.getName();addCompressFile(fileInputStream, fileName, zipOutputStream);//String fileName = zipFilePath.replace("\\", "/") + "/" + sourceFile.getName();//addCompressFile(fileInputStream, fileName, zipOutputStream);}}}/*** 处理文件夹** @param dir 文件夹* @param zipOut 压缩包输出流* @param zipFilePath 压缩包中的文件夹路径* @throws IOException*/private static void handDirectory(File dir, ZipOutputStream zipOut, String zipFilePath) throws IOException {File[] files = dir.listFiles();if (ArrayUtils.isEmpty(files)) {ZipEntry zipEntry = new ZipEntry(zipFilePath + dir.getName() + File.separator);zipOut.putNextEntry(zipEntry);zipOut.closeEntry();return;}for (File file : files) {doCompressFile(file, zipOut, zipFilePath + dir.getName() + File.separator);}}/*** 获取压缩文件流** @param documentList 需要压缩的文件集合* @return ByteArrayOutputStream*//*public static OutputStream compressFile(List<FileInfo> documentList, OutputStream outputStream) {Map<String, List<FileInfo>> documentMap = new HashMap<>();documentMap.put("", documentList);return compressFile(documentMap, outputStream);}*//*** 将文件打包到zip** @param documentMap 需要下载的附件集合 map的key对应zip里的文件夹名* @return ByteArrayOutputStream*//*public static OutputStream compressFile(Map<String, List<FileInfo>> documentMap, OutputStream outputStream) {CheckedOutputStream checkedOutputStream = new CheckedOutputStream(outputStream, new CRC32());ZipOutputStream zipOutputStream = new ZipOutputStream(checkedOutputStream);try {for (Map.Entry<String, List<FileInfo>> documentListEntry : documentMap.entrySet()) {String dirName = documentMap.size() > 1 ? documentListEntry.getKey() : "";Map<String, Integer> fileNameToLen = new HashMap<>();//记录单个合同号文件夹下每个文件名称出现的次数(对重复文件名重命名)for (FileInfo document : documentListEntry.getValue()) {try {//防止单个文件夹下文件名重复 对重复的文件进行重命名String documentName = document.getFileName();if (fileNameToLen.get(documentName) == null) {fileNameToLen.put(documentName, 1);} else {int fileLen = fileNameToLen.get(documentName) + 1;fileNameToLen.put(documentName, fileLen);documentName = documentName + "(" + fileLen + ")";}String fileName = documentName + "." + document.getSuffix();if (StringUtils.isNotBlank(dirName)) {fileName = dirName + File.separator + fileName;}addCompressFile(document.getFileInputStream(), fileName, zipOutputStream);} catch (Exception e) {logger.info("filesToZip exception :", e);}}}} catch (Exception e) {logger.error("filesToZip exception:" + e.getMessage(), e);}return outputStream;}*//*** 将单个文件写入文件压缩包** @param inputStream 文件输入流* @param fileName 文件在压缩包中的相对全路径* @param zipOutputStream 压缩包输出流*/private static void addCompressFile(InputStream inputStream, String fileName, ZipOutputStream zipOutputStream) {try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {ZipEntry zipEntry = new ZipEntry(fileName);zipOutputStream.putNextEntry(zipEntry);byte[] bytes = new byte[1024];int length;while ((length = bufferedInputStream.read(bytes)) >= 0) {zipOutputStream.write(bytes, 0, length);zipOutputStream.flush();}zipOutputStream.closeEntry();//System.out.println("map size, value is " + RamUsageEstimator.sizeOf(zipOutputStream));} catch (Exception e) {log.info("addFileToZip exception:", e);throw new RuntimeException(e);}}/*** 通过网络请求下载zip** @param sourceFilePath 需要压缩的文件路径* @param response HttpServletResponse* @param zipName 压缩包名称* @throws IOException*/public static void httpDownloadCompressFile(String sourceFilePath, HttpServletResponse response, String zipName) throws IOException {File sourceFile = new File(sourceFilePath);if (!sourceFile.exists()) {throw new RuntimeException(sourceFilePath + "不存在!");}if (StringUtils.isBlank(zipName)) {zipName = sourceFile.getName();}try (ServletOutputStream servletOutputStream = response.getOutputStream()) {CompressUtil.compressFile(sourceFile, servletOutputStream);response.setContentType("application/zip");response.setHeader("Content-Disposition", "attachment; filename=\"" + zipName + ".zip\"");servletOutputStream.flush();}}public static void httpDownloadCompressFileOld(String sourceFilePath, HttpServletResponse response, String zipName) throws IOException {try (ServletOutputStream servletOutputStream = response.getOutputStream()) {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();byte[] zipBytes = byteArrayOutputStream.toByteArray();response.setContentType("application/zip");response.setHeader("Content-Disposition", "attachment; filename=\"" + zipName + ".zip\"");response.setContentLength(zipBytes.length);servletOutputStream.write(zipBytes);servletOutputStream.flush();}}/*** 通过网络请求下载zip** @param sourceFilePath 需要压缩的文件路径* @param response HttpServletResponse* @throws IOException*/public static void httpDownloadCompressFile(String sourceFilePath, HttpServletResponse response) throws IOException {httpDownloadCompressFile(sourceFilePath, response, null);}/*** 检查文件名是否已经存在,如果存在,就在文件名后面加上“(1)”,如果文件名“(1)”也存在,则改为“(2)”,以此类推。如果文件名不存在,就直接创建一个新文件。** @param filename 文件名* @return File*/public static File createNewFile(String filename) {File file = new File(filename);if (!file.exists()) {try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}} else {String base = filename.substring(0, filename.lastIndexOf("."));String ext = filename.substring(filename.lastIndexOf("."));int i = 1;while (true) {String newFilename = base + "(" + i + ")" + ext;file = new File(newFilename);if (!file.exists()) {try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}break;}i++;}}return file;}}
相关文章:

Java列表导出时将附件信息压缩成一个zip
一:使用场景 在最近的工作当中遇到了一个需求,在列表导出时,不仅需要将列表信息导出为excel文件,同时也需要将列表每一条数据所对应的附件信息放在同一个文件夹当中,并且压缩成一个zip响应给浏览器。首先后端需要写两…...

简单美观易上手的 Docker Compose 可视化管理器 Dockge
本文首发于只抄博客,欢迎点击原文链接了解更多内容。 前言 Dockge 是 Uptime Kuma 作者的新作品,因此 UI 风格与 Uptime Kuma 基本一致,如果你正在使用 Uptime Kuma 的话,那么 Dockge 的 UI 设计应该也不会让你失望。Dockge 主打…...

贴片 RS8752XK 封装SOP-8 250MHz,2通道高速运放
传感器信号放大:在传感器应用中,RS8752XK可以用于放大微弱的传感信号,如压力、温度、光强等传感器的信号。 数据采集系统:在数据采集设备中,RS8752XK可以用于放大和调理模拟信号,以供模数转换器࿰…...
图论-最短路算法
1. Floyd算法 作用:用于求解多源最短路,可以求解出任意两点的最短路 利用动态规划只需三重循环即可(动态规划可以把问题求解分为多个阶段)定义dp[k][i][j]表示点i到点j的路径(除去起点终点)中最大编号不超…...

家政预约小程序05服务管理
目录 1 设计数据源2 后台管理3 后端API4 调用API总结 家政预约小程序的核心是展示家政公司提供的各项服务的能力,比如房屋维护修缮,家电维修,育婴,日常保洁等。用户在选择家政服务的时候,价格,评价是影响用…...

Django自定义命令
Django自定义命令 我们知道,Django内部内置了很多命令,例如 python manage.py runserver python manage.py makemigrations python manage.py migrate我们可以在python控制台中查看所有命令 我们也可以自定义命令,让python manage.py执行…...

详解VLSM技术
在现代网络设计中,如何高效地分配和管理IP地址是一个关键问题。传统的子网划分方法虽然简单,但在实际应用中常常导致IP地址的浪费。为了应对这一问题,VLSM(Variable Length Subnet Mask,可变长子网掩码)技术…...

面向浏览器端免费开源的三维可视化编辑器,包含BIM轻量化,CAD解析预览等特色功能。
ES 3DEditor 🌍Github地址 https://github.com/mlt131220/ES-3DEditor 🌍在线体验 https://editor.mhbdng.cn/#/ 基于vue3与ThreeJs,具体查看Doc 主要功能: 模型导入展示,支持OBJ、FBX、GLTF、GLB、RVT、IFC、SEA、3…...

Nacos 进阶篇---Nacos服务端怎么维护不健康的微服务实例 ?(七)
一、引言 在 Nacos 后台管理服务列表中,我们可以看到微服务列表,其中有一栏叫“健康实例数” (如下图),表示对应的客户端实例信息是否可用状态。 那Nacos服务端是怎么感知客户端的状态是否可用呢 ? 本章…...

【oracle004】oracle内置函数手册总结(已更新)
1.熟悉、梳理、总结下oracle相关知识体系。 2.日常研发过程中使用较少,随着时间的推移,很快就忘得一干二净,所以梳理总结下,以备日常使用参考 3.欢迎批评指正,跪谢一键三连! 总结源文件资源下载地址&#x…...

建模:Maya
一、常用按键 1、alt 左键 —— 环绕查看 2、alt 中键 —— 拖动模型所在面板 3、空格 —— 进入三视图模式;空格 左键按住拖动 —— 切换到对应视图 二、骨骼归零 1、T Pose 旋转模式,点击模型,摆好T姿势即可 2、复制模型设置200距离…...

持续总结中!2024年面试必问 20 道 Redis面试题(四)
上一篇地址:持续总结中!2024年面试必问 20 道 Redis面试题(三)-CSDN博客 七、Redis过期键的删除策略? Redis 过期键的删除策略主要涉及以下几种方式: 1. 定时删除(Timed Expirationÿ…...

Java中关于List的一些常用操作
先定义一个List,代码如下 //定义一个实例类 public class Model{private String id;private String code;private String name;//setter getter 方法省略}//定义一个List,赋值过程省略 List<Model> list new ArrayList<>();1.将List中每一个对象的id…...

Docker仓库解析
目录 1、Docker仓库类型2、Docker仓库的作用3、工作原理4、管理与使用最佳实践 Docker仓库是Docker生态系统中的重要组成部分,它是用于存储和分发Docker镜像的集中化服务。无论是公共还是私有,仓库都是开发者之间共享和复用容器镜像的基础。 1、Docker仓…...

开发人员容易被骗的原因有很多,涉及技术、安全意识、社会工程学以及工作环境等方面。以下是一些常见原因:
技术方面: 漏洞和补丁管理不当:未及时更新软件和依赖库可能存在已知漏洞,容易被攻击者利用。缺乏安全编码实践:没有遵循安全编码规范,容易引入SQL注入、跨站脚本(XSS)等安全漏洞。错误配置&…...

使用Python实现深度学习模型:自动编码器(Autoencoder)
自动编码器(Autoencoder)是一种无监督学习的神经网络模型,用于数据的降维和特征学习。它由编码器和解码器两个部分组成,通过将输入数据编码为低维表示,再从低维表示解码为原始数据来学习数据的特征表示。本教程将详细介…...

数据结构--树与二叉树--编程实现以孩子兄弟链表为存储结构递归求树的深度
数据结构–树与二叉树–编程实现以孩子兄弟链表为存储结构递归求树的深度 题目: 编程实现以孩子兄弟链表为存储结构,递归求树的深度。 ps:题目来源2025王道数据结构 思路: 从根结点开始 结点 N 的高度 max{N 孩子树的高度 1, N兄弟树的…...

Property xxx does not exist on type ‘Window typeof globalThis‘ 解决方法
问题现象 出现以上typescript警告,是因为代码使用了window的非标准属性,即原生 window 对象上不存在该属性。 解决办法 在项目 根目录 或者 src目录 下新建 xxx.d.ts 文件,然后进行对该 属性 进行声明即可。 注意:假如xxx.d.ts文…...

BOM..
区别:...

rust的版本问题,安装问题,下载问题
rust的版本、安装、下载问题 rust版本问题, 在使用rust的时候,应用rust的包,有时候包的使用和rust版本有关系。 error: failed to run custom build command for pear_codegen v0.1.2 Caused by: process didnt exit successfully: D:\rus…...

SDUT 链表9-------7-9 sdut-C语言实验-约瑟夫问题
7-9 sdut-C语言实验-约瑟夫问题 分数 20 全屏浏览 切换布局 作者 马新娟 单位 山东理工大学 n个人想玩残酷的死亡游戏,游戏规则如下: n个人进行编号,分别从1到n,排成一个圈,顺时针从1开始数到m,数到m的…...

Anthropic绘制出了大型语言模型的思维图:大型语言模型到底是如何工作
今天,我们报告了在理解人工智能模型的内部运作方面取得的重大进展。我们已经确定了如何在 Claude Sonnet(我们部署的大型语言模型之一)中表示数百万个概念。这是对现代生产级大型语言模型的首次详细了解。这种可解释性的发现将来可以帮助我们…...

网络工程师练习题
网络工程师 随着company1网站访问量的不断增加,公司为company1设立了多台服务器。下面是不同用户ping网站www.company1.com后返回的IP地址及响应状况,如图8.58所示。从图8.58可以看出,域名www.company1.com对应了多个IP地址,说明在图8.59所示的NDS属性中启用了循环功能。在…...

思科模拟器--03.RIP协议路由--24.5.17
1.首先,先创建两个个人电脑:PC0和PC1和三个路由器:R1,R2和R3. (诀窍:建议用文本框标注一下重要简短的内容; 目的:降低失误概率,提高成功率!) 第0步:(个人电脑的IP,子网掩码和默认网关配置) 接着,可以先将个人电脑的IP和网关先配置一下…...

当实时互动遇上新硬件:GIAC 全球互联网架构大会「新硬件」专题论坛
今年,被广泛预见为 AI 技术关键转折点的年份,生成式 AI 热度不断攀升,应用落地加速深化。在这个过程中,为了适应日益复杂的业务需求,背后的架构也将迎来新一轮的革新。 而在这场技术变革的浪潮中,GIAC 全球…...

赶紧收藏!2024 年最常见 20道 Redis面试题(三)
上一篇地址:赶紧收藏!2024 年最常见 20道 Redis面试题(二)-CSDN博客 五、Redis的持久化机制是什么? Redis 是一个高性能的键值存储系统,支持多种类型的数据结构,如字符串、哈希、列表、集合、…...

VMware 和 VirtualBox开机自启指定虚拟机详细教程
VMware上虚拟机随宿主机开机自启 1. 设置自动启动虚拟机 网上教程旧版的,界面和新版有所差异。17版本设置如下:VMware Workstation工作台 -> 文件 -> 配置自动启动虚拟机 -> 按顺序选择需要启动的虚拟机 VMWare17配置自动启动虚拟机提示&…...

note-网络是怎样连接的2 协议栈和网卡
助记提要 协议栈的结构协议栈创建连接的实际过程协议栈发送数据包的2个判断依据TCP确认数据收到的原理断开连接的过程路由表和ARPMAC地址的分配MAC模块的工作通过电信号读取数据的原理网卡和协议栈接收包的过程ICMPUDP协议的适用场景 2章 用电信号传输TCP/IP数据 探索协议栈和…...

ros学习之路径规划
一、全局路径规划中的地图 1、栅格地图(Grid Map)2、概率图(Cost Map)3、特征地图(Feature Map4、拓扑地图(Topological Map) 二、全局路径规划算法 1、Dijkstra 算法 2、最佳路径优先搜索算…...

Qt 顺序容器的详细介绍
一.顺序容器介绍 Qt 中的顺序容器包括 QVector、QList、QLinkedList 和 QStack。这些容器都提供了类似于 C STL 中的容器的功能,但是在 Qt 中提供了更多的功能和接口。 二.具体介绍 1.QVector QVector:是一个动态数组,可以在其末尾快速插入…...