首页 > 编程语言 >EasyExcel 实践与源码梳理

EasyExcel 实践与源码梳理

时间:2022-12-02 22:38:43浏览次数:28  
标签:return EasyExcel excel public class 源码 new import 梳理


目录

  • ​​1. 写在最前​​
  • ​​1.1 EasyExcel版本​​
  • ​​1.2 初探源码​​
  • ​​2. 表头实体类 MyUser​​
  • ​​3. 最简单的导出Excel文件​​
  • ​​4. 源码demo:​​
  • ​​4.1 读Excel​​
  • ​​1. 最简单的读​​
  • ​​2. 指定列的下标或者列名​​
  • ​​4.2 写Excel​​
  • ​​1. 最简单的写​​
  • ​​2. 根据参数只导出指定列​​
  • ​​3. 指定写入的列​​
  • ​​4. 复杂头写入​​
  • ​​5. 重复多次写入​​
  • ​​6. 日期、数字或者自定义格式转换​​
  • ​​7. 图片导出(五种方式写入图片)​​
  • ​​8. 根据模板写入​​
  • ​​9. 列宽、行高(使用注解控制)​​
  • ​​10. 注解形式自定义样式​​
  • ​​11. 拦截器形式自定义样式​​
  • ​​12. 合并单元格​​
  • ​​13. 使用table去写入​​
  • ​​14. 动态头,实时生成头写入​​
  • ​​15. 自动列宽(不太精确)​​
  • ​​16. 对单元格进行操作:下拉,超链接等自定义拦截器​​
  • ​​17. 可变标题处理(包括标题国际化等)​​
  • ​​18. 不创建对象的写​​
  • ​​5. 照葫芦画瓢-自定义​​
  • ​​5.1 写Excel工具类:​​
  • ​​5.2 公共读工具类:​​
  • ​​问题记录:​​

1. 写在最前

不吹不黑,这玩意是阿里开源的项目,相比原生的poi来说,用起来确实方便。github地址:​​https://github.com/alibaba/easyexcel​

这里简单记录下自己的使用过程,以及翻阅源码,学习下阿里大佬是怎么写代码的。我是从​​1.0.4​​​版本过来的,此版本的源码相对简单些,如果感觉新版本源码过多的话,可以先从​​1.0.4​​撸起。

1.1 EasyExcel版本

为了避免由于版本不一致导致,后面的demo中某些类无法引入的问题,我这里使用的是easyExcel 2.2.6版本的,也是写这篇博客时最新的版本。

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
<!-- <version>1.0.4</version> -->
</dependency>

1.2 初探源码

目前easyExcel的源码包结构如图:

EasyExcel 实践与源码梳理_easyExcel


在包的最外面,独立出来了四个类,可见这四个货举足轻重。

真正开箱即用的读写类就三个文件,因为​​EasyExcel.java​​ 就是空的,加上它的理由是为了名称看起来更好。哈哈,这个理由有点意思。

/**
* This is actually {@link EasyExcelFactory}, and short names look better.
*
* @author jipengfei
*/
public class EasyExcel extends EasyExcelFactory {}

可以看到其继承了​​EasyExcelFactory​​,里面包含了读和写的各种方法。

2. 表头实体类 MyUser

这里加上一个BaseModel 类,由于从低版本的easyExcel过来的,在低版本中需要表头的类继承BaseRowModel,高版本中舍弃了此类,无需继承,可直接定义表头类。为了构造通用的工具类,这里我仍然使用了一个BaseModel 类,为了在后面的通用工具类里面使用泛型。

public class BaseModel {

}

MyUser 此类作为demo中的实体类,对应读写excel文件的表头类。

import com.alibaba.excel.annotation.ExcelProperty;
import com.lin.test.excel.write.BaseModel;

public class MyUser extends BaseModel {
@ExcelProperty(value ={"82班记录表","姓名"},index = 0)
private String name;
@ExcelProperty(value ={"82班记录表","年龄"},index = 1)
private Integer age;
@ExcelProperty(value ={"82班记录表","学号"},index = 2)
private String idNum;

public MyUser() {
super();
}
public MyUser(String name, Integer age, String idNum) {
super();
this.name = name;
this.age = age;
this.idNum = idNum;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getIdNum() {
return idNum;
}
public void setIdNum(String idNum) {
this.idNum = idNum;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{\"name\":\"");
builder.append(name);
builder.append("\", \"age\":\"");
builder.append(age);
builder.append("\", \"idNum\":\"");
builder.append(idNum);
builder.append("\"}");
return builder.toString();
}
}
public static List<MyUser> getUserAll(){
List<MyUser> myUsers = new ArrayList<MyUser>();
MyUser myUser = null;
for (int i = 0; i < 5; i++) {
myUser = new MyUser("name" + i, i+1, "idNum" + i);
myUsers.add(myUser);
}
return myUsers;
}

3. 最简单的导出Excel文件

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteWorkbook;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.lin.test.excel.entity.MyUser;
import com.lin.test.excel.write.BaseExcelWriter;
import com.lin.test.excel.write.CustomEasyExcel;

/**
* 使用目前最新版本2.2.6版本的easyExcel
* @author linmengmeng
* @date 2020年8月13日 下午9:19:55
*/
public class NewTestWriteExcel {


// 文件输出位置
private static String outPath_xlsx = "C:\\Users\\lmm\\Desktop\\testWrite.xlsx";

private static String outPath_xls = "C:\\Users\\lmm\\Desktop\\testWrite.xls";

public static void main(String[] args) {
customMyResourceCode();
System.out.println("-----0k-----");
}

public static void customMyResourceCode() {
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(outPath_xls);
} catch (FileNotFoundException e) {
e.printStackTrace();
}

List<MyUser> userList = OldTestWriteExcel.getUserAll();

WriteWorkbook writeWorkbook = new WriteWorkbook();
writeWorkbook.setClazz(MyUser.class);
writeWorkbook.setExcelType(ExcelTypeEnum.XLS);
writeWorkbook.setOutputStream(outputStream);

WriteSheet writeSheet = new WriteSheet();
writeSheet.setSheetNo(0);
writeSheet.setSheetName("手动设置sheetName");

ExcelWriter excelWriter = new ExcelWriter(writeWorkbook);
excelWriter.write(userList, writeSheet);

excelWriter.finish();
try {
outputStream.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

4. 源码demo:

4.1 读Excel

读取时需要先定义模板的数据读取类,可以是总的工具类,又或者是某个具体的功能对应的实体类。

另外一个就是监听器,监听器可以在读取时检测异常或者数据格式校验,校验通过的数据,可以在监听器里面暂存读取的内容,内容也即是我们定义的数据读取类。最简单的就是讲读取的内容暂存在List里面,如果我们需要校验excel里面的内容的话,可以将当前读取的行号给存起来,然后在校验失败时,可以在业务层外面,拿到行号和错误的异常信息。

下面先看下源码的demo是怎么处理读取事件的,后面贴上我自己用到的读取工具类。

1. 最简单的读

/**
* 最简单的读
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <p>
* 3. 直接读即可
*/
@Test
public void simpleRead() {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法1:
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

// 写法2:
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}

模板的读取类

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
* 模板的读取类
*
* @author Jiaju Zhuang
*/
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<DemoData> list = new ArrayList<DemoData>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;

public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
}

/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}

/**
* 这个每一条数据解析都会来调用
*
* @param data
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}

/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据解析完成!");
}

/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
demoDAO.save(list);
LOGGER.info("存储数据库成功!");
}
}
/**
* 基础数据类.这里的排序和excel里面的排序一致
*
* @author Jiaju Zhuang
**/
@Data
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}

