开源轮子 - EasyExcel01(核心api)

EasyExcel01 - 核心api

本文整理自掘金大佬 - 竹子爱熊猫
https://juejin.cn/post/7405158045662576640

文章目录

  • EasyExcel01 - 核心api
    • 一:初相识EasyExcel
      • 1:写入excel入门
      • 2:读取Excel入门
    • 二:数据模型注解
      • 1:读写通用注解
        • 1.1:@ExcelProperty注解
        • 1.2:@ExcelIgnore注解
        • 1.3:@ExcelIgnoreUnannotated注解
        • 1.4:@DateTimeFormat注解
        • 1.5:@NumberFormat注解
      • 2:生成注解
        • 2.1:@ColumnWidth注解
        • 2.2:@ContentFontStyle注解
        • 2.3:其他注解
    • 三:核心API全解
      • 1:EasyExcel入口类
      • 2:读对象
        • 2.1:ExcelReaderBuilder
        • 2.2:ExcelReaderSheetBuilder
        • 2.3:ReadListener
      • 3:写对象
        • 3.1:ExcelWriterBuilder
        • 3.2:ExcelWriterSheetBuilder
        • 3.3:ExcelWriterTableBuilder
        • 3.4:WriteHandler
      • 4:导入导出接口

一:初相识EasyExcel

EasyExcel是一个高效、低内存占用的Excel处理框架,提供了简洁易用的API接口,使得我们能更加高效、灵活的处理Excel文件

当然,EasyExcel并非一个完全从零构建的开源项目,而是阿里巴巴在POI的基础上做了进一步封装

下面内容来自easyExcel官网:

POI也好,JXL也罢,它们都存在一个严重的问题就是非常的耗内存,尽管POI提供了一套SAX模型能在一定程度上解决OOM问题,但它仍然存在一些缺陷,对内存消耗依旧较大。

因此,EasyExcel重写了POI07Excel文件的解析,一个3MB的文件用原生的POI-SAX去解析,需要消耗100MB左右的内存,改用EasyExcel可以降低到几MB,并且再大的文件也不会出现内存溢出;03版依赖POI-SAX模式,也在上层做了模型转换的封装,让使用者更加简单方便。

在这里插入图片描述

一个75MBExcel文件,每行数据二十五列,总计46W条数据,能够在23秒解析、读取完成,并且仅消耗了16MB内存,这个结果无疑是惊人的!

同时,如果内存足够充裕,还可以开启急速模式换取更快的处理性能。

当然,这个数据来源于实验环境,在硬件配置较差、或读取数据后还需要清洗、落库等情况下,性能上会有所差异。但是不管怎么说,在资源占用、性能方面都完胜POI原生的处理方式

EasyExcel有了基本认知后,下面来一起快速上手实操一下,首先咱们需要导入下相关依赖

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

这里我们可以使用最新的4.0.2版本,也可以选择之前的稳定版本,3.1.x以后的版本API大致相同,新的版本也会向前兼容(3.1.x之前的版本,部分API可能在高版本被废弃)

关于POI、JDK版本适配问题

在这里插入图片描述

1:写入excel入门

package com.example.bootrocketmq.study.wheel.easyexcel;

import lombok.*;

import java.util.Date;

/**
 * @author cui haida
 * 2024/12/21
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private Long id; // 主键id
    private String name; // 姓名
    private String sex; // 性别
    private String hobby; // 爱好
    private Date birthday; // 生日
}

假设有一个Person集合,目前想把其中的数据,生成并写入到Excel文件该怎么做?十分简单

package com.example.bootrocketmq.study.wheel.easyexcel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author cui haida
 * 2024/12/21
 */
public class SimpleWriteDemo {
    private static void writeDemo() {
        List<Person> personList = new ArrayList<>();
        Person person = new Person(1L, "cuihaida", "男", "打游戏", new Date());
        Person person2 = new Person(2L, "cuihaida2", "女", "打游戏2", new Date());
        personList.add(person);
        personList.add(person2);

        // 可以写绝对路径
        String fileName = "person数据-" + System.currentTimeMillis() + ".xlsx";
        // 创建ExcelWriterBuilder, 默认创建内存模式,通过write方式指定文件名和实体类。
        ExcelWriterBuilder write = EasyExcel.write(fileName, Person.class);
        // 创建ExcelWriterSheetBuilder,默认创建第一个sheet,通过sheet方式指定sheet名
        ExcelWriterSheetBuilder sheet = write.sheet("person列表");
        // 通过dowrite(list)的方式写入到excel中
        sheet.doWrite(personList);
        write.autoCloseStream(true);

        System.out.println("写入成功");
    }

