使用CompletionService进行多个文件打包为zip下载

news2024/11/24 14:02:14

最近没怎么写博客了,因为前段时间在准备软考复习,昨天考完试,现在总算轻松一点了,有更多自由的时间了,总结一下JUC包下的一些并发工具类,主要是从使用场景入手。

CompletionService可以用于实现任务并行化,提高任务处理的效率。以下是CompletionService在项目中的应用场景:

  1. 多个任务需要并行执行,但是它们的执行时间不同,需要优先处理先执行完成的任务。
  2. 需要获取多个任务的执行结果,但是不关心任务的执行顺序。
  3. 处理任务的线程池数量有限,需要充分利用线程池资源,提高任务处理的效率。

举例来说,假设有一个需要下载多个文件的任务,使用CompletionService可以将文件下载任务分配给多个线程并行执行,而不需要等待所有文件都下载完成才开始处理文件。同时,由于每个线程的下载时间可能不同,CompletionService可以优先获取下载完成的文件,并在下载完成后立即处理文件,提高任务处理的效率。

以下就是使用CompletionService从minio文件服务器下载文件,并进行打包压缩的使用场景;因为我们对文件进行打包压缩,并不关心下载的多个文件的下载顺序,哪个文件先下载完,就先处理哪个文件,然后最后统一放入到一个文件夹进行打包压缩,提供下载

业务场景如下:

需求是选中多个附件,然后批量下载,下载下来后是一个zip文件,附件使用的是minio文件存储服务

业务实现代码如下:

 

import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dcboot.module.common.dto.request.ContractDocumentPageQueryRequestDTO;
import com.dcboot.module.common.mapper.ContractAttachmentMidMapper;
import com.dcboot.module.common.service.ContractAttachmentMidService;
import com.dcboot.module.common.util.CaseFormatUtil;
import com.dcboot.module.entity.ContractAttachmentMid;
import com.dcboot.module.minio.configuration.MinioConfig;
import com.dcboot.module.minio.entity.MinioFile;
import com.dcboot.module.minio.service.MinioFileService;
import com.dcboot.module.util.MinioClientUtils;
import com.dcboot.module.util.ZipUtil;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author xiaomifeng1010
 * @version 1.0
 * @date: 2023/4/24 16:46
 * @Description
 */
@Service
@Setter(onMethod_ = @Autowired)
public class ContractAttachmentMidServiceImpl extends ServiceImpl<ContractAttachmentMidMapper, ContractAttachmentMid> implements ContractAttachmentMidService {

    private MinioClientUtils minioClientUtils;

    private MinioConfig minioConfig;

    private MinioFileService minioFileService;