可以看到每次读取一行,都会调用​​invoke​​方法,

2. 指定列的下标或者列名

/**
* 指定列的下标或者列名
*
* <p>
* 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
* <p>
* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
* <p>
* 3. 直接读即可
*/
@Test
public void indexOrNameRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里默认读取第一个sheet
EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}
import java.util.Date;

import com.alibaba.excel.annotation.ExcelProperty;

import lombok.Data;

/**
* 基础数据类
*
* @author Jiaju Zhuang
**/
@Data
public class IndexOrNameData {
/**
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double doubleData;
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
}
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
* 模板的读取类
*
* @author Jiaju Zhuang
*/
public class IndexOrNameDataListener extends AnalysisEventListener<IndexOrNameData> {
private static final Logger LOGGER = LoggerFactory.getLogger(IndexOrNameDataListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<IndexOrNameData> list = new ArrayList<IndexOrNameData>();

@Override
public void invoke(IndexOrNameData data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
list.add(data);
if (list.size() >= BATCH_COUNT) {
saveData();
list.clear();
}
}

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

/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
LOGGER.info("存储数据库成功!");
}
}

4.2 写Excel

测试类:​​com.alibaba.easyexcel.test.demo.write.WriteTest​

/**
* 基础数据类
*
* @author Jiaju Zhuang
**/
@Data
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}

1. 最简单的写

/**
* 最简单的写
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 直接写即可
*/
@Test
public void simpleWrite() {
// 写法1
String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());

// 写法2
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}

2. 根据参数只导出指定列

/**
* 根据参数只导出指定列
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 根据自己或者排除自己需要的列
* <p>
* 3. 直接写即可
*
* @since 2.1.1
*/
@Test
public void excludeOrIncludeWrite() {
String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";

// 根据用户传入字段 假设我们要忽略 date
Set<String> excludeColumnFiledNames = new HashSet<String>();
excludeColumnFiledNames.add("date");
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板")
.doWrite(data());

fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
// 根据用户传入字段 假设我们只要导出 date
Set<String> includeColumnFiledNames = new HashSet<String>();
includeColumnFiledNames.add("date");
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板")
.doWrite(data());
}

3. 指定写入的列

/**
* 指定写入的列
* <p>
* 1. 创建excel对应的实体对象 参照{@link IndexData}
* <p>
* 2. 使用{@link ExcelProperty}注解指定写入的列
* <p>
* 3. 直接写即可
*/
@Test
public void indexWrite() {
String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data());
}
/**
* 基础数据类
*
* @author Jiaju Zhuang
**/
@Data
public class IndexData {
@ExcelProperty(value = "字符串标题", index = 0)
private String string;
@ExcelProperty(value = "日期标题", index = 1)
private Date date;
/**
* 这里设置3 会导致第二列空的
*/
@ExcelProperty(value = "数字标题", index = 3)
private Double doubleData;
}

4. 复杂头写入

/**
* 复杂头写入
* <p>
* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
* <p>
* 2. 使用{@link ExcelProperty}注解指定复杂的头
* <p>
* 3. 直接写即可
*/
@Test
public void complexHeadWrite() {
String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());
}

头部实体类。

/**
* 复杂头数据.这里最终效果是第一行就一个主标题,第二行分类
*
* @author Jiaju Zhuang
**/
@Data
public class ComplexHeadData {
@ExcelProperty({"主标题", "字符串标题"})
private String string;
@ExcelProperty({"主标题", "日期标题"})
private Date date;
@ExcelProperty({"主标题", "数字标题"})
private Double doubleData;
}

5. 重复多次写入

可分页读取数据,逐次写入,最后使用​​excelWriter.finish();​​关闭资源。也可在同一个Excel里面定义不同的Sheet直接一次性生成文件。注意sheetNo和SheetName必须不一样。

/**
* 重复多次写入
* <p>
* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
* <p>
* 2. 使用{@link ExcelProperty}注解指定复杂的头
* <p>
* 3. 直接调用二次写入即可
*/
@Test
public void repeatedWrite() {
// 方法1 如果写到同一个sheet
String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
ExcelWriter excelWriter = null;
try {
// 这里 需要指定写用哪个class去写
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
// 这里注意 如果同一个sheet只要创建一次
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
for (int i = 0; i < 5; i++) {
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}

// 方法2 如果写到不同的sheet 同一个对象
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
try {
// 这里 指定文件
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}

// 方法3 如果写到不同的sheet 不同的对象
fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
try {
// 这里 指定文件
excelWriter = EasyExcel.write(fileName).build();
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 5; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class 实际上可以一直变
WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
List<DemoData> data = data();
excelWriter.write(data, writeSheet);
}
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}

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

/**
* 日期、数字或者自定义格式转换
* <p>
* 1. 创建excel对应的实体对象 参照{@link ConverterData}
* <p>
* 2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解
* <p>
* 3. 直接写即可
*/
@Test
public void converterWrite() {
try {
System.out.println(TestFileUtil.getPath());
String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data());
System.out.println("-----ok------");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 基础数据类.这里的排序和excel里面的排序一致
*
* @author Jiaju Zhuang
**/
@Data
public class ConverterData {
/**
* 我想所有的 字符串起前面加上"自定义:"三个字
*/
@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
private String string;
/**
* 我想写到excel 用年月日的格式
*/
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@ExcelProperty("日期标题")
private Date date;
/**
* 我想写到excel 用百分比表示
*/
@NumberFormat("#.##%")
@ExcelProperty(value = "数字标题")
private Double doubleData;
}

自定义处理规范

/**
* String and string converter
*
* @author Jiaju Zhuang
*/
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class supportJavaTypeKey() {
return String.class;
}

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

/**
* 这里是读的时候会调用 不用管
*
* @param cellData
* NotNull
* @param contentProperty
* Nullable
* @param globalConfiguration
* NotNull
* @return
*/
@Override
public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return cellData.getStringValue();
}

/**
* 这里是写的时候会调用 不用管
*
* @param value
* NotNull
* @param contentProperty
* Nullable
* @param globalConfiguration
* NotNull
* @return
*/
@Override
public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new CellData("自定义:" + value);
}

}

