当前位置: 首页 > article >正文

EasyExcel的应用

一、简单使用

        引入依赖:
        这里我们可以使用最新的4.0.2版本,也可以选择之前的稳定版本,3.1.x以后的版本API大致相同,新的版本也会向前兼容(3.1.x之前的版本,部分API可能在高版本被废弃),关于POI、JDK版本适配问题,具体可参考官网-版本说明。

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

        下载excel文件:

    @GetMapping("/download")
    public void excelDownload(HttpServletResponse response) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        EasyExcel.write(response.getOutputStream(), Data.class).sheet("模板").doWrite(datas);
        String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    }

        读取excel文件:

    @PostMapping("/read")
    public void read(MultipartFile file) throws IOException {
        
          1、这只是简单演示,一般不使用 doReadSync 方法,
             此方法同步执行的,即它会阻塞当前线程,直到读取完整个Excel文件并返回所有数据。
             读取大型文件时,可能会导致程序响应变慢或阻塞。
          2、使用head映射字段时,该实体类上不能加 @Accessors 注解,加上此注解
             会字段映射不成功。
          3、一般会使用监听器 + doRead 方法实现excel文件的读取
        List<Data> datas = EasyExcel.read(file.getInputStream()).sheet().head(Data.class).doReadSync();
        System.out.println(datas);
    }

二、常用注解

        1、@ExcelProperty注解

                这个注解应该是最常用的注解,通常用来映射字段跟excel的列名,有以下几个属性:

名称默认值描述
value用于匹配excel中的头,必须全匹配,如果有多行头,会匹配最后一行头
orderInteger.MAX_VALUE优先级高于value,会根据order的顺序来匹配实体和excel中数据的顺序
index-1优先级高于valueorder,会根据index直接指定到excel中具体的哪一列
converter自动选择指定当前字段用什么转换器,默认会自动选择。写的情况下只要实现com.alibaba.excel.converters.Converter#convertToExcelData(com.alibaba.excel.converters.WriteConverterContext<T>) 方法即可

         注意: 

         1、如果没有特殊的调整一般,使用value属性就够了,在读取或者导出时都能匹配或者映射为对应的列名。
         2、value 跟 index 可以在导出数据的时候配合使用,value指定列名,index指定该列的顺序,例如:

    @ExcelProperty(value = "性别",index = 3) 代表列名为 性别,导出到第三列的位置。
    但是在导入时,如果设置了order属性,表示会根据指定列来匹配字段,例如上面就会将第三列匹配为性别字段,如果该列字段为空,或者字段类型不匹配就会报错,一般在读取数据时不会这么使用这个属性。

        3、order 属性代表按顺序匹配,比如说导出数据时,会按照字段上该属性的顺序,依次为列设置对应字段的值,比如order最小的,就匹配第一列的值,依次往后,在导出时也是一样,order最小的值,导出到第一列依次往后。

        4、converter:自定义的类型转换器,该属性可以实现自定义处理类,这个功能通常用来在 Excel 数据与 Java 对象之间进行特定格式的转换,例如日期、布尔值、自定义对象等。

  • 实现 Converter 接口
    要自定义一个转换器,需要实现 EasyExcel 提供的 Converter 接口。

  • 重写必要的方法

    • supportJavaTypeKey(): 指定支持的 Java 数据类型。
    • convertToExcelData(): 将 Java 数据类型转换为 Excel 单元格数据。
    • convertToJavaData(): 将 Excel 单元格数据转换为 Java 数据类型

        EasyExcel 自带了一些常用的转换器(例如 LocalDateConverterIntegerConverter 等),可以直接使用而无需自定义。

         例如:在姓名上加上该属性:

@ExcelProperty(value = "姓名",converter = ExcelStringConverter.class)
private String name;

        实现自定义处理类:

public class ExcelStringConverter implements Converter<String> {


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

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

    @Override
    public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return cellData.getStringValue() + "导入数据进行处理!";
    }

    @Override
    public WriteCellData<?> convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return new WriteCellData<>(value + "导出数据进行处理");
    }

}

        例如实现在 Excel 中用 "是""否" 表示布尔值,而不是默认的 true/false

public class BooleanStringConverter implements Converter<Boolean> {

    @Override
    public Class<?> supportJavaTypeKey() {
        return Boolean.class; // 支持的 Java 类型
    }

    @Override
    public WriteCellData<?> convertToExcelData(Boolean value, ExcelContentProperty contentProperty) {
        return new WriteCellData<>(value ? "是" : "否"); // 将布尔值转为字符串
    }

