SpringBoot中大量数据导出使用EasyExcel导出多个excel文件并压缩

SpringBoot中大量数据导出使用EasyExcel导出多个excel文件并压缩SpringBoot 的同步 excel 导出方式中 服务会阻塞直到 Excel 文件生成完毕 如果导出数据很多时 效率低体验差

欢迎大家来到IT世界,在知识的湖畔探索吧!

SpringBoot的同步excel导出方式中,服务会阻塞直到Excel文件生成完毕,如果导出数据很多时,效率低体验差。有效的方案是将导出数据拆分后利用CompletableFuture,将导出任务异步化,并行使用easyExcel导出多个excel文件,最后将所有文件压缩成ZIP格式以方便下载。

Springboot环境下基于以上方案,下面代码的高质量的完成导出销售订单信息到Excel文件,并将多个Excel文件打包成一个ZIP文件,最后发送给客户端:

  1. 控制器层代码
@RestController public class SalesOrderController { @Resource private SalesOrderExportService salesOrderExportService; @PostMapping(value = "/salesOrder/export") public void salesOrderExport(@RequestBody @Validated RequestDto req, HttpServletResponse response) { salesOrderExportService.salesOrderExport(req, response); } } 

欢迎大家来到IT世界,在知识的湖畔探索吧!

  1. 服务层代码

负责执行销售订单的导出逻辑:

  • 1. 将多个Excel文件打包成ZIP文件
  • 2. 多线程ThreadPoolTaskExecutor并行处理销售订单的导出
欢迎大家来到IT世界,在知识的湖畔探索吧!@Slf4j @Service public class SalesOrderExportService { @Autowired @Qualifier("threadPoolTask") private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Resource private OrderManager OrderManager; public void salesOrderExport(RequestDto req, HttpServletResponse response) { // 获取导出数据,每个SalesOrder实例需要分别导出到一个excel文件 List<SalesOrder> orderDataList = OrderManager.getOrder(req.getUserCode()); // 略...校验数据 InputStream zipFileInputStream = null; Path tempZipFilePath = null; Path tempDir = null; // 获取导出模板 try (InputStream templateInputStream = this.getClass().getClassLoader().getResourceAsStream("template/order_template.xlsx"); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();) { if (Objects.isNull(templateInputStream)) { throw new RuntimeException("获取模版文件异常"); } // 多线程服用一个文件流 IOUtils.copy(templateInputStream, outputStream); // 创建临时excel文件导出目录,用于将多个excel导出到此目录下 Path tmpDirRef = (tempDir = Files.createTempDirectory(req.userCode() + "dir_prefix")); // 每5个salesOrder一个线程并行导出到excel文件中 CompletableFuture[] salesOrderCf = Lists.partition(orderDataList, 5).stream() .map(orderDataSubList -> CompletableFuture .supplyAsync(() -> orderDataSubList.stream() .map(orderData -> this.exportExcelToFile(tmpDirRef, outputStream, orderData)) .collect(Collectors.toList()), threadPoolTaskExecutor) .exceptionally(e -> {throw new RuntimeException(e);})) .toArray(CompletableFuture[]::new); // 等待所有excel文件导出完成 CompletableFuture.allOf(salesOrderCf).get(3, TimeUnit.MINUTES); // 创建临时zip文件 tempZipFilePath = Files.createTempFile(req.userCode() + TMP_ZIP_DIR_PRE, ".zip"); // 将excel目录下的所有文件压缩到zip文件中,zipUtil有很多工具包都有 ZipUtil.zip(tempDir.toString(), tempZipFilePath.toString()); response.setContentType("application/octet-stream;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(tempZipFilePath.toFile().getName(), "utf-8")); // 写zip文件流到response zipFileInputStream = Files.newInputStream(tempZipFilePath); IOUtils.copy(zipFileInputStream, response.getOutputStream()); } catch (Exception e) { log.error("salesOrderExport,异常:", e); throw new RuntimeException("导出异常,请稍后重拾"); } finally { try { // 关闭流 if (Objects.nonNull(zipFileInputStream)) { zipFileInputStream.close(); } // 删除临时文件及目录 if (Objects.nonNull(tempDir)) { Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.deleteIfExists(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.deleteIfExists(dir); return FileVisitResult.CONTINUE; } }); } if (Objects.nonNull(tempZipFilePath)) { Files.deleteIfExists(tempZipFilePath); } } catch (Exception e) { log.error("salesOrderExport, 关闭文件流失败:", e); } } 
  • 使用EasyExcel库基于模板导出每个销售订单到单独的Excel文件中
 / * 导出单个excle文件,上面的多线程代码调用 / private Path exportExcelToFile(Path temporaryDir, ByteArrayOutputStream templateOutputStream, SalesOrder data) { Path temproaryFilePath = null; try { // 创建临时文件 temproaryFilePath = Files.createTempFile(temporaryDir, data.getOrderNo(), ExcelTypeEnum.XLSX.getValue()); } catch (IOException e) { throw new RuntimeException("exportExcelToFile,创建excel临时文件失败:" + data.getOrderNo()); } try (InputStream templateInputStream = new ByteArrayInputStream(templateOutputStream.toByteArray()); OutputStream temporaryFileOs = Files.newOutputStream(temproaryFilePath); BufferedOutputStream tempOutStream = new BufferedOutputStream(temporaryFileOs)) { // 使用easyExcel的模板功能导出订单数据到临时文件中 ExcelWriter excelWriter = EasyExcel.write(tempOutStream, SalesOrder.class) .withTemplate(templateInputStream).excelType(ExcelTypeEnum.XLSX).build(); // 填充模板数据 WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); excelWriter.fill(new FillWrapper("goods", data.getGoodsList()), fillConfig, writeSheet); excelWriter.fill(data, writeSheet); excelWriter.finish(); // temproaryFilePath.toFile().deleteOnExit(); return temproaryFilePath; } catch (Exception e) { throw new RuntimeException("exportExcelToFile,导出excel文件失败:" + data.getOrderNo(), e); } } 

代码亮点分析

  1. 多线程处理
  2. 通过CompletableFuture和ThreadPoolTaskExecutor,将销售订单的导出任务分配给多个线程并行执行,显著提高了处理大量订单时的性能。
  3. 使用Lists.partition方法将订单列表分割成多个子列表,每个子列表由一个线程处理,这里每5个订单一个线程。
  4. Excel模板导出
  5. 利用EasyExcel的模板功能,可以基于预定义的Excel模板填充数据,从而生成格式统一的销售订单Excel文件。
  6. 模板文件通过类加载器的getResourceAsStream方法加载,便维护。
  7. 将多个Excel文件打包成一个ZIP文件,方便用户下载和管理。
  8. 资源清理
  9. 方法执行完毕后,及时关闭打开的文件流和删除临时生成的Excel文件和目录,避免了资源泄露。
  10. 使用try-with-resources和try-catch-finally来确保资源的正确关闭和清理。
  11. 错误处理
  12. 在方法执行过程中,对可能出现的异常进行了捕获和处理,确保服务的健壮。
  13. 对于无法恢复的错误,通过抛出运行时异常的方式通知调用者,并记录了详细的错误日志。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/87139.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信