7. 图片导出(五种方式写入图片)

/**
* 图片导出
* <p>
* 1. 创建excel对应的实体对象 参照{@link ImageData}
* <p>
* 2. 直接写即可
*/
@Test
public void imageWrite() throws Exception {
String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx";
// 如果使用流 记得关闭
InputStream inputStream = null;
try {
List<ImageData> list = new ArrayList<ImageData>();
ImageData imageData = new ImageData();
list.add(imageData);
String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg";
// 放入五种类型的图片 实际使用只要选一种即可
imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));
imageData.setFile(new File(imagePath));
imageData.setString(imagePath);
inputStream = FileUtils.openInputStream(new File(imagePath));
imageData.setInputStream(inputStream);
imageData.setUrl(new URL(
"https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg"));
EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list);
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
import java.io.File;
import java.io.InputStream;
import java.net.URL;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.converters.string.StringImageConverter;

import lombok.Data;

/**
* 图片导出类
*
* @author Jiaju Zhuang
*/
@Data
@ContentRowHeight(100)
@ColumnWidth(100 / 8)
public class ImageData {
private File file;
private InputStream inputStream;
/**
* 如果string类型 必须指定转换器,string默认转换成string
*/
@ExcelProperty(converter = StringImageConverter.class)
private String string;
private byte[] byteArray;
/**
* 根据url导出
*
* @since 2.1.1
*/
private URL url;
}

8. 根据模板写入

/**
* 根据模板写入
* <p>
* 1. 创建excel对应的实体对象 参照{@link IndexData}
* <p>
* 2. 使用{@link ExcelProperty}注解指定写入的列
* <p>
* 3. 使用withTemplate 写取模板
* <p>
* 4. 直接写即可
*/
@Test
public void templateWrite() {
String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data());
}

9. 列宽、行高(使用注解控制)

/**
* 列宽、行高
* <p>
* 1. 创建excel对应的实体对象 参照{@link WidthAndHeightData}
* <p>
* 2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度
* <p>
* 3. 直接写即可
*/
@Test
public void widthAndHeightWrite() {
String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data());
}
import java.util.Date;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;

import lombok.Data;

/**
* 基础数据类
*
* @author Jiaju Zhuang
**/
@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
/**
* 宽度为50
*/
@ColumnWidth(50)
@ExcelProperty("数字标题")
private Double doubleData;
}

10. 注解形式自定义样式

/**
* 注解形式自定义样式
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoStyleData}
* <p>
* 3. 直接写即可
*
* @since 2.2.0-beta1
*/
@Test
public void annotationStyleWrite() {
String fileName = TestFileUtil.getPath() + "annotationStyleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoStyleData.class).sheet("模板").doWrite(data());
}
import java.util.Date;

import org.apache.poi.ss.usermodel.FillPatternType;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ContentFontStyle;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadStyle;

import lombok.Data;

/**
* 样式的数据类
*
* @author Jiaju Zhuang
**/
@Data
// 头背景设置成红色 IndexedColors.RED.getIndex()
@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 10)
// 头字体设置成20
@HeadFontStyle(fontHeightInPoints = 20)
// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()
@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 17)
// 内容字体设置成20
@ContentFontStyle(fontHeightInPoints = 20)
public class DemoStyleData {
// 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex()
@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 14)
// 字符串的头字体设置成20
@HeadFontStyle(fontHeightInPoints = 30)
// 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex()
@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40)
// 字符串的内容字体设置成20
@ContentFontStyle(fontHeightInPoints = 30)
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
}

11. 拦截器形式自定义样式

/**
* 拦截器形式自定义样式
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 创建一个style策略 并注册
* <p>
* 3. 直接写即可
*/
@Test
public void handlerStyleWrite() {
String fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";
// 头的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 背景设置为红色
headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 20);
headWriteCellStyle.setWriteFont(headWriteFont);
// 内容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
// 背景绿色
contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
WriteFont contentWriteFont = new WriteFont();
// 字体大小
contentWriteFont.setFontHeightInPoints((short) 20);
contentWriteCellStyle.setWriteFont(contentWriteFont);
// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);

// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板")
.doWrite(data());
}

12. 合并单元格

/**
* 合并单元格
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData} {@link DemoMergeData}
* <p>
* 2. 创建一个merge策略 并注册
* <p>
* 3. 直接写即可
*
* @since 2.2.0-beta1
*/
@Test
public void mergeWrite() {
// 方法1 注解
String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
// 在DemoStyleData里面加上ContentLoopMerge注解
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(data());

// 方法2 自定义合并单元格策略
fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
// 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data());
}

等于是将固定的位置进行合并,如果存在不确定的Excel数据内容,硬编码合并单元格不太适用。

/**
* 样式的数据类
*
* @author Jiaju Zhuang
**/
@Data
// 将第6-7行的2-3列合并成一个单元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {
// 这一列 每隔2行 合并单元格
@ContentLoopMerge(eachRow = 2)
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
}

13. 使用table去写入

/**
* 使用table去写入
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 然后写入table即可
*/
@Test
public void tableWrite() {
String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx";
// 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
// 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();
// 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要
WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();
WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();
// 第一次写入会创建头
excelWriter.write(data(), writeSheet, writeTable0);
// 第二次写如也会创建头,然后在第一次的后面写入数据
excelWriter.write(data(), writeSheet, writeTable1);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}

14. 动态头,实时生成头写入

即是使用了表头生成策略的实体类以外的另一种方式,使用​​List​​去存储表头,写入时根据List的内容生成表头。

/**
* 动态头,实时生成头写入
* <p>
* 思路是这样子的,先创建List<String>头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据
*
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 然后写入table即可
*/
@Test
public void dynamicHeadWrite() {
String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";
EasyExcel.write(fileName)
// 这里放入动态头
.head(head()).sheet("模板")
// 当然这里数据也可以用 List<List<String>> 去传入
.doWrite(data());
}
private List<List<String>> head() {
List<List<String>> list = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("字符串" + System.currentTimeMillis());
List<String> head1 = new ArrayList<String>();
head1.add("数字" + System.currentTimeMillis());
List<String> head2 = new ArrayList<String>();
head2.add("日期" + System.currentTimeMillis());
list.add(head0);
list.add(head1);
list.add(head2);
return list;
}
private List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}