    @Override
    public void export2Zip(List<Long> fileIds, HttpServletResponse response) throws IOException {
        if (CollectionUtils.isNotEmpty(fileIds)) {
            List<File> fileList = new ArrayList<>();
            ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("zip-file-%d").build();
            ExecutorService threadPoolExecutor = new ThreadPoolExecutor(12, 20, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
                    threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
            threadPoolExecutor = new DelegatingSecurityContextExecutorService(threadPoolExecutor);
            CompletionService<File> completionService = new ExecutorCompletionService<>(threadPoolExecutor);
            List<Callable<File>> callableList = Lists.newArrayList();
            fileIds.forEach(fileId -> {
                MinioFile minioFile = minioFileService.getById(fileId);
                if (minioFile != null) {
                    callableList.add(() -> {
                        String fileName = minioFile.getFileName();
                        String originalFileName = minioFile.getOriginalFileName();
                        originalFileName = StringUtils.substringBeforeLast(originalFileName, ".");
                        String fileExtName = minioFile.getFileExtName();
                        Integer access = minioFile.getAccess();
                        String bucketName = minioConfig.getBucketName();
//                   值为1 默认公有桶, 2 私有桶
                        if (access == 2) {
                            bucketName = minioConfig.getBucketNamePrivate();
                        }
                        try {
                            InputStream inputStream = minioClientUtils.getObject(bucketName, fileName);
                            if (inputStream != null) {
                                File javaIoTmpDir = SystemUtils.getJavaIoTmpDir();
                                String finalFilePath = javaIoTmpDir.getAbsolutePath() + File.separator + originalFileName + "." + fileExtName;
                                File newFile = FileUtil.newFile(finalFilePath);
                                if (newFile.exists()) {
                                    originalFileName = originalFileName + RandomStringUtils.randomNumeric(5);
                                }
                                if (StringUtils.length(originalFileName) < 3) {
                                    originalFileName = originalFileName + RandomStringUtils.randomNumeric(3);
                                }
                                File tempFile = FileUtil.createTempFile(originalFileName, "." + fileExtName, javaIoTmpDir, true);
                                tempFile = FileUtil.writeFromStream(inputStream, tempFile);
                                return tempFile;
                            }
                        } catch (Exception e) {
                            log.error("从minio获取文件失败:", e);
                        }
                        return null;
                    });


                }
            });
            if (CollectionUtils.isNotEmpty(callableList)) {
                for (Callable<File> fileCallable : callableList) {
                    completionService.submit(fileCallable);

                }

                for (int i = 0; i < callableList.size(); i++) {

                    try {
                        File temFile = completionService.take().get();
                        if (temFile != null) {
                            fileList.add(temFile);
                        }

                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    } catch (ExecutionException e) {
                        log.error("completionService获取文件对象失败:{}", e);
                    }

                }

            }
            threadPoolExecutor.shutdown();
            String zipFileName = "合同管理";
            File tempZipFile = FileUtil.createTempFile(zipFileName, ".zip", true);

            response.setContentType("application/zip");
            response.setHeader("Location", tempZipFile.getName());
            response.setHeader("Content-Disposition", "attachment; filename=" + tempZipFile.getName());
            OutputStream outputStream = response.getOutputStream();
            ZipUtil.filesToZip(fileList, outputStream);
            tempZipFile.delete();
            outputStream.flush();
            outputStream.close();
//            清理临时文件
            fileList.forEach(File::delete);

        }

    }



}

minio工具类,可以参照我的另一篇安装使用minio的博文中,有非常详细的用法MinIO文件服务器,从安装到使用

还有一个ziputil工具类

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ZipUtil {
    private static final Logger log = LoggerFactory.getLogger(ZipUtil.class);
    private static String destFileName;

    public ZipUtil() {
    }

    public static List<String> unZipFiles(File zipFile, String descDir) throws IOException {
        List<String> fileNames = new ArrayList();
        ZipFile zip = new ZipFile(zipFile, Charset.forName("GBK"));
        String name = zip.getName().substring(zip.getName().lastIndexOf(92) + 1, zip.getName().lastIndexOf(46));
        File pathFile = new File(descDir + name);
        if (!pathFile.exists()) {
            pathFile.mkdirs();
        }

        String outPath = "";
        Enumeration entries = zip.entries();

        while(true) {
            InputStream in;
            do {
                if (!entries.hasMoreElements()) {
                    pathFile.delete();
                    return fileNames;
                }

                ZipEntry entry = (ZipEntry)entries.nextElement();
                String zipEntryName = entry.getName();
                fileNames.add(zipEntryName);
                in = zip.getInputStream(entry);
                outPath = (descDir + name + "/" + zipEntryName).replaceAll("\\*", "/");
                File file = new File(outPath.substring(0, outPath.lastIndexOf(47)));
                if (!file.exists()) {
                    file.mkdirs();
                }
            } while((new File(outPath)).isDirectory());

            FileOutputStream out = new FileOutputStream(outPath);
            byte[] buf1 = new byte[1024];

            int len;
            while((len = in.read(buf1)) > 0) {
                out.write(buf1, 0, len);
            }

            in.close();
            out.close();
        }
    }

    public static void docToZip(String srcDir, String targetFile, boolean KeepDirStructure) throws RuntimeException, FileNotFoundException {
        long start = System.currentTimeMillis();
        ZipOutputStream zos = null;
        FileOutputStream out = new FileOutputStream(new File(targetFile));

        try {
            zos = new ZipOutputStream(out);
            File sourceFile = new File(srcDir);
            destFileName = targetFile.substring(targetFile.lastIndexOf(File.separator) + 1, targetFile.length());
            System.out.println(destFileName);
            compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
            long end = System.currentTimeMillis();
            System.out.println("压缩完成,耗时:" + (end - start) + " ms");
        } catch (Exception var20) {
            throw new RuntimeException("zip error from ZipUtils", var20);
        } finally {
            if (zos != null) {
                try {
                    zos.close();
                } catch (IOException var19) {
                    log.error(var19.getMessage(), var19);
                }
            }

            if (out != null) {
                try {
                    out.close();
                } catch (IOException var18) {
                    log.error(var18.getMessage(), var18);
                }
            }

        }

    }

    public static void filesToZip(List<File> srcFiles, OutputStream out) throws RuntimeException {
        long start = System.currentTimeMillis();
        ZipOutputStream zos = null;
        short BUFFER_SIZE = 2048;

        try {
            zos = new ZipOutputStream(out);
            Iterator var6 = srcFiles.iterator();

            while(var6.hasNext()) {
                File srcFile = (File)var6.next();
                byte[] buf = new byte[BUFFER_SIZE];
                zos.putNextEntry(new ZipEntry(srcFile.getName()));
                FileInputStream in = new FileInputStream(srcFile);

                int len;
                while((len = in.read(buf)) != -1) {
                    zos.write(buf, 0, len);
                }

                zos.closeEntry();
                in.close();
            }

            long end = System.currentTimeMillis();
            System.out.println("压缩完成,耗时:" + (end - start) + " ms");
        } catch (Exception var18) {
            throw new RuntimeException("zip error from ZipUtils", var18);
        } finally {
            if (zos != null) {
                try {
                    zos.close();
                } catch (IOException var17) {
                    log.error(var17.getMessage(), var17);
                }
            }

        }
    }

    private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean KeepDirStructure) throws Exception {
        int BUFFER_SIZE = 2048;
        byte[] buf = new byte[BUFFER_SIZE];
        System.out.println(name);
        if (sourceFile.isFile()) {
            if (!destFileName.equals(sourceFile.getName())) {
                zos.putNextEntry(new ZipEntry(name));
                FileInputStream in = new FileInputStream(sourceFile);

                int len;
                while((len = in.read(buf)) != -1) {
                    zos.write(buf, 0, len);
                }

                zos.closeEntry();
                in.close();
            }
        } else {
            File[] listFiles = sourceFile.listFiles();
            if (listFiles != null && listFiles.length != 0) {
                File[] var12 = listFiles;
                int var8 = listFiles.length;

                for(int var9 = 0; var9 < var8; ++var9) {
                    File file = var12[var9];
                    if (KeepDirStructure) {
                        compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
                    } else {
                        compress(file, zos, file.getName(), KeepDirStructure);
                    }
                }
            } else if (KeepDirStructure) {
                zos.putNextEntry(new ZipEntry(name + "/"));
                zos.closeEntry();
            }
        }

    }
}


