EasyExcel合并单元格,通过注解方式实现自定义合并策略
简介
今天博主探讨如何实现EasyExcel自定义实现单元格合并策略,考虑需要抽象代码、适用于更多的业务场景,提供了两个工具类,参考过很多博主的方案,并整合优化了一下。
主要思路提供的博主:十一技术斩
代码
- 首先自定义注解CustomMerge,用于标记哪些属性需要合并,哪个是主键**(这里做了一个优化,可以标记多个主键)**
/**
* 自定义注解,用于判断是否需要合并以及合并的主键
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CustomMerge {
/**
* 是否需要合并单元格
*/
boolean needMerge() default false;
/**
* 是否是主键,即该字段相同的行合并
*/
boolean isPk() default false;
}
- 再创建自定义单元格合并策略类CustomMergeStrategy,当Excel中两列主键相同时,合并被标记需要合并的列**(当前类增加多主键判断是否需要合并)**
/**
* 自定义单元格合并策略
*/
public class CustomMergeStrategy implements RowWriteHandler {
/**
* 主键下标集合
*/
private List<Integer> pkColumnIndex = new ArrayList<>();
/**
* 需要合并的列的下标集合
*/
private List<Integer> needMergeColumnIndex = new ArrayList<>();
/**
* DTO数据类型
*/
private Class<?> elementType;
public CustomMergeStrategy(Class<?> elementType) {
this.elementType = elementType;
}
@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Integer relativeRowIndex, Boolean isHead) {
// 如果是标题,则直接返回
if (isHead) {
return;
}
// 获取当前sheet
Sheet sheet = writeSheetHolder.getSheet();
// 获取标题行
Row titleRow = sheet.getRow(0);
if (pkColumnIndex.isEmpty()) {
this.lazyInit(writeSheetHolder);
}
// 判断是否需要和上一行进行合并
// 不能和标题合并,只能数据行之间合并
if (row.getRowNum() <= 1) {
return;
}
// 获取上一行数据
Row lastRow = sheet.getRow(row.getRowNum() - 1);
// 将本行和上一行是同一类型的数据(通过主键字段进行判断),则需要合并
boolean margeBol = true;
for (Integer pkIndex : pkColumnIndex) {
String lastKey = lastRow.getCell(pkIndex).getCellType() == CellType.STRING ? lastRow.getCell(pkIndex).getStringCellValue() : String.ValueOf(lastRow.getCell(pkIndex).getNumericCellValue());
String currentKey = row.getCell(pkIndex).getCellType() == CellType.STRING ? row.getCell(pkIndex).getStringCellValue() : String.ValueOf(row.getCell(pkIndex).getNumericCellValue());
if (!StringUtils.equalsIgnoreCase(lastKey, currentKey)) {
margeBol = false;
break;
}
}
if (margeBol) {
for (Integer needMerIndex : needMergeColumnIndex) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(),
needMerIndex, needMerIndex);
sheet.addMergedRegionUnsafe(cellRangeAddress);
}
}
}
/**
* 初始化主键下标和需要合并字段的下标
*/
private void lazyInit(WriteSheetHolder writeSheetHolder) {
// 获取当前sheet
Sheet sheet = writeSheetHolder.getSheet();
// 获取标题行
Row titleRow = sheet.getRow(0);
// 获取DTO的类型
Class<?> eleType = this.elementType;
// 获取DTO所有的属性
Field[] fields = eleType.getDeclaredFields();
int i = 0;
// 遍历所有的字段,因为是基于DTO的字段来构建excel,所以字段数 >= excel的列数
for (Field theField : fields) {
// 获取@ExcelProperty注解,用于获取该字段对应在excel中的列的下标
ExcelProperty easyExcelAnno = theField.getAnnotation(ExcelProperty.class);
// 为空,则表示该字段不需要导入到excel,直接处理下一个字段
if (null == easyExcelAnno) {
continue;
}
// 获取自定义的注解,用于合并单元格
CustomMerge customMerge = theField.getAnnotation(CustomMerge.class);
// 没有@CustomMerge注解的默认不合并
if (null == customMerge) {
continue;
}
// 判断是否有主键标识
if (customMerge.isPk()) {
pkColumnIndex.add(i);
}
// 判断是否需要合并
if (customMerge.needMerge()) {
needMergeColumnIndex.add(i);
}
}
// 没有指定主键,则异常
if (pkColumnIndex.isEmpty()) {
throw new IllegalStateException("使用@CustomMerge注解必须指定主键");
}
}
}
总结
实现效果可以参考上面十一技术斩,这里主要对博主的自定义策略做了一些小小的优化,希望能帮到更多需要同学。