15. 自动列宽(不太精确)

/**
* 自动列宽(不太精确)
* <p>
* 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照 {@link LongestMatchColumnWidthStyleStrategy}重新实现.
* <p>
* poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。
*
* <p>
* 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData}
* <p>
* 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy}
* <p>
* 3. 直接写即可
*/
@Test
public void longestMatchColumnWidthWrite() {
String fileName =
TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, LongestMatchColumnWidthData.class)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());
}

private List<LongestMatchColumnWidthData> dataLong() {
List<LongestMatchColumnWidthData> list = new ArrayList<LongestMatchColumnWidthData>();
for (int i = 0; i < 10; i++) {
LongestMatchColumnWidthData data = new LongestMatchColumnWidthData();
data.setString("测试很长的字符串测试很长的字符串测试很长的字符串" + i);
data.setDate(new Date());
data.setDoubleData(1000000000000.0);
list.add(data);
}
return list;
}
@Data
public class LongestMatchColumnWidthData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题很长日期标题很长日期标题很长很长")
private Date date;
@ExcelProperty("数字")
private Double doubleData;
}
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.usermodel.Cell;

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;

/**
* Take the width of the longest column as the width.
* <p>
* This is not very useful at the moment, for example if you have Numbers it will cause a newline.And the length is not
* exactly the same as the actual length.
*
* @author Jiaju Zhuang
*/
public class LongestMatchColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {

private static final int MAX_COLUMN_WIDTH = 255;

private Map<Integer, Map<Integer, Integer>> cache = new HashMap<Integer, Map<Integer, Integer>>(8);

@Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head,
Integer relativeRowIndex, Boolean isHead) {
boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
if (!needSetWidth) {
return;
}
Map<Integer, Integer> maxColumnWidthMap = cache.get(writeSheetHolder.getSheetNo());
if (maxColumnWidthMap == null) {
maxColumnWidthMap = new HashMap<Integer, Integer>(16);
cache.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
}
Integer columnWidth = dataLength(cellDataList, cell, isHead);
if (columnWidth < 0) {
return;
}
if (columnWidth > MAX_COLUMN_WIDTH) {
columnWidth = MAX_COLUMN_WIDTH;
}
Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
}
}

private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {
if (isHead) {
return cell.getStringCellValue().getBytes().length;
}
CellData cellData = cellDataList.get(0);
CellDataTypeEnum type = cellData.getType();
if (type == null) {
return -1;
}
switch (type) {
case STRING:
return cellData.getStringValue().getBytes().length;
case BOOLEAN:
return cellData.getBooleanValue().toString().getBytes().length;
case NUMBER:
return cellData.getNumberValue().toString().getBytes().length;
default:
return -1;
}
}
}

16. 对单元格进行操作:下拉,超链接等自定义拦截器

/**
* 下拉,超链接等自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)
* <p>
* demo这里实现2点。1. 对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel 2. 对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 注册拦截器 {@link CustomCellWriteHandler} {@link CustomSheetWriteHandler}
* <p>
* 2. 直接写即可
*/
@Test
public void customHandlerWrite() {
String fileName = TestFileUtil.getPath() + "customHandlerWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CustomSheetWriteHandler())
.registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data());
}
import java.util.List;

import org.apache.poi.common.usermodel.HyperlinkType;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Hyperlink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.AbstractCellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;

/**
* 自定义拦截器。对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel
*
* @author Jiaju Zhuang
*/
public class CustomCellWriteHandler extends AbstractCellWriteHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(CustomCellWriteHandler.class);

@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
// 这里可以对cell进行任何操作
LOGGER.info("第{}行,第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex());
if (isHead && cell.getColumnIndex() == 0) {
CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);
hyperlink.setAddress("https://github.com/alibaba/easyexcel");
cell.setHyperlink(hyperlink);
}
}

}
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;

/**
* 自定义拦截器.对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2
*
* @author Jiaju Zhuang
*/
public class CustomSheetWriteHandler implements SheetWriteHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(CustomSheetWriteHandler.class);

@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

}

@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
LOGGER.info("第{}个Sheet写入成功。", writeSheetHolder.getSheetNo());

// 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行
CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0);
DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper();
DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"测试1", "测试2"});
DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
writeSheetHolder.getSheet().addValidationData(dataValidation);
}
}

17. 可变标题处理(包括标题国际化等)

/**
* 可变标题处理(包括标题国际化等)
* <p>
* 简单的说用List<List<String>>的标题 但是还支持注解
* <p>
* 1. 创建excel对应的实体对象 参照{@link ConverterData}
* <p>
* 2. 直接写即可
*/
@Test
public void variableTitleWrite() {
// 写法1
String fileName = TestFileUtil.getPath() + "variableTitleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName, ConverterData.class).head(variableTitleHead()).sheet("模板").doWrite(data());
}

private List<List<String>> variableTitleHead() {
List<List<String>> list = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("string" + System.currentTimeMillis());
List<String> head1 = new ArrayList<String>();
head1.add("number" + System.currentTimeMillis());
List<String> head2 = new ArrayList<String>();
head2.add("date" + System.currentTimeMillis());
list.add(head0);
list.add(head1);
list.add(head2);
return list;
}

18. 不创建对象的写

/**
* 不创建对象的写
*/
@Test
public void noModelWrite() {
// 写法1
String fileName = TestFileUtil.getPath() + "noModelWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList());
}

private List<List<String>> head() {
List<List<String>> list = new ArrayList<List<String>>();
List<String> head0 = new ArrayList<String>();
head0.add("字符串" + System.currentTimeMillis());
List<String> head1 = new ArrayList<String>();
head1.add("数字" + System.currentTimeMillis());
List<String> head2 = new ArrayList<String>();
head2.add("日期" + System.currentTimeMillis());
list.add(head0);
list.add(head1);
list.add(head2);
return list;
}
private List<List<Object>> dataList() {
List<List<Object>> list = new ArrayList<List<Object>>();
for (int i = 0; i < 10; i++) {
List<Object> data = new ArrayList<Object>();
data.add("字符串" + i);
data.add(new Date());
data.add(0.56);
list.add(data);
}
return list;
}

5. 照葫芦画瓢-自定义

5.1 写Excel工具类:

使用2.2.6版本的jar包

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
<!-- <version>1.0.4</version> -->
</dependency>
import java.util.List;

import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.exception.ExcelGenerateException;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteWorkbook;