    public static void main(String[] args) {
        writeDemo();
    }
}

在这里插入图片描述

2:读取Excel入门

看完写入操作后,下面来看看如何读取Excel文件里的数据,咱们就以前面生成的文件作为解析目标,首先需要定义一个监听器

package com.example.bootrocketmq.study.wheel.easyexcel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;

import java.util.ArrayList;
import java.util.List;

/**
 * @author cui haida
 * 2024/12/22
 */
public class PersonListener extends AnalysisEventListener<Person> {

    // 一会读出来的数据都放在这里
    private final List<Person> data = new ArrayList<>();

    /**
     * 读excel一行一行的读, 每次都会调用这个invoke方法
     * @param person person实体数据
     * @param analysisContext 上下文对象
     */
    @Override
    public void invoke(Person person, AnalysisContext analysisContext) {
        System.out.println("每读取到一行,都会触发这个函数,读取到的内容是: " + person.toString());
        // 加入到结果中
        data.add(person);
    }

    /**
     * 读取完所有数据后调用这个方法
     * @param analysisContext 上下文对象
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        System.out.println("读取完毕,一共读取了" + analysisContext.readSheetHolder().getTotal() + "行");
    }

    /**
     * 获取读取的数据
     * @return 数据
     */
    public List<Person> getData() {
        return data;
    }
}

这个监听器很简单,无非就是将每条读取到的数据添加到data集合里,然后对外提供了一个获取数据的方法

其实这个监听器不定义也可以,因为EasyExcel有默认的监听器

package com.example.bootrocketmq.study.wheel.easyexcel;

import com.alibaba.excel.EasyExcel;

import java.util.List;

/**
 * @author cui haida
 * 2024/12/22
 */
public class SimpleReadDemo {
    public static void main(String[] args) {
        String fileName = "person数据-1734779595717.xlsx";
        PersonListener listener = new PersonListener();
        // 读取excel的内容,要转化成的对象(Person.class), 监听器(PersonListener)
        EasyExcel.read(fileName, Person.class, listener).sheet().doRead();
        List<Person> data = listener.getData();
        System.out.println(data);
    }
}

在这里插入图片描述

二:数据模型注解

从上面可以发现,不管是读取文件还是写入文件,都需要传递一个具体的class

这个类叫做数据模型类,主要是用来映射Java对象和excel列的匹配关系的。【java对象 - excel列】

为了更好的满足各种报表需求,easyExcel提供了各种注解进行辅助,主要有两大类

1:读写通用注解

通用注解是指在类、字段上添加后,对于读写场景都会生效的注解,而这些通用注解也是最常用的

1.1:@ExcelProperty注解

@ExcelProperty注解在读取用于映射excel表头,在写入时用于控制生成的excel表头

作用范围:数据实体类的字段上

注解释义:用于映射excel列和实体字段的匹配关系

可选参数:(注意优先级 -> index > order > value)

参数说明
value以列名形式匹配excel列,多行头会匹配最后一行
order以权重形式匹配excel列,值越小越靠前,优先级高于value
index以索引形式匹配excel列,优先级高于order & value
converter数据格式转换器,解析时会根据指定的转换器处理对应数据

该注解是EasyExcel中最重要的注解,它提供了列名、权重、索引三种列匹配模式

不过在解析excel文件时,官方只建议同时使用一种,因为就算指定了多种,它们也会有优先级关系,大可不必多此一举。

在写入的场景中,确实常常将index,value一起使用 -> value用于控制列名称,index用于控制列的顺序。

@ExcelProperty(value="姓名", index=1)
private String name;

这代表name字段在excel文件的列名为“姓名”,位于表格的第二列(B列)

1.2:@ExcelIgnore注解

作用范围:数据实体类的字段上

注解释义:当前字段不参与excel的匹配,即处理时候忽略这个字段。

@ExcelIgnore
private String name; // name字段不参与excel列的映射匹配
1.3:@ExcelIgnoreUnannotated注解

作用范围:数据模型类上

注解释义:匹配列时忽略所有未使用@ExcelProperty注解的字段

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

1.4:@DateTimeFormat注解

作用范围:数据实体类的字段上

