【EasyExcel实践】导出多个sheet到多个excel文件,并压缩到一个zip文件

news/2024/7/21 5:25:15 标签: excel, 导出多个excel, 多个sheet, 压缩, zip

文章目录

  • 前言
  • 正文
    • 一、项目依赖
    • 二、封装表格实体和Sheet实体
      • 2.1 表格实体
      • 2.2 Sheet实体
    • 三、核心实现
      • 3.1 核心实现之导出为输出流
      • 3.2 web导出
      • 3.3 导出为字节数组
    • 四、调试
      • 4.1 构建调试用的实体类
      • 4.2 控制器调用
      • 4.3 测试结果
    • 五、注册大数转换器,长度大于15时,转换为字符串
      • 5.1 实现转换器
      • 5.2 使用转换器

前言

工作中遇到一个需求,一次导出多个Excel 文件,并且每个excel中可能存在1到多个sheet页。
好在没有那种单元格合并的要求。

总体的思路是,设计两个实体,一个表示表格,一个表示sheet 数据。并且表格包含一个list 类型的sheet对象。

然后再使用ZipOutputStreamExcelWriterBuilderEasyExcel#writerSheet(...) 等类和方法去组装表格,最终进行压缩

项目整体使用 java 8 和 阿里的easyexcel工具包。

正文

一、项目依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.2.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
        </dependency>


        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.11</version>

            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

二、封装表格实体和Sheet实体

2.1 表格实体

/**
 * excel数据
 */
@Data
public static class ExcelData {
	// sheet的列表
    private final List<ExcelShellData<?>> shellDataList = new ArrayList<>();
    // 表格文件名
    private String filename;

    public void addShellData(ExcelShellData<?> excelShellData) {
        this.shellDataList.add(excelShellData);
    }
}

2.2 Sheet实体

/**
 * sheet数据
 */
@Data
@AllArgsConstructor
public static class ExcelShellData<T> {
	// 数据
    private List<T> list;
    // sheet名
    private String sheetName;
    // 数据实体类型
    private Class<T> clazz;
}

三、核心实现

3.1 核心实现之导出为输出流

