package com.chinaunicom.asset.common.utils.compress;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.util.FileSystemUtils;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
@Slf4j
public abstract class AbstractArchive implements Closeable {
private static final int THRESHOLD_ENTRIES = 10_000;
private static final int THRESHOLD_SIZE = 1_000_000_000; // 1 GB
private final Path archive;
protected AbstractArchive(Path archive) {
this.archive = archive;
}
protected Path getArchive() {
return archive;
}
protected abstract ArchiveEntry nextEntry() throws IOException;
protected abstract InputStream getInputStream() throws IOException;
protected abstract boolean skipEntry();
// 不解压macOS的meta data文件
protected static boolean isMacOSMetaData(String entryName) {
if (entryName.startsWith("__MACOSX/")) {
return true;
}
return entryName.endsWith("/.DS_Store") || entryName.equals(".DS_Store");
}
public void decompress(Path decompressedPath) throws IOException {
ArchiveEntry entry;
int totalSize = 0;
int archiveEntries = 0;
while ((entry = nextEntry()) != null) {
InputStream in = getInputStream();
if (skipEntry()) {
continue;
}
Path entryDest = decompressedPath.resolve(entry.getName());
if (entry.isDirectory()) {
Files.createDirectories(entryDest);
} else {
writeToDest(in, entryDest);
validateTotal(archiveEntries++, decompressedPath);
totalSize += entry.getSize();
validateTotalSize(totalSize, decompressedPath);
}
}
}
public static AbstractArchive newArchive(Path archivePath) throws IOException {
String extension = FilenameUtils.getExtension(archivePath.getFileName().toString());
switch (extension.toLowerCase()) {
case "zip":
return new ZipArchive(archivePath);
case "7z":
return new SevenZArchive(archivePath);
default:
throw new IllegalArgumentException("Unsupported Archive Type:" + extension);
}
}
private static void writeToDest(InputStream in, Path entryDest) throws IOException {
Files.createDirectories(entryDest.getParent());
try (OutputStream out = Files.newOutputStream(entryDest)) {
IOUtils.copy(in, out);
}
}
private static void validateTotalSize(int totalSize, Path decompressedPath) {
if (totalSize > THRESHOLD_SIZE) {
log.error("文件过大:{},终止解压", totalSize);
cleanOnFailed(decompressedPath);
throw new IllegalArgumentException("压缩文件过大,解压失败");
}
}
private static void validateTotal(int archiveEntries, Path decompressedPath) {
if (archiveEntries > THRESHOLD_ENTRIES) {
log.error("文件数量过多,终止解压");
cleanOnFailed(decompressedPath);
throw new IllegalArgumentException("压缩文件内文件过多,解压失败");
}
}
private static void cleanOnFailed(Path targetPath) {
try {
FileSystemUtils.deleteRecursively(targetPath);
} catch (IOException e) {
log.error("清理文件失败:{}", e.getMessage(), e);
}
}
}
package com.chinaunicom.asset.common.utils.compress;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
@Slf4j
public class ZipArchive extends AbstractArchive {
private final ArchiveInputStream inputStream;
private ArchiveEntry currentEntry;
public ZipArchive(Path archivePath) throws IOException {
super(archivePath);
this.inputStream = new ZipArchiveInputStream(Files.newInputStream(getArchive()));
}
@Override
protected ArchiveEntry nextEntry() throws IOException {
currentEntry = inputStream.getNextEntry();
return currentEntry;
}
@Override
protected InputStream getInputStream() {
return inputStream;
}
@Override
protected boolean skipEntry() {
// 跳过无法读取的文件
if (!inputStream.canReadEntryData(currentEntry)) {
log.warn("压缩包内存在无法读取的文件");
return true;
}
return isMacOSMetaData(currentEntry.getName());
}
@Override
public void close() throws IOException {
this.inputStream.close();
}
}
package com.chinaunicom.asset.common.utils.compress;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public class SevenZArchive extends AbstractArchive {
private final SevenZFile sevenZFile;
private SevenZArchiveEntry currentEntry;
public SevenZArchive(Path archivePath) throws IOException {
super(archivePath);
this.sevenZFile = new SevenZFile(Files.newByteChannel(getArchive()));
}
@Override
protected ArchiveEntry nextEntry() throws IOException {
currentEntry = sevenZFile.getNextEntry();
return currentEntry;
}
@Override
protected InputStream getInputStream() throws IOException {
return sevenZFile.getInputStream(currentEntry);
}
@Override
protected boolean skipEntry() {
return isMacOSMetaData(currentEntry.getName());
}
@Override
public void close() throws IOException {
this.sevenZFile.close();
}
}
package com.chinaunicom.asset.common.utils;
import cn.hutool.core.lang.UUID;
import com.chinaunicom.asset.common.exception.BaseException;
import com.chinaunicom.asset.common.utils.compress.AbstractArchive;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@Slf4j
public class ArchiveFiles {
private ArchiveFiles() {
}
public static Path decompress(Path archivePath) {
// 检查文件存在
if (!Files.exists(archivePath) || Files.isDirectory(archivePath)) {
throw new IllegalArgumentException("压缩文件不存在");
}
// 在压缩文件的父目录生成目录,以存储解压后的文件
String decompressedDir = UUID.randomUUID().toString(true);
Path decompressedPath = archivePath.getParent().resolve(decompressedDir);
try (AbstractArchive archive = AbstractArchive.newArchive(archivePath)) {
archive.decompress(decompressedPath);
} catch (IOException e) {
log.error(e.getMessage());
throw new BaseException("解压文件失败");
} catch (Exception e) {
throw new BaseException(e);
}
return decompressedPath;
}
}