公司项目里有个需求,需要以压缩包的方式批量下载文件。前几天开发完成功能后,一直没时间总结,这里做个总结,希望可以帮到别的朋友。
先贴下代码,如下:
package cn.deepcap.common.utils;
import cn.deepcap.common.exception.PlatformAlertException;
import cn.deepcap.common.support.nas.FileStorageUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.time.StopWatch;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Slf4j
public class ZipUtils {
/**
* @param srcDir 压缩文件夹逻辑路径集合
* @param KeepDirStructure 是否保留原来的目录结构,
* true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static String toZip(List<String> srcDir, boolean KeepDirStructure) throws Exception {
// 生成
String outDir = generateOutDir(srcDir);
OutputStream out = new FileOutputStream(new File(outDir));
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try (ZipOutputStream zos = new ZipOutputStream(out)) {
List<File> sourceFileList = new ArrayList<>();
for (String dir : srcDir) {
File sourceFile = new File(FileStorageUtils.getAbsolutePath(dir));
sourceFileList.add(sourceFile);
}
compress(sourceFileList, zos, KeepDirStructure, outDir);
stopWatch.stop();
log.info("压缩完成,大小:{} KB , 耗时:{} ms",
Files.size(new File(outDir).toPath()) / 1024,
stopWatch.getTime());
// 返回逻辑路径-\temp\xxx.zip
return outDir.substring(FileStorageUtils.getAbsolutePath().length());
} catch (Exception e) {
if (e instanceof PlatformAlertException) {
throw new PlatformAlertException(e.getLocalizedMessage());
}
throw new RuntimeException("toZip error", e);
}
}
/**
* 生成压缩文件路径
*/
private static String generateOutDir(List<String> srcDir) {
File file = new File(srcDir.get(0));
String outTempDir = FileStorageUtils.getAbsolutePath(
FileStorageUtils.getProperties().getTemp());
File tempDir = new File(outTempDir);
if (!tempDir.exists()) {
//noinspection ResultOfMethodCallIgnored
tempDir.mkdirs();
}
// 防止文件名重复 下载完删除错误
outTempDir += File.separator + UUID.randomUUID().toString().replaceAll("-", "") + file.getName();
srcDir = srcDir.stream()
.filter(i -> new File(FileStorageUtils.getAbsolutePath(i)).exists())
.collect(Collectors.toList());
if (srcDir.size() == 1) {
return outTempDir + ".zip";
} else {
return outTempDir + "_等文件.zip";
}
}
/**
* 递归压缩方法
*
* @param sourceFileList 源文件列表
* @param zos zip输出流
* @param outDir zip压缩包路径
* @param KeepDirStructure 是否保留原来的目录结构,
* true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws Exception
*/
private static void compress(List<File> sourceFileList,
ZipOutputStream zos,
boolean KeepDirStructure, String outDir) throws Exception {
for (File sourceFile : sourceFileList) {
String name = sourceFile.getName();
if (sourceFile.isFile()) {
handleFile(sourceFile, zos, name, outDir);
} else {
handleDir(zos, KeepDirStructure, outDir, sourceFile, name);
}
}
}
/**
* 递归压缩方法
*
* @param sourceFile 源文件
* @param zos zip输出流
* @param name 压缩后的名称
* @param KeepDirStructure 是否保留原来的目录结构,
* true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws Exception
*/
private static void compress(File sourceFile, ZipOutputStream zos,
String name, boolean KeepDirStructure, String outDir) throws Exception {
if (sourceFile.isFile()) {
handleFile(sourceFile, zos, name, outDir);
} else {
handleDir(zos, KeepDirStructure, outDir, sourceFile, name);
}
}
/**
* 处理 文件
*
* @param zos
* @param name
* @param outDir
* @throws IOException
*/
private static void handleFile(File sourceFile, ZipOutputStream zos, String name, String outDir) throws IOException {
zos.putNextEntry(new ZipEntry(name));
Files.copy(sourceFile.toPath(), zos);
// 验证压缩包大小
long zipSize = Files.size(new File(outDir).toPath());
if (zipSize > FileStorageUtils.getProperties().getMaxFileSize().toBytes()) {
throw new PlatformAlertException("zip is too large");
}
zos.closeEntry();
}
/**
* 处理文件夹
*
* @param zos
* @param KeepDirStructure
* @param outDir
* @param sourceFile
* @param name
* @throws Exception
*/
private static void handleDir(ZipOutputStream zos, boolean KeepDirStructure, String outDir, File sourceFile, String name) throws Exception {
File[] listFiles = sourceFile.listFiles();
if (ArrayUtils.isEmpty(listFiles)) {
if (KeepDirStructure) {
zos.putNextEntry(new ZipEntry(name + File.separator));
zos.closeEntry();
}
} else {
for (File file : listFiles) {
if (KeepDirStructure) {
compress(file, zos, name + File.separator + file.getName(), KeepDirStructure, outDir);
} else {
compress(file, zos, file.getName(), KeepDirStructure, outDir);
}
}
}
}
}
批量下载思路如下:
- 因为是下载,所以首先要知道文件的绝对路径,但是文件的绝对路径一般以直接或间接的形式存在数据库中,所以需要根据实际情况进行处理
- 其中最重要的是知道数据库中是否存了文件所在文件夹的路径,如果存了,那么就很好办了,直接调用下面工具类即可
- 一般情况下数据库中是指存文件信息(文件路径、大小、类型等信息),我们这项目里就是这样,所以需要先把要下载的文件复制到一个地方,并且复制到和文件真实情况一致的文件夹下,最后根据生成的文件夹作为根路径进行压缩包下载
- 因为每个人要下载的不同,所以把要下载的文件复制到一个临时目录比较合适,打成压缩包后删除即可
- 同时因为可能同时多个人进行下载相同文件夹和文件,所以最好把要下载的文件放到一个随机名字的文件夹下,防止删除掉别人临时要下载的文件
- 关键是生成文件和文件夹的操作,可以通过NIO提供的Files和Paths进行操作
- 如果数据库中存了文件夹的路径信息,那么压缩包中可以包含空文件夹,但是如果没有存文件夹路径信息,压缩包中的文件夹下必须要存在文件
- 具体生成的文件夹名称需要和数据库中存的文件夹名一致,具体根据表结构来处理
关键方法如下:
Files.createFile(Path filePath):根据指定文件path生成文件
Files.createDirectories(Path dirPath): 根据指定文件夹path生成文件夹,会生成多层级文件夹
Files.copy(Path sourcePath, Path targetPath): 将真实的文件内容拷贝到生成的空文件中,其中参数也可以是流
需要注意的是:生成文件的Files.createFile方法调用时,文件所在文件夹必须存在,否则会报错,所以需要先调用createDirectories方法。
顺便总结下文件操作的方法:
// 根据mime类型获取文件后缀 application/msword -> .doc
String extension = MimeTypes.getDefaultMimeTypes().forName(String fileType).getExtension()
// 根据文件路径字符串获取Path
Path path = Paths.get(String filePath)
// 判断文件是否存在
Files.notExists(Path filePath)
// 删除文件夹(级联删除下面所有文件和文件夹)或文件 cn.hutool.core.io.FileUtil
FileUtil.del(Path path)
// 根据文件路径获取文件名 d:\path\Desktop\fortest.txt -> fortest.txt
StringUtils.getFilename(String path)
// 根据文件名或文件路径获取文件后缀 d:\path\Desktop\fortest.txt或者fortest.txt -> txt
StringUtils.getFilenameExtension(String path)