注解释义:用String接收日期数据时,会根据指定格式转换日期

可选参数:

参数说明
value日期数据转换为字符串的目标格式
use1904windowingexcel日期数据默认从1900年开始,但有些会从1904开始

在解析excel文件时,如果使用String字段接收日期数据,就会根据指定的格式转换数据,格式可以参考java.text.SimpleDateFormat的写法,例如yyyy-MM-dd HH:mm:ss

而在往excel写数据时,如果Java中的字段类型为Date、LocalDate、LocalDateTime等日期类型,则会将日期数据转换为指定的格式写入对应列

1.5:@NumberFormat注解

作用范围:数据实体类的字段上

注解释义:用String接受数据类型时,会根据指定格式转换数值

可选参数:

参数说明
value数值转成字符串的目标格式
roundingMode数值格式化时的舍入模式,如四舍五入、向上取整等

这个注解和前一个注解类似,只不过是用于将非整数类型的数值数据转换成给定格式,格式可以参考java.text.DecimalFormat的写法,如#.##

除了可以指定格式外,还可以指定舍入模式,枚举可参考java.math.RoundingMode

2:生成注解

2.1:@ColumnWidth注解

作用范围:数据模型类上、字段上

注解释义:设置列的宽度

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

2.2:@ContentFontStyle注解

作用范围:数据模型类上、字段上

注解释义:用于设置单元格内容字体格式的注解

可选参数:

参数说明
fontName字体名称,如“黑体、宋体、Arial”等
fontHeightInPoints字体高度,以磅为单位
italic是否设置斜体(字体倾斜)
strikeout是否设置删除线
color字体的颜色,通过RGB值来设置
typeOffset偏移量,用于调整字体的位置
underline是否添加下划线
bold是否对字体加粗
charset设置编码格式,只能对全局生效(字段上设置无效)

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

2.3:其他注解
  • @HeadFontStyle注解 -> 用于定制标题字体格式
  • @HeadRowHeight注解 -> 用于设置标题行的行高
  • @HeadStyle注解 -> 用于设置标题样式
  • @OnceAbsoluteMerge注解 -> 用于合并指定的单元格
  • @ContentLoopMerge注解 -> 用于合并单元格

三:核心API全解

1:EasyExcel入口类

基于一开始的简单读写案例,会发现所有excel相关的动作都始于EasyExcel这个类,而这个类也被称为入口类,主要用来构建各种excel操作的实例(如读、写)。

public class EasyExcel extends EasyExcelFactory {
    public EasyExcel() {
    }
}

可以发现这是一个空的实现,EasyExcel类直接继承了EasyExcelFactory,拥有的所有API底层也由EasyExcelFactory提供,所以,我们直接分析EasyExcelFactory即可:

// 基于给定的文件、数据模型类,构建一个Excel写入对象
public static ExcelWriterBuilder write(File file, Class head)

// 基于给定的流、数据模型类,构建一个Excel写入对象
public static ExcelWriterBuilder write(OutputStream outputStream, Class head)

// 基于给定的sheet号、sheet名称,构建一个Sheet写入对象
public static ExcelWriterSheetBuilder writerSheet(Integer sheetNo, String sheetName)

// 基于给定的表格号,构建一个Table写入对象
public static ExcelWriterTableBuilder writerTable(Integer tableNo)

// 基于给定的文件、数据模型类,构建一个Excel读取对象
public static ExcelReaderBuilder read(File file, ReadListener readListener)

// 基于给定的文件、数据模型类,构建一个Excel读取对象
public static ExcelReaderBuilder read(InputStream inputStream, ReadListener readListener)

// 基于给定的sheet号、sheet名称,构建一个Sheet读取对象
public static ExcelReaderSheetBuilder readSheet(Integer sheetNo, String sheetName)

对于重复的方法不在说明,上面列出的是EasyExcelFactory类中的核心方法,其作用主要是构建读、写excel文件的对象,以便于后续读取、写入excel数据

简单了解这个入口类后,下面来看看这些构建出的读写对象

2:读对象

EasyExcel中,与读取数据相关的核心类主要有三个:ExcelReaderBuilder、ExcelReaderSheetBuilder、ReadListener

2.1:ExcelReaderBuilder

ExcelReaderBuilder只是为了满足链式调用封装出的Builder类,其内部会构建一个读取工作簿的ReadWorkbook对象,这就相当于你在实体类上加了个@Builder注解