/**
* 自定义 easyExcel 导出excel的工具类
* @author linmengmeng
* @date 2020年8月11日 下午2:48:07
* @param <T> 表头实体类
*/
public class BaseExcelWriter extends ExcelWriter {

public BaseExcelWriter(WriteWorkbook writeWorkbook) {
super(writeWorkbook);
}

protected static final String DEFAULT_SHEET_NAME = "Sheet1";

protected static final Integer DEFAULT_SHEET_NO = 1;

protected static final int DEFAULT_HEAD_LINE_Num = 1;

protected WriteSheet writeSheet;

/**
* custom writeSheet
* @return
*/
protected void setWriteSheet(Integer sheetNo, String sheetName){
WriteSheet writeSheet = new WriteSheet();
writeSheet.setSheetNo(sheetNo);
writeSheet.setSheetName(sheetName);
this.writeSheet = writeSheet;
}

protected WriteSheet getWriteSheet(){
if (writeSheet == null) {
setWriteSheet(DEFAULT_SHEET_NO, DEFAULT_SHEET_NAME);
}
return writeSheet;
}


/**
* custom headLine
* @return
*/
protected int getHeadLineNum(){
return DEFAULT_HEAD_LINE_Num;
}

/**
* 由子类自定义实现 处理待输出的内容
* 如:数据库查出来的code 处理成中文
* @param objectList
* @return
*/
protected List<? extends BaseModel> formateData(List<? extends BaseModel> objectList){
return objectList;
}

/**
* 输出缓冲层 往第一个sheet的第一张表中写入数据
* 由于数据库查出来的是LinkedMap,这里加了formateData转换List<Object> objectList至List<T>
* 未测试分页查询循环写入
* @auther linmengmeng
* @Date 2020-08-12 下午5:05:19
* @param objectList
* @param baseExcelWriter
* @return
*/
public ExcelWriter consumeWrite(List<? extends BaseModel> objectList, BaseExcelWriter baseExcelWriter) {
if (objectList == null || objectList.isEmpty()) {
return this;
}
if (baseExcelWriter == null) {
throw new IllegalArgumentException("baseExcelWriter can not be null");
}
List<? extends BaseModel> data = baseExcelWriter.formateData(objectList);
if (data == null || data.isEmpty()) {
throw new ExcelGenerateException("the output must not be empty");
}
super.write(data, baseExcelWriter.getWriteSheet());
return this;
}

/**
* 分页查询循环写入
* 放弃此方法,可以在外层循环调用上面的consumeWrite即可
*/
// public ExcelWriter repeatedWrite(List<Object> objectList, BaseExcelWriter<T> baseExcelWriter, WriteSheet writeSheet) {
// this = consumeWrite(objectList, baseExcelWriter, writeSheet);
// return baseExcelWriter;
// }

}

BaseModel.java

/**
* 空的类 如果存在多个模块导出数据,可以以此类作为模块总的父类
* 继承此类,可以直接使用工具类BaseExcelWriter或者CustomEasyExcel
* @author linmengmeng
* @date 2020年8月24日 上午9:11:28
*/
public class BaseModel {

}

MyUser Demo:

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

import com.alibaba.excel.write.metadata.WriteWorkbook;
import com.lin.test.excel.entity.MyUser;

public class MyUserExcelWrite extends BaseExcelWriter{

public MyUserExcelWrite(WriteWorkbook writeWorkbook) {
super(writeWorkbook);
}

@Override
protected List<? extends BaseModel> formateData(List<? extends BaseModel> objectList) {
List<MyUser> myUsers = new ArrayList<MyUser>();
MyUser myUser = null;
for (BaseModel baseModel : objectList) {
if (baseModel instanceof MyUser) {
myUser = (MyUser) baseModel;
//这里可以对myUser实体类的字段进行格式化或者code转msg
myUsers.add(myUser);
}
}
return myUsers;
}

}

又或者使用下面的总的工具类:

import java.util.List;

import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.exception.ExcelGenerateException;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;

/**
* easyexcel 2.0.6版本可用的自定义输出工具类
* @author linmengmeng
* @date 2020年8月24日 上午9:13:17
*/
public class CustomEasyExcel extends EasyExcel {

private static String DEFAULT_SHEET_NAME = "Sheet1";

private static Integer DEFAULT_SHEET_NO = 0;

protected WriteSheet getWriteSheet(){
//ExcelWriterSheetBuilder excelWriterSheetBuilder = MyCustomEasyExcel.writerSheet(DEFAULT_SHEET_NO, DEFAULT_SHEET_NAME);
WriteSheet writeSheet = new WriteSheet();
writeSheet.setSheetNo(DEFAULT_SHEET_NO);
writeSheet.setSheetName(DEFAULT_SHEET_NAME);
return writeSheet;
}

/**
* 自定义Excel样式内容
* @return
*/
protected HorizontalCellStyleStrategy customCellStyle(){
return getDefaultStyle();
}

/**
* 自定义Excel样式 头的策略
* @return
*/
protected WriteCellStyle customHeadWriteCellStyle(WriteCellStyle headWriteCellStyle){
if (headWriteCellStyle == null) {
headWriteCellStyle = new WriteCellStyle();
// 背景色
headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 12);
headWriteCellStyle.setWriteFont(headWriteFont);
}
return headWriteCellStyle;
}

/**
* 自定义Excel样式 内容的策略
* @return
*/
protected WriteCellStyle customContentWriteCellStyle(WriteCellStyle contentWriteCellStyle){
if (contentWriteCellStyle == null) {
contentWriteCellStyle = new WriteCellStyle();
WriteFont contentWriteFont = new WriteFont();
// 字体大小
contentWriteFont.setFontHeightInPoints((short) 12);
contentWriteCellStyle.setWriteFont(contentWriteFont);
//设置 自动换行
contentWriteCellStyle.setWrapped(true);
//设置 垂直居中
contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
// //设置 水平居中
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// //设置边框样式
contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
}
return contentWriteCellStyle;
}


/**
* 默认Excel样式内容
* 默认灰色背景表头,宋体 12号 表头字体加粗 单线边框
* @return
*/
private HorizontalCellStyleStrategy getDefaultStyle(){
// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现,自己实现后将实现类传入writeSheet.registerWriteHandler()
return new HorizontalCellStyleStrategy(customHeadWriteCellStyle(null), customContentWriteCellStyle(null));
}

