hutool压缩问题

76 阅读3分钟

前言

  交易关联合同订单,需要把相关附件压缩打包分类

事故经过

发现生产服务器根路径下一对图片,然后找运维查看,没找到问题,后来想起来有个上线的压缩文件的需求,然后就查询文档及代码底层源码原理

服务器根路径

企业微信截图_17272352058422.png

代码

package com.cogo.collect.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ZipUtil;

import java.io.File;
import java.util.List;

/**
 * @author 86156
 * @project cogo-collect
 * @date 2024-05-23
 * @desc
 */
public class OssUploadZipUtils {

    //需要手动删除源文件
    public static File createBatchZipStream(List<File> files, String fileName) {
        File zipFile = new File(fileName, ".zip");
        List<File> fileList = CollUtil.newArrayList();
        fileList.addAll(files);
        File file = ZipUtil.zip(zipFile, false, fileList.toArray(new File[files.size()]));
        return file;
    }

}

问题剖析

ZipUtil压缩工具底层调用IO流copy,一个文件复制到另一个文件,但是源文件没有DELETE;

底层代码

/**
 * 对文件或文件目录进行压缩
 *
 * @param zipOutputStream    生成的Zip到的目标流,不关闭此流
 * @param withSrcDir 是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩
 * @param filter     文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩)
 * @param srcFiles   要压缩的源文件或目录。如果压缩一个文件,则为该文件的全路径;如果压缩一个目录,则为该目录的顶层目录路径
 * @throws IORuntimeException IO异常
 * @since 5.1.1
 */
public static void zip(ZipOutputStream zipOutputStream, boolean withSrcDir, FileFilter filter, File... srcFiles) throws IORuntimeException {
   String srcRootDir;
   try{
      for (File srcFile : srcFiles) {
         if (null == srcFile) {
            continue;
         }
         // 如果只是压缩一个文件,则需要截取该文件的父目录
         srcRootDir = srcFile.getCanonicalPath();
         if (srcFile.isFile() || withSrcDir) {
            // 若是文件,则将父目录完整路径都截取掉;若设置包含目录,则将上级目录全部截取掉,保留本目录名
            srcRootDir = srcFile.getCanonicalFile().getParentFile().getCanonicalPath();
         }
         // 调用递归压缩方法进行目录或文件压缩
         zip(srcFile, srcRootDir, zipOutputStream, filter);
         zipOutputStream.flush();
      }
      zipOutputStream.finish();
   } catch (IOException e) {
      throw new IORuntimeException(e);
   }
}
/**
 * 递归压缩文件夹<br>
 * srcRootDir决定了路径截取的位置,例如:<br>
 * file的路径为d:/a/b/c/d.txt,srcRootDir为d:/a/b,则压缩后的文件与目录为结构为c/d.txt
 *
 * @param out        压缩文件存储对象
 * @param srcRootDir 被压缩的文件夹根目录
 * @param file       当前递归压缩的文件或目录对象
 * @param filter     文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩)
 * @throws UtilException IO异常
 */
private static void zip(File file, String srcRootDir, ZipOutputStream out, FileFilter filter) throws UtilException {
   if (null == file || (null != filter && false == filter.accept(file))) {
      return;
   }

   final String subPath = FileUtil.subPath(srcRootDir, file); // 获取文件相对于压缩文件夹根目录的子路径
   if (file.isDirectory()) {// 如果是目录,则压缩压缩目录中的文件或子目录
      final File[] files = file.listFiles();
      if (ArrayUtil.isEmpty(files) && StrUtil.isNotEmpty(subPath)) {
         // 加入目录,只有空目录时才加入目录,非空时会在创建文件时自动添加父级目录
         addDir(subPath, out);
      }
      // 压缩目录下的子文件或目录
      for (File childFile : files) {
         zip(childFile, srcRootDir, out, filter);
      }
   } else {// 如果是文件或其它符号,则直接压缩该文件
      addFile(file, subPath, out);
   }
}
/**
 * 拷贝流,拷贝后不关闭流
 *
 * @param in             输入流
 * @param out            输出流
 * @param bufferSize     缓存大小
 * @param streamProgress 进度条
 * @return 传输的byte数
 * @throws IORuntimeException IO异常
 */
public static long copy(InputStream in, OutputStream out, int bufferSize, StreamProgress streamProgress) throws IORuntimeException {
   Assert.notNull(in, "InputStream is null !");
   Assert.notNull(out, "OutputStream is null !");
   if (bufferSize <= 0) {
      bufferSize = DEFAULT_BUFFER_SIZE;
   }

   byte[] buffer = new byte[bufferSize];
   if (null != streamProgress) {
      streamProgress.start();
   }
   long size = 0;
   try {
      for (int readSize; (readSize = in.read(buffer)) != EOF; ) {
         out.write(buffer, 0, readSize);
         size += readSize;
         out.flush();
         if (null != streamProgress) {
            streamProgress.progress(size);
         }
      }
   } catch (IOException e) {
      throw new IORuntimeException(e);
   }
   if (null != streamProgress) {
      streamProgress.finish();
   }
   return size;
}

问题解决

public static InputStream zipInputStreams(String[] fileNames, InputStream[] inputStreams) throws IOException {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    try (ZipOutputStream zipOut = new ZipOutputStream(byteArrayOutputStream)) {
        for (int i = 0; i < inputStreams.length; i++) {
            if (inputStreams[i] != null) {
                ZipEntry zipEntry = new ZipEntry(fileNames[i]);
                zipOut.putNextEntry(zipEntry);

                // 将 InputStream 的内容写入到 ZipOutputStream
                byte[] buffer = new byte[1024];
                int length;
                while ((length = inputStreams[i].read(buffer)) > 0) {
                    zipOut.write(buffer, 0, length);
                }
                zipOut.closeEntry();
            }
        }
    }
    // 返回 ZIP 包的 InputStream
    return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}