所谓的工作簿,可以简单理解成一个Excel文件,下面看看它的API

API参数说明
converter数据转换器,默认内置了很多,如果读取的数据需要特定转换,则可以按需添加
registerReadListener注册监听器,读取数据时会触发已注册的监听器
headRowNumber文件中头的行数,默认为1,文件的列头是多行时设置
headexcel数据的表头列表(以字符串列表形式指定),官方建议使用模型类的方式
clazz与excel数据映射的模型类,与head二选一,都未指定时会用Map读取所有数据
use1904windowing日期格式是否以1904年开始,兼容特殊的excel文件
useScuentificFormat数字转文本的时候在较大的数值的是否是否采用科学计数法
autoTrim是否开启自动去除前后空格,开启后会对读到的头信息、数据去空格

上述这些参数,也是ExcelReaderSheetBuilder对象具备的通用属性

下面是独特参数:

  • excelType:指定要读取的excel文件类型,支持XLS、XLSX、CSV
  • inputStream:指定读取数据的文件流,底层会创建临时文件来转换数据;
  • file:要读取数据的目标文件,与inputStream二选一;
  • mandatoryUseInputStream:强制从流中读数据(不会创建临时文件,性能会变差);
  • charset:设置文件内容编码格式,读取CSV文件时有效,默认UTF-8
  • autoCloseStream:读取完毕后是否自动关闭读取的流(默认开启);
  • readCache:设置读取数据的缓存配置(默认5M内使用内存,其余使用EhCache);
  • readCacheSelector:设置什么时候使用内存、什么时候使用磁盘来存储缓存数据;
  • ignoreEmptyRow:读取数据行时,是否忽略所有字段为空的行,默认开启;
  • password:读取excel文件的密码(文件加密时使用);
  • xlsxSAXParserFactoryName:指定POI-SAX模式读取使用的实现类;
  • useDefaultListener:是否使用默认监听器来将excel数据行转换为Java对象;
  • extraReadSet:接收额外需要读取内容的set,如评论、超链接、合并单元的内容;
  • readDefaultReturn:对于excel中不点击单元格看不到的内容数据、接收时的格式;

了解这些参数后,我们在读取Excel文件时如何设置呢?先来看看最开始读取文件的代码:

EasyExcel.read(fileName, ZhuZi.class, zhuZiListener).sheet().doRead();

这行代码中,EasyExcel.read()方法执行后,会返回一个ExcelReaderBuilder对象

执行sheet()方法后会返回一个ExcelReaderSheetBuilder对象

而最后的doRead()方法则是触发真正解析excel、读取数据的步骤

既然如此,那么我们在调用EasyExcel.read()方法后、sheet()方法前,就可以继续调用上面提到的一系列方法设置参数,例如:

EasyExcel.read(fileName, ZhuZi.class, zhuZiListener)
        .excelType(ExcelTypeEnum.CSV) // 通过excelType()将读取目标的格式切换成了.csv文件
        .sheet().doRead();
2.2:ExcelReaderSheetBuilder

ExcelReaderBuilder对象一样,ExcelReaderSheetBuilder内部维护着一个ReadSheet对象,Builder类单纯用于满足链式调用的编程风格。

接触过Excel的小伙伴应该都知道一点,一个Excel文件内部可以维护多个Sheet(工作表单),而ReadSheet就是专门用来读取表单数据的类,前面在聊ExcelReaderBuilder类时提到的公用参数,ReadSheet也同样具备,那么相同的参数作用在不同的对象上,区别是什么?

很简单,如果针对整个工作簿设置的参数,会对所有Sheet生效;单独针对ReadSheet设置的参数,则只针对特定的表单有效。

如果一个文件的不同Sheet数据格式不一样,就可以基于这种特性来实现不同的解析逻辑。那么下面来看看ReadSheet独有的参数:

  • sheetNo:指定需要读取数据的目标Sheet编码(默认为0,表示读第一个);
  • sheetName:根据表单名字去匹配要读取数据的目标Sheet

是的,你没有看错,ReadSheet的独立参数就这两个,如果你在创建表单读取对象时就已经指定了编号、名称,那么这两个参数甚至也用不到。好了,那如何针对不同Sheet设置参数呢?

大家还记得ReadSheet对象是在何时创建的吗?当调用.sheet()方法后创建的,所以为Sheet设置参数很简单