CompletionService调用线程池异步从minio下载文件,下载好的文件放到List集合,然后使用ziputil进行 压缩,有个注意事项,就是在创建临时文件的时候,文件名的字符长度不能小于3,否则会抛出异常。所以在代码中有个文件名字符长度的判断。

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

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

相关文章

数据结构入门(C语言版)图的概念和功能函数实现

图的概念和功能函数实现 前言1.图的定义和术语1.1 定义1.2 常用术语 2.图的存储结构2.1 图的数组(邻接矩阵)存储表示2.2 图的邻接表存储表示 3.图的遍历3.1 深度优先搜索3.2 广度优先搜索3.3 示例 4.连通网的最小生成树4.1 克鲁斯卡尔(Kruskal)算法4.2 普里姆(Prim)算法 5.图的…

企业云盘软件世界排行榜:提升企业文件管理效率的最佳工具

企业云盘是一种面向企业用户提供的在线存储和文件分享服务&#xff0c;它能够帮助企业实现数据备份、文件共享、办公协同等多种功能。通过企业云盘&#xff0c;企业可以将数据集中管理&#xff0c;避免了传统存储方式的不便和风险。 企业云盘的优势 1. 集中管理&#xff1a;企业…

【js】对象属性的拦截和Proxy代理与Reflect映射的用法与区别

