From c1c26519526d6ca3fff1376d5d47762d75aea20f Mon Sep 17 00:00:00 2001 From: AprilWind <2100166581@qq.com> Date: Fri, 24 Apr 2026 16:43:03 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=20Excel=20=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dromara/common/excel/utils/ExcelUtil.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java index 6079ee97f..2f586a3d5 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java @@ -119,15 +119,22 @@ public class ExcelUtil { * @param pageSize 每页条数 */ public static void exportExcelZip(List list, String sheetName, Class clazz, HttpServletResponse response, int pageSize) { + // 数据分页 List> pageList = ListUtil.partition(list, pageSize); + // 只有一页,直接导出普通Excel if (pageList.size() <= 1) { exportSingleExcel(list, sheetName, clazz, response); return; } + // 多线程生成所有Excel文件(字节数组) Map excelMap = buildExcelZipData(pageList, sheetName, clazz); + // 写入ZIP并下载 writeExcelZipResponse(sheetName, response, excelMap); } + /** + * 导出【单文件】Excel + */ private static void exportSingleExcel(List list, String sheetName, Class clazz, HttpServletResponse response) { try { resetResponse(sheetName, response); @@ -138,15 +145,26 @@ public class ExcelUtil { } } + /** + * 多线程并行生成多个Excel文件 + * 使用虚拟线程,高并发、低资源占用 + * + * @return Map<文件名, 文件字节数组> + */ private static Map buildExcelZipData(List> pageList, String sheetName, Class clazz) { + // 有序Map,保证文件按页码顺序打包 Map excelMap = new LinkedHashMap<>(pageList.size()); List>> futures = new ArrayList<>(pageList.size()); + + // 使用虚拟线程池执行导出任务 try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { + // 1. 提交所有分页导出任务 for (int i = 0; i < pageList.size(); i++) { int pageNum = i + 1; List pageData = pageList.get(i); futures.add(executor.submit(() -> buildExcelZipEntry(pageData, sheetName, clazz, pageNum))); } + // 2. 获取所有线程执行结果 for (Future> future : futures) { Map.Entry excel = getExcelZipEntry(future); excelMap.put(excel.getKey(), excel.getValue()); @@ -155,6 +173,13 @@ public class ExcelUtil { return excelMap; } + /** + * 单页Excel生成任务(线程执行单元) + * + * @param pageData 当前页数据 + * @param pageNum 当前页码 + * @return 文件名 + 文件字节 + */ private static Map.Entry buildExcelZipEntry(List pageData, String sheetName, Class clazz, int pageNum) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { String exportSheetName = sheetName + "_第" + pageNum + "页"; @@ -165,10 +190,15 @@ public class ExcelUtil { } } + /** + * 安全获取异步任务结果 + * 处理中断异常、执行异常,保证任务稳定 + */ private static Map.Entry getExcelZipEntry(Future> future) { try { return future.get(); } catch (InterruptedException e) { + // 恢复中断标志 Thread.currentThread().interrupt(); throw new RuntimeException("Excel导出线程被中断", e); } catch (ExecutionException e) { @@ -176,11 +206,19 @@ public class ExcelUtil { } } + /** + * 将多个Excel文件打包成ZIP并输出到浏览器下载 + * + * @param excelMap 文件名 -> 文件字节 + */ private static void writeExcelZipResponse(String sheetName, HttpServletResponse response, Map excelMap) { try { + // 设置ZIP下载响应头 response.setContentType("application/zip"); response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + URLEncoder.encode(sheetName, StandardCharsets.UTF_8) + ".zip"); + + // 压缩写入多个Excel文件 try (ZipOutputStream zipOut = ZipUtil.getZipOutputStream(response.getOutputStream(), CharsetUtil.CHARSET_UTF_8)) { for (Map.Entry entry : excelMap.entrySet()) { zipOut.putNextEntry(new ZipEntry(entry.getKey()));