项目实战系列之以压缩包的方式批量下载文件【三】

公司项目里有个需求,需要以压缩包的方式批量下载文件。前几天开发完成功能后,一直没时间总结,这里做个总结,希望可以帮到别的朋友。

先贴下代码,如下:

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值