前言
交易关联合同订单,需要把相关附件压缩打包分类
事故经过
发现生产服务器根路径下一对图片,然后找运维查看,没找到问题,后来想起来有个上线的压缩文件的需求,然后就查询文档及代码底层源码原理
服务器根路径
代码
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());
}