java将文件压缩打包后进行下载

今天受到一个需求,需要查出文件,然后将文件打包后下载。看了下项目里默认代码有压缩功能,以此修改了下,项目使用了hutool。项目是若依项目

定义zip的数据传输对象,ossId可以是文件表的id

@Data
public class SysOssZipDTO {/*** 关联OSS对象存储ID*/
})private String ossId;/*** 压缩包文件内文件夹路径,例子(main/java/com/haoyu/flowForm/domain/)*/private String zipItemFolderPath;}

sevice层

void downloadZip(List<SysOssZipDTO> zipDtoList, HttpServletResponse response, String taskId) throws IOException;

serviceImpl层

 @Overridepublic void downloadZip(List<SysOssZipDTO> zipDtoList, HttpServletResponse response, String taskId) throws IOException {if(zipDtoList == null || zipDtoList.isEmpty()){return;}ZipOutputStream zip = new ZipOutputStream(response.getOutputStream());final int bufferSize = 1024 * 1024 * 5;      // 5MBfinal byte[] buffer = new byte[bufferSize];long totalFileSize = 0;  // 文件总大小,估计值// 任务进度keyfinal String progressKey = OssConstant.SYS_OSS_PROGRESS+taskId;for (int i =0;i<zipDtoList.size();i++){// 设置进度(可选,用于压缩进度查询,就是将进度存入redis里,然后设计个接口供前端查询进度)/*SysOssProgressBO sysOssProgressBO = new SysOssProgressBO();sysOssProgressBO.setProgressNumber(Long.valueOf(i));sysOssProgressBO.setTotalProgress(Long.valueOf(zipDtoList.size()));RedisUtils.setCacheObject(progressKey,sysOssProgressBO, Duration.ofMinutes(10));*/SysOssZipDTO zipDTO = zipDtoList.get(i);if(StrUtil.isEmpty(zipDTO.getOssId())|| StrUtil.isEmpty(zipDTO.getZipItemFolderPath())){continue;}// 通过id查找文件信息SysOssVo sysOss = matchingUrl(SpringUtils.getAopProxy(this).getById(zipDTO.getOssId()));if (ObjectUtil.isNull(sysOss)) {continue;}// 通过文件信息获取文件流,本文用的Oss存储,如果用别的可以自己写,核心就是读取流OssClient storage = OssFactory.instance(sysOss.getService());try(InputStream inputStream = storage.getObjectContent(sysOss.getUrl());){// 这里使用文件名了,拼上前缀序号解决文件名重复问题zip.putNextEntry(new ZipEntry(zipDTO.getZipItemFolderPath() +"("+i+")"+sysOss.getOriginalName().replaceAll("/","_" )));int available = inputStream.available();totalFileSize += available;// 防Oom,可以用下面注释的也可以用hutool的/*int len = 0;while((len = inputStream.read(buffer)) != -1){zip.write(buffer, 0, len);}*/IoUtil.copy(inputStream, zip, bufferSize);zip.flush();zip.closeEntry();}catch (Exception e) {throw new ServiceException(e.getMessage());}}IoUtil.close(zip);// 生成zip文件// 由于前面已经开启了response的outputstream,所以这里不能调用reset()方法,否则会导致流异常关闭
//        response.reset();response.addHeader("Access-Control-Allow-Origin", "*");response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");response.addHeader("Content-Length", "" + totalFileSize);response.setContentType("application/octet-stream; charset=UTF-8");// 下载后的文件名可以由前端控制,此处由前端控制所以注释掉了/*if(StrUtil.isNotEmpty(zipName)){FileUtils.setAttachmentResponseHeader(response, zipName + ".zip");}*/}

其中的文件查询进度是可选的,可以让前端生成个任务id传到服务器
controller层

@GetMapping("/downloadZipByTrainId/{trainId}")public void downloadZipByTrainId(HttpServletResponse response, @PathVariable String trainId) throws IOException {// downloadZipByTrainId()方法里面调用downloadZip()this.iTrainMaterialsService.downloadZipByTrainId(trainId,response);}

前端代码,此处使用的是vue3

// 需要npm安装file-saver
import { saveAs } from 'file-saver'// 点击按钮触发事件
const clickDownLoadMaterials = async (row) =>{if(row.materialCount > 0 ){let res = await zip(`/train/trainMaterials/downloadZipByTrainId/${row.id}?projectName=${row.trainName}`,`${row.trainName}`)}else{ElMessage({message: '暂无材料可下载!',type: 'warning',})}
}const zip = (url, name) => {isShowLoadingDownLoadMaterial.value = trueurl = baseURL + urlaxios({method: 'get',url: url,responseType: 'blob',headers: {// 请求头token'Authorization': 'Bearer ' + getToken()}}).then(async (res) => {if(res.status == 200){isShowLoadingDownLoadMaterial.value = false}const isLogin = await blobValidate(res.data);if (isLogin) {const blob = new Blob([res.data], { type: 'application/zip' })saveAss(blob, name)} else {await this.printErrMsg(res.data);}})
}const saveAss = (text, name, opts) =>{saveAs(text, name, opts);
}// 验证是否为blob格式
export async function blobValidate(data) {try {const text = await data.text();JSON.parse(text);return false;} catch (error) {return true;}
}

优化
新增进度条,以及取消下载的功能

const clickDownLoadMaterials = async (row) =>{if(row.materialCount > 0 ){let taskId = await getZipTaskIdThenStartProgress();progressTaskId.value = taskId;// 使用axios.CancelToken取消请求,先赋值,后面点取消请求的时候会用到cancelSource.value = axios.CancelToken.source();let params = {trainId:row.id,taskId}let res = await  proxy.$download.zipThenCallback(`/train/trainMaterials/downloadZipByBo`,`${row.trainName}`,params,cancelSource.value.token,closeZipProgress)}else{ElMessage({message: '暂无材料可下载!',type: 'warning',})}
}/**取消下载压缩文件 */
async function  cancelZipTask (){// 中断进度条await closeZipProgress();cancelSource.value?.cancel("410");
}/**打开进度条,返回任务id */
const getZipTaskIdThenStartProgress =async () =>{showProgress.value = true;// 发接口获取任务idlet taskRes = await generateProgressTaskId();let taskId = taskRes.data;progressIntervalKey.value = setInterval(() => {// 根据任务id获取进度getProgressByTaskId(taskId).then(progressRes=>{let progressObj = progressRes.data;if(progressObj!= null && progressObj.totalProgress != 0){let progressPercentGet = Number.parseInt((progressObj.progressNumber *100) /progressObj.totalProgress)progressPercent.value = progressPercentGet;if(progressPercentGet==100){clearInterval(progressIntervalKey.value);}}});}, 1000);return taskId;
}
/** 关闭进度条 */
async function closeZipProgress(){// 关闭定时器clearInterval(progressIntervalKey.value);// 发接口删掉任务id,后端判断如果没有任务id则不再进行压缩处理await removeProgressByTaskId(progressTaskId.value);showProgress.value=false;progressPercent.value=0;
}// 下载压缩文件,带回调函数,可以中断zipThenCallback(url, name,params,cancelToken,callback){url = baseURL + urlaxios({method: 'get',url: url,responseType: 'blob',cancelToken,params,headers: {'Authorization': 'Bearer ' + getToken(),}}).then(async (res) => {if(res.status == 200 && callback != undefined){// 执行回调callback();}const isLogin = await blobValidate(res.data);if (isLogin) {const blob = new Blob([res.data], { type: 'application/zip' })saveAs(blob, name)} else {await this.printErrMsg(res.data);}}).catch( (error)=>{console.log(error)/*if(error.name!='"CanceledError"'){ElMessage.error(error);}*/})},

杂谈
这次的需求整理到了不少东西,一开始担心oom的问题,于是调试时改变了缓冲区大小,发现java使用的内存也会对应的发生变化,这是为什么呢?实际上原因很简单,final byte[] buffer = new byte[bufferSize];这个变量就是占用这么多字节的内存呀!

当缓冲区设成0的时候,会发现读的特别特别慢,这是为什么呢?这是由于读的大小太小了,java调取磁盘io的频率变多,而调取磁盘io是一个特别消耗资源的行为也特别慢,因此就慢了。设置合适的缓冲区大小,可以一次性读取对应大小的内容从而减少磁盘io的交互从而提升效率,但也要避免缓冲区过大导致变量占用内存过大而oom。

同时,java流的操作不是一次性将整个文件都读入内存中的,而是仅仅获取文件的句柄,同时读偏移量,不停的读,移动偏移量,直至文件读写结束(大佬原话)。而且系统加载文件到内存也是按块加载的,并不会一次性全读到内存中(如一个几十g的游戏,打开并不会占用几十g内存,而是用到的时候在加载,不足的时候就会根据算法按页或者按块替换)

最后,开了流一定要关流!response.getOutputStream()自带的流可以不用手动关servlet在请求结束的时候会自己关掉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/10230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ACM 的代码编码示例

写在最前面的 实践的顺序&#xff0c; 应该是先将基础的 数据结构题目类型给实现。 然后再开始尝试 实现对应类型的算法题目&#xff0c;如回溯算法&#xff0c; 贪心算法&#xff0c; 动态规划&#xff0c; 图论&#xff1b; 基础的数据结构&#xff0c; 推荐卡尔的&#xff…

Github 配置 SSH key

一、前言 问题描述 通过 ssh 的 url 使用 git 命令克隆 github 上私有项目出现 fatal: Could not read from remote repository. 本地仓库无法从远程仓库读取数据克隆失败 问题定位 一般是仓库 URL 错误或者权限问题这里排除 URL 错误&#xff0c;初步定位为访问权限问题 解决…

对比五款基于HMM和N-gram模型的开源语音识别工具

在语音识别技术的飞速发展中&#xff0c;开源工具以其灵活性和成本效益&#xff0c;为开发者和研究者提供了宝贵的资源。本文将深入对比五款基于HMM和N-gram模型的开源语音识别工具&#xff1a;CMUSphinx&#xff0c;Kaldi&#xff0c;HTK&#xff0c;Julius和ISIP&#xff0c;…

[Fork.dev] 增加用idea打开

用Fork做git管理工具时, 只有vscode 和sublime 等. 没有idea的. 今天研究了下如何操作.记录一下 点击 Action 文本框进行编辑 Path填写idea的执行位置. Parameters: 填写 ${repo:path} 代表用idea打开的文件夹路径为当前. 最终显示效果

端到端将重塑智驾?获10亿美金融资,解密英国AI独角兽Wayve

‍作者 |张马也 编辑 |德新 就在前两天&#xff0c;英国AI公司Wayve宣布获得新一轮10.5亿美元融资&#xff0c;投资方为软银、英伟达和现有投资人微软&#xff0c;可以说是顶级豪华阵容。 作为一家英国公司&#xff0c;Wayve这轮融资也创造了英国AI公司有史以来最大的单笔融资…

Linux——mysql运维篇

回顾基本语句&#xff1a; 数据定义语言 ( DDL ) 。这类语言用于定义和修改数据库的结构&#xff0c;包括创建、删除和修改数据库、表、视图和索引等对象。主要的语句关键字包括 CREATE 、 DROP 、 ALTER 、 RENAME 、 TRUNCATE 等。 create database 数据库 &…

CCC数字钥匙各版本关系

CCC钥匙规范版本关系 CCC数字钥匙架构Overview

2024精选7个wordpress模板

通用多用途wordpress模板 中国红WordPress模板&#xff0c;适合服务行业企业建站的通用多用途wordpress模板。 WordPress是一款使用PHP语言开发的开源内容管理系统(CMS)&#xff0c;最初设计用于个人博客&#xff0c;但随着时间的发展&#xff0c;它已经演化成为一个功能强大的…

String s = “hello“和String s = new String(“hello“)的区别

这涉及字符串加载到字符串常量池的原理&#xff1a;由于字符串字面量先在编译阶段加载到class常量池中&#xff0c;然后在类加载阶段从类常量池中加载到运行时常量池中&#xff0c;当字符串字面量被调用的时候&#xff0c;会检查字符串常量池中是否包含该字符串对象&#xff0c…

BS架构和CS架构的区别

BS架构&#xff08;Browser/Server Architecture&#xff09;和CS架构&#xff08;Client/Server Architecture&#xff09;是两种常见的软件架构模式&#xff0c;它们有以下主要区别&#xff1a; BS架构&#xff1a; BS架构是指基于浏览器的软件架构&#xff0c;其中应用程序的…

谷歌上架攻略:个人号20人连续14天封闭测试的详细流程及相关注意事项

众所周知&#xff0c;近年来&#xff0c;Google play为了确保应用质量和用户体验&#xff0c;对开发者提出不少新要求。其中&#xff0c;对于个人开发者的一项要求是&#xff0c;自2023年11月13日起&#xff0c;新注册的个人开发者账号在上架正式版应用前&#xff0c;必须经过2…

JNA POSTMESSAGE

JNA(Java Native Access)是一个Java库,它允许Java程序直接调用本地(native)共享库(如Windows的DLLs)中的函数,而无需使用Java Native Interface (JNI)。PostMessage 是Windows API中的一个函数,用于将消息发送到窗口的消息队列中。 如果你想使用JNA来调用Windows API…

项目数据接口国密支持

项目数据接口国密支持 说明 国密即国家密码局认定的国产密码算法&#xff0c;即商用密码。 国密主要有SM1&#xff0c;SM2&#xff0c; SM3&#xff0c; SM4。密钥长度和分组长度均为128位。 1、SM1为对称加密。其加密强度与AES(高级加密标准, Advanced Encryption Standard…

2024.5.9 关于 SpringCloud —— Nacos 的安装与配置

目录 Windos 安装步骤 docker 启动 nacos Windos 安装步骤 1&#xff09;点击下方链接&#xff0c;进入并访问 nacos 官网 Nacos官网 | Nacos 官方社区 | Nacos 下载 | Nacos 2&#xff09;按照下图箭头指示下载对应版本的压缩包 3&#xff09;此时我们将得到一个压缩包&…

内容自动化的进阶之路:Kompas.ai带你走进智能创作时代

在数字化媒体的浪潮中&#xff0c;内容创作和管理正变得越来越复杂和挑战性。为了应对这一挑战&#xff0c;内容自动化技术应运而生&#xff0c;它通过使用人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;算法&#xff0c;自动化内容创作的多个环节&a…

Sass详解:颠覆CSS开发的新时代

在前端开发领域&#xff0c;CSS是网页样式设计的重要组成部分&#xff0c;而Sass作为CSS的扩展语言&#xff0c;为开发者提供了更加强大和灵活的样式编写方式。本文将深入探讨Sass的各项特性、优势以及应用场景&#xff0c;帮助读者更好地理解和运用这一强大工具。 1. Sass是什…

引入外部依赖集成示例

1、package.json引入相关依赖 “dataview/engine”: “2.0.0-beta.7”, “dataview/plugin-tech”: “1.0.0-beta.1”, Engine为引擎&#xff0c;plugin-tech为Dataview中的科技风元件&#xff0c;若不引入会造成使用了科技风元件的页面无法正确渲染。引入后&#xff0c;重新执…

历史人物数字人如何成为地方文化推广大使?

如今&#xff0c;文旅产业业态已经从“打卡式旅游”逐渐走向“体验型旅游”转变。数字人可以为游客提供情绪价值、引起情感共鸣的文化体验。不少文旅品牌通过打造历史人物数字人&#xff0c;将城市民俗风情、非物质文化遗产等相结合&#xff0c;并且结合AI交互数字人的人机对话…

【C++】继承(上)(超详细,保证你学会)

什么是继承&#xff1f; 1.语法 1.1例子 通过这种方法&#xff0c;Student和Teacher这两个类就继承了Person的成员变量和成员函数&#xff0c;可以直接调用它们。 如图&#xff0c;如果成员变量和成员函数在基类中是公有的话就可以直接访问&#xff01;但如果是私有和保护的话就…

4种前端处理文本换行展示

序: 后端传递了一大段包含了回车符的文本内容,前端展示的时候所有文字堆在一起,没有换行展示。以下方法中content为后端返回的文本内容 方法一: “↵”符号在html中会识别别为\r,\n等转义字符,所以我们可以使用\r\n去替换(.replace(/(\r\n|\n|\r)/gm, ’< br />…