EasyExcel.read(fileName, ZhuZi.class, zhuZiListener)
        .sheet(1)
        .headRowNumber(2) // 表示从第二个Sheet中读取数据,而这个表单里的表头是两行,即:从第三行才正式开始读取数据
        .doRead();
2.3:ReadListener

读取监听器,EasyExcel框架中的监听器体系结构如下:

在这里插入图片描述

  • Listener:内部为空实现,仅抽象为顶层接口,用于维护体系结构及拓展性;
  • ReadListener:读取监听器的顶层接口,读取excel文件时会被触发;
  • IgnoreExceptionReadListener:读取时发生异常会触发的监听器,可以在这里处理异常确保读取不会终止;
  • AnalysisEventListener:解析事件监听器,每当读取到一行数据,都会触发该类或子类的invoke()方法;
  • PageReadListener:分页读取监听器,当读取的数据量达到指定的数量会被触发,处理完一页后才会读下页数据;
  • ModelBuildEventListener:默认装载的监听器,用于在解析到excel数据行时,将数据转换为指定类对象;
  • SyncReadListener:解析事件监听器的子类,默认会将所有数据行添加到一个Object集合(使用小数据场景);
  • ZhuZiListener:自定义的监听器,继承自解析事件监听器,内部带有自定义的业务逻辑

在调用EasyExcel.read()方法时,传递的第三个入参就是监听器

或者可以通过registerReadListener()来注册,不过有一点要注意:同一个工作簿或表单读取对象,可以同时注册多个监听器。

这就有点类似于Filter过滤器链一样,而EasyExcel存在多个监听器时,会根据添加的顺序来触发。

上面列出的一堆监听器,重点关注AnalysisEventListener即可,因为在实现导入的业务时,通常会继承它来自定义处理业务数据的逻辑,比如可以在invoke()方法里实现数据校验、落库等逻辑。

3:写对象

关于Excel数据写入的核心类,主要ExcelWriterBuilder、ExcelWriterSheetBuilder、ExcelWriterTableBuilder、WriterHandler这四个

3.1:ExcelWriterBuilder

ExcelWriterBuilder代表一个写入工作簿对象,也就是调用EasyExcel.writer()方法返回的对象,内部维护着一个WriteWorkbook对象,它支持设置的参数列表如下:

  • converter:数据转换器,如果读取的数据需要特定转换,则可以按需添加;
  • head:excel数据的表头列表(以字符串列表形式指定);
  • clazz:与excel数据映射的模型类,与head二选一;
  • autoTrim:是否开启自动去除前后空格,开启后写入数据前会对数据去空格;
  • use1904windowing:日期格式是否以1904年开始,兼容特殊的excel文件;
  • useScientificFormat:数字转文本的时候在较大的数值的是否是否采用科学计数法;

上面这些是ExcelReaderBuilder类中也存在通用参数,甚至作用都是一模一样的

  • registerWriteHandler:注册写入处理器,总共有工作簿、表单、数据行、单元格四个级别;
  • relativeHeadRowIndex:写入到excel时,和顶部间隔几行,默认为0
  • needHead:是否需要生成数据表头(列头),默认开启;
  • useDefaultStyle:是否使用默认的样式(即简单写入案例中那个样式);
  • automaticMergeHead:是否开启自动合并头,头中相同的字段名,上下左右都会尝试合并;
  • excludeColumnIndexes:写入数据时,数据模型类中要忽略的字段下标;
  • excludeColumnFieldNames:写入数据时,数据模型类中要忽略的字段名称;
  • includeColumnIndexes:写入数据时,只需要导出的字段下标;
  • includeColumnFieldNames:写入数据时,只需要导出的字段名称;
  • orderByIncludeColumn:是否开启字段排序,默认不开启(根据类字段或指定head排序);
  • filedCacheLocation:数据模型的字段缓存模式,默认为ThreadLocal,可以改为纯内存或不缓存。

上述参数是当前对象、WriterSheet、WriterTable三者皆备的通用参数

再来看看ExcelWriterBuilder的专属参数:

  • excelType:写出的excel文件类型;
  • charset:写出的数据编码格式(仅CSV支持);
  • password:为生成的文件设置查看密码;
  • autoCloseStream:写入完成后是否自动关闭流,默认开启;
  • file:数据要写入的目标文件;
  • outputStream:数据要写入的目标流;
  • templateFile:模板文件(填充场景使用);
  • templateInputStream:模板文件流(填充场景使用);
  • inMemory:是否基于内存生成文件,默认不开启,会生成临时文件;
  • writeExcelOnException:写入过程抛出异常时,是否尝试把已有数据写入excel,默认关闭。

