使用Java实现下载多Sheet的Excel并将阿里云OSS文件下载最终写入Zip文件

背景

最近有个需求,是需要根据后台系统给的筛选条件导出一个Zip文件,Zip文件最终包含的数据有以下两个方面

  1. 查询出指定得数据,并将数据根据某个字段进行分组并 分为多个Sheet写入excel, 写入最终的Zip
  2. 将查询出的数据的 oss的url下载下来压缩为ZIP文件,也一并写入Zip文件

1 需求分析

1.1 ZipOutputStream

既然牵扯到了ZIP文件的下载,那么也就毫无疑问要牵扯到一个类ZipOutputStream,我们来看下这个类的源码:

/**
 * This class implements an output stream filter for writing files in the
 * ZIP file format. Includes support for both compressed and uncompressed
 * entries.
 *
 * @author      David Connelly
 */
public
class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {

public void putNextEntry(ZipEntry e) throws IOException{}

public synchronized void write(byte[] b, int off, int len){}

public void closeEntry() throws IOException { }

翻译过来大概就是:
此类实现了一个输出流过滤器,用于以 ZIP 文件格式写入文件。包括对压缩和未压缩条目的支持。
我摘取了其中三个API,

方法名解释
putNextEntry写入新的 ZIP 文件条目并将流定位到条目数据的开头
write将指定的字节数组写入ZIP条目中
closeEntry关闭当前的Entry

1.2 EasyExcel多Sheet写入

使用EasyExcel将数据写入多Sheet,一般使用其Api,按照如下格式即可。

 File fileExcel = new File(xlsxPath);
 ExcelWriter excelWriter = EasyExcel.write(fileExcel, User.class).build();

// 对数据分组,然后写入每一个Sheet中
Map<String, List<User>> sheetListMap = requests.stream()
                .collect(Collectors.groupingBy(User::getName));
        int index = 0;
        // 遍历写入sheet中
        for (Map.Entry<String, List<User>> entry : sheetListMap.entrySet()) {
            String key = entry.getKey();
            List<User> value = entry.getValue();
            WriteSheet writeSheet = EasyExcel.writerSheet(index++, key).build();
            excelWriter.write(value, writeSheet);
        }
        excelWriter.finish();

1.3 Aliyun-oss的下载文件

一般我们拿到Ali-oss下载的文件路径后,通过如下操作即可拿到其OutputStream,然后进行读写操作即可。

