Java实现批量下载多文件(夹)压缩包(zip)

您所在的位置:网站首页 拍照比镜子里看到的自己丑好多 Java实现批量下载多文件(夹)压缩包(zip)

Java实现批量下载多文件(夹)压缩包(zip)

2023-08-19 09:26| 来源: 网络整理| 查看: 265

设计思路 1.文件(夹)层级结构传递参数设计 public class DownloadFileParam { /** *文件(夹)名称 **/ private String fileName; /** *文件id,文件夹不传 **/ private String fileId; /** *是否为文件夹,0.否,1.是 **/ private Integer isFolder; /** * 子文件(夹)信息 **/ private List childs; /** * 文件(夹)在服务器上的绝对路径,前端无需传递 * 在后台递归下载文件到服务器或者在服务器上创建 * 文件夹时赋值。 **/ private String file; }

想象一下,每个DownloadFileParam代表一颗资料目录树,如果是下载的多个文件夹,前端应传递List。

2.不同用户同名文件夹文件下载

存在一种场景A、B用户同时下载同名文件夹(或者同一文件夹下的不通资料)

​ A

/     ~~~~~     \     ~~~~     \

A1     ~~~~     A2     ~~~~     A3

​              ~~~~~~~~~~~~~             \     ~~~~     \

​          ~~~~~~~~~         A2-1.log     ~~~~     A3-1.txt

A用户下载A-A1、A-A2-A2-1.txt;B用户下载A-A1、A-A2-A3-1.txt,怎么保证下载zip包的正确性?

我们可以对于每一次下载请求,生成随机的uuid作为根文件夹目录,再在根据文件夹下,创建用户A、B的文件夹和临时文件。

//伪代码实现 //创建虚拟文件夹 String mockFileName = IdGenerator.newShortId(); String tmpDir = System.getProperty("user.dir") + "/downloadfile/" + mockFileName; FileUtil.mkdir(tmpDir); try { //xxx } finally { FileUtil.del(tmpDir); } 3.在服务器创建文件夹和临时文件

结合2.中提到的临时文件夹,我们不难将前端传递的List与随机生成的根目录合并成一颗资料树,通过递归的方式创建文件夹和下载文件到服务器

//伪代码实现 private void downloadFileToServer(String tmpDir, DownloadFileParam downloadFileParam) throws Exception { List childs = downloadFileParam.getChilds(); if (EmptyUtils.isNotEmpty(childs)) { for (int i = 0; i < childs.size(); i++) { DownloadFileParam param = childs.get(i); if (param.getIsFolder() == 1) { //如果是文件夹则创建文件夹 } else { //否则下载文件到tmpDir } //递归下载文件到服务器 downloadFileToServer(tmpDir, param); } } } 4.压缩服务器文件(夹)并写入到输出流

Java提供了ZipOutputStream输出流结合hutool包下的ZipUtils方法,很容易的能够把压缩包流返回给前端下载

/** * 对文件或文件目录进行压缩 * * @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) 完整代码:

DownloadFileParam.java

public class DownloadFileParam { /** *文件(夹)名称 **/ private String fileName; /** *文件id,文件夹不传 **/ private String fileId; /** *是否为文件夹,0.否,1.是 **/ private Integer isFolder; /** * 子文件(夹)信息 **/ private List childs; /** * 文件(夹)在服务器上的绝对路径,前端无需传递 * 在后台递归下载文件到服务器或者在服务器上创建 * 文件夹时赋值。 **/ private String file; }

接口定义

@PostMapping(value = "/batchDownloadFile", produces = "application/octet-stream;") public void batchDownloadFile(@RequestBody List params) throws Exception { try { fileService.batchDownloadFile(params, getRequest(), getResponse()); } catch (Exception e) { logger.error("downloadFileBy error params={}", params, e); throw e; } }

fileService.batchDownloadFile

@Override public void batchDownloadFile(List params, HttpServletRequest request, HttpServletResponse response) throws Exception { //创建虚拟文件夹 String mockFileName = IdGenerator.newShortId(); String tmpDir = System.getProperty("user.dir") + "/downloadfile/" + mockFileName; FileUtil.mkdir(tmpDir); try { //设置响应 response.reset(); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(DateFormatUtil.formatDate(DateFormatUtil.yyyyMMdd, new Date()) + ".zip", "UTF-8")); //参数组装 ZipOutputStream zos = new ZipOutputStream(response.getOutputStream()); DownloadFileParam downloadFileParam = new DownloadFileParam(); downloadFileParam.setFileName(mockFileName); downloadFileParam.setIsFolder(1); downloadFileParam.setChilds(params); //在服务器上生成指定文件 downloadFileToServer(tmpDir, downloadFileParam); //压缩并写到输出流 ZipUtil.zip(zos, false, pathname -> true, new File(tmpDir)); } finally { FileUtil.del(tmpDir); } }

downloadFileToServer

/** * 递归创建文件夹或者下载文件到服务器 * * @param tmpDir * @param downloadFileParam * @throws Exception * @author ZhouNing * @date 2021/8/6 10:57 **/ private void downloadFileToServer(String tmpDir, DownloadFileParam downloadFileParam) throws Exception { List childs = downloadFileParam.getChilds(); if (EmptyUtils.isNotEmpty(childs)) { final String finalPath = tmpDir; //设置文件或者文件夹的绝对路径层级 childs.stream().forEach(dwp -> dwp.setFile(finalPath + File.separator + dwp.getFileName())); for (int i = 0; i < childs.size(); i++) { DownloadFileParam param = childs.get(i); if (param.getIsFolder() == 1) { //如果是文件夹则创建文件夹 tmpDir = tmpDir + File.separator + param.getFileName(); FileUtil.mkdir(param.getFile()); } else { //否则下载文件到tmpDir File tmpFile = new File(param.getFile()); // 创建基于文件的输出流 FileOutputStream fos = new FileOutputStream(tmpFile); //从mongodb或者下载文件到本地 FileInfo fileInfo = fileInfoDao.findById(param.getFileId()).orElseThrow(() -> new Exception("文件不存在")); List gridFSFileList = fileChunkDao.findAll(fileInfo.getFileMd5()); if (gridFSFileList != null && gridFSFileList.size() > 0) { try { for (GridFsResource gridFSFile : gridFSFileList) { InputStream inputStream = gridFSFile.getInputStream(); try { int len; byte[] bytes = new byte[1024]; while ((len = inputStream.read(bytes)) != -1) { fos.write(bytes, 0, len); } } finally { IoUtil.close(inputStream); } } } catch (Exception e) { e.printStackTrace(); } finally { IoUtil.close(fos); } } } //递归下载文件到服务器 downloadFileToServer(tmpDir, param); } } } 实现思路总结

文件(夹)的压缩下载,程序实现的复杂度和下载参数设计有很大关系。笔者曾经遇到过类似如下方式传递文件(夹)参数

/1111&/新建文件夹A(1)&/新建文件夹A/OA请假.docx$f43ea9e25a504e899404d9b026ea2cc9&/新建文件夹A/新建文件夹B/api-ms-win-core-console-l1-1-0.dll$ebd2b5ca5f0d447aa3c02caae16dff67&/新建文件夹(1)/1.py$3a51e7f6c30d4af89fc190cedd5711b3

这种实现也未尝不可,真正写起来代码可读性、可维护性堪忧。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3