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…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...

企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...

算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...