这一步主要组装表格数据,以及生成sheet。最终将数据放到输出流outputStream中 。


    private static void exportZipStream(List<ExcelData> excelDataList, OutputStream outputStream) {
        try {
            // 开始存入
            try (ZipOutputStream zipOut = new ZipOutputStream(outputStream)) {
                try {
                    for (ExcelData excelData : excelDataList) {
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                        com.alibaba.excel.ExcelWriter excelWriter = null;
                        try {
                            ExcelWriterBuilder builder = EasyExcel.write(byteArrayOutputStream).autoCloseStream(false)
                                    // 自动适配
                                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());

                            excelWriter = builder.build();
                            zipOut.putNextEntry(new ZipEntry(excelData.getFilename()));
                            // 开始写入excel
                            for (ExcelShellData<?> shellData : excelData.getShellDataList()) {
                                WriteSheet writeSheet = EasyExcel.writerSheet(shellData.getSheetName()).head(shellData.getClazz()).build();
                                excelWriter.write(shellData.getList(), writeSheet);
                            }

                        } catch (Exception e) {
                            throw new RuntimeException("导出Excel异常", e);
                        } finally {
                            if (excelWriter != null) {
                                excelWriter.finish();
                            }
                        }
                        byteArrayOutputStream.writeTo(zipOut);
                        zipOut.closeEntry();
                    }
                } catch (Exception e) {
                    throw new RuntimeException("导出Excel异常", e);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常", e);
        }
    }

3.2 web导出

可以调用本方法,直接在Controller中调用之后,当访问对应url,会直接下载到浏览器。

    /**
     * 导出多个sheet到多个excel文件,并压缩到一个zip文件
     */
    public static void exportZip(String zipFilename, List<ExcelData> excelDataList, HttpServletResponse response) {
        try {
            // 这里URLEncoder.encode可以防止中文乱码
            zipFilename = URLEncoder.encode(zipFilename, "utf-8");
            // 指定文件名
            response.setHeader("Content-disposition", "attachment;filename=" + zipFilename);
            response.setContentType("application/x-msdownload");
            response.setCharacterEncoding("utf-8");
            exportZipStream(excelDataList, response.getOutputStream());
        } catch (IOException e) {
            throw new RuntimeException("导出Excel异常", e);
        }
    }

3.3 导出为字节数组

当我们需要导出为字节数组时,可以调用本方法。之后随你怎么加工。

    /**
     * 导出多个sheet到多个excel文件,并压缩到一个zip文件。最终得到一个字节数组。
     */
    public static byte[] exportZip(List<ExcelData> excelDataList) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        exportZipStream(excelDataList, byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();
    }

四、调试

4.1 构建调试用的实体类

指定简单的几个字段作为导出数据。

    @Data
    @AllArgsConstructor
    public static class DemoData {
        @ExcelProperty("字符串标题")
        private String string;
        @ExcelProperty("日期标题")
        private Date date;
        @ExcelProperty("数字标题")
        private Double doubleData;
    }

4.2 控制器调用

组装假数据,进行导出。

@Controller
@RequestMapping("/excel")
public class ExcelDemoController {
	@GetMapping("/exportTransportDetail")
    public String exportTransportDetail(HttpServletResponse response) throws IOException {

        // 压缩包文件名
        String fileName = "结算单运单明细-" + System.currentTimeMillis() + ".zip";
        List<ExcelData> excelDataList = new ArrayList<>();
        // 第一个Excel
        ExcelData excelData1 = new ExcelData();
        excelData1.setFilename("结算单运单明细-1-" + System.currentTimeMillis() + ".xlsx");

        List<DemoData> demoData1 = new ArrayList<>();
        demoData1.add(new DemoData("excel-sheet1", new Date(), 123112.321));
        demoData1.add(new DemoData("excel-sheet1", new Date(), 34.3));

        List<DemoData> demoData2 = new ArrayList<>();
        demoData2.add(new DemoData("excel-sheet2", new Date(), 123112.321));
        demoData2.add(new DemoData("excel-sheet2", new Date(), 34.3));
        ExcelShellData<DemoData> shellData1 = new ExcelShellData<>(demoData1, "sheet1", DemoData.class);
        ExcelShellData<DemoData> shellData2 = new ExcelShellData<>(demoData2, "sheet2", DemoData.class);
        excelData1.addShellData(shellData1);
        excelData1.addShellData(shellData2);

        // 第2个Excel
        ExcelData excelData2 = new ExcelData();
        excelData2.setFilename("结算单运单明细-2-" + System.currentTimeMillis() +".xlsx");

        List<DemoData> demoData21 = new ArrayList<>();
        demoData21.add(new DemoData("excel-sheet21", new Date(), 123112.321));
        demoData21.add(new DemoData("excel-sheet22", new Date(), 34.3));

        List<DemoData> demoData22 = new ArrayList<>();
        demoData22.add(new DemoData("excel-sheet21", new Date(), 123112.321));
        demoData22.add(new DemoData("excel-sheet22", new Date(), 34.3));
        ExcelShellData<DemoData> shellData21 = new ExcelShellData<>(demoData21, "sheet1", DemoData.class);
        ExcelShellData<DemoData> shellData22 = new ExcelShellData<>(demoData22, "sheet2", DemoData.class);
        excelData2.addShellData(shellData21);
        excelData2.addShellData(shellData22);

        excelDataList.add(excelData1);
        excelDataList.add(excelData2);

        // 写法1///
        // exportZip(fileName, excelDataList , response);

        // 写法2///
        byte[] bytes = exportZip(excelDataList);
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);
        response.setContentType("application/x-msdownload");
        response.setCharacterEncoding("utf-8");
        response.getOutputStream().write(bytes);
        response.getOutputStream().flush();

        return "succ";
    }
}

4.3 测试结果

可以看到压缩包解压后的效果:
在这里插入图片描述
其中一个文件内容如下:
sheet1:
在这里插入图片描述

sheet2:
在这里插入图片描述

五、注册大数转换器,长度大于15时,转换为字符串

