目录
- 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.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);
}
问题记录:
- 设置导出的文件,只读功能为实现。后面有时间再研究研究。
- 在读取excel内容时,如果传入的excel文件类型有误(上传的xls格式的文件,读取传入xlsx,或者反过来),则会出现空指针异常。需要手动校验excel文件格式,传入对应的格式即可。