开发者

SpringBoot整合EasyExcel实现大规模数据的并行导出与压缩下载

开发者 https://www.devze.com 2024-10-31 10:29 出处:网络 作者: Yaml墨韵
目录一、描述1、EasyExcel 简介2、并行导出的优势3、Zip 压缩下载的作用二、案例1、添加依赖2、准备数据模型3、使用 EasyExcel 导出 Excel 文件4、实现并行导出逻辑5、压缩文件为 zip6. 实现下载功能7. 完整的业务流
目录
  • 一、描述
    • 1、EasyExcel 简介
    • 2、并行导出的优势
    • 3、Zip 压缩下载的作用
  • 二、案例
    • 1、添加依赖
    • 2、准备数据模型
    • 3、使用 EasyExcel 导出 Excel 文件
    • 4、实现并行导出逻辑
    • 5、压缩文件为 zip
    • 6. 实现下载功能
    • 7. 完整的业务流程
  • 三、总结

    一、描述

    在 Spring Boot 应用中,整合 EasyExcel 实现并行导出数据并进行 Zip 压缩下载可以极大地提高数据处理效率和用户体验。以下是详细描述及结合代码的示例:

    1、EasyExcel 简介

    EasyExcel 是一个 Java 操作 Excel 的开源工具,它能以简单的方式读写大型 Excel 文件,并且性能高效、内存占用低。

    2、并行导出的优势

    在处理大量数据导出时,传统的单线程导出方式可能会非常耗时,导致用户等待时间过长。而并行导出可以充分利用多核处理器的优势,将数据分成多个部分同时进行处理,从而大大提高导出速度。

    3、Zip 压缩下载的作用

    当导出的数据量较大时,直接下载可能会导致网络传输缓慢或者出现问题。通过将导出的 Excel 文件进行 Zip 压缩,可以减小文件大小,提高下载速度,并且方便用户管理和存储。

    二、案例

    1、添加依赖

       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>easyexcel</artifactId>
           <version>3.1.1</version>
       </dependency>
     
        <!-- 简化实体类的get,set操作 --> 
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>
     
     
        <!-- Commons IO(用于压缩文件操作) -->    
        <dependency>        
            <groupId>commons-io</groupId>        
            <artifactId>commons-io</artifactId>        
            <version>2.11.0</version>    
        </dependency>

    2、准备数据模型

    创建一个用于存储数据的实体类:

    @Data
    public class Order {
        
        private Long id;    
        private String productName;    
        private Integer quantity;    
        private BigDecimal price;    
     
    }

    3、使用 EasyExcel 导出 Excel 文件

    定义了一个名为ExcelExportUtil 的工具类,其中包含一个静态方法writeToExcel。

    这个方法的主要作用是将一个订单列表(List<Order>)中的数据写入到指定路径的 Excel 文件中。它使用了阿里巴巴的 EasyExcel 库来实现这个功能。

    import com.alibaba.excel.EasyExcel;
    import java.io.File;
    import java.util.List;
     
    public class ExcelExportUtil {
        public static void writeToExcel(List<Order> orders, String filePath) {
            try {
                // 编程客栈使用 EasyExcel 进行 Excel 文件写入操作
                // 指定输出文件路径 filePath、数据类型 Order.class 和工作表名称 "订单数据"
                EasyExcel.write(filePath, Order.class)
                       .sheet("订单数据")
                       .doWrite(orders);
            } catch (Exception e) {
                // 如果在写入过程中出现异常,打印异常信息
                e.printStackTrace();
            }
        }
    }

    4、实现并行导出逻辑

    import java.util.List;
     
    public intephprface ExportService {
     
        // 并行导出订单的方法
        void exportOrdersInParallel(List<List<Order>> ordersList, String outputDir);
     
    }

    实现ExportService接口,这段代码实现了并行导出订单的功能。它使用了 Java 的CompletableFuture和自定义的线程池来同时处理多个订单列表的导出任务

    import java.util.List;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.stream.IntStream;
     
    @Service
    public class ExportServiceImpl implements ExportService{
     
        // 创建一个固定大小为 10 的线程池
        private final ExecutorService executor = Executors.newFixedThreadPool(10);
     
        // 并行导出订单的方法
        public void exportOrdersInParallel(List<Liwww.devze.comst<Order>> ordersList, String outputDir) {
            // 创建一个 CompletableFuture 数组来存储每个任务的 Future 对象
            CompletableFuture<Void>[] futures = new CompletableFuture[ordersList.size()];
     
            // 遍历订单列表,为每个子列表创建一个异步任务
            IntStream.range(0, ordersList.size()).forEach(index -> {
                List<Order> orders = ordersList.get(index);
                futures[index] = CompletableFuture.runAsync(() -> {
                    // 生成文件名
                    String fileName = "订单_" + Thread.currentThread().getId() + ".xlsx";
                    try {
                        // 调用工具方法将订单写入 Excel 文件
                        ExcelExportUtil.writeOrdersToExcel(orders, outputDir + fileName);
                    } catch (Exception e) {
                        // 如果出现异常,打印异常信息
                        e.printStackTrace();
                    }
                }, executor);
            });
     
            // 等待所有任务完成
            CompletableFuture.allOf(futures).join();
        }
    }

    以下是对代码的简单解释:

    1. 创建一个固定大小为 10 的线程池Executors,用于执行异步任务。

    2. exportOrdersInParallel方法接受一个订单列表和输出目录作为参数。

    3. 创建一个CompletableFuture<Void>[]数组来存储每个异步任务的 Future 对象。

    4. 使用IntStream.range遍历订单列表的索引,为每个订单子列表创建一个异步任务。

    5. 在异步任务中,生成文件名,然后尝试调用ExcelExportUtil.writeOrdersToExcel方法将订单写入 Excel 文件。如果出现异常,打印异常信息。

    6. 最后,使用CompletableFuture.allOf等待所有异步任务完成。

    请注意,这段代码假设存在一个Order类和一个ExcelExportUtil工具类,其中包含了将订单写入 Excel 文件的方法

    5、压缩文件为 zip

    完成 Excel 文件的导出后,我们需要将这些文件压缩成一个 zip 文件。

    5.1 使用 ZipOutputStream

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipOutputStream;
     
    public class ZipUtil {
        public static void zipFiles(String sourceDir, String zipFile) throws IOException {
            // 创建输出的 ZIP 文件流
            try (FileOutputStream fos = new FileOutputStream(zipFile);
                 ZipOutputStream androidzipOut = new ZipOutputStream(fos)) {
     
                // 获取要压缩的源目录下的文件列表
                File fileToZip = new File(sourceDir);
                if (fileToZip.isDirectory()) {
                    for (File file : fileToZip.listFiles()) {
                        if (file.isFile()) {
                            try (FileInputStream fis = new FileInputStream(file)) {
                                // 创建 ZIP 条目,表示要添加到 ZIP 文件中的文件
                                ZipEntry zipEntry = new ZipEntry(file.getName());
                                zipOut.putNextEntry(zipEntry);
     
                                byte[] bytes = new byte[1024];
                                int length;
                                // 循环读取文件内容并写入 ZIP 文件
                                while ((length = fis.read(bytes)) >= 0) {
                                    zipOut.write(bytes, 0, length);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    以下是对代码的详细解释:

    1. zipFiles方法接受两个参数:sourceDir表示要压缩的源目录路径,zipFile表示输出的 ZIP 文件路径。

    2. 在方法内部,首先创建了一个FileOutputStream和一个ZipOutputStream,用于写入 ZIP 文件。

    3. 然后获取源目录下的文件列表。如果源目录是一个文件夹,则遍历其中的文件。

    4. 对于每个文件,创建一个FileInputStream来读取文件内容。

    5. 创建一个ZipEntry,表示要添加到 ZIP 文件中的文件条目,条目名称为文件的名称。

    6. 将ZipEntry添加到ZipOutputStream中。

    7. 使用一个循环,每次读取 1024 字节的数据,并将其写入到 ZIP 文件中。

    8. 最后,关闭所有的输入流和输出流。

    请注意,这段代码假设源目录中只包含文件,不包含子文件夹。如果需要递归压缩子文件夹中的文件,可以对代码进行进一步的扩展

    6. 实现下载功能

    在 Spring Boot 中,可以通过 HTTP 响应的形式将生成的 zip 文件提供给前端下载。

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import org.springframework.core.io.InputStreamResource;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
     
    @RestController
    public class FileDownloadController {
        @GetMapping("/downloadzip")
        public ResponseEntity<InputStreamResource> downloadZip() throws IOException {
            // 定义源目录路径,这里假设是 /tmp/excel_files
            String sourceDir = "/tmp/excel_files";
            // 定义生成的 ZIP 文件路径,这里假设是 /tmp/orders.zip
            String zipFilePath = "/tmp/orders.zip";
     
            try {
                // 调用 ZipUtil 类的方法来压缩源目录中的文件到指定的 ZIP 文件
                ZipUtil.zipFiles(sourceDir, zipFilePath);
     
                // 创建一个 File 对象表示要下载的 ZIP 文件
                File file = new File(zipFilePath);
     
                // 创建一个 InputStreamResource 对象,从文件输入流中读取数据
                InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
     
                // 设置 HTTP 响应头信息
                HttpHeaders headers = new HttpHeaders();
                // 设置内容处置头,指示浏览器下载文件,并指定文件名
                headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName());
                // 设置内容类型为 application/octet-stream,表示二进制数据
                headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
                // 设置内容长度,以便浏览器正确显示下载进度
                headers.setContentLength(file.length());
     
                // 返回一个包含 InputStreamResource 的 ResponseEntity 对象,
                // 表示成功的 HTTP 响应,包含要下载的文件数据和设置好的响应头
                return ResponseEntity.ok()
                       .headers(headers)
                       .body(resource);
            } catch (IOException e) {
                // 如果在压缩文件或读取文件过程中出现 IOException,返回内部服务器错误响应
                return ResponseEntity.status(500).build();
            } finally {
                // 在方法结束时,无论是否发生异常,尝试删除生成的 ZIP 文件
                File zipFile = new File(zipFilePath);
                if (zipFile.exists()) {
                    zipFile.delete();
                }
            }
        }
    }

    7. 完整的业务流程

    1. 数据分批处理:假设我们需要导出上百万条订单数据,为了高效管理和处理,首先需要根据用户ID、订单日期或其他相关条件将庞大的数据集进行分片。每片数据将被独立处理并导出到不同的 Excel 文件中,这样可以有效减少单次处理的数据量,避免内存溢出等问题。

    2. 并行处理:为了进一步提升导出效率,我们可以利用 Java 的 CompletableFuture 框架来并行处理各个数据片段。通过为每个数据片段分配一个独立的导出任务,并让这些任务在多个线程上同时执行,可以确保多个 Excel 文件能够同时生成,从而显著加快整体处理速度。

    3. 文件压缩:在所有 Excel 文件成功生成后,我们需要将这些文件整合到一个压缩包中以便于传输和存储。这时,可以使用 ZipOutputStream 类来创建一个 zip 文件,并将所有生成的 Excel 文件逐一添加到这个 zip 文件中进行压缩。这样做不仅可以减少文件占用的空间,还能提高文件传输的效率。

    4. 提供下载:为了让用户能够方便地获取压缩后的数据包,我们需要在前端提供一个下载链接。当用户点击该链接时,服务器会将压缩包发送给用户的浏览器进行下载。为了确保下载过程的安全性和可靠性,可以采用 HTTPS 协议进行数据传输,并对下载链接进行时效性验证和权限控制。

    三、总结

    本文介绍了使用Spring Boot和EasyExcel实现大规模数据高效导出的方法,通过数据分批处理、并行处理和文件压缩等技术手段,提升了导出效率并优化了用户体验,特别适用于需要处理大量数据的企业系统。

    以上就是SpringBoot整合EasyExcel实现大规模数据的并行导出与压缩下载的详细内容,更http://www.devze.com多关于SpringBoot EasyExcel数据导出和下载的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号