【excel】easy excel如何导出动态列

动态也有多重含义:本文将描述两种动态场景下的解决方案

场景一:例如表头第一列固定为动物,且必定有第二列,第二列的表头可能为猫 也可能为狗;这是列数固定,列名不固定的场景;

场景二:更复杂的场景则为 第二列可能为猫 可能为狗,第三列可能为熊,也可能没有第三列,甚至可能会有第四列;这是表头数和列数都不固定的场景;

文章目录

场景一:表头名不固定

如下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);
相关推荐
魔道不误砍柴功33 分钟前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉34 分钟前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v36 分钟前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge36 分钟前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@37 分钟前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄38 分钟前
SpringBoot
java·spring
数据小小爬虫1 小时前
如何用Java爬虫“偷窥”淘宝商品类目API的返回值
java·爬虫·php
暮春二十四1 小时前
关于用postman调用接口成功但是使用Java代码调用却失败的问题
java·测试工具·postman
java小吕布2 小时前
Java中Properties的使用详解
java·开发语言·后端
爱吃土豆的程序员2 小时前
在oracle官网下载资源显示400 Bad Request Request Header Or Cookie Too Large 解决办法
java·数据库·oracle·cookie