5.1 实现转换器


    /**
     * Excel 数值长度大于maxLength的数值转换为字符串
     */
    public static class ExcelBigNumberConvert implements Converter<Long> {

        private final int maxLength;

        public ExcelBigNumberConvert() {
            this(15);
        }

        public ExcelBigNumberConvert(Integer maxLength) {
           this.maxLength = maxLength;
        }

        @Override
        public Class<Long> supportJavaTypeKey() {
            return Long.class;
        }

        @Override
        public CellDataTypeEnum supportExcelTypeKey() {
            return CellDataTypeEnum.STRING;
        }

        @Override
        public Long convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
            Object data = cellData.getData();
            if (data == null) {
                return null;
            }
            String s = String.valueOf(data);
            if (s.matches("^\\d+$")) {
                return Long.parseLong(s);
            }
            return null;
        }

        @Override
        public CellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
            if (object != null) {
                String str = object.toString();
                if (str.length() > maxLength) {
                    return new CellData<>(str);
                }
            }
            return null;
        }
    }

5.2 使用转换器

在构建建造器时,增加注册转换器即可。
在这里插入图片描述


http://www.niftyadmin.cn/n/5223815.html

相关文章

Java---权限修饰符、final、static

文章目录 1. 权限修饰符2. final(最终态)3. static(静态) 1. 权限修饰符 修饰符同一个类中同一个包中的子类和无关类不同包的子类不同包的无关类private√默认√√protected√√√public√√√√ 2. final(最终态) 1. final关键字是最终的意思&#xff0c;可以修饰成员方法、…

多平台小程序编译适配,是否会让更多App互联互通?

随着科技的飞速发展&#xff0c;我们正迅速进入一个以数字化为主导的时代。 在这个时代中&#xff0c;通信、小程序、快应用、云服务器等平台连接类软件如火如荼的发展&#xff0c;手机、手表、AR/VR眼镜等智能移动穿戴设备迅速的升级迭代&#xff0c;5G、芯片、算力等基础设施…

第二十章——多线程

一.线程简介 线程的特点 1.进程是资源分配的最小单位&#xff0c;线程是最小的执行单位 2.一个进程可以有多个线程 3.线程共享进程资源 二.创建线程 1.继承Thread类 1.Thread类是java.lang包中的一个类&#xff0c;从这个类实例化的对象代表线程&#xff0c;程序员启动一…

C/C++---------------LeetCode第35. 搜索插入位置

插入的位置 题目及要求二分查找在main内使用 题目及要求 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: …

开源堡垒机Jumpserver

文章目录 开源堡垒机JumpserverJumpserver介绍安装环境部署安装jumpserver访问jumpserver的web界面 开源堡垒机Jumpserver Jumpserver介绍 Jumpserver 是全球首款完全开源的堡垒机&#xff0c;使用 GNU GPL v2.0 开源协议&#xff0c;是符合 4A 的运维安全审计系统。 Jumpse…

Apache2.4 AliasMatch导致301重定向问题?

环境&#xff1a;ubuntu18.04-desktop apache2版本&#xff1a; rootubuntu:/etc/apache2# apache2ctl -v Server version: Apache/2.4.29 (Ubuntu) Server built: 2023-03-08T17:34:33apache配置&#xff1a; DocumentRoot /var/www/html # Alias就没事 # Alias "/my…

nodejs接收前端分片并组合成完整文件

前端将文件分成多个块&#xff0c;并将每个块上传到服务器。 服务器使用Node.js创建一个HTTP服务器或Express服务器。 在服务器端&#xff0c;我们可以使用Multer或Busboy模块来处理上传的文件块。 在文件上传完成之后&#xff0c;我们可以使用fs模块的createWriteStream()方…

SNMP常用OID合集-系统参数

SNMP常用OID合集 系统参数&#xff08;1.3.6.1.2.1.1&#xff09; OID描述备注请求方式.1.3.6.1.2.1.1.1.0获取系统基本信息SysDescGET.1.3.6.1.2.1.1.3.0监控时间sysUptimeGET.1.3.6.1.2.1.1.4.0系统联系人sysContactGET.1.3.6.1.2.1.1.5.0获取机器名SysNameGET.1.3.6.1.2.1.…