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…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