/**
* 单个数据源,写入单个sheet里面
* @auther linmengmeng
* @Date 2020-08-18 下午3:47:29
* @param objectList
* @param customEasyExcel
*/
public void customWriteOneSheet(List<? extends BaseModel> objectList, CustomEasyExcel customEasyExcel, ExcelWriter excelWriter){
if (customEasyExcel == null) {
throw new IllegalArgumentException("customEasyExcel can not be null");
}
List<? extends BaseModel> data = customEasyExcel.formateData(objectList);
if (data == null || data.isEmpty()) {
throw new ExcelGenerateException("the output must not be empty");
}
WriteSheet writeSheet;
writeSheet = EasyExcel.writerSheet(0, "默认sheetName")
.head(objectList.get(0).getClass())
// .registerWriteHandler(this.customCellStyle())
.registerWriteHandler(customEasyExcel.customCellStyle())
//.registerWriteHandler(new CustomCellWriteHandler())
.build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
//List<MyUser> data = OldTestWriteExcel.getUser();
excelWriter.write(data, writeSheet);
}

/**
* 由子类自定义实现 处理待输出的内容
* 如:数据库查出来的code 处理成中文
* @param objectList
* @return
*/
protected List<? extends BaseModel> formateData(List<? extends BaseModel> objectList){
return objectList;
}
}

默认使用方式,使用上面默认的样式,如需自定义样式,可以实现上面的CustomEasyExcel 类,重写对应方法即可:

public static void customBaseTestStyle() {

String outPath_xlsx = "C:\\Users\\jadl\\Desktop\\testWrite.xlsx";

FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(outPath_xlsx);
} catch (FileNotFoundException e) {
e.printStackTrace();
}

//创建ExcelWriter 对象,这里使用的绝对路径,可以看下EasyExcel.write()方法,支持多种参数,web端一般使用response操作输出流
ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
//这里使用的默认样式,自定义时改成子类实例对象即可。
CustomEasyExcel customEasyExcel = new CustomEasyExcel();
//模拟待导出数据
List<MyUser> userList = getUser();
//执行写入数据到Excel流中
customEasyExcel.customWriteOneSheet(userList, customEasyExcel, excelWriter);

// finish 关闭流
excelWriter.finish();

}

public static List<MyUser> getUser(){
List<MyUser> myUsers = new ArrayList<MyUser>();
MyUser myUser = null;
for (int i = 0; i < 5; i++) {
if (i == 2) {
myUser = new MyUser();
}else if (i == 3) {
myUser = new MyUser("name" + i, null, "idNum" + i);
}else {
myUser = new MyUser("name" + i, i+1, "idNum" + i);
}
myUsers.add(myUser);
}
return myUsers;
}

5.2 公共读工具类:

easyExcel的读取excel使用的是类似监听器的方式,首先定义读取某个excel的监听器,然后使用监听器去逐个读取excel单元格数据。

定义一个公共的基类监听器:BaseModelExcelListener

package com.jadlsoft.utils.excel;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.read.context.AnalysisContext;
import com.alibaba.excel.read.event.AnalysisEventListener;
import com.alibaba.excel.read.exception.ExcelAnalysisException;
import com.jadlsoft.utils.DateUtils;

/**
* 带有校验结果的模型 解析监听器
* @author linmengmeng
* @date 2020年8月6日 上午9:05:38
* @param BaseRowModel
*/
public class BaseModelExcelListener extends AnalysisEventListener<BaseRowModel> {

protected Logger log = Logger.getLogger(BaseModelExcelListener.class);

/**
* 暂存读取的数据 <当前行号, data>
*/
private Map<Integer, BaseRowModel> dataMap = new HashMap<Integer, BaseRowModel>();

/**
* checkResultInfo为校验结果
*
* 可以自定义校验结果类型
*/
protected Object checkResultInfo;

/**
* 自定义读取位置
*/
protected Sheet sheet;

/**
* 默认从excel的第一个表的第一行开始读取
* 子类可自定义实现读取位置
* @return sheet
*/
public Sheet getSheet() {
if (sheet == null) {
return new Sheet(1, 1);
}
return sheet;
}

@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}

public Map<Integer, BaseRowModel> getDatas() {
return dataMap;
}

@Override
public void invoke(BaseRowModel baseRowModel, AnalysisContext context) {
//存在非法数据时直接抛出异常,不用接着往下读取Excel中的数据了
if (checkResultInfo != null) {
throw new ExcelAnalysisException(checkResultInfo.toString());
}
Object customCheck = customCheckSheet(baseRowModel, context);
if (customCheck != null) {
checkResultInfo = customCheck;
return;
}
dataMap.put(context.getCurrentRowNum()+1, baseRowModel);
}

/**
* 交由子类自定义实现校验规则
* @param object
* @param currentRowNum
* @return
*/
protected Object customCheckSheet(BaseRowModel object, AnalysisContext context) {
return null;
}

public Object getCheckResultInfo(){
return checkResultInfo;
}

/**
* 核验日期格式 可为空,不为空则必须为yyyyMMdd格式
* @auther linmengmeng
* @Date 2020-08-05 下午5:16:55
* @return boolean
*/
protected boolean checkDateStr(String dateStr){
if (StringUtils.isBlank(dateStr)) {
return true;
}
if (dateStr.trim().length() != 8) {
return false;
}
Pattern pattern = Pattern.compile("[0-9]+");
Matcher isNum = pattern.matcher(dateStr);
if(!isNum.matches()){
return false;
}
if (compareDateStr(dateStr, DateUtils.DATE_PATTERN_NO_SPLIT, new Date()) > 0) {
return false;
}
return true;
}

/**
* 判断时间字符串 时间
* 在date之前返回-1 之后返回 1 同一天返回0
* @auther linmengmeng
* @Date 2020-08-05 下午6:02:10
* @param dateStr 时间字符串
* @param formatter 时间格式
* yyyyMMdd
* yyyy-MM-dd
* yyyy-MM-dd HH:mm:ss
* @param date 对比时间
* @return
*/
protected int compareDateStr(String dateStr, String formatter, Date date){
Date createDate = DateUtils.createDate(dateStr, formatter);
if (createDate != null) {
return createDate.compareTo(date);
}
return 0;
}

/**
* 是否是非空数字字符串
* @auther linmengmeng
* @Date 2020-08-11 上午8:58:08
* @param numberStr 数字字符串
* @param strLength 长度, null时不校验长度
* @return 非空数字字符串:true
*/
protected boolean isNumberStr(String numberStr, Integer strLength){
if (StringUtils.isEmpty(numberStr)) {
return false;
}
if (strLength != null) {
if (numberStr.length() < strLength) {
return false;
}
}
Pattern pattern = Pattern.compile("[0-9]+");
Matcher isNum = pattern.matcher(numberStr);
if(isNum.matches()){
return true;
}
return false;
}
}

