一、前言
之前,写了 EasyExcel复杂表头导入(一对多)的博客,效果不错,好多网友留言让我再写一个导入的。盛情难却,就写了一个,发现问题很多。
现已通过自定义拦截器的形式完美解决了该博客的末尾的遗留问题,地址如下:
关于EasyPoi 框架的问题,在此不再赘述,参考我的另一篇博客,地址如下:
EasyExcel复杂表头导入(一对多)_间歇性悲伤患者的博客-CSDN博客_easyexcel复杂表头
实际上,官方文档和网上都没有详细的导出教程,需要自己参考官方去实现。我搞了半天只弄成功了一个半桶水的,十分惭愧,在此记录一下,方便回溯。
说明:EasyExcel无法处理List里面嵌套List的情况,我采用的方案是通过表格合并的来实现。
下面,先给出具体实现,然后再谈谈问题。
二、EasyExcel一对多导出的实现
2.1 Entity 对象
/**
* 客户信息导出类,指明导出模板样式等。是真正和EasyPoi交互的类
*/
@Data
@EqualsAndHashCode
@HeadRowHeight(30)
@ContentRowHeight(20)
@ColumnWidth(20)
@HeadStyle(fillForegroundColor = 44)
@NoArgsConstructor
class Customer extends BaseRowModel implements Serializable {
@ExcelProperty({"客户编号"})
private String userCode;
@ExcelProperty({"客户名称"})
private String userName;
@ColumnWidth(25)
@ExcelProperty({"客户所在地址"})
private String address;
@ExcelProperty({"联系人信息", "联系人姓名"})
private String personName;
@ExcelProperty({"联系人信息", "联系电话"})
private String telephone;
public Customer(CustomerInfo customerInfo) {
this.userCode = customerInfo.getUserCode();
this.userName = customerInfo.getUserName();
this.address = customerInfo.getAddress();
this.personName = customerInfo.getPersonList().get(0).getPersonName();
this.telephone = customerInfo.getPersonList().get(0).getTelephone();
}
public Customer(CustomerInfo.Person person) {
this.personName = person.getPersonName();
this.telephone = person.getTelephone();
}
}
/**
* 客户基本信息类,类比程序从service层拿到的信息
* 实际上,EasyPoi只能读取简单String、boolean、integer、float byte[] 等简单数据类型。无法处理List、Map等数据类型。
* 当前,你也可以只定义类型转换器Converter,具体见文章末尾的参考连接。
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class CustomerInfo {
private String userCode;
private String userName;
private String address;
private List<Person> personList;
@Data
@AllArgsConstructor
@NoArgsConstructor
static class Person {
private String personName;
private String telephone;
}
}
2.2 Controller 层
@IgnoreUserToken
@GetMapping("/exportExcel")
@ApiOperation("导出Excel")
public void exportExcel(HttpServletResponse response) throws Exception {
// 获取导出数据,模拟从service层拿到list数据。
List<CustomerInfo> list = getData();
// 获取所有客户信息中,联系人最多的数量。并以此数量作为行合并的依据
int maxColNum = 1;
for (CustomerInfo ele : list) {
if (ele.getPersonList().size() > maxColNum) {
maxColNum = ele.getPersonList().size();
}
}
// 整理,讲List包含List的数据格式,改成List的格式
final int finalMaxColNum = maxColNum;
List<Customer> result = list.stream()
.flatMap(ele -> {
List<Customer> temp = new ArrayList<>();
// 获取当前客户联系人的数量
int len = ele.getPersonList().size();
// 先添加一条完整的客户信息
temp.add(new Customer(ele));
for (int i = 1; i < finalMaxColNum; i++) {
if (i > len) {
// 达不到maxColNum的,补null占位。
temp.add(new Customer());
} else {
// 只添加联系人信息
temp.add(new Customer(ele.getPersonList().get(i)));
}
}
return temp.stream();
}).collect(Collectors.toList());
// 设置excel表头样式
WriteSheet sheet = EasyExcel.writerSheet("客户信息").head(Customer.class).sheetNo(1).build();
// 设置excel表格样式
ExcelWriter writer = EasyExcel.write(response.getOutputStream()).needHead(true).excelType(ExcelTypeEnum.XLSX)
.registerWriteHandler(new LoopMergeStrategy(maxColNum, 0)) // 设置第一列每maxColNum行合并
.registerWriteHandler(new LoopMergeStrategy(maxColNum, 1)) // 设置第二列每maxColNum行合并
.registerWriteHandler(new LoopMergeStrategy(maxColNum, 2)) // 设置第三列每maxColNum行合并
.build();
// 写入excel数据
writer.write(result, sheet);
// 通知浏览器以附件的形式下载处理,设置返回头要注意文件名有中文
response.setHeader("Content-disposition", "attachment;filename=" + new String("客户信息列表".getBytes("gb2312"), "ISO8859-1") + ".xlsx");
response.setContentType("multipart/form-data");
response.setCharacterEncoding("utf-8");
writer.finish();
}
2.3 getDate方法(用于模拟service层拿到的数据)
public static List<CustomerInfo> getData() {
List<CustomerInfo> data = new ArrayList<>();
CustomerInfo customer = new CustomerInfo();
customer.setUserCode("CT_jx001");
customer.setUserName("江西电信公司");
customer.setAddress("江西省南昌市青山湖区");
CustomerInfo.Person person1 = new CustomerInfo.Person("张三", "12345678910");
CustomerInfo.Person person2 = new CustomerInfo.Person("李四", "10987654321");
List<CustomerInfo.Person> personList = new ArrayList<>();
personList.add(person1);
personList.add(person2);
customer.setPersonList(personList);
data.add(customer);
CustomerInfo customer2 = new CustomerInfo();
customer2.setUserCode("CT_jx002");
customer2.setUserName("广东电信公司");
customer2.setAddress("广东省广州市花都区");
CustomerInfo.Person person12 = new CustomerInfo.Person("小明", "12345678910");
CustomerInfo.Person person22 = new CustomerInfo.Person("小红", "10987654321");
CustomerInfo.Person person23 = new CustomerInfo.Person("小王", "12345678910");
List<CustomerInfo.Person> personList2 = new ArrayList<>();
personList2.add(person12);
personList2.add(person22);
personList2.add(person23);
customer2.setPersonList(personList2);
data.add(customer2);
return data;
}
2.4 效果
接口是get类型的,浏览器直接访问,即可下载文件,效果如下。
顺便给出debug效果,方便理解。
debug效果
三、问题及展望
从实现效果即可看出本方法的问题,就是会出现空行。
但是,这个空行避免不了,因为多行合并,只能按照最大值给,且不能动态调整。
这种方式,虽然可以实现复杂表头的导出,但显示不是令人满意的,主要是因为存在空行的问题。
我实在是找不出更好的解决方案,在此仅提供如下思路。
也你能通过EasyPoi的 自定义拦截器、数据格式转换器、模板写入、合并单元格、重复多次写入等功能实现。
四、参考链接
除官网地址外,参考如下文章。
Can not find ‘Converter‘ support class List问题解决_我取这个昵称总没被使用吧?的博客-CSDN博客问题描述com.alibaba.excel.exception.ExcelDataConvertException: Can not find ‘Converter’ support class List.问题解释EasyExcel开源框架中Converter接口的convertToExcelData只实现了转换BigDecimal、Bolean、Byte[]、btye[]、Byte、Date、Double、File、Float、InputStream、Integer、Long、Short、URL这些https://blog.csdn.net/qq_41049371/article/details/120156305EasyExcel ExcelDataConvertException:Can not find ‘Converter‘ support class ArrayList问题解决_旭东怪的博客-CSDN博客问题描述:com.alibaba.excel.exception.ExcelDataConvertException:Cannotfind'Converter'supportclassArrayList.问题分析:1、查看doWrite(List data)的源码时发现Converter接口的convertToExcelData只实现了转换BigDecimal、Bolean、Byte[]、btye[]、Byte、Date、Double、File、Float、InputStream、...https://blog.csdn.net/qq_38974638/article/details/116609844