这些参数在写入excel时怎么设置呢?同样先来看最开始的写入案例:

EasyExcel.write(fileName, ZhuZi.class).sheet("竹子数据").doWrite(zhuZis);
  • 调用EasyExcel.write()方法后会得到一个ExcelWriterBuilder对象
  • 而执行sheet()后会返回一个ExcelWriterSheetBuilder对象
  • 最后的doWrite()方法代表真正触发数据写入逻辑。

为此,想要设置这些参数,只要在.write()方法之后、.sheet()方法之前调用对应方法即可

3.2:ExcelWriterSheetBuilder

ExcelWriterSheetBuilder内部维护着一个WriteSheet对象,代表表单写入对象,和表单读取对象类似

与上一个对象的唯一区别就在于:可以针对某一个具体sheet去设置参数,而WriteSheet具备的独立参数也就俩:

  • sheetNo:将数据写入指定编码的Sheet表单(默认为0);
  • sheetName:将数据写入到指定名字的表单中。

如果要为单独某一个表单设置参数,则只需要在调用了sheet()方法之后、调用doWrite()方法之前设置即可

3.3:ExcelWriterTableBuilder

与读取Excel时不同,写入时多了一个WriterTable

这个类的作用是:按表格形式写入数据到Excel文件中,即:可以向一个表单里面写入多个数据表格,

而表格的专属参数只有一个,即:

  • tableNo:需要写入的表格编码,默认值为0;

如果只往sheet写入一个表格,这时就算把tableNo设置成999都没用。

只有当一个sheet里写入多个表格时,就可以通过这个编号来控制先后顺序

而表格之间可以通过relativeHeadRowIndex这个通用参数来控制间隔。

3.4:WriteHandler

写入处理器WriteHandler,是EasyExcel框架在生成excel时非常重要的组件,它可以在写入过程中执行各种自定义的操作,比如自定义单元格样式、自定义合并单元格、数据行等。

因为框架内部实现了非常多的子类,所以整个WriteHandler体系比读取监听器大上很多,光WriteHandler就有二十多个子类,但其中很多我们用不到,因此就不一一做分析了,这里介绍几个较为重要的子类:

  • CellWriteHandler:单元格写入处理器,每写入一个单元格时会被触发;
  • RowWriteHandler:数据行写入处理器,每写入一行数据时会被触发;
  • SheetWriteHandler:表单写入处理器,每写入一个表单时会被触发;
  • WorkbookWriteHandler:工作簿写入处理器,写入一个excel文件时被触发;
  • ......

其实上面列出的四个类依旧是接口,大家可以根据具体的需求选择性实现,你可以在实现类里精准控制每个单元格的样式、更改内容、合并等,最后将其注册到对应的工作簿、表单写入对象上就能生效

4:导入导出接口

想要实现导入导出接口,关键点就在于如何从网络请求中读取Excel数据,以及如何将生成的excel文件返回

其实这个很简单,回想下前面读写核心类的参数,是不是支持通过流来指定要读取/写入的目标?

因此,我们可以基于输入/输出流来实现:

@RestController
@RequestMapping("/excel")
public class SimpleExcelController {
    
    /*
     * Excel导入接口(要解析的目标文件传入file字段)
     * */
    @PostMapping("/import")
    public void excelImport(MultipartFile file) {
        if (null == file) {
            throw new RuntimeException("哎呀,网络出小差啦……");
        }
        ZhuZiListener listener = new ZhuZiListener();
        try {
            // 从上传的文件中获取流,并基于流读取excel文件的数据
            EasyExcel.read(file.getInputStream(), ZhuZi.class, listener).sheet().doRead();
        } catch (IOException e) {
            throw new RuntimeException("Excel文件解析失败,请稍后重试~");
        }
        // 这里会获取到excel里的所有数据行,可以基于该数据进行校验、落库等业务操作
        List<ZhuZi> excelData = listener.getData();
    }

    /*
     * Excel导出接口(需要将response作为参数注入)
     * */
    @GetMapping("/export")
    public void excelExport(HttpServletResponse response) {
        // 内容类型也可以设置成application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName = "竹子数据-" + System.currentTimeMillis() + ".xlsx";
        try {
            // 防止中文乱码,使用URLEncoder重新编码
            fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Excel导出失败,请稍后重试");
        }
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);

