diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java b/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java index 842dbff1e..e8bd619be 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java @@ -1,7 +1,11 @@ package cn.hutool.core.io.file.visitor; import cn.hutool.core.io.file.PathUtil; +import cn.hutool.core.util.StrUtil; +import com.sun.nio.zipfs.ZipFileSystem; +import com.sun.nio.zipfs.ZipPath; +import java.io.File; import java.io.IOException; import java.nio.file.CopyOption; import java.nio.file.FileAlreadyExistsException; @@ -23,6 +27,8 @@ public class CopyVisitor extends SimpleFileVisitor { private final Path source; private final Path target; private boolean isTargetCreated; + private final boolean isZipFile; + private String dirRoot = null; private final CopyOption[] copyOptions; /** @@ -38,15 +44,28 @@ public class CopyVisitor extends SimpleFileVisitor { } this.source = source; this.target = target; + this.isZipFile = target instanceof ZipPath; this.copyOptions = copyOptions; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - initTarget(); - // 将当前目录相对于源路径转换为相对于目标路径 - final Path targetDir = target.resolve(source.relativize(dir)); + final Path targetDir; + if (isZipFile) { + ZipPath zipPath = (ZipPath) target; + ZipFileSystem fileSystem = zipPath.getFileSystem(); + if (dirRoot == null) { + targetDir = fileSystem.getPath(dir.getFileName().toString()); + dirRoot = dir.getFileName().toString() + File.separator; + } else { + targetDir = fileSystem.getPath(dirRoot, StrUtil.subAfter(dir.toString(), dirRoot, false)); + } + } else { + initTarget(); + // 将当前目录相对于源路径转换为相对于目标路径 + targetDir = target.resolve(source.relativize(dir)); + } try { Files.copy(dir, targetDir, copyOptions); } catch (FileAlreadyExistsException e) { @@ -59,8 +78,17 @@ public class CopyVisitor extends SimpleFileVisitor { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - initTarget(); - Files.copy(file, target.resolve(source.relativize(file)), copyOptions); + if (isZipFile) { + if (dirRoot == null) { + Files.copy(file, target, copyOptions); + } else { + ZipPath zipPath = (ZipPath) target; + Files.copy(file, zipPath.getFileSystem().getPath(dirRoot, StrUtil.subAfter(file.toString(), dirRoot, false)), copyOptions); + } + } else { + initTarget(); + Files.copy(file, target.resolve(source.relativize(file)), copyOptions); + } return FileVisitResult.CONTINUE; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index c40ded1cd..dc8d55230 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -9,6 +9,8 @@ import cn.hutool.core.io.FastByteArrayOutputStream; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.file.FileSystemUtil; +import cn.hutool.core.io.file.visitor.CopyVisitor; import cn.hutool.core.io.resource.Resource; import java.io.BufferedInputStream; @@ -20,6 +22,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -75,6 +83,29 @@ public class ZipUtil { } } + /** + * 在zip文件中添加新文件, 如果已经存在则不会有效果 + * + * @param zipFilePathStr zip文件存储路径 + * @param appendFilePathStr 待添加文件路径(可以是文件夹) + */ + public static void addFile(String zipFilePathStr, String appendFilePathStr) throws IOException { + Path zipPath = Paths.get(zipFilePathStr); + Path appendFilePath = Paths.get(appendFilePathStr); + + try (FileSystem zipFileSystem = FileSystemUtil.createZip(zipPath.toString())) { + Path root = zipFileSystem.getPath("/"); + Path dest = zipFileSystem.getPath(root.toString(), appendFilePath.getFileName().toString()); + if (!Files.isDirectory(appendFilePath)) { + Files.copy(appendFilePath, dest, StandardCopyOption.COPY_ATTRIBUTES); + } else { + Files.walkFileTree(appendFilePath, new CopyVisitor(appendFilePath, zipFileSystem.getPath(zipFilePathStr))); + } + } catch (FileAlreadyExistsException ignored) { + // 文件已存在, 跳过 + } + } + /** * 打包到当前目录,使用默认编码UTF-8 * diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 0246f6688..acd389ab1 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.compress.ZipReader; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.lang.Console; @@ -12,6 +13,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; /** * {@link ZipUtil}单元测试 @@ -20,6 +23,50 @@ import java.nio.charset.Charset; */ public class ZipUtilTest { + @Test + public void addFileTest() throws IOException { + File appendFile = FileUtil.file("test-zip/addFile.txt"); + File zipFile = FileUtil.file("test-zip/test.zip"); + + // 用于测试完成后将被测试文件恢复 + File tempZipFile = FileUtil.createTempFile(FileUtil.file("test-zip")); + tempZipFile.deleteOnExit(); + FileUtil.copy(zipFile, tempZipFile, true); + + // test file add + List beforeNames = zipEntryNames(zipFile); + ZipUtil.addFile(zipFile.getAbsolutePath(), appendFile.getAbsolutePath()); + List afterNames = zipEntryNames(zipFile); + Assert.assertTrue(afterNames.containsAll(beforeNames)); + Assert.assertTrue(afterNames.contains(appendFile.getName())); + + // test dir add + beforeNames = afterNames; + File addDirFile = FileUtil.file("test-zip/test-add"); + ZipUtil.addFile(zipFile.getAbsolutePath(), addDirFile.getAbsolutePath()); + afterNames = zipEntryNames(zipFile); + + Assert.assertTrue(afterNames.containsAll(beforeNames)); + Assert.assertTrue(afterNames.contains(appendFile.getName())); + + // rollback + FileUtil.copy(tempZipFile, zipFile, true); + Assert.assertTrue(String.format("delete temp file %s failed", tempZipFile.getCanonicalPath()), tempZipFile.delete()); + } + + /** + * 获取zip文件中所有一级文件/文件夹的name + * + * @param zipFile 待测试的zip文件 + * @return zip文件中一级目录下的所有文件/文件夹名 + */ + private List zipEntryNames(File zipFile) { + List fileNames = new ArrayList<>(); + ZipReader reader = ZipReader.of(zipFile, CharsetUtil.CHARSET_UTF_8); + reader.read(zipEntry -> fileNames.add(zipEntry.getName())); + reader.close(); + return fileNames; + } @Test @Ignore diff --git a/hutool-core/src/test/resources/test-zip/addFile.txt b/hutool-core/src/test/resources/test-zip/addFile.txt new file mode 100644 index 000000000..8d1c2fee6 --- /dev/null +++ b/hutool-core/src/test/resources/test-zip/addFile.txt @@ -0,0 +1,2 @@ +this file will be used to add into the test.zip +before the add action, the test.zip won't have this file. diff --git a/hutool-core/src/test/resources/test-zip/test-add/test.txt b/hutool-core/src/test/resources/test-zip/test-add/test.txt new file mode 100644 index 000000000..e69de29bb diff --git a/hutool-core/src/test/resources/test-zip/test.zip b/hutool-core/src/test/resources/test-zip/test.zip new file mode 100644 index 000000000..86126d555 Binary files /dev/null and b/hutool-core/src/test/resources/test-zip/test.zip differ