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

文章目录

前言

工作中遇到一个需求,一次导出多个Excel 文件,并且每个excel中可能存在1到多个sheet页。

好在没有那种单元格合并的要求。

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

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

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

正文

一、项目依赖

xml 复制代码
<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 表格实体

java 复制代码
/**
 * 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实体

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

三、核心实现

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

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

java 复制代码
    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,会直接下载到浏览器。

java 复制代码
    /**
     * 导出多个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 导出为字节数组

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

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

四、调试

4.1 构建调试用的实体类

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

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

4.2 控制器调用

组装假数据,进行导出。

java 复制代码
@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 实现转换器

java 复制代码
    /**
     * 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 使用转换器

在构建建造器时,增加注册转换器即可。

相关推荐
程序猿乐锅10 小时前
【苍穹外卖 Day12| Excel 入门】
excel
用户2986985301416 小时前
Python 数据处理:XML 与 Excel 互转的实用指南
后端·python·excel
xiaopai9451 天前
采购系统和Excel表格区别:钢结构项目物资管理的真实较量
excel·建米软件·采购系统
协享科技1 天前
2026 年 AI 表格工具怎么选:ChatGPT、原生 Copilot/Gemini 与公式助手
人工智能·chatgpt·excel·copilot
用户298698530142 天前
前端实战:在 React 中使用 JavaScript 一键导出 Excel 图表与形状为图片
javascript·react.js·excel
Q一件事2 天前
arcpy实现分区统计到一张excel中
excel
AI刀刀2 天前
deepseek 内容粘贴后符号丢失怎么办?AI 导出鸭实测解决排版乱码问题
android·人工智能·excel·ai导出鸭
圣光SG3 天前
Excel 函数大全
算法·excel
小小的木头人3 天前
Python 批量解析 Excel 经纬度,调用高德地图 API 获取中文地址
开发语言·python·excel
观远数据3 天前
从离线开发到实时同步:DataFlow如何支撑企业级数据治理闭环
java·windows·microsoft·excel