        // 这里模拟从库里读取的集合数据
        List<ZhuZi> zhuZis = new ArrayList<>();
        ZhuZi zhuZi = new ZhuZi(1L, "竹子", "男", "熊猫", new Date());
        zhuZis.add(zhuZi);

        try {
            // 这里指定excel数据的输出目标为响应流
            EasyExcel.write(response.getOutputStream(), ZhuZi.class).sheet().doWrite(zhuZis);
        } catch (IOException e) {
            throw new RuntimeException("Excel导出失败,请稍后重试");
        }
    }
}

基于EasyExcel来实现导入、导出格外简单,导入时,只需要通过MultipartFile对象来接收excel文件,然后从它的流中读取数据解析即可。

当然,如果你想对excel表里的数据进行业务处理,可以选择在数据导入完成后,也可以直接在自定义的Listener监听器类中,读到一条数据就处理一次。

再来看导出接口,这里会基于response对象来实现,首先会将响应的数据声明为Excel文件,等待EasyExcel生成数据后,就会通过的OutputStream输出流返回给调用方。

只不过要注意,如果文件名是中文,则需要在内部先做UTF-8编码,否则调用方得到的文件名称就会乱码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/941179.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实验13 C语言连接和操作MySQL数据库

一、安装MySQL 1、使用包管理器安装MySQL sudo apt update sudo apt install mysql-server2、启动MySQL服务&#xff1a; sudo systemctl start mysql3、检查MySQL服务状态&#xff1a; sudo systemctl status mysql二、安装MySQL开发库 sudo apt-get install libmysqlcli…

【java基础系列】实现数字的首位交换算法

在java中&#xff0c;手写实现一个数字的首位交换算法实现 实现效果 实现代码 核心业务代码 public static void main(String[] args) {int[] arr {1,2,3,4,5};int temp arr[0];for (int i 0; i < arr.length; i) {System.out.print(arr[i]);}System.out.println(&quo…

kubeadm一键部署K8S 集群架构

kubeadm一键部署K8S 集群架构(centos7) https://www.k8src.cn/ https://kubernetes.io/zh-cn/docs/home/ https://blog.csdn.net/m0_58709145/article/details/140128179 https://blog.csdn.net/jiaqijiaqi666/article/details/129745828 Kubeadm init报错[ERROR CRI]: contai…

【LeetCode: 876. 链表的中间结点 + 链表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门hello world输出【入门一】

开发环境搭建&#xff1a;Linux-Ubuntu下搭建ESP32的开发环境的步骤&#xff0c;使用乐鑫最新稳定版的esp-idf-CSDN博客 一、安装好开发环境后&#xff0c;在esp目录下再创建一个esp32的目录【用于编程测试demo】 二、进入esp32目录&#xff0c;打开终端【拷贝esp-idf的hello工…

单节点calico性能优化

在单节点上部署calicov3273后&#xff0c;发现资源占用 修改calico以下配置是资源消耗降低 1、因为是单节点&#xff0c;没有跨节点pod网段组网需要&#xff0c;禁用overlay方式网络(ipip&#xff0c;vxlan),使用route方式网络 配置calico-node的环境变量 CALICO_IPV4POOL_I…

基于鲲鹏服务器的打砖块小游戏部署

案例介绍 鲲鹏服务器是基于鲲鹏处理器的新一代数据中心服务器&#xff0c;适用于大数据、分布式存储、高性能计算和数据库等应用。鲲鹏服务器具有高性能、低功耗、灵活的扩展能力&#xff0c;适合大数据分析、软件定义存储、Web等应用场景。 本案例将指导开发者如何在鲲鹏服务…

#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

机器学习基础算法 (一)-线性回归

python 环境的配置参考 从零开始&#xff1a;Python 环境搭建与工具配置 线性回归的 Python 实现 线性回归是一种经典的机器学习算法&#xff0c;用于预测连续的目标变量。它假设目标变量和特征之间存在线性关系。本文将详细介绍线性回归的原理、Python 实现、模型评估和调优&…

基于linux下实现的ping程序(C语言)

