动态也有多重含义:本文将描述两种动态场景下的解决方案
场景一:例如表头第一列固定为动物,且必定有第二列,第二列的表头可能为猫 也可能为狗;这是列数固定,列名不固定的场景;
场景二:更复杂的场景则为 第二列可能为猫 可能为狗,第三列可能为熊,也可能没有第三列,甚至可能会有第四列;这是表头数和列数都不固定的场景;
文章目录
场景一:表头名不固定
如下dto所示,content字段对应的表头可能会变换,我们只需要在ExcelProperty注解中使用占位符替代
java
@HeadRowHeight(30)
@ContentRowHeight(20)
@Data
public class EasyExcelDTO {
@ColumnWidth(30)
@ExcelProperty("标题")
private String title;
@ColumnWidth(30)
@ExcelProperty("${content}")
private String content;
}
测试代码:
(其中IoUtil是hutool包的工具类)
java
public static void main(String[] args) throws FileNotFoundException {
File file = new File(("C:\\Users\\10057\\Desktop\\easy_excel.xls"));
List<EasyExcelDTO> res = new ArrayList<>();
EasyExcelDTO easyExcelDTO = new EasyExcelDTO();
Map<String, String> map = new HashMap<>();
// DTO里面有title字段
// map.put("title","1");
// 相当于bean拷贝 (下面这行是cglib里面的代码)
BeanMap.create(easyExcelDTO).putAll(map);
System.out.println(easyExcelDTO);
easyExcelDTO.setContent("666");
res.add(easyExcelDTO);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
JSONObject jsonObject = new JSONObject();
jsonObject.put("content", "测试动态标题111");
EasyExcel.write(byteArrayOutputStream, EasyExcelDTO.class).sheet()
.registerWriteHandler(new ExcelHandler(jsonObject))
.doWrite(res);
FileOutputStream fos = new FileOutputStream(file);
IoUtil.copy(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()), fos);
}
导出的excel结果示例:可以看到第二列的表头内容成功被替换
场景二:表头名和列数都不固定
实体类代码:
(实际使用中,将DynamicFieldExcelDTO作为一个基类,而涉及了动态导出的DTO类继承该类 ,例如 AnimalExcelDTO extends DynamicFieldExcelDTO, 这么做的目的 主要是需要设定一个dynamicFields字段,用于处理器里面映射并替换值)
dynamicFields的key值则为表头,value为内容值
java
import com.alibaba.excel.annotation.ExcelIgnore;
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;
import java.util.Map;
@HeadRowHeight(30)
@ContentRowHeight(20)
@Data
public class DynamicFieldExcelDTO {
@ColumnWidth(20)
@ExcelProperty("订单编号")
private String orderNo;
/**
* K: 表头 V:值 (注意要加上@ExcelIgnore)
*/
@ExcelIgnore
private Map<String,Object> dynamicFields;
}
测试代码:
java
/**
* 动态字段导出方法 (不需要调整之前的固定字段)
* usage: exportDTO类 <b><u>已有固定字段</u></b> ,需要新增动态字段(即需要追加动态表头及对应数据)
* 模拟数据: orderNo为固定字段,dynamicFields map中的key为动态表头
* {@link DynamicColumnWriteHandler}
*/
@Test
public void dynamicHeadMixWrite() {
String fileName = "D:\\test\\out.xlsx";
List<String> header = Arrays.asList("动态头部1","动态头部2","动态头部3");
List<DynamicFieldExcelDTO> dataList = new ArrayList<>();
DynamicFieldExcelDTO d1 = new DynamicFieldExcelDTO();
d1.setOrderNo("订单111");
Map<String, Object> map = new HashMap<>();
map.put("动态头部1","aaa111");
map.put("动态头部2","aaa222");
map.put("动态头部3","aaa333");
d1.setDynamicFields(map);
DynamicFieldExcelDTO d2 = new DynamicFieldExcelDTO();
d2.setOrderNo("订单222");
Map<String, Object> map2 = new HashMap<>();
map2.put("动态头部1","bbb111");
map2.put("动态头部2","bbb222");
map2.put("动态头部3","bbb333");
d2.setDynamicFields(map2);
DynamicFieldExcelDTO d3 = new DynamicFieldExcelDTO();
d3.setOrderNo("订单333");
Map<String, Object> map3 = new HashMap<>();
map3.put("动态头部1","ccc111");
map3.put("动态头部2","ccc222");
map3.put("动态头部3","ccc333");
d3.setDynamicFields(map3);
dataList.add(d1);
dataList.add(d2);
dataList.add(d3);
EasyExcel.write(fileName,DynamicFieldExcelDTO.class)
.registerWriteHandler(new DynamicColumnWriteHandler<>(header,dataList))
.sheet()
.doWrite(dataList);
}
/**
* 如果全部为动态字段 将动态数据传参即可
*/
@Test
public void dynamicHeadWrite() {
String fileName = "D:\\test\\out.xlsx";
EasyExcel.write(fileName)
// 这里放入动态头
.head(new ArrayList<>()).sheet("模板")
// dataList
.doWrite(new ArrayList());
}
处理器代码:
主要逻辑是在afterRowDispose方法里面追加cell,并通过判断列数、是否创建过表头,避免重复添加
java
/**
* 动态导出
* @author csdn:孟秋与你
*/
public class DynamicColumnWriteHandler<T extends DynamicFieldExcelDTO> implements RowWriteHandler {
private final List<String> dynamicHeaders;
private final List<T> dataList;
private ConcurrentHashMap<String,Boolean> isHeaderCreated = new ConcurrentHashMap<>();
/**
* 用于记录表头列数
*/
private AtomicInteger totalHeaderColumns = new AtomicInteger(-1);
public DynamicColumnWriteHandler(List<String> dynamicHeaders, List<T> dataList) {
this.dynamicHeaders = dynamicHeaders;
this.dataList = dataList;
}
@Override
public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Integer rowIndex, Integer relativeRowIndex, Boolean isHead) { }
@Override
public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer rowIndex, Boolean isHead) {}
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer rowIndex, Boolean isHead) {
if (isHead && rowIndex == 0) {
// 获取默认的头部样式
CellStyle headerCellStyle = row.getCell(0).getCellStyle();
// 创建动态列头部
int lastCellIndex = row.getLastCellNum() == -1 ? 0 : row.getLastCellNum();
// 记录表头的总列数
totalHeaderColumns.set(lastCellIndex + dynamicHeaders.size());
for (int i = 0; i < dynamicHeaders.size(); i++) {
if (Objects.equals(isHeaderCreated.get(dynamicHeaders.get(i)), Boolean.TRUE)) {
continue;
}
Cell cell = row.createCell(lastCellIndex + i);
cell.setCellValue(dynamicHeaders.get(i));
// 设置样式
cell.setCellStyle(headerCellStyle);
isHeaderCreated.put(dynamicHeaders.get(i),Boolean.TRUE);
}
} else if (!isHead) {
// 检查数据行是否超出表头的列数
if (row.getLastCellNum() + dynamicHeaders.size() > totalHeaderColumns.get()) {
// 如果超出,不写入该行数据
return;
}
// 数据行的写入逻辑
if (rowIndex < dataList.size()) {
T dto = dataList.get(rowIndex);
// k:与表头内容对应
Map<String, Object> dynamicFields = dto.getDynamicFields();
int lastCellIndex = row.getLastCellNum() == -1 ? 0 : row.getLastCellNum();
for (int i = 0; i < dynamicHeaders.size(); i++) {
Cell cell = row.createCell(lastCellIndex + i);
Object value = dynamicFields.get(dynamicHeaders.get(i));
cell.setCellValue(value == null ? "" : value.toString());
}
}
}
}
}
导出的excel结果示例:可以看到订单编号固定字段,以及其它动态字段 都准确的导出。
其中处理器写的比较仓促,网上用处理器实现动态列的案例很少,博主自己测试通过,如果发现有问题或者更好的建议 欢迎各位在评论区指出;
如果追求稳妥的话,直接在head里面传动态参数即可
(dynamicHeadWrite 方法,可以参考github中easy excel给出的官方demo ),
缺点就是需要将所有的表头都放到headers里面。
假设有个业务场景:你们项目中已有导出近百个字段的报表功能,这些字段之前都是固定的,突然加了个需求 需要导出数个动态字段; 为了这几个动态字段 把之前所有固定字段都写一遍 存入list中 代码可能会很丑陋, 如:
java
List headers = new ArrayList();
headers.add("第一列");
headers.add("第二列");
// ...
headers.add("第一百列");
// 动态列
List dynamicColumn = xxx;
headers.addAll(dynamicColumn);
String fileName = "D:\\test\\out.xlsx";
EasyExcel.write(fileName)
// 这里放入动态头
.head(headers).sheet("模板")
// dataList 你的数据
.doWrite(dataList);