可以看到该类继承了AnalysisEventListener类,该类为抽象类,只定义了两个抽象方法,具体可由子类实现:

public abstract class AnalysisEventListener<T> {

/**
* when read one row trigger invoke function
*
* @param object one row data
* @param context read context
*/
public abstract void invoke(T object, AnalysisContext context);

/**
* if have something to do after all read
*
* @param context context
*/
public abstract void doAfterAllAnalysed(AnalysisContext context);
}

这两个方法,invoke为读取每一行数据之后,会自动执行​​invoke​​​方法。读取excel数据结束后会执行​​doAfterAllAnalysed​​​方法。显而易见,数据校验的话,我们这里只需要实现​​invoke​​方法即可。

在自定义的​​BaseModelExcelListener​​​类中定义了一个​​dataMap​​用来暂存读取到的excel行数据。

/**
* 暂存读取的数据 <当前行号, data>
*/
private Map<Integer, BaseRowModel> dataMap = new HashMap<Integer, BaseRowModel>();

其中​​BaseRowModel​​​为easyExcel中定义的一个空对象,前面有所提到,新版本该类给删掉了,如果使用的新版本,可以自定义一个基类,或者修改下面工具类方法中用到​​BaseRowModel​​的地方,直接声明使用泛型即可。

主要看下​​invoke​​方法:

@Override
public void invoke(BaseRowModel baseRowModel, AnalysisContext context) {
//存在非法数据时直接抛出异常,不用接着往下读取Excel中的数据了
if (checkResultInfo != null) {
throw new ExcelAnalysisException(checkResultInfo.toString());
}
Object customCheck = customCheckSheet(baseRowModel, context);
if (customCheck != null) {
checkResultInfo = customCheck;
return;
}
dataMap.put(context.getCurrentRowNum()+1, baseRowModel);
}

这里在校验时,添加了​​checkResultInfo​​​属性,用来暂存校验结果,如果一旦发现该属性的值为非空,则证明读取内容中,含有非法数据。该方法调用了​​customCheckSheet​​,这里也是定义的抽象方法,毕竟每个excel的内容和校验规则不一样,可以继承该类后,自由实现校验规则。

/**
* 交由子类自定义实现校验规则
* @param object
* @param currentRowNum
* @return
*/
protected Object customCheckSheet(BaseRowModel object, AnalysisContext context) {
return null;
}

子类实现​​customCheckSheet​​方法,实现自定义校验规则。

package com.jadlsoft.utils.excel.read;
import org.apache.commons.lang.StringUtils;

import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.read.context.AnalysisContext;
import com.jadlsoft.domain.excel.QzclInfo;
import com.jadlsoft.utils.excel.BaseModelExcelListener;

/**
* 核验导入上传的excel中数据有效性 (必填项是否为空,日期是否非法)
* @author linmengmeng
* @date 2020年8月5日 下午2:46:19
*/
public class QzModelExcelListener extends BaseModelExcelListener {

/**
* 单次上传附件中最大数据量(加上表头占用行)
*/
private static final Integer FILE_MAX_ROWS_NUM = 502;

public QzModelExcelListener() {
//super();
this.sheet = new Sheet(1, 2, QzclInfo.class);
}


@Override
protected String customCheckSheet(BaseRowModel baseRowModel, AnalysisContext context) {
Integer currentRowNum = context.getCurrentRowNum()+1;
String errorMsg = null;
if (currentRowNum.compareTo(FILE_MAX_ROWS_NUM) > 0) {
errorMsg = "单次上传附件中最多上传" + (FILE_MAX_ROWS_NUM-2) + "条数据";
}
QzclInfo qzclInfo = null;
while (baseRowModel instanceof QzclInfo) {
qzclInfo = (QzclInfo) baseRowModel;
if (StringUtils.isEmpty(qzclInfo.getQzzl())) {
return errorMsg = "第" + currentRowNum + "行种类录入错误,请重新录入";
}
if (StringUtils.isEmpty(qzclInfo.getQzxh())) {
return errorMsg = "第" + currentRowNum + "行型号录入错误,请重新录入";
}
if (StringUtils.isEmpty(qzclInfo.getCxd())) {
return errorMsg = "第" + currentRowNum + "行产销地录入错误,请重新录入";
}
if (StringUtils.isEmpty(qzclInfo.getQh())) {
return errorMsg = "第" + currentRowNum + "行号录入错误,请重新录入";
}
if (StringUtils.isEmpty(qzclInfo.getSccj())) {
return errorMsg = "第" + currentRowNum + "行生产厂家录入错误,请重新录入";
}
if (!checkDateStr(qzclInfo.getScrqStr())) {
return errorMsg = "第" + currentRowNum + "行生产日期录入错误,请重新录入";
}
if (!checkDateStr(qzclInfo.getGmrqStr())) {
return errorMsg = "第" + currentRowNum + "行购买日期录入错误,请重新录入";
}
baseRowModel = null;
}
return errorMsg;
}
}

QzclInfo.java为定义的映射Excel表头的实体类。

package com.jadlsoft.domain.excel;

import java.util.Date;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;

public class QzclInfo extends BaseRowModel {
@ExcelProperty(value ={"种类"},index = 0)
private String qzzl;

@ExcelProperty(value ={"型号"},index =1)
private String qzxh;

@ExcelProperty(value ={"产销地"},index = 2)
private String cxd;

@ExcelProperty(value ={"号"},index = 3)
private String qh;

//读取excel中的数据
@ExcelProperty(value ={"生产日期"},index = 4)
private String scrqStr;

//实际时间
private Date scrq;

@ExcelProperty(value ={"生产厂家"},index = 5)
private String sccj;

@ExcelProperty(value ={"购买日期"},index = 6)
private String gmrqStr;

private Date gmrq;


public String getQzzl() {
return qzzl;
}

public void setQzzl(String qzzl) {
this.qzzl = qzzl;
}

public String getQzxh() {
return qzxh;
}

public void setQzxh(String qzxh) {
this.qzxh = qzxh;
}

public String getCxd() {
return cxd;
}

public void setCxd(String cxd) {
this.cxd = cxd;
}

public String getQh() {
return qh;
}

public void setQh(String qh) {
this.qh = qh;
}

public String getScrqStr() {
return scrqStr;
}

public void setScrqStr(String scrqStr) {
this.scrqStr = scrqStr;
}

public Date getScrq() {
return scrq;
}

public void setScrq(Date scrq) {
this.scrq = scrq;
}

public String getSccj() {
return sccj;
}

public void setSccj(String sccj) {
this.sccj = sccj;
}

public Date getGmrq() {
return gmrq;
}

public void setGmrq(Date gmrq) {
this.gmrq = gmrq;
}

public String getGmrqStr() {
return gmrqStr;
}

public void setGmrqStr(String gmrqStr) {
this.gmrqStr = gmrqStr;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{\"qzzl\":\"");
builder.append(qzzl);
builder.append("\", \"qzxh\":\"");
builder.append(qzxh);
builder.append("\", \"cxd\":\"");
builder.append(cxd);
builder.append("\", \"qh\":\"");
builder.append(qh);
builder.append("\", \"scrqStr\":\"");
builder.append(scrqStr);
builder.append("\", \"scrq\":\"");
builder.append(scrq);
builder.append("\", \"sccj\":\"");
builder.append(sccj);
builder.append("\", \"gmrqStr\":\"");
builder.append(gmrqStr);
builder.append("\", \"gmrq\":\"");
builder.append(gmrq);
builder.append("\"}");
return builder.toString();
}
}