linux下实现的ping程序 一、设计目的 PING程序是我们使用的比较多的用于测试网络连通性的程序。PING程序基于ICMP&#xff0c;使用ICMP的回送请求和回送应答来工作。由计算机网络课程知道&#xff0c;ICMP是基于IP的一个协议&#xff0c;ICMP包通过IP的封装之后传递。 课程设…

WPF 布局控件

wpf 布局控件有很多&#xff0c;常用的有&#xff1a;Grid, UniformGrid, Border, StackPanel, WrapPanel, DockPanel。 1. Grid Grid 经常作为控件的 Content 使用&#xff0c;常作为 Windows, UserControl 等 UI 元素的根节点。它用来展示一个 n 行 n 列的排版。 因此就有…

内网渗透横向移动技巧

在正常情况中&#xff0c;横向移动是在已经获取了足够的权限的情况下进行横向移动&#xff0c;下面中的方法大部分也需要高权限的操作。 https://www.freebuf.com/articles/network/251364.html 内网横向移动分为三种情况&#xff1a; 1.在VPN环境中进行横向移动&#xff1b; 2…

MONI后台管理系统-swagger3(springdoc-openapi)集成

springdoc-openapi Java 库有助于使用 Spring Boot 项目自动生成 API 文档。springdoc-openapi 通过在运行时检查应用程序来根据 Spring 配置、类结构和各种注释推断 API 语义。 该库会自动生成 JSON/YAML 和 HTML 格式的页面文档。生成的文档可以使用swagger-api注释进行补充。…

C/C++圣诞树

系列文章 序号直达链接1C/C爱心代码2C/C跳动的爱心3C/C李峋同款跳动的爱心代码4C/C满屏飘字表白代码5C/C大雪纷飞代码6C/C烟花代码7C/C黑客帝国同款字母雨8C/C樱花树代码9C/C奥特曼代码10C/C精美圣诞树11C/C俄罗斯方块12C/C贪吃蛇13C/C孤单又灿烂的神-鬼怪14C/C闪烁的爱心15C…

前端网页开发学习(HTML+CSS+JS)有这一篇就够!

目录 HTML教程 ▐ 概述 ▐ 基础语法 ▐ 文本标签 ▐ 列表标签 ▐ 表格标签 ▐ 表单标签 CSS教程 ▐ 概述 ▐ 基础语法 ▐ 选择器 ▐ 修饰文本 ▐ 修饰背景 ▐ 透明度 ▐ 伪类 ▐ 盒子模型 ▐ 浮动 ▐ 定位 JavaScript教程 ▐ 概述 ▐ 基础语法 ▐ 函数 …

vue3和element-plus笔记

对子组件直接使用v-model 子组件内定义如下 const props defineProps({modelValue: {type: String,required: true} }) const emits defineEmits(["update:modelValue"]) 父组件定义如下 <script setup> const deleteId ref(null) </script> <…

Buck开关电源闭环控制的仿真研究15V/5V[Matlab/simulink源码+Word文档]

课题设计要求 ⑴输入直流电压(VIN)&#xff1a;15V ⑵输出电压(VO)&#xff1a;5.0V ⑶负载电阻&#xff1a;R2欧 ⑷输出电压纹波峰-峰值 Vpp≤50mV &#xff0c;电感电流脉动&#xff1a;输出电流的10% ⑸开关频率(fs)&#xff1a;100kHz ⑹BUCK主电路二极管的通态压降VD0.5V…

单元测试使用记录

什么是单元测试 简单来说就是对一个类中的方法进行测试&#xff0c;对输出的结果检查判断是否符合预期结果 但是在多年的工作中&#xff0c;从来没有哪个项目中真正系统的用到了单元测试&#xff0c;因此对它还是很陌生的&#xff0c;也就造成更加不会在项目中区使用它。 如何…

麒麟操作系统服务架构保姆级教程(三)ssh远程连接

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 作为一名成熟运维架构师&#xff0c;我们需要管理的服务器会达到几十台&#xff0c;上百台&#xff0c;上千台&#xff0c;甚至是上万台服务器&#xff0c;而且咱们的服务器还不一定都在一个机房&am…

2024年图像处理、多媒体技术与机器学习

重要信息 官网&#xff1a;www.ipmml.org 时间&#xff1a;2024年12月27-29日 地点&#xff1a;中国-大理 简介 2024年图像处理、多媒体技术与机器学习&#xff08;CIPMT 2024&#xff09;将于2024年12月27-29日于中国大理召开。将围绕图像处理与多媒体技术、机器学习等在…