    @Override
    public Boolean convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty) {
        String stringValue = cellData.getStringValue();
        return "是".equals(stringValue); // 将字符串 "是"/"否" 转为布尔值
    }
}

         2、@ExcelIgnore注解
  • 作用范围:数据实体类的字段上;
  • 注解释义:当前字段不参与excel列的匹配,即处理时忽略该字段;

        在默认情况下,数据模型类中的所有字段都会参与匹配,可如果你定义的Java类中,有些字段在读写时并不需要参与进来,这时就可以给对应字段加上@ExcelIgnore注解,具备该注解的字段会被忽略。

        3、@ExcelIgnoreUnannotated注解 
  • 作用范围:数据模型类上;
  • 注解释义:匹配列时忽略所有未使用@ExcelProperty注解的字段;

        如果类中许多字段都不想参与excel读写,而你又嫌挨个加@ExcelIgnore注解麻烦,这时就可以直接在类上加一个@ExcelIgnoreUnannotated注解,以此来忽略所有未添加@ExcelProperty注解的字段。

        4、@DateTimeFormat注解
  • 作用范围:数据实体类的字段上;
  • 注解释义:用String接收日期数据时,会根据指定格式转换日期;
  • 可选参数:
    • value:日期数据转换为字符串的目标格式;
    • use1904windowing:excel日期数据默认从1900年开始,但有些会从1904开始;

        在解析excel文件时,如果使用String字段接收日期数据,就会根据指定的格式转换数据,格式可以参考java.text.SimpleDateFormat的写法,例如yyyy-MM-dd HH:mm:ss。而在往excel写数据时,如果Java中的字段类型为Date、LocalDate、LocalDateTime等日期类型,则会将日期数据转换为指定的格式写入对应列。

        例如:

    @DateTimeFormat(value = "yyyy年MM月dd日 HH时mm分ss秒")

        5、@NumberFormat注解     
  • 作用范围:数据实体类的字段上;
  • 注解释义:用String接收数值数据时,会根据指定格式转换数值;
  • 可选参数:
    • value:数值转换为字符串的目标格式;
    • roundingMode:数值格式化时的舍入模式,如四舍五入、向上取整等;

        这个注解和前一个注解类似,只不过是用于将非整数类型的数值数据转换成给定格式,格式可以参考java.text.DecimalFormat的写法,如#.##。除了可以指定格式外,还可以指定舍入模式,枚举可参考java.math.RoundingMode类。

        使用方法:

  • 指定数字格式
    使用 @NumberFormat 注解的 value 属性指定数字格式。例如:

    • #: 表示一个数字字符(整数部分)。
    • 0: 表示一个数字字符(小数部分,不足补零)。
    • ,: 表示千分位分隔符。
    • .: 表示小数点。
  • @ExcelProperty 搭配使用
    在数值类型字段上添加 @NumberFormat,并用 @ExcelProperty 指定列名。

    @ExcelProperty("销售金额")
    @NumberFormat("#,##0.00") // 指定数字格式,保留两位小数,带千分位
    private BigDecimal salesAmount;

       

三、常用生成注解

        1、@ColumnWidth注解
  • 作用范围:数据模型类上、字段上;
  • 注解释义:设置列的宽度;

        这个注解如果加在类上,则会对所有字段生效;如果单独加在某个字段上,则只对特定的列有效,单位是px

        例如:

    @ExcelProperty("销售金额")
    @ColumnWidth(200)
    private BigDecimal salesAmount;
        2、@ContentFontStyle注解
  • 作用范围:数据模型类上、字段上;
  • 注解释义:用于设置单元格内容字体格式的注解;
  • 可选参数:
    • fontName:字体名称,如“黑体、宋体、Arial”等;
    • fontHeightInPoints:字体高度,以磅为单位;
    • italic:是否设置斜体(字体倾斜);
    • strikeout:是否设置删除线;
    • color:字体的颜色,通过RGB值来设置;
    • typeOffset:偏移量,用于调整字体的位置;
    • underline:是否添加下划线;
    • bold:是否对字体加粗;
    • charset:设置编码格式,只能对全局生效(字段上设置无效)。

        这个注解用于设置主体内容的字体样式(不包含表头),与上个注解同理,加在类上对整个excel文件生效,加在字段上只对单列有效,可以通过该注解来设置字体风格、高度、是否斜体等属性。

    @ExcelProperty(value ="日期")
    @DateTimeFormat(value = "yyyy年MM月dd日 HH时mm分ss秒")
    @ContentFontStyle(
            fontName = "黑体",/* 字体类型 */
            fontHeightInPoints = 50, /* 字体高度,以磅为单位; */
            italic = BooleanEnum.TRUE,/* 是否设置斜体(字体倾斜); */
            strikeout = BooleanEnum.TRUE,/* 是否设置删除线; */
            color = 14,/* 字体的颜色,通过RGB值来设置;  0	黑色 (默认)
                                                    9	红色
                                                    10	绿色
                                                    12	蓝色
                                                    13	黄色
                                                    14	粉色
                                                    15	青色
                                                    16	白色 */
            typeOffset = 1,/*偏移量,用于调整字体的位置; */
            underline = 1,/* 是否添加下划线; */
            bold = BooleanEnum.TRUE/* 是否对字体加粗; */
    )
    private LocalDateTime date;

       

        3、@ContentRowHeight注解
  • 作用范围:数据模型类上;
  • 注解释义:用于设置行高。

        这个注解只能加在类上面,作用就是设置单元格的高度,但这里不能像Excel那样精准设置不同行的高度,只能设置所有单元格统一的高度。

@ContentRowHeight(80)
        4、@ContentStyle注解
  • 作用范围:数据模型类上、字段上;
  • 注解释义:用于设置内容格式;
