• java 批量下载将多个文件(minio中存储)压缩成一个zip包


    我的需求是将minio中存储的文件按照查询条件查询出来统一压成一个zip包然后下载下来。

    思路:针对这个需求,其实可以有多个思路,不过也大同小异,一般都是后端返回流文件前端再处理下载,也有少数是压缩成zip包之后直接给下载链接返回到前端,前端收到链接url直接window.open()进行下载,不过这种下载zip包的路径要确保是在网站下,否则访问不到,还有一个缺点就是文件没法删除,占用存储空间,后期需人为动作清理,选择哪种思路就可以看具体需求啦,我选择的是第一种思路,以下就针对第一种后端返回流方式进行具体介绍。

    首先说第一种方法:将需要下载的文件找到,minio中有查询方法将文件转成inputStream,这里就不多说了,拿到一组InputStream,我们就可以写入一个zip包里了,创建临时zip路径,将流遍历写入文件,读取临时zip文件再写入response中的outputStream,最后删除临时文件。

    前端处理方法最后统一介绍,后端核心代码如下:

    1. public void downloadZip(String name, List filePaths,HttpServletResponse response){
    2. File zipFile = compressedFileToZip(name,filePaths);
    3. ByteArrayOutputStream os = new ByteArrayOutputStream();
    4. try {
    5. FileInputStream ins = new FileInputStream(zipFile);
    6. WritableByteChannel writableByteChannel = Channels.newChannel(os);
    7. FileChannel fileChannel = ins.getChannel();
    8. fileChannel.transferTo(0, fileChannel.size(), writableByteChannel);
    9. fileChannel.close();
    10. response.setCharacterEncoding("UTF-8");
    11. name = URLEncoder.encode(name, "UTF-8");
    12. response.setContentType("application/octet-stream");
    13. response.addHeader("Content-Disposition", "attachment;filename=" + new String(name.getBytes("iso8859-1")));
    14. response.setContentLength(os.size());
    15. response.setHeader("filename", name);
    16. response.addHeader("Content-Length", "" + os.size());
    17. var outputstream = response.getOutputStream();
    18. os.writeTo(outputstream);
    19. os.flush();
    20. os.close();
    21. outputstream.flush();
    22. outputstream.close();
    23. writableByteChannel.close();
    24. if(zipFile.exists()){
    25. //删除临时文件
    26. zipFile.delete();
    27. }
    28. } catch (IOException e) {
    29. e.printStackTrace();
    30. } finally {
    31. try {
    32. os.close();
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. }
    36. }
    37. }
    38. /**
    39. * 构建临时zip文件
    40. **/
    41. public File compressedFileToZip(String name, List mediaFileEntityList) {
    42. String zipName = name.concat(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))).concat(".zip");
    43. //临时zip路径
    44. String fileZipPath = System.getProperty("user.dir").concat("/").concat(zipName);
    45. OutputStream os = null;
    46. ZipOutputStream zos = null;
    47. File file = new File(fileZipPath);
    48. try {
    49. if(!file.exists()){
    50. file.createNewFile();
    51. }
    52. os= new FileOutputStream(file);
    53. zos = new ZipOutputStream(os) ;
    54. for (MediaFileEntity entity:mediaFileEntityList
    55. ) {
    56. zos.putNextEntry(new ZipEntry(entity.getFileName()));
    57. //minio 获取流
    58. InputStream ins = ossService.getObject(OssConfiguration.bucket,entity.getObjectKey());
    59. FileInputStream insf=convertToFileInputStream(ins);
    60. WritableByteChannel writableByteChannel = Channels.newChannel(zos);
    61. FileChannel fileChannel = insf.getChannel();
    62. fileChannel.transferTo(0, fileChannel.size(), writableByteChannel);
    63. zos.closeEntry();
    64. fileChannel.close();
    65. ins.close();
    66. }
    67. } catch (IOException e) {
    68. e.printStackTrace();
    69. }finally {
    70. if(zos != null){
    71. try {
    72. zos.close();
    73. } catch (IOException e) {
    74. e.printStackTrace();
    75. }
    76. }
    77. if(os != null){
    78. try {
    79. os.close();
    80. } catch (IOException e) {
    81. e.printStackTrace();
    82. }
    83. }
    84. }
    85. return file;
    86. }

    第二种方法:安装hutool依赖,调用hutool包中的ZipUtil工具类中的zip方法进行下载,。此方法需要有3个参数,分别是OutoutStream,每个流对应的文件名字符串数组,文件的InputStream数组。

    首先将需要下载的文件找到,拿到一组InputStream,也就是zip方法中的第3个参数,第一个参数顾名思义就是你想要输出的地方,我们是返回给前端所以就是response.getOutputStream(),第二个参数我们遍历文件时也可以拿到,废话不多说了,上代码看吧。

    在项目下安装hutool依赖

    
        cn.hutool
        hutool-all
        5.5.7
    
    1. /**
    2. * 下载多个文件转zip压缩包
    3. *
    4. * @param mediaFileEntityList
    5. * @param response
    6. * @throws Exception
    7. */
    8. public void dowloadToZip(List mediaFileEntityList, HttpServletResponse response) throws Exception {
    9. int i = 0;
    10. //如果有附件 进行zip处理
    11. if (mediaFileEntityList != null && mediaFileEntityList.size() > 0) {
    12. try {
    13. //被压缩文件流集合
    14. InputStream[] srcFiles = new InputStream[mediaFileEntityList.size()];
    15. //被压缩文件名称
    16. String[] srcFileNames = new String[mediaFileEntityList.size()];
    17. for (MediaFileEntity entity : mediaFileEntityList) {
    18. //以下代码为获取图片inputStream
    19. InputStream ins = ossService.getObject(OssConfiguration.bucket,entity.getObjectKey());
    20. if (ins == null) {
    21. continue;
    22. }
    23. //塞入流数组中
    24. srcFiles[i] = ins;
    25. srcFileNames[i] = entity.getFileName();
    26. i++;
    27. }
    28. response.setCharacterEncoding("UTF-8");
    29. response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("下载.zip", "UTF-8"));
    30. //多个文件压缩成压缩包返回
    31. ZipUtil.zip(response.getOutputStream(), srcFileNames, srcFiles);
    32. } catch (IOException e) {
    33. e.printStackTrace();
    34. }
    35. }
    36. }

    Controller这边可以直接写成没有返回值的接口,我的例子如下,仅供参考:

    1. @GetMapping("/{workspace_id}/fileDownList")
    2. @ApiOperation(value = "查询文件的下载地址")
    3. public void getFileStreamList(@PathVariable(name = "workspace_id") String workspaceId,
    4. @RequestParam(name = "ids") String ids, HttpServletResponse response) throws Exception {
    5. List fileIds= Arrays.stream(ids.split(",")).collect(Collectors.toList()).stream()
    6. .map(Integer::parseInt)
    7. .collect(Collectors.toList());
    8. List mediaFileEntityList = fileService.getMediaListById(workspaceId, fileIds);
    9. // fileUtil.downloadZip("111",mediaFileEntityList,response);//第一种方法
    10. fileUtil.dowloadToZip(mediaFileEntityList,response);//第二种方法
    11. }

     到此,后端zip下载就完毕了,下面我们说说前端如何处理

    网上查询前端处理大致都是如下,但是我自己使用的时候下载总是提示损坏,后找了一个工具类直接调用,就可以了,示例代码请求是get,如需调整,可根据情况自行调整

    核心代码如下:

    1. import { saveAs } from 'file-saver';
    2. const baseURL = (window as any).config.VITE_APP_BASE_API; //import.meta.env.VITE_APP_BASE_API;
    3. export default {
    4. zip(url: string, name: string) {
    5. url = baseURL + url;
    6. axios({
    7. method: 'get',
    8. url: url,
    9. responseType: 'blob',
    10. headers: { Authorization: 'Bearer ' + getToken() },
    11. }).then(res => {
    12. const isBlob = blobValidate(res.data);
    13. if (isBlob) {
    14. const blob = new Blob([res.data], { type: 'application/zip' });
    15. this.saveAs(blob, name);
    16. } else {
    17. this.printErrMsg(res.data);
    18. }
    19. });
    20. },
    21. saveAs(text: any, name: string, opts?: any) {
    22. saveAs(text, name, opts);
    23. },
    24. async printErrMsg(data: any) {
    25. const resText = await data.text();
    26. const rspObj = JSON.parse(resText);
    27. const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
    28. // ElMessage.error(errMsg);
    29. },
    30. blobValidate(data: any) {
    31. return data.type !== 'application/json';
    32. }
    33. };

    按钮绑定方法直接调用zip下载方法,传参为url和要导出zip的名称,示例如下:

    1. import download from '@/plugins/download';
    2. function batchDownload(){
    3. ElMessage.success("文件下载中,请勿重复点击!");
    4. download.zip(`/media/api/v1/files/${workspaceId}/fileDownList?ids=${selectlist.value.join(",")}`,"MediaFiles"+new Date().toLocaleDateString()+".zip")
    5. }

  • 相关阅读:
    学校报名测评小程序开发制作功能介绍
    Hadoop核心之MapReduce案例总结Ⅱ
    非全自研可视化表达引擎-RuleLinK
    【实习】DLL相关
    docker镜像编译与docker-compose部署与编排
    KMP算法
    Spring Cloud Gateway + Knife4j 4.3 实现微服务网关聚合接口文档
    批量转换json到java bean工具说明
    [附源码]Python计算机毕业设计Django共享汽车系统
    DevOps --- Pipeline和Yaml文件
  • 原文地址:https://blog.csdn.net/weixin_41043580/article/details/132598816