				// 首先获取OssClient客户端
				OSSClient ossClient = getOSSClient();
				Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
                GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest("bucket名称", "文件地址", HttpMethod.GET);
                // 设置过期时间。
                request.setExpiration(expiration);
                // 生成签名URL(HTTP GET请求)。
                URL signedUrl = ossClient.generatePresignedUrl(request);
                // 使用签名URL发送请求。
                OSSObject ossObject = ossClient.getObject(signedUrl, new HashMap<>());
                if (ossObject != null) {
                    InputStream inputStream = ossObject.getObjectContent();
                    // 后续操作不再演示
                    ......
                   }

2 流程&编码

大致的流程图:
流程图

废话不多说,直接上代码

2.1 Controller层

@RequestMapping(value = "/user/exportUserZipFile",  method = RequestMethod.GET)
    public void getExportData(UserQueryDTO userQueryDTO,
                                                HttpServletResponse response) {
        try {
            userService.exportUserData(sampleQueryDTO, response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.2 Service层

public void exportUserData(UserQueryDTO userQueryDTO, HttpServletResponse response) throws IOException {
        // 查询过程不在演示
        ......
        List<User> userList  = new ArrayList();
       // 1 定义三个临时文件,分别代表最终生成的zip文件、需要写入zip中的文件1-excel、需要写入zip中的文件2-pdf.zip
          // 获取当前系统的临时目录
           String FilePath = System.getProperty("java.io.tmpdir") + File.separator;
           // 最终生成的zip文件
            String zipFileName = "export.zip";
            String tempzipPath = FilePath + zipFileName;
            //需要加到ZIP的文件1- request.xlsx
            String xlsxPath = FilePath + "export.xlsx";
            // 需要加到ZIP的文件2- pdf.zip
            String jpgPath = "jpg.zip";
            String jpgZipPath = FilePath + jpgPath;
            
		// 2 任务1:excel的处理
		ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(tempzipPath));

            CompletableFuture cf1 = CompletableFuture.runAsync(() -> {
                FileInputStream fileInputStream = null;
                try {
                    // 1 先处理Excel文件,将Excel数据写入文件,然后写入ZIP中
                    File fileExcel = new File(xlsxPath);
                    ExcelWriter excelWriter = EasyExcel.write(fileExcel, User.class).build();
                    doProcessExcelByMultiSheet(excelWriter, userList);
                    byte[] buffer = new byte[4096];
                    // 读取文件写入zip
                    fileInputStream = new FileInputStream(fileExcel);
                    zipOutputStream.putNextEntry(new ZipEntry(fileExcel.getName()));
                    int len;
                    // 读入需要下载的文件的内容,打包到zip文件
                    while ((len = fileInputStream.read(buffer)) > 0) {
                        zipOutputStream.write(buffer, 0, len);
                    }
                } catch (Exception e) {
                    log.error("导出zip文件,写入excel 失败,{}", e);
                } finally {
                    StreamUtils.closeStream(fileInputStream);
                }
            }, executor);
	// 2 任务2:aliyun-oss文件 的处理
  CompletableFuture cf2 = CompletableFuture.runAsync(()-> {
                FileInputStream fileInputStream2 = null;
                try {
                    //  2 处理所有oss图片,打包成一个zip
                    File filePdf = new File(jpgZipPath );
                    Map<String, String> userFileNameMap = userList.stream()
                            .collect(Collectors.toMap(CovidSampleDTO::getImageUrl,
                                    getUserJpgFileName(),
                                    (v1,v2)-> v1));
                    //2.1  从aliyun-oss读取数据然后写入pdfZipOps中
                    AliYunOssClientUtil.batchDownLoadOssFileAndSaveTempFile(userFileNameMap,jpgZipPath );
                    //将image的zip文件写入到最后的zip中
                    byte[] buffer2 = new byte[4096];
                    fileInputStream2 = new FileInputStream(filePdf);
                    zipOutputStream.putNextEntry(new ZipEntry(filePdf.getName()));
                    int len2;
                    // 2.2 读入需要下载的文件的内容,打包到zip文件
                    while ((len2 = fileInputStream2.read(buffer2)) > 0) {
                        zipOutputStream.write(buffer2, 0, len2);
                    }
                } catch (Exception e){
                    log.error("导出zip文件,pdf.zip 失败");
                } finally {
                    StreamUtils.closeStream(fileInputStream2);
                }
            }, executor);

   CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(cf1, cf2);
            voidCompletableFuture.whenComplete((x, throwable) -> {
                try {
                    zipOutputStream.closeEntry();
                    zipOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (throwable != null) {
                    log.error("发生异常了");
                    return;
                } else {
                   if (FileUtil.file(tempzipPath).exists()) {
                       try {
                           downLoadFile(response, zipFileName, tempzipPath, xlsxPath, pdfZipPath);
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                   }
                }
            });
            voidCompletableFuture.join()
}

3 出现的问题

3.1 ZipOutputStream

使用这个API的时候,zipOutputStream.putNextEntry(new ZipEntry(“fileName”)),必须保证fileName是独一无二的,否则会报错:

  public void putNextEntry(ZipEntry e) throws IOException {
       ......
        // 这个地方
        if (! names.add(e.name)) {
            throw new ZipException("duplicate entry: " + e.name);
        }

3.2 Aliyun下载的时候

这个地方需要注意的是,需要把OSS的url进行处理,把你的公共前缀给去掉,否则会报错在指定的bucket上找不到文件,就是因为前缀未处理的原因。

例如: https://xxx.xxx.cn/jpg/xxx/12312.jpg
需要将https://xxx.xxx.cn前缀给去掉,否则会报错

3.3 为什么考虑异步呢?

因为导出Excel和导出用户头像的ZIP文件是两个不相干的操作,没必要让他同步进行,将其异步,主线程阻塞等待两个线程都完成,然后下载即可。

感谢大家,如果有帮助到您欢迎点个红心,你们的支持是我最大的动力,有问题欢迎评论区指正或者留言~~~~

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值