属性名类型功能描述
horizontalAlignmentHorizontalAlignment设置单元格内容的水平对齐方式(如左对齐、居中、右对齐)。
verticalAlignmentVerticalAlignment设置单元格内容的垂直对齐方式(如顶部对齐、中间对齐、底部对齐)。
wrappedboolean是否自动换行(true 开启自动换行)。
dataFormatshort设置单元格的数据格式(例如日期格式、数字格式等)。
fillPatternTypeFillPatternType设置单元格的填充模式(如纯色填充、斜线填充等)。
fillForegroundColorshort设置单元格的前景色(通过颜色索引表示)。
fillBackgroundColorshort设置单元格的背景色(通过颜色索引表示)。
borderLeftBorderStyle设置单元格左边框样式(如实线、虚线等)。
borderRightBorderStyle设置单元格右边框样式。
borderTopBorderStyle设置单元格顶部边框样式。
borderBottomBorderStyle设置单元格底部边框样式。
leftBorderColorshort设置单元格左边框颜色。
rightBorderColorshort设置单元格右边框颜色。
topBorderColorshort设置单元格顶部边框颜色。
bottomBorderColorshort设置单元格底部边框颜色。
    @ContentStyle(
            horizontalAlignment = HorizontalAlignmentEnum.CENTER,/* 水平对齐方式,如居中、左对齐等; */
            verticalAlignment = VerticalAlignmentEnum.CENTER,/*垂直对齐方式,如上对齐、下对齐等;*/
            wrapped = BooleanEnum.TRUE, /* 设置文本是否应该换行(自动根据内容长度换行); */
            dataFormat = 0, /*数据格式,对应excel的内置数据格式; */
            fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND,/*设置单元格的填充模式(如纯色填充、斜线填充等)*/
            fillForegroundColor = 9,/*设置单元格的前景色(通过颜色索引表示)。*/
            fillBackgroundColor = 12,/*设置单元格的背景色(通过颜色索引表示)。*/
            borderLeft = BorderStyleEnum.THICK,/*设置单元格左边框样式(如实线、虚线等)。*/
            borderRight = BorderStyleEnum.THICK,/*设置单元格右边框样式。*/
            borderTop = BorderStyleEnum.THICK,/*设置单元格顶部边框样式。*/
            borderBottom = BorderStyleEnum.THICK,/*设置单元格底部边框样式。*/
            leftBorderColor = 14,/*设置单元格左边框颜色。*/
            rightBorderColor = 14,/*设置单元格右边框颜色。*/
            topBorderColor = 14,/*设置单元格顶部边框颜色。*/
            bottomBorderColor = 14/*设置单元格底部边框颜色。*/
    )

        这个注解的属性还有很多,需要的话可以自行再查阅。

        5、@HeadFontStyle注解
  • 作用范围:数据模型类上、字段上;
  • 注解释义:用于定制标题字体格式。

        这个注解的作用和可选参数,与@ContentFontStyle注解类似,不过这个注解是针对列头(表头)有效罢了。

  • 可选参数:
    • fontName:字体名称,例如 Arial宋体 等。
    • fontHeightInPoints:字体大小,以磅为单位.
    • bold:是否加粗。
    • color:字体颜色,使用 Excel 的内置颜色索引值。

        6、@HeadRowHeight注解
  • 作用范围:数据模型类上;
  • 注解释义:用于设置标题行的行高。

        此注解的作用参考@ContentRowHeight注解,当前注解只对表头生效。        

@HeadRowHeight(30) // 设置表头行高为 30pt
        7、@HeadStyle注解 
  • 作用范围:数据模型类上
  • 注解释义:用于设置标题样式。

        该注解的作用和可选参数参考@ContentStyle注解,但是当前注解只对表头生效。

        8、@OnceAbsoluteMerge注解
  • 作用范围:数据模型类上;
  • 注解释义:用于合并指定的单元格;
  • 可选参数:
    • firstRowIndex:从哪行开始合并;
    • lastRowIndex:到哪行结束合并;
    • firstColumnIndex:从哪列开始合并;
    • lastColumnIndex:到哪列结束合并。

        从这个注解提供的可选参数就能看出具体作用,这是通过单元格行、列索引的方式,指定生成excel文件时要合并的区域。不过要注意,使用该注解只能合并一次(对应OnceAbsoluteMerge这个合并策略类)。

@OnceAbsoluteMerge(firstRowIndex = 0, lastRowIndex = 1, firstColumnIndex = 0, lastColumnIndex = 1)

        9、@ContentLoopMerge注解
  • 作用范围:数据模型类的字段上;
  • 注解释义:用于合并单元格;
  • 可选参数:
    • eachRow:指定每x行合并为一行;
    • columnExtend:指定每x列合并为一列。

        该注解也是用于合并单元格的,但是可以合并多次,不过只能实现每隔n个单元格合并,使用起来限制很大,通常也不会选择通过这种注解的形式来合并单元格,这里了解即可。

四、常用读取Excel方法:

        1、同步读取所有数据后返回

        同步的返回,不推荐使用,如果数据量大会把数据放到内存里面,会影响性能。这里只做简单得举例:

    @PostMapping("/import")
    public void importExcel(MultipartFile file) throws IOException {

        // head: 指定读用哪个class去读
        // headRowNumber: 指定行头,如果行头指定错误可能会读取不到数据。如果多行头,可以设置其他值。没有指定头,也就是默认是第1行。
        // sheet:指定读哪个sheet页,从0开始,第一个sheet是0,第二个是1,默认就是读第一个
        // doReadSync: 同步读,读取完所有数据返回
        List<ExcelDemo> list = EasyExcel.read(file.getInputStream()).headRowNumber(1).head(ExcelDemo.class).sheet().doReadSync();
        for (ExcelDemo data : list) {
            log.info("读取到数据:{}", JSON.toJSONString(data));
        }

    }
        2、使用监听器读取所有数据

        监听器是EasyExcel常用的一个方法,监听器的好处就是可以一行一行获取数据,不用全部读完在进行处理。

        监听器示例:

@Slf4j
public class ExcelDemoListener extends AnalysisEventListener<ExcelDemo> {

    private final List<ExcelDemo> data = new ArrayList<>();

    /**
     * 每解析一条数据都会触发一次invoke()方法
     */
    @Override
    public void invoke(ExcelDemo excelDemo, AnalysisContext analysisContext) {
        log.info("成功解析到一条数据:{}", excelDemo);
        data.add(excelDemo);
    }

