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…...
能耗管理系统是什么?主要有哪几种关键功能和应用场景?
能耗管理系统的基本功能解析 具备多种核心功能,为了实时监测能源的使用状况,提升能效并降低相关成本。其中、在线计量功能让企业可以实时掌握用电情况,进而进行针对性的管理。超功率告警能够及时发现异常能耗,防止无意中的过度浪费…...
边缘设备LLM推理性能与热管理对比研究
1. 边缘设备LLM推理性能与热管理对比研究概述在人工智能技术快速发展的今天,大型语言模型(LLM)的边缘部署已成为行业热点。将LLM直接部署在终端设备上,能够实现离线运行、降低延迟并保护用户隐私,这对需要持续响应用户查询的智能助手类应用尤…...
AI编程助手如何对抗能力错觉?agentic-learning技能包实战指南
1. 项目概述:一个能让你真正学会编程的AI伙伴 如果你用过Cursor、Claude Code或者GitHub Copilot这类AI编程助手,大概率有过这样的体验:你抛出一个问题,它瞬间给你一段完美的代码。你复制粘贴,程序跑起来了࿰…...
Decantr:AI生成UI的设计智能治理工具,解决前端一致性难题
1. 项目概述与核心价值如果你和我一样,在过去一年里深度使用过 Claude Code、Cursor 或者 Windsurf 这类 AI 编程助手来构建前端界面,那你一定经历过那种“甜蜜的烦恼”:助手能快速生成一个漂亮的登录页面,但当你让它接着做用户仪…...
Python 爬虫高级实战:网盘资源信息批量爬虫开发
前言 在互联网资源分发场景中,网盘已成为文档、教程、软件、影视、学习资料等资源的核心存储与分发载体。海量公开网盘资源分散在各类资源站点、论坛、分享页面中,依靠人工逐条检索、整理链接效率极低,且难以实现批量汇总、分类归档与失效链接筛查。依托 Python 开发网盘资…...
AI赋能非洲医疗:疾病预测模型落地实战与挑战解析
1. 项目概述:当AI遇见非洲医疗的十字路口“AI赋能非洲医疗”,这个标题背后,远不止是一个技术应用的故事,它更像是一场在资源、文化与技术之间寻找平衡点的深刻实践。作为一名长期关注技术落地与全球健康议题的从业者,我…...
复制粘贴:那些年芯片工程师的“黑科技“
这是一个真实的笑话,也是一段不算久远的历史。大模型出现之前,芯片工程师提升效率的核心手段,说出来让人笑中带泪:CtrlC,然后CtrlV。那时候的工程师是认真在维护一个"代码片段收藏夹"的。一个写了三年RTL的工…...
无人机无人车空地协同巡逻任务分配与路径规划【附程序】
✨ 本团队擅长数据搜集与处理、建模仿真、程序设计、仿真代码、EI、SCI写作与指导,毕业论文、期刊论文经验交流。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,可以私信,或者点击《获取方式》 (1)考虑实际路网约束与动态汇…...
CANN/metadef:Tensor创建API文档
CreateFollowing 【免费下载链接】metadef Ascend Metadata Definition 项目地址: https://gitcode.com/cann/metadef 函数功能 创建一个指定数据类型以及大小的Tensor,其数据在TensorV2对象后连续排布。 函数原型 传入元素个数和数据类型,创建…...
opencode-mcp:让AI编码助手学会“摇人”的本地自动化工具
1. 项目概述:当你的AI助手学会“摇人” 如果你和我一样,每天都在和Claude、Cursor这类AI编码助手打交道,那你肯定遇到过这个场景:你让它“重构一下这个模块”,它噼里啪啦给你生成了一堆代码,然后你发现它没…...