定义工具类结束了,下面看下如何具体使用:
​​​EasyExcelUtil.java​​工具类中定义读取Excel的方法,这里传入获取到的数据流InputStream ,如何获取这里就不提了,方式有很多,看自己具体的实现了。

/**
* 校验excel中数据
* @auther linmengmeng
* @Date 2020-08-05 下午3:08:40
* @param listener 自定义Excel监听器
* @param inputStream
* @param clazz Excel接收实体类对象
* @param excelTypeEnum excel后缀名
* @return
*/
public static Map<String, Object> checkExcelWithModel(BaseModelExcelListener listener, InputStream inputStream, ExcelTypeEnum excelTypeEnum){
ExcelReader excelReader = new ExcelReader(inputStream, excelTypeEnum, null, listener);
try {
excelReader.read(listener.getSheet());
} catch (ExcelAnalysisException e) {
logger.error("附件内容校验未通过:" + e.getMessage());
}
Map<String, Object> resultMap = new HashMap<String, Object>();
resultMap.put("checkResultInfo", listener.getCheckResultInfo());
resultMap.put("resultData", listener.getDatas());
return resultMap;
}

可以看到,方法中try-catch代码块中监听了​​ExcelAnalysisException​​异常,这里对应invok方法中校验结果非空时抛出此异常,

//存在非法数据时直接抛出异常,不用接着往下读取Excel中的数据了
if (checkResultInfo != null) {
throw new ExcelAnalysisException(checkResultInfo.toString());
}

业务层使用时,直接调用:

Map<String, Object> checkExcelWithModel = EasyExcelUtil.checkExcelWithModel(new SjsbModelExcelListener(), inputStream, ExcelTypeEnum.XLSX);
if (checkExcelWithModel == null || checkExcelWithModel.isEmpty()) {
log.error("读取excel文件异常,checkExcelWithModel:" + checkExcelWithModel);
}
Object checkResultInfo = checkExcelWithModel.get("checkResultInfo");
if (checkResultInfo != null) {
log.error("excel中包含非法数据,checkResultInfo:" + checkResultInfo.toString());
}
Object resultData = checkExcelWithModel.get("resultData");
if (resultData == null) {
log.error("读取excel文件异常,checkExcelWithModel:" + checkExcelWithModel);
}
@SuppressWarnings("unchecked")
Map<Integer, SjsbMxWriteRowModel> sjsbMxMap = (Map<Integer, SjsbMxWriteRowModel>) resultData;
if(sjsbMxMap.isEmpty()){
log.error("读取excel文件内容为空:" + checkExcelWithModel);
}

问题记录:

  1. 设置导出的文件,只读功能为实现。后面有时间再研究研究。
  2. 在读取excel内容时,如果传入的excel文件类型有误(上传的xls格式的文件,读取传入xlsx,或者反过来),则会出现空指针异常。需要手动校验excel文件格式,传入对应的格式即可。


标签:return,EasyExcel,excel,public,class,源码,new,import,梳理
From: https://blog.51cto.com/linmengmeng/5907638

相关文章

  • 学习Spring源码问题总结
    记录一下学习源码遇到的问题:​​1.编译时报错:Failedtoapplyplugin[id'com.gradle.build-scan']​​​​2.报错Groovy:compilermismatchprojectlevelis:2.4Works......
  • (旧)springboot 快速实现登录、注册功能(附Demo源码)
    1.直接跑通Demo,修改配置文件。导入数据库sql文件即可。2.跟着一步一步实现。当然你也可以先跑通Demo,在尝试自己跟着来一遍1.跑通Demo需要源码和Demo跳转新项目跳转新......
  • Dubbo源码-11-服务引用流程
    一入口publicstaticvoidmain(String[]args){//引用远程服务此实例很重封装了与注册中心的连接以及与提供者的连接ReferenceConfig<DemoServic......
  • Dubbo源码-12-Cluster
    一接口声明@SPI(FailoverCluster.NAME)publicinterfaceCluster{/***Mergethedirectoryinvokerstoavirtualinvoker.**@param<T>......
  • Bert源码学习
    文章目录​​前言​​​​1.bert模型网络modeling.py​​​​1.1整体架构BertModel(object):​​​​1.2embedding层​​​​1.2.1embedding_lookup​​​​1.2.2词向......
  • 源码注释中的"方法签名"是什么意思?
    我们经常可以在源码注释中看到methodsignature,也就是方法签名,那它指的是方法中的哪部分呢?好比@Async中的第二段注释中《Java语言程序设计》一书中对方法的描述中有......
  • 直播系统app源码,设置样式(字体样式、行列宽高、对齐方式、边框)
    直播系统app源码,设置样式(字体样式、行列宽高、对齐方式、边框)1.字体样式 fromopenpyxlimportWorkbookfromopenpyxl.stylesimportFontwb=Workbook()ws=wb.act......
  • Mysql 源码解读-执行器
    Mysql源码解读-执行器一条sql执行过程中,首先进行词法分析和语法分析,然后将由优化器进行判断,如何执行更有效率,生成执行计划,后面的任务就交给了执行器。在执行的过程中,执......
  • 结合RocketMQ 源码,带你了解并发编程的三大神器
    摘要:本文结合RocketMQ源码,分享并发编程三大神器的相关知识点。本文分享自华为云社区《读RocketMQ源码,学习并发编程三大神器》,作者:勇哥java实战分享。这篇文章,笔者结......
  • 开发一个家政小程序有什么优势呢?|家政小程序源码开发
    随着居民生活水平的提高,家政行业的市场规模也在不断增大,传统的家政公司不管是在服务方式还是推广方式上效率都很低下,导致用户体验感很差,而家政小程序的开发就能弥补这些不足......