    /**
     * 当一个excel文件所有数据解析完成后,会触发此方法
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("所有数据都已解析完毕!");
    }

    public List<ExcelDemo> getData() {
        return data;
    }
}

        使用监听器读取excel文件:

    @PostMapping("/import")
    public void importExcel(MultipartFile file) throws IOException {

        // 监听器需要手动new出来
        ExcelDemoListener excelDemoListener = new ExcelDemoListener();
        // 将监听器放入read方法中,会走监听器内部的方法来读取数据                      EasyExcel.read(file.getInputStream(),ExcelDemo.class,excelDemoListener).sheet().doRead();
        List<ExcelDemo> data = excelDemoListener.getData();
        log.info("总共解析到到" + data.size() + "条数据!");

    }

        监听器内部逻辑可以自行定义,一般会设置数据上限,当数据读取到上限时,就自动批量存储到数据库中。

        3、读多个sheet

        一次读取全部sheet,需要使用  doReadAll 方法,这个方法一次读取全部sheet页,并传给监听器处理。这中适用于全部sheet页全部都是同一个实体类接收。

        ExcelDemoListener excelDemoListener = new ExcelDemoListener();
        EasyExcel.read(file.getInputStream(),ExcelDemo.class,excelDemoListener).doReadAll();
        List<ExcelDemo> data = excelDemoListener.getData();
        log.info("总共解析到到" + data.size() + "条数据!");

        读取指定的sheet页,并指定不同的实体类接收。

        try (ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build()) {
            // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
            ExcelDemoListener excelDemoListener = new ExcelDemoListener();
            ReadSheet readSheet1 =
                    EasyExcel.readSheet(0).head(ExcelDemo.class).registerReadListener(excelDemoListener).build();
            ReadSheet readSheet2 =
                    EasyExcel.readSheet(1).head(ExcelDemo.class).registerReadListener(excelDemoListener).build();
            // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
            excelReader.read(readSheet1, readSheet2);
        }

        4、日期、数字或者自定义格式转换

        需要用到上面的注解,以及自定义转换器实现类。

        5、多行头

        当读取excel时,如果行头并不是第一行,就需要配合 headRowNumber 方法指定行头是哪一行,但是如果指定的不对,会导致数据读取失败。

        ExcelDemoListener excelDemoListener = new ExcelDemoListener();
        EasyExcel.read(file.getInputStream(),ExcelDemo.class,excelDemoListener).sheet()
                // 默认读取第一行为表头,如果第一行不是,需要单独设置headRowNumber,从0开始
                .headRowNumber(1).doRead();
        List<ExcelDemo> data = excelDemoListener.getData();
        log.info("总共解析到到" + data.size() + "条数据!");
        6、读取表头数据

        只需要在监听器中实现一个方法,只要重写invokeHeadMap方法即可

    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
        // 如果想转成成 Map<Integer,String>
        // 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener
        // 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换
    }

 解析到一条头数据:{0:{"columnIndex":0,"dataFormatData":{"format":"General","index":0},"rowIndex":0,"stringValue":"姓名","type":"STRING"},1:{"columnIndex":1,"dataFormatData":{"$ref":"$[0].dataFormatData"},"rowIndex":0,"stringValue":"日期","type":"STRING"},2:{"columnIndex":2,"dataFormatData":{"$ref":"$[0].dataFormatData"},"rowIndex":0,"stringValue":"年龄","type":"STRING"},3:{"columnIndex":3,"dataFormatData":{"$ref":"$[0].dataFormatData"},"rowIndex":0,"stringValue":"薪水","type":"STRING"},4:{"columnIndex":4,"dataFormatData":{"$ref":"$[0].dataFormatData"},"rowIndex":0,"stringValue":"地址","type":"STRING"}} 

成功解析到一条数据:ExcelDemo(name=张三, age=18, salary=11111.11, address=北京, dateTime=2024-12-21T20:20:20) 

        7、额外信息(批注、超链接、合并单元格信息读取)

        一般很少用,需要可以再了解一下,这里只简单做示例:
        需要 在监听器里面多实现一个 extra 方法:

 @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));
        switch (extra.getType()) {
            case COMMENT:
                log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(),
                    extra.getText());
                break;
            case HYPERLINK:
                if ("Sheet1!A1".equals(extra.getText())) {
                    log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(),
                        extra.getColumnIndex(), extra.getText());
                } else if ("Sheet2!A1".equals(extra.getText())) {
                    log.info(
                        "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{},"
                            + "内容是:{}",
                        extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
                        extra.getLastColumnIndex(), extra.getText());
                } else {
                    Assert.fail("Unknown hyperlink!");
                }
                break;
            case MERGE:
                log.info(
                    "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
                    extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
                    extra.getLastColumnIndex());
                break;
            default:
        }
    }
 // 这里 需要指定读用哪个class去读,然后读取第一个sheet
        EasyExcel.read(fileName, DemoExtraData.class, new DemoExtraListener())
            // 需要读取批注 默认不读取
            .extraRead(CellExtraTypeEnum.COMMENT)
            // 需要读取超链接 默认不读取
            .extraRead(CellExtraTypeEnum.HYPERLINK)
            // 需要读取合并单元格信息 默认不读取
            .extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();

        8、读取公式和单元格类型

        也比较少用,需要自行钻研,只简单举例:

@Getter
@Setter
@EqualsAndHashCode
public class CellDataReadDemoData {
    private CellData<String> string;
    // 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是number
    private CellData<Date> date;
    private CellData<Double> doubleData;
    // 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复
    private CellData<String> formulaValue;
}
   @Test
    public void cellDataRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "cellDataDemo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet
        EasyExcel.read(fileName, CellDataReadDemoData.class, new CellDataDemoHeadDataListener()).sheet().doRead();
    }
        9、数据转换等异常处理

        需要在监听器里面实现重写onException方法即可:

  @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
                excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }
    }
        10、不创建对象的读

        不创建对象的读,可以接收一个map集合,然后在监听器中自行进行转换。

@Slf4j
public class DataListener extends AnalysisEventListener<Map<Integer, String>> {
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    private List<Map<Integer, String>> dataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        dataList.add(data);
        if (dataList.size() >= BATCH_COUNT) {
            saveData();
            dataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", dataList.size());
        log.info("存储数据库成功!");
    }
}
        // 这里 只要,然后读取第一个sheet 同步读取会自动finish
        EasyExcel.read(fileName, new DataListener()).sheet().doRead();

五、常用导出Excel方法

        1、简单导出

         注意:简单导出 在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入

    @PostMapping("/export")
    public void exportExcel(HttpServletResponse response) throws IOException {

        // write 指定输出流,跟模板对象
        // sheet 指定导出sheet页名称
        // dowrite 指定数据源
        EasyExcel.write(response.getOutputStream(), ExcelDemo.class)
                .sheet("excel模板")
                .doWrite(this::getDatas);

        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("测试导出excel", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8" + fileName + ".xlsx");
    }
        2、根据参数只导出指定列

              根据参数名列表,导出指定字段,需要使用 includeColumnFieldNames 方法:

        //导出指定字段
        Set<String> exportFields = new HashSet<String>();
        exportFields.add("name");
        exportFields.add("age");

        // write 指定输出流,跟模板对象
        // sheet 指定导出sheet页名称
        // dowrite 指定数据源
        EasyExcel.write(response.getOutputStream(), ExcelDemo.class)
                .includeColumnFieldNames(exportFields)
                .sheet("excel模板")
                .doWrite(getDatas());

        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("测试导出excel", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8" + fileName + ".xlsx");

        根据参数名列表,忽略指定字段,需要使用 excludeColumnFiledNames 方法:

        //导出指定字段
        Set<String> noExportFields = new HashSet<String>();
        noExportFields.add("name");
        noExportFields.add("age");

        // write 指定输出流,跟模板对象
        // sheet 指定导出sheet页名称
        // dowrite 指定数据源
        EasyExcel.write(response.getOutputStream(), ExcelDemo.class)
                .excludeColumnFieldNames(noExportFields)
                .sheet("excel模板")
                .doWrite(getDatas());

        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("测试导出excel", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8" + fileName + ".xlsx");

         导出指定列的数据还可以结合 @ExcelIgnore注解 或 @ExcelIgnoreUnannotated注解 使用,来导出指定列,或者忽略某些列。

        3、复杂头写入

        使用@ExcelProperty注解即可设置:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExcelDemo {

    @ExcelProperty({"表头1","姓名"})
    private String name;

    @ExcelProperty({"表头1","年龄"})
    private int age;

    @ExcelProperty({"薪水"})
    private BigDecimal salary;

    @ExcelProperty({"表头2","地址"})
    private String address;

    @ExcelProperty({"表头2","日期"})
    private LocalDateTime dateTime;

}

        

         相同表头会合并。

        4、重复多次写入(写到单个或者多个Sheet)

        重复写入同一个sheet页:

        // 方法1: 如果写到同一个sheet
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExcelDemo.class).build()) {
            // 这里注意 如果同一个sheet只要创建一次
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
            for (int i = 0; i < 5; i++) {
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<ExcelDemo> data = getDatas();
                excelWriter.write(data, writeSheet);
            }
        }

        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("测试导出excel", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8" + fileName + ".xlsx");

        写入不同得sheet页,同一个对象:

        // 方法2: 如果写到不同的sheet 同一个对象
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExcelDemo.class).build()) {
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<ExcelDemo> data = getDatas();
                excelWriter.write(data, writeSheet);
            }
        }

        写入不同的sheet页,不同的对象:

        // 方法3 如果写到不同的sheet 不同的对象
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build()) {
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class
                // 实际上可以一直变
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(ExcelDemo.class).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<ExcelDemo> data = getDatas();
                excelWriter.write(data, writeSheet);
            }
        }

        5、日期、数字或者自定义格式转换

        日期、数字、自定义格式转行,可以结合 注解:
        @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
        @NumberFormat("#.##%")
        Converter 自定义转换器
        上面写了,不再举例

       

        6、图片导出

        7、超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式

        8、根据模板写入

        我得理解就是,在已经提供了一份excel文件中,继续导入数据,例如我们现在有一份excel文件如下所示:

        
        现在我们要以这个文件为模板,在这个文件得基础上继续导出数据,会得到如下所示:
       

        导出代码:

       String templateFileName = "C:\\Users\\Administrator\\Desktop\\ces\\" + "response111.xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 这里要注意 withTemplate 的模板文件会全量存储在内存里面,所以尽量不要用于追加文件,如果文件模板文件过大会OOM
        // 如果要再文件中追加(无法在一个线程里面处理,可以在一个线程的建议参照多次写入的demo) 建议临时存储到数据库 或者 磁盘缓存(ehcache) 然后再一次性写入
        EasyExcel.write(response.getOutputStream(), ExcelDemo.class).withTemplate(templateFileName).sheet().doWrite(getDatas());
         9、列宽、行高

        需要结合注解: 
        @ContentRowHeight(10) :设置行高为10px
        @HeadRowHeight(20):设置标题行的行高为20px
        @ColumnWidth(25):列的宽度为25px

@Data
@AllArgsConstructor
@NoArgsConstructor
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class ExcelDemo {

    @ExcelProperty({"表头1","姓名"})
    private String name;

    @ExcelProperty({"表头1","年龄"})
    private int age;

    @ExcelProperty({"薪水"})
    private BigDecimal salary;

    @ExcelProperty({"表头2","地址"})
    private String address;

    @ExcelProperty({"表头2","日期"})
    private LocalDateTime dateTime;
}
        10、注解形式自定义样式

        需要结合上面讲述得注解:

@Data
@AllArgsConstructor
@NoArgsConstructor
// 头背景设置成红色 IndexedColors.RED.getIndex()
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10)
// 头字体设置成20
@HeadFontStyle(fontHeightInPoints = 20)
// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()
@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17)
// 内容字体设置成20
@ContentFontStyle(fontHeightInPoints = 20)
public class ExcelDemo {

    // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex()
    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 14)
    // 字符串的头字体设置成20
    @HeadFontStyle(fontHeightInPoints = 30)
    // 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex()
    @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40)
    // 字符串的内容字体设置成20
    @ContentFontStyle(fontHeightInPoints = 30)
    @ExcelProperty({"表头1","姓名"})
    private String name;

    @ExcelProperty({"表头1","年龄"})
    private int age;

    @ExcelProperty({"薪水"})
    private BigDecimal salary;

    @ExcelProperty({"表头2","地址"})
    private String address;

    @ExcelProperty({"表头2","日期"})
    private LocalDateTime dateTime;

}

        一般只会设置一些简单样式,具体需要可以自行查询相应注解。

        11、使用内置策略模式设置自定义样式

         EasyExcel中提供了两个样式策略,用来是设置导出文件得样式,只需要简单配置即可使用:
        HorizontalCellStyleStrategy:允许设置每一行的样式一致,或者隔行样式一致。
        AbstractVerticalCellStyleStrategy:使用这个策略时,可以为每一列单独设置样式,需要通过回调函数来定义不同列的样式。

         设置每一行得样式:

        HorizontalCellStyleStrategy 方法,一般接收两个 WriteCellStyle 类型得参数,第一个是设置表头格式得,第二个是设置内容格式的。
        WriteCellStyle 是 EasyExcel 中用于设置 Excel 单元格样式的类。每个属性用于定义单元格的不同样式,包括字体、边框、填充颜色、对齐方式等。下面是对每个参数详细得简单的讲解:

        WriteCellStyle cellStyle = new WriteCellStyle();

        // 1. DataFormatData 设置数据格式
        // 这里假设你设置为数字格式, 示例代码中未使用具体的 DataFormatData
        // cellStyle.setDataFormatData(new DataFormatData("0.00"));

        // 2. WriteFont 设置字体
        WriteFont font = new WriteFont();
        font.setFontHeightInPoints((short) 12);  // 字体大小为 12
        font.setBold(true);  // 设置加粗
        font.setFontName("Arial");  // 字体设置为 Arial
        cellStyle.setWriteFont(font);

        // 3. hidden 设置隐藏
        cellStyle.setHidden(false);  // 设置为可见

        // 4. locked 设置是否锁定
        cellStyle.setLocked(true);  // 设置单元格为锁定,不能编辑

        // 5. quotePrefix 设置前缀引号
        cellStyle.setQuotePrefix(true);  // 设置文本前加引号

        // 6. HorizontalAlignment 设置水平对齐
        cellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);  // 设置为居中对齐

        // 7. wrapped 设置是否换行
        cellStyle.setWrapped(true);  // 设置文本自动换行

        // 8. VerticalAlignment 设置垂直对齐
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);  // 设置为垂直居中

        // 9. rotation 设置文本旋转
        cellStyle.setRotation((short) 90);  // 设置文本旋转 90 度

        // 10. indent 设置缩进
        cellStyle.setIndent((short) 2);  // 设置缩进为 2 个字符

        // 11-14. BorderStyle 设置边框样式
        cellStyle.setBorderLeft(BorderStyle.THIN);  // 设置左边框为细边框
        cellStyle.setBorderRight(BorderStyle.MEDIUM);  // 设置右边框为中等边框
        cellStyle.setBorderTop(BorderStyle.THICK);  // 设置顶部边框为粗边框
        cellStyle.setBorderBottom(BorderStyle.DOTTED);  // 设置底部边框为虚线

        // 15-18. 边框颜色设置
        cellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());  // 左边框颜色为黑色
        cellStyle.setRightBorderColor(IndexedColors.BLUE.getIndex());  // 右边框颜色为蓝色
        cellStyle.setTopBorderColor(IndexedColors.GREEN.getIndex());  // 顶部边框颜色为绿色
        cellStyle.setBottomBorderColor(IndexedColors.RED.getIndex());  // 底部边框颜色为红色

        // 19. FillPatternType 设置填充模式
        cellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);  // 设置为实心填充模式

        // 20-21. 背景颜色和前景颜色设置
        cellStyle.setFillBackgroundColor(IndexedColors.YELLOW.getIndex());  // 设置背景色为黄色
        cellStyle.setFillForegroundColor(IndexedColors.ORANGE.getIndex());  // 设置前景色为橙色

        // 22. shrinkToFit 设置是否自适应缩小
        cellStyle.setShrinkToFit(true);  // 设置文本自适应缩小

        简单使用:

        //行头字体
        WriteFont headFont = new WriteFont();
        headFont.setFontName("华文楷体");
        headFont.setFontHeightInPoints((short) 18);
        headFont.setBold(true);
        //内容字体
        WriteCellStyle cellStyle = createCellStyle();
        WriteFont contentFont = new WriteFont();
        contentFont.setFontName("宋体");
        contentFont.setFontHeightInPoints((short) 10);
        contentFont.setBold(false);
        //行头设置
        WriteCellStyle headCellStyle = new WriteCellStyle();
        headCellStyle.setWriteFont(headFont);
        headCellStyle.setFillForegroundColor(IndexedColors.WHITE1.getIndex());
        headCellStyle.setBorderTop(BorderStyle.THIN);
        headCellStyle.setBorderBottom(BorderStyle.THIN);
        headCellStyle.setBorderLeft(BorderStyle.THIN);
        headCellStyle.setBorderRight(BorderStyle.THIN);
        headCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //内容设置
        WriteCellStyle contentCellStyle = new WriteCellStyle();
        contentCellStyle.setWriteFont(contentFont);
        contentCellStyle.setBorderTop(BorderStyle.THIN);
        contentCellStyle.setBorderBottom(BorderStyle.THIN);
        contentCellStyle.setBorderLeft(BorderStyle.THIN);
        contentCellStyle.setBorderRight(BorderStyle.THIN);
        contentCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headCellStyle, cellStyle);

        EasyExcel.write(response.getOutputStream(), ExcelDemo.class)
                .registerWriteHandler(horizontalCellStyleStrategy)
                .sheet("模板")
                .doWrite(getDatas());

        

        设置列的样式:

  AbstractVerticalCellStyleStrategy 是一个抽象类,必须通过继承并重写方法来实现自定义的列样式。主要是重写下面两个方法:

       headCellStyle(Head head)          ----设置标题栏的列样式
       contentCellStyle(Head head)     ----设置表格内容的列样式

       具体实现代码:
       先实现重写AbstractVerticalCellStyleStrategy

public class CustomVerticalCellStyleStrategy extends AbstractVerticalCellStyleStrategy {


    // 重写定义表头样式的方法
    @Override
    protected WriteCellStyle headCellStyle(Head head) {

        WriteCellStyle writeCellStyle = new WriteCellStyle();

        if(head.getColumnIndex() == 0) {
            //设置行头的第一列  单元格水平居中
            writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        }else if(head.getColumnIndex() == 1){
            //设置行头的第二列  单元格水平居左
            writeCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
        }else if(head.getColumnIndex() == 2){
            //设置行头的第三列  单元格水平居右
            writeCellStyle.setHorizontalAlignment(HorizontalAlignment.RIGHT);
        }else if(head.getColumnIndex() == 3){
            //设置行头大于第三列  单元格水平居右
            writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        }
        return writeCellStyle;
    }

    // 重写定义内容部分样式的方法
    @Override
    protected WriteCellStyle contentCellStyle(Head head) {
        WriteCellStyle writeCellStyle = new WriteCellStyle();
        writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        writeCellStyle.setFillBackgroundColor(IndexedColors.GREEN.getIndex());
        writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        return writeCellStyle;
    }

}

        再将自定义样式策略注册到Excel中:

        //数据表格的自定义列样式
        CustomVerticalCellStyleStrategy customVerticalCellStyleStrategy
                = new CustomVerticalCellStyleStrategy();
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(response.getOutputStream(), ExcelDemo.class)
                .registerWriteHandler(customVerticalCellStyleStrategy)
                .sheet("模板")
                .doWrite(getDatas());

        上面是简单的示例,具体每一列的样式,可以根据自己的需要定义。

        12、自定义单元格

        通过实现 CellWriteHandler 接口,可以完全自定义单元格的写入行为和样式。

        CellWriteHandler 接口,继承自 WriteHandler,用于在 Excel 写入过程中处理单元格的不同生命周期阶段。每个方法都处理特定的操作,通常用于 Excel 文件的写入时,针对单元格的创建、数据转换、处理和销毁等环节进行自定义处理。

        这个接口的作用是提供钩子方法(默认方法)来让用户在写入数据到 Excel 时,能够在每个关键阶段插入自己的逻辑。以下是对每个方法的详细解释:
 

  • 1. beforeCellCreate(CellWriteHandlerContext context)
    • 作用:在单元格创建之前调用,用于在写入单元格之前进行一些准备工作。
    • 参数:方法内部通过 context 参数提取了多个信息,包括 WriteSheetHolder(写入的工作表)、WriteTableHolder(写入的表格)、Row(当前行)、Head(表头信息)、columnIndex(列索引)、relativeRowIndex(相对行索引)、isHead(是否是表头)等。
    • 默认实现:这个方法默认调用了另一个重载版本 beforeCellCreate(WriteSheetHolder, WriteTableHolder, Row, Head, Integer, Integer, Boolean),该重载方法的默认实现为空,表示没有默认操作,用户可以在实现接口时进行自定义。
  • 2. beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead)
    • 作用:实际执行创建单元格之前的处理逻辑的方法。可以根据需要对 Excel 单元格的创建过程进行自定义操作,如设置格式、填充数据等。
    • 参数:接受了各个具体的参数,允许用户在不同的上下文中访问这些信息。
  • 3. afterCellCreate(CellWriteHandlerContext context)
    • 作用:在单元格创建之后调用。这个方法在单元格已经创建后执行,可以用来处理与单元格创建相关的操作,例如设置样式或处理其他后续步骤。
    • 参数:同样是通过 context 提供相关的信息,包括工作表、表格、当前单元格、表头信息、相对行索引等。
    • 默认实现:默认调用了另一个重载版本 afterCellCreate(WriteSheetHolder, WriteTableHolder, Cell, Head, Integer, Boolean),该重载方法默认没有实现任何逻辑,允许用户进行自定义。
  • 4. afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead)
    • 作用:执行单元格创建后的一些自定义处理逻辑。与 beforeCellCreate 方法类似,但在单元格已创建之后进行操作。
    • 参数:该方法接受详细的参数,允许用户访问到当前工作表、单元格、表头等信息。
  • 5. afterCellDataConverted(CellWriteHandlerContext context)
    • 作用:在单元格的数据被转换(例如格式化、数据类型转换)之后调用。这个方法允许用户在数据转换后进行一些额外的处理,比如修改转换后的数据或进一步调整单元格的内容。
    • 参数:context.getCellDataList() 提供了当前单元格的数据,方法会根据需要选择第一个数据进行处理(假设该列表不为空)。其他参数和前面的钩子方法类似。
  • 6. afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, WriteCellData<?> cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead)
    • 作用:执行数据转换后的处理逻辑。用户可以在此时进一步调整数据内容、修改样式或执行其他操作。
    • 参数:cellData 是转换后的数据,允许用户访问和操作。
  • 7. afterCellDispose(CellWriteHandlerContext context)
    • 作用:在单元格被销毁之前调用。这个方法是在单元格生命周期的最后阶段,用于清理或执行一些最终的操作。通常会在单元格数据处理和格式化完成后进行一些额外的操作,如记录日志或执行清理工作。
    • 参数:context.getCellDataList() 提供了当前单元格的所有数据列表,用户可以在此进行处理。
  • 8. afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead)
    • 作用:处理单元格销毁之后的操作。此方法接收了与前面方法相同的详细参数,允许用户在最后一步进行操作。
    • 参数:cellDataList 包含了所有需要处理的单元格数据,cell 是当前单元格,head 是表头信息,isHead 是一个布尔值,指示是否为表头行        

        总结:
        这个接口主要用于对 Excel 写入过程中的每个关键步骤提供自定义的处理机制。通过实现CellWriteHandler 接口,用户可以在:

  • 单元格创建之前 (beforeCellCreate)
  • 单元格创建之后 (afterCellCreate)
  • 数据转换之后 (afterCellDataConverted)
  • 单元格销毁之前 (afterCellDispose)

        等多个阶段进行干预和扩展,实现自定义的单元格处理逻辑。例如,可以用于设置单元格样式、数据格式化、数据验证、日志记录等。

        实际上上述的方法都可以进行单元格的样式设置,但是每个方法代表不同的时期,不同的时期能获取到的信息不同,所以可以选择合适的方法来进行自定义格式,但是其实如果不是有复杂的需求,其实不推荐使用该方法进行自定义单元格样式。

        简单示例:

        EasyExcel.write(response.getOutputStream(), ExcelDemo.class)
                .registerWriteHandler(new CellWriteHandler() {
                    @Override
                    public void afterCellDispose(CellWriteHandlerContext context) {
                        // 当前事件会在 数据设置到poi的cell里面才会回调
                        // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true
                        if (BooleanUtils.isNotTrue(context.getHead())) {
                            // 第一个单元格
                            // 只要不是头 一定会有数据 当然fill的情况 可能要context.getCellDataList() ,这个需要看模板,因为一个单元格会有多个 WriteCellData
                            WriteCellData<?> cellData = context.getFirstCellData();
                            // 这里需要去cellData 获取样式
                            // 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat
                            // ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了
                            // 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回
                            WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
                            writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
                            // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND
                            writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);

                            // 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 cell里面去 所以可以不用管了
                        }
                    }
                }).sheet("模板")
                .doWrite(getDatas());

        使用poi的样式设置,用的很少也不推荐使用,只做简单使用:

// 方法3: 使用poi的样式完全自己写 不推荐
        // @since 3.0.0-beta2
        // 坑1:style里面有dataformat 用来格式化数据的 所以自己设置可能导致格式化注解不生效
        // 坑2:不要一直去创建style 记得缓存起来 最多创建6W个就挂了
        fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";
        EasyExcel.write(fileName, DemoData.class)
            .registerWriteHandler(new CellWriteHandler() {
                @Override
                public void afterCellDispose(CellWriteHandlerContext context) {
                    // 当前事件会在 数据设置到poi的cell里面才会回调
                    // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true
                    if (BooleanUtils.isNotTrue(context.getHead())) {
                        Cell cell = context.getCell();
                        // 拿到poi的workbook
                        Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();
                        // 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式
                        // 不同单元格尽量传同一个 cellStyle
                        CellStyle cellStyle = workbook.createCellStyle();
                        cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
                        // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND
                        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                        cell.setCellStyle(cellStyle);

                        // 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确

                        // 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到
                        // cell里面去 会导致自己设置的不一样
                        context.getFirstCellData().setWriteCellStyle(null);
                    }
                }
            }).sheet("模板")
            .doWrite(data());

        13、合并单元格

        可以配合注解:
        @ContentLoopMerge
        @OnceAbsoluteMerge
        来进行单元格的合并。

// 将第6-7行的2-3列合并成一个单元格
 @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class ExcelDemo {

    // 这一列 每隔2行 合并单元格
    @ContentLoopMerge(eachRow = 2)
    @ExcelProperty({"姓名"})
    private String name;

    @ExcelProperty({"年龄"})
    private int age;

    @ExcelProperty({"薪水"})
    private BigDecimal salary;

    @ExcelProperty({"地址"})
    private String address;

    @ExcelProperty({"日期"})
    private LocalDateTime dateTime;

}


http://www.kler.cn/a/507224.html

相关文章:

  • VSCode代理配置导致的SSL证书验证错误及解决方案
  • 从 SQL 语句到数据库操作
  • 【进程与线程】前端进程与后端进程
  • Git 版本控制:.gitignore 文件完全指南
  • Redis哨兵(Sentinel)
  • Kylin Linux V10 替换安装源,并在服务器上启用 EPEL 仓库
  • springcloud中的Feign调用
  • GB44495-2024 汽车整车信息安全技术要求 - V2X部分前置要求
  • javaScript 入门与程序设计
  • 北京科技创新实力强劲,将在 CES Asia 2025 精彩呈现
  • halcon opencv-python C# 自适应不同大小图像并保持纵横比
  • DNS介绍与部署-Day 01
  • Lambda 架构之批处理层深度解析:从原理到 Java 实战
  • DETR论文阅读
  • openCV项目实战——信用卡数字识别
  • Vue 开发者的 React 实战指南:测试篇
  • CMake构建C#工程(protobuf)
  • Web 实时消息推送的七种实现方案
  • SpringBoot链接Kafka
  • 在 .NET 9 中使用 Scalar 替代 Swagger
  • 基于 Python 的财经数据接口库:AKShare
  • NFTScan | 01.06~01.12 NFT 市场热点汇总
  • 图论基础,如何快速上手图论?
  • Redis哨兵模式搭建示例(配置开机自启)
  • 代码随想录25 回溯算法
  • 78_Redis网络模型