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


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

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

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

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

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

    业务场景如下:

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

    业务实现代码如下:

    1. import cn.hutool.core.io.FileUtil;
    2. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    3. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    4. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    5. import com.xiaomifeng.common.mapper.AttachmentMidMapper;
    6. import com.xiaomifeng.common.service.AttachmentMidService;
    7. import com.xiaomifeng.common.util.CaseFormatUtil;
    8. import com.xiaomifeng.entity.AttachmentMid;
    9. import com.xiaomifeng.minio.configuration.MinioConfig;
    10. import com.xiaomifeng.minio.entity.MinioFile;
    11. import com.xiaomifeng.minio.service.MinioFileService;
    12. import com.xiaomifeng.util.MinioClientUtils;
    13. import com.xiaomifeng.util.ZipUtil;
    14. import com.google.common.collect.Lists;
    15. import com.google.common.util.concurrent.ThreadFactoryBuilder;
    16. import lombok.Setter;
    17. import org.apache.commons.collections4.CollectionUtils;
    18. import org.apache.commons.collections4.MapUtils;
    19. import org.apache.commons.lang3.RandomStringUtils;
    20. import org.apache.commons.lang3.StringUtils;
    21. import org.apache.commons.lang3.SystemUtils;
    22. import org.springframework.beans.factory.annotation.Autowired;
    23. import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService;
    24. import org.springframework.stereotype.Service;
    25. import javax.servlet.http.HttpServletResponse;
    26. import java.io.File;
    27. import java.io.IOException;
    28. import java.io.InputStream;
    29. import java.io.OutputStream;
    30. import java.util.ArrayList;
    31. import java.util.List;
    32. import java.util.Map;
    33. import java.util.concurrent.*;
    34. /**
    35. * @author xiaomifeng1010
    36. * @version 1.0
    37. * @date:
    38. * @Description
    39. */
    40. @Service
    41. @Setter(onMethod_ = @Autowired)
    42. public class AttachmentMidServiceImpl extends ServiceImpl implements AttachmentMidService {
    43. private MinioClientUtils minioClientUtils;
    44. private MinioConfig minioConfig;
    45. private MinioFileService minioFileService;
    46. @Override
    47. public void export2Zip(List fileIds, HttpServletResponse response) throws IOException {
    48. if (CollectionUtils.isNotEmpty(fileIds)) {
    49. List fileList = new ArrayList<>();
    50. ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("zip-file-%d").build();
    51. ExecutorService threadPoolExecutor = new ThreadPoolExecutor(12, 20, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
    52. threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
    53. threadPoolExecutor = new DelegatingSecurityContextExecutorService(threadPoolExecutor);
    54. CompletionService completionService = new ExecutorCompletionService<>(threadPoolExecutor);
    55. List> callableList = Lists.newArrayList();
    56. fileIds.forEach(fileId -> {
    57. MinioFile minioFile = minioFileService.getById(fileId);
    58. if (minioFile != null) {
    59. callableList.add(() -> {
    60. String fileName = minioFile.getFileName();
    61. String originalFileName = minioFile.getOriginalFileName();
    62. originalFileName = StringUtils.substringBeforeLast(originalFileName, ".");
    63. String fileExtName = minioFile.getFileExtName();
    64. Integer access = minioFile.getAccess();
    65. String bucketName = minioConfig.getBucketName();
    66. if (access == 2) {
    67. bucketName = minioConfig.getBucketNamePrivate();
    68. }
    69. try {
    70. InputStream inputStream = minioClientUtils.getObject(bucketName, fileName);
    71. if (inputStream != null) {
    72. File javaIoTmpDir = SystemUtils.getJavaIoTmpDir();
    73. String finalFilePath = javaIoTmpDir.getAbsolutePath() + File.separator + originalFileName + "." + fileExtName;
    74. File newFile = FileUtil.newFile(finalFilePath);
    75. if (newFile.exists()) {
    76. originalFileName = originalFileName + RandomStringUtils.randomNumeric(5);
    77. }
    78. if (StringUtils.length(originalFileName) < 3) {
    79. originalFileName = originalFileName + RandomStringUtils.randomNumeric(3);
    80. }
    81. File tempFile = FileUtil.createTempFile(originalFileName, "." + fileExtName, javaIoTmpDir, true);
    82. tempFile = FileUtil.writeFromStream(inputStream, tempFile);
    83. return tempFile;
    84. }
    85. } catch (Exception e) {
    86. log.error("从minio获取文件失败:", e);
    87. }
    88. return null;
    89. });
    90. }
    91. });
    92. if (CollectionUtils.isNotEmpty(callableList)) {
    93. for (Callable fileCallable : callableList) {
    94. completionService.submit(fileCallable);
    95. }
    96. for (int i = 0; i < callableList.size(); i++) {
    97. try {
    98. File temFile = completionService.take().get();
    99. if (temFile != null) {
    100. fileList.add(temFile);
    101. }
    102. } catch (InterruptedException e) {
    103. Thread.currentThread().interrupt();
    104. } catch (ExecutionException e) {
    105. log.error("completionService获取文件对象失败:{}", e);
    106. }
    107. }
    108. }
    109. threadPoolExecutor.shutdown();
    110. String zipFileName = "附件管理";
    111. File tempZipFile = FileUtil.createTempFile(zipFileName, ".zip", true);
    112. response.setContentType("application/zip");
    113. response.setHeader("Location", tempZipFile.getName());
    114. response.setHeader("Content-Disposition", "attachment; filename=" + tempZipFile.getName());
    115. OutputStream outputStream = response.getOutputStream();
    116. ZipUtil.filesToZip(fileList, outputStream);
    117. tempZipFile.delete();
    118. outputStream.flush();
    119. outputStream.close();
    120. // 清理临时文件
    121. fileList.forEach(File::delete);
    122. }
    123. }
    124. }

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

    还有一个ziputil工具类

    1. import java.io.File;
    2. import java.io.FileInputStream;
    3. import java.io.FileNotFoundException;
    4. import java.io.FileOutputStream;
    5. import java.io.IOException;
    6. import java.io.InputStream;
    7. import java.io.OutputStream;
    8. import java.nio.charset.Charset;
    9. import java.util.ArrayList;
    10. import java.util.Enumeration;
    11. import java.util.Iterator;
    12. import java.util.List;
    13. import java.util.zip.ZipEntry;
    14. import java.util.zip.ZipFile;
    15. import java.util.zip.ZipOutputStream;
    16. import org.slf4j.Logger;
    17. import org.slf4j.LoggerFactory;
    18. @Slf4j
    19. public class ZipUtil {
    20. private static String destFileName;
    21. public static List unZipFiles(File zipFile, String descDir) throws IOException {
    22. List fileNames = new ArrayList();
    23. ZipFile zip = new ZipFile(zipFile, Charset.forName("GBK"));
    24. String name = zip.getName().substring(zip.getName().lastIndexOf(92) + 1, zip.getName().lastIndexOf(46));
    25. File pathFile = new File(descDir + name);
    26. if (!pathFile.exists()) {
    27. pathFile.mkdirs();
    28. }
    29. String outPath = "";
    30. Enumeration entries = zip.entries();
    31. while(true) {
    32. InputStream in;
    33. do {
    34. if (!entries.hasMoreElements()) {
    35. pathFile.delete();
    36. return fileNames;
    37. }
    38. ZipEntry entry = (ZipEntry)entries.nextElement();
    39. String zipEntryName = entry.getName();
    40. fileNames.add(zipEntryName);
    41. in = zip.getInputStream(entry);
    42. outPath = (descDir + name + "/" + zipEntryName).replaceAll("\\*", "/");
    43. File file = new File(outPath.substring(0, outPath.lastIndexOf(47)));
    44. if (!file.exists()) {
    45. file.mkdirs();
    46. }
    47. } while((new File(outPath)).isDirectory());
    48. FileOutputStream out = new FileOutputStream(outPath);
    49. byte[] buf1= new byte[1024];
    50. int len;
    51. while((len = in.read(buf1)) > 0) {
    52. out.write(buf1, 0, len);
    53. }
    54. in.close();
    55. out.close();
    56. }
    57. }
    58. public static void docToZip(String srcDir, String targetFile, boolean KeepDirStructure) throws RuntimeException, FileNotFoundException {
    59. long start = System.currentTimeMillis();
    60. ZipOutputStream zos = null;
    61. FileOutputStream out = new FileOutputStream(new File(targetFile));
    62. try {
    63. zos = new ZipOutputStream(out);
    64. File sourceFile = new File(srcDir);
    65. destFileName = targetFile.substring(targetFile.lastIndexOf(File.separator) + 1, targetFile.length());
    66. System.out.println(destFileName);
    67. compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
    68. long end = System.currentTimeMillis();
    69. System.out.println("压缩完成,耗时:" + (end - start) + " ms");
    70. } catch (Exception var20) {
    71. throw new RuntimeException("zip error from ZipUtils", var20);
    72. } finally {
    73. if (zos != null) {
    74. try {
    75. zos.close();
    76. } catch (IOException var19) {
    77. log.error(var19.getMessage(), var19);
    78. }
    79. }
    80. if (out != null) {
    81. try {
    82. out.close();
    83. } catch (IOException var18) {
    84. log.error(var18.getMessage(), var18);
    85. }
    86. }
    87. }
    88. }
    89. public static void filesToZip(List srcFiles, OutputStream out) throws RuntimeException {
    90. long start = System.currentTimeMillis();
    91. ZipOutputStream zos = null;
    92. short BUFFER_SIZE = 2048;
    93. try {
    94. zos = new ZipOutputStream(out);
    95. Iterator var6 = srcFiles.iterator();
    96. while(var6.hasNext()) {
    97. File srcFile = (File)var6.next();
    98. byte[] buf = new byte[BUFFER_SIZE];
    99. zos.putNextEntry(new ZipEntry(srcFile.getName()));
    100. FileInputStream in = new FileInputStream(srcFile);
    101. int len;
    102. while((len = in.read(buf)) != -1) {
    103. zos.write(buf, 0, len);
    104. }
    105. zos.closeEntry();
    106. in.close();
    107. }
    108. long end = System.currentTimeMillis();
    109. System.out.println("压缩完成,耗时:" + (end - start) + " ms");
    110. } catch (Exception var18) {
    111. throw new RuntimeException("zip error from ZipUtils", var18);
    112. } finally {
    113. if (zos != null) {
    114. try {
    115. zos.close();
    116. } catch (IOException var17) {
    117. log.error(var17.getMessage(), var17);
    118. }
    119. }
    120. }
    121. }
    122. private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean KeepDirStructure) throws Exception {
    123. int BUFFER_SIZE = 2048;
    124. byte[] buf = new byte[BUFFER_SIZE];
    125. System.out.println(name);
    126. if (sourceFile.isFile()) {
    127. if (!destFileName.equals(sourceFile.getName())) {
    128. zos.putNextEntry(new ZipEntry(name));
    129. FileInputStream in = new FileInputStream(sourceFile);
    130. int len;
    131. while((len = in.read(buf)) != -1) {
    132. zos.write(buf, 0, len);
    133. }
    134. zos.closeEntry();
    135. in.close();
    136. }
    137. } else {
    138. File[] listFiles = sourceFile.listFiles();
    139. if (listFiles != null && listFiles.length != 0) {
    140. File[] var12 = listFiles;
    141. int var8 = listFiles.length;
    142. for(int var9 = 0; var9 < var8; ++var9) {
    143. File file = var12[var9];
    144. if (KeepDirStructure) {
    145. compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
    146. } else {
    147. compress(file, zos, file.getName(), KeepDirStructure);
    148. }
    149. }
    150. } else if (KeepDirStructure) {
    151. zos.putNextEntry(new ZipEntry(name + "/"));
    152. zos.closeEntry();
    153. }
    154. }
    155. }
    156. }

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

  • 相关阅读:
    好消息,完整版的jwt鉴权机制来了呦
    【全志T113-S3_100ask】5-编写按键驱动(input子系统+内核按键驱动)
    增量与存量,谈投资与市场的规模与人类的未来
    什么是哈希表?如何使用哈希表进行数据存储和查找?
    计算机三级数据库后台编程技术练习题(一)
    web网页设计期末课程大作业:旅游网页主题网站设计——中国风的温泉酒店预订网(13页)HTML+CSS+JavaScript
    vue 3 第三十五章:集成 tailwind Css
    python:flatten()参数详解
    网络安全人才发展史
    crypto加密
  • 原文地址:https://blog.csdn.net/u011174699/article/details/130911870