EasyExcel的应用

news2025/1/18 18:44:48

一、简单使用

        引入依赖:
        这里我们可以使用最新的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.coloradmin.cn/o/2278591.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【git】如何删除本地分支和远程分支?

1.如何在 Git 中删除本地分支 本地分支是您本地机器上的分支&#xff0c;不会影响任何远程分支。 &#xff08;1&#xff09;在 Git 中删除本地分支 git branch -d local_branch_name git branch 是在本地删除分支的命令。-d是一个标志&#xff0c;是命令的一个选项&#x…

wps数据分析000002

目录 一、快速定位技巧 二、快速选中技巧 全选 选中部分区域 选中部分区域&#xff08;升级版&#xff09; 三、快速移动技巧 四、快速录入技巧 五、总结 一、快速定位技巧 ctrl→&#xff08;上下左右&#xff09;快速定位光标对准单元格的上下部分双击名称单元格中…

Java算法 二叉树入门 力扣简单题相同的树 翻转二叉树 判断对称二叉树 递归求二叉树的层数

目录 模版 先序遍历 中序遍历 后序遍历 力扣原题 相同的二叉树 力扣原题 翻转二叉树 遍历树的层数 题目 静态变量 核心逻辑 模版 // 二叉树public static class Node{public int value;public Node left;public Node right;public Node(int v) {valuev;}} 先序遍历 …

【Mysql进阶知识】Mysql 程序的介绍、选项在命令行配置文件的使用、选项在配置文件中的语法

目录 一、程序介绍 二、mysqld--mysql服务器介绍 三、mysql - MySQL 命令行客户端 3.1 客户端介绍 3.2 mysql 客户端选项 指定选项的方式 mysql 客户端命令常用选项 在命令行中使用选项 选项(配置)文件 使用方法 选项文件位置及加载顺序 选项文件语法 使用举例&am…

ESP32云开发二( http + led + lcd)

文章目录 前言先上效果图platformio.iniwokwi.tomldiagram.json源代码编译编译成功上传云端完结撒花⭐⭐⭐⭐⭐ 前言 阅读此篇前建议先看 此片熟悉下wokwi https://blog.csdn.net/qq_20330595/article/details/144289986 先上效果图 Column 1Column 2 platformio.ini wokwi…

医疗集群系统中基于超融合数据库架构的应用与前景探析

一、引言 1.1 研究背景与意义 随着医疗信息化的飞速发展,医疗数据呈爆炸式增长。从日常诊疗记录、患者病历,到各类医疗影像、检查检验数据等,海量信息不断涌现。据统计,医疗数据的年增长率高达 30% 以上 ,2025 年,全球医疗数据量将达到 2314 艾字节(EB)。如此庞大的数…

Flask学习入门笔记

Flask学习入门笔记 前言1. 安装Flask2. 创建一个简单的Flask应用3. 路由与视图函数3.1 基本路由3.2 动态路由3.3 HTTP方法 4. 请求与响应4.1 获取请求数据4.2 返回响应 5. 模板渲染5.1 基本模板渲染5.2 模板继承 6. 静态文件6.1 静态文件的目录结构6.2 在模板中引用静态文件6.2…

Java File、IO流、字节输入流 、字节输出流 、字符输入流 、字符输入流 、缓冲流、转换流、打印流、数据流、序列化流、IO框架

一. File File是Java.io.包下的类&#xff0c;File类的对象用于代表当前操作系统的文件/文件夹 File类只能对文件本身进行操作&#xff0c;不能读写文件里面存储的数据。 1. 创建对象 构造器说明public File(String pathname)根据文件路径创建文件对象public File(String pare…

宇泰串口卡驱动在Ubuntu22.04编译、安装汇总

从官网下载驱动官网地址 上传到Ubuntu, 目录结构如下&#xff1a; 驱动源代码: 驱动代码是基于开源项目编译来的 编译路径不能有中文路径&#xff0c;否则可能有类似错误 源码是基于Linux2.3内核编译&#xff0c;我当前是6.8.0-51&#xff0c;数据结构有升级&#xff0c;需要调…

Linux -- 初识HTTP协议

目录 什么是HTTP协议 什么是 URL &#xff1f; 理解 URL 初识HTTP 请求与响应格式 代码验证 gitee HTTP.hpp 结果 什么是HTTP协议 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;主要用于客户端&#xff08;通常是浏览器&#…

重返未来1999梁月养成攻略 雷电云手机速刷养成材料

在重返未来1999这款游戏中&#xff0c;1月16日上新的版本中新春限定角色【梁月】已经火热上线&#xff0c;今天就给大家一些养成攻略。 1.梁月是一名可适配多种体系的输出位角色&#xff0c;同时自身还有免疫和全队减伤&#xff0c;可以提升队伍的生存能力&#xff0c;比较推荐…

Pytorch|YOLO

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、 前期准备 1. 设置GPU 如果设备上支持GPU就使用GPU,否则使用CPU import torch import torch.nn as nn import torchvision.transforms as transforms im…

ASP.NET Core 中,认证(Authentication)和授权(Authorization)

在 ASP.NET Core 中&#xff0c;认证&#xff08;Authentication&#xff09;和授权&#xff08;Authorization&#xff09;是两个非常重要的概念。它们确保用户能够安全地访问应用程序&#xff0c;并且在访问过程中能按其权限被正确地控制。接下来&#xff0c;我将详细解释这两…

ThinkPHP 8的一对多关联

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…

基于SpringBoot+Vue的药品管理系统【源码+文档+部署讲解】

系统介绍 基于SpringBootVue实现的药品管理系统采用前后端分离的架构方式&#xff0c;系统实现了用户登录、数据中心、药库管理、药房管理、物资管理、挂号管理、系统管理、基础设置等功能模块。 技术选型 开发工具&#xff1a;idea2020.3Webstorm2020.3 运行环境&#xff…

tomcat状态一直是Exited (1)

docker run -di -p 80:8080 --nametomcat001 你的仓库地址/tomcat:9执行此命令后tomcat一直是Exited(1)状态 解决办法&#xff1a; 用以下命令创建运行 docker run -it --name tomcat001 -p 80:8080 -d 你的仓库地址/tomcat:9 /bin/bash最终结果 tomcat成功启动

递归40题!再见递归

简介&#xff1a;40个问题&#xff0c;有难有易&#xff0c;均使用递归完成&#xff0c;需要C/C的指针、字符串、数组、链表等基础知识作为基础。 1、数字出现的次数 由键盘录入一个正整数&#xff0c;求该整数中每个数字出现的次数。 输入&#xff1a;19931003 输出&#xf…

《leetcode-runner》【图解】【源码】如何手搓一个debug调试器——架构

前文&#xff1a; 《leetcode-runner》如何手搓一个debug调试器——引言 文章目录 设计引入为什么这么设计存在难点1. 环境准备2. 调试程序 仓库地址&#xff1a;leetcode-runner 本文主要聚焦leetcode-runner对于debug功能的整体设计&#xff0c;并讲述设计原因以及存在的难点…

PyTorch使用教程(1)—PyTorch简介

PyTorch是一个开源的深度学习框架&#xff0c;由Facebook人工智能研究院&#xff08;FAIR&#xff09;于2016年开发并发布&#xff0c;其主要特点包括自动微分功能和动态计算图的支持&#xff0c;使得模型建立更加灵活‌。官网网址&#xff1a;https://pytorch.org。以下是关于…

用LLM做测试驱动开发:有趣又高效的尝试

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…