✍️ 作者简介: 前端新手学习中。 &#x1f482; 作者主页: 作者主页查看更多前端教学 &#x1f393; 专栏分享&#xff1a;css重难点教学 Node.js教学 从头开始学习 ajax学习 文章目录 对象属性的拦截介绍SetGet 对象的拦截介绍使用对象属性拦截和对象拦截区别练习题 映射…

【Python实战】Python采集地震信息

前言 昨天,我们这里发生了地震,不过,没有太大的问题,我就想着能不能把近几年发生地震的信息,收集下来,我们发现中国地震台网的官方微博会分布近几年发生地震的信息。我们可以直接在这里获取。 环境使用 python 3.9pycharm模块使用 requests模块介绍 requests requ…

每日一博 - 浅析事务隔离级别 MVCC机制

文章目录 DB四个隔离级别MVCC如何工作的 &#xff1f;小结 DB四个隔离级别 数据库隔离允许事务执行&#xff0c;就像没有其他并发运行的事务一样。 下面的图说明了四个隔离级别。 Serializalble: 这是最高的隔离级别。并发交易保证按顺序执行。Repeatable Read: 事务开始时读…

Flutter 笔记 | Flutter 事件与通知

原始指针事件处理 命中测试 在移动端&#xff0c;各个平台或UI系统的原始指针事件模型基本都是一致&#xff0c;即&#xff1a;一次完整的事件分为三个阶段&#xff1a;手指按下、手指移动、和手指抬起&#xff0c;而更高级别的手势&#xff08;如点击、双击、拖动等&#xf…

重学迭代器和生成器

重学迭代器和生成器 之前在 JavaScript 高级程序设计第 7 章 迭代器和生成器 学习笔记 其实包含过 iterator 和 generator 的学习笔记&#xff0c;不过依旧温故而知新&#xff0c;有了一些实际上手的经验后重新再回滚一边会有比较深刻的理解&#xff0c;而不是只是 cv 书上的内…

硬件基础常识【3】--详细说说贴片电容器,可能有你不知道的

目录 贴片电容介绍MLCC的制作过程电容失效的头号大敌电容失效的最主要原因电容的容值、耐压值与封装尺寸的关系 电容串并联串联并联 电容的等效电路选取电容的建议总结 贴片电容介绍 贴片电容相信干电子技术活的基本都使用过&#xff0c;他的全称为&#xff1a;多层片式陶瓷电…

基础学习——读txt数据、字符串转list或数组、画PR曲线、画Loss曲线

文章目录 字符串转数组字符串中的数组转列表转整数列表 读数据&#xff0c;然后画PR曲线读取txt数据关于PR曲线代码 读数据画Loss曲线读txt数据代码 字符串转数组 .split() 是Python中的一个字符串方法&#xff0c;它可以将一个字符串按照指定的分隔符分割成多个子字符串&…

智能工厂 | 联合汽车电子有限公司汽车驱动科技上海智能工厂

智能制造是我国加快建设制造强国的主攻方向&#xff0c;是上海城市数字化转型的重要抓手。智能工厂是推动智能制造的切入点和突破口&#xff0c;是制造业数字化转型的重要载体&#xff0c;以智能工厂为载体布局新赛道、触发新动能、带动新终端&#xff0c;从而实现制造业高质量…

scanf读取字符数组的注意点

起因&#xff1a;scanf的%c格式可以读取空格和回车 读取中间无空格隔开的二维字符数组时 #include<bits/stdc.h> using namespace std; char mp[10][10]; signed main() {for(int i1;i<3;i){for(int j1;j<3;j){scanf("%c",&mp[i][j]);}getchar();/…

Zookeeper集群 + Kafka集群

Zookeeper 概述 Zookeeper 定义 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目。 Zookeeper 工作机制 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理大家都关心的…

液体压强、浮力与密度分析

如图所示&#xff0c;将甲、乙两个容器放在水平桌面上&#xff0c;甲、乙两容器的底面积为S甲&#xff0c;S乙&#xff0c;甲容器中盛有密度为p1的液体&#xff0c;乙容器中盛有密度为p2的液体。现将体积相等的A、B两个物体分别放入甲、乙两容器后&#xff0c;物体A悬浮&#x…

学习TypeScript快速入门

&#x1f341; 作者主页&#xff1a;&#x1f496;仙女不下凡&#x1f496; &#x1f341; 前言介绍&#xff1a;以下&#x1f447; 内容是根据“阿宝哥”的教材自学总结&#xff0c;定期更新欢迎持续关注&#xff01; &#x1f341; 学习前提&#xff1a;学习该文章之前需要…

数据在内存中的存储(1)——整形

目录 1、数据类型介绍 1.1、类型的基本归类 整形家族 浮点数家族 指针类型 空类型 构造类型 2、整形在内存中的存储 2.1、原码、反码、补码 2.2、大小端介绍 2.3、有符号与无符号 2.4、练习 例一 例二 例三 例四 例五 1、数据类型介绍 我们先来简单了解一下我们前面所学的基…

【腾讯云Finops Crane集训营】降本增效之 Crane 初体验

1. Crane 初识2. Crane 如何进行成本优化&#xff1f;3. Crane 快速上手体验3.1 安装 Prometheus 和 Grafana3.2 安装 Crane 和 Fadvisor3.3 验证安装是否成功3.4 访问 Dashboard 4. Crane 初体验 - 总结&建议5. 关于腾讯云 Finops Crane 集训营 最近有幸参加了腾讯云 Fino…

新星计划【Java微服务+云原生】赛道开启!

前排提醒&#xff1a;这里是新星计划2023【微服务云原生】学习方向的报名入口&#xff0c;一经报名&#xff0c;不可更换。 ↓↓↓报名入口&#xff1a;新星计划2023【微服务云原生】学习方向报名入口&#xff01;-CSDN社区 一、关于本学习方向导师 博客昵称&#xff1a;鹤冲…

opengl灯光基础:2.1 光照基础知识

光照&#xff1a; 光照以不同的方式影响着我们看到的世界&#xff0c;有时甚至是以很戏剧化的方式。当手电筒照射在物体上时&#xff0c;我们希望物体朝向光线的一侧看起来更亮。我们所居住的地球上的点&#xff0c;在中午朝向太阳时候被照得很亮&#xff0c;但随着地球的自转…

【使用VS开发的第一个QT项目——实现相机功能(包括QT下载、配置、摄像头程序)】

使用VS开发的第一个QT项目 一、QT(WIN10)安装1.首先下载QT(VS有对应的QT)2.安装QT 二、将QT加载到VS中三、QT设置1.在VS"Qt Vs Tools"→"QT Versions"中添加"msvc2017_64"qmake的路径2.在"General"→"QT Designer"中将"…

克里金插值(Kriging)在MATLAB中的实现【优化】

该部分是基于克里金插值&#xff08;Kriging&#xff09;在MATLAB中的实现&#xff08;克里金工具箱&#xff09;&#xff0c;由于在运行过程中有部分问题&#xff0c;基于此做的一些理解优化。 工具箱的下载见上面的链接&#xff0c;其提供了工具箱。 clc clearload(data_kr…