测试代码地址:https://gitee.com/wangtianwen1996/cento-practice/tree/master/src/test/java/com/xiaobai/easyexcel/dynamiccolumn
官方文档:https://easyexcel.opensource.alibaba.com/docs/2.x/quickstart/write
一、实现方式
1、根据需要导出的列找到返回类对象属性的ExcelProperty和ColumnWidth注解,最终生成需要显示的列名和每列的列宽;
2、根据需要导出的列获取Excel中的行数据;
3、添加自定义单元格拦截策略(实现com.alibaba.excel.write.handler.WriteHandler接口)和数据类型转换策略(实现com.alibaba.excel.converters.Converter接口);
4、创建Excel的Sheet页,设置第一步获取的列宽;
二、代码实现
(一)添加基础数据类型转换器(LocalDate、LocalDateTime、LocalTime、Integer)
package com.xiaobai.easyexcel.dynamiccolumn;
import cn.hutool.core.date.DateTime;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @author wangtw
* @ClassName DataConverter
* @description: 类型转换器
* @date 2024/2/919:23
*/
public class DataConverter {
public static class CoreConverter<T> implements Converter<T> {
private final Class<T> tClass;
public CoreConverter(Class<T> tClass) {
this.tClass = tClass;
}
/**
* 导出支持的类型
* @return
*/
@Override
public Class supportJavaTypeKey() {
return tClass;
}
/**
* 导入支持的Excel类型
* @return
*/
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 导入类型转换
* @param cellData
* @param excelContentProperty
* @param globalConfiguration
* @return
* @throws Exception
*/
@Override
public T convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
if (cellData.getData() instanceof LocalDate) {
return (T) LocalDate.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
if (cellData.getData() instanceof LocalTime) {
return (T) LocalTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("HH:mm:ss"));
}
if (cellData.getData() instanceof LocalDateTime) {
return (T) LocalDateTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
if (cellData.getData() instanceof Integer) {
return (T) Integer.valueOf(cellData.getStringValue());
}
return null;
}
/**
* 导出类型转换
* @param obj
* @param excelContentProperty
* @param globalConfiguration
* @return
* @throws Exception
*/
@Override
public CellData convertToExcelData(T obj, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
if (obj instanceof LocalDate) {
return new CellData(((LocalDate) obj).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
if (obj instanceof LocalDateTime) {
return new CellData(((LocalDateTime) obj).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
if (obj instanceof LocalTime) {
return new CellData(((LocalTime) obj).format(DateTimeFormatter.ofPattern("HH:mm:ss")));
}
if (obj instanceof Integer) {
return new CellData(String.valueOf(obj));
}
return null;
}
}
/**
* localDate类型转换
*/
public static class LocalDateConverter extends CoreConverter<LocalDate> {
public LocalDateConverter() {
super(LocalDate.class);
}
}
/**
* localTime类型转换
*/
public static class LocalTimeConverter extends CoreConverter<LocalTime> {
public LocalTimeConverter() {
super(LocalTime.class);
}
}
/**
* LocalDateTime类型转换
*/
public static class LocalDateTimeConverter extends CoreConverter<LocalDateTime> {
public LocalDateTimeConverter() {
super(LocalDateTime.class);
}
}
/**
* Integer
*/
public static class IntegerConverter extends CoreConverter<Integer> {
public IntegerConverter() {
super(Integer.class);
}
}
}
(二)导出实现代码
package com.xiaobai.easyexcel.dynamiccolumn;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sun.istack.internal.NotNull;
import jdk.nashorn.internal.runtime.regexp.joni.ast.StringNode;
import org.apache.poi.util.IOUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* @author wangtw
* @ClassName DynamicColumnExport
* @description: EasyExcel动态列导出
* @date 2024/2/918:07
*/
public class DynamicColumnExport {
/**
*
* @param d 查询数据方法参数
* @param vClass 返回类型
* @param getDataFun 查询数据函数式接口
* @param outputStream 输出流
* @param includeColumns 需要导出的列
* @param writeHandlerList 自定义拦截器
* @param converterList 自定义数据格式化转换器
* @param <D>
* @param <U>
* @param <V>
*/
public static <D, U, V> void export(D d, Class<V> vClass,
@NotNull Function<D, List<V>> getDataFun,
@NotNull OutputStream outputStream,
@NotNull List<String> includeColumns,
@Nullable List<? extends WriteHandler> writeHandlerList,
@Nullable List<? extends Converter> converterList) {
/**
* 1、根据需要导出的列获取每列的列名和单元格的列宽
*/
// 单元格宽度
int columnIndex = 0;
Map<Integer, Integer> columnWidthMap = new HashMap<>();
// 获取表格头
List<List<String>> headList = new ArrayList<>();
List<Field> columnList = new ArrayList<>();
Field[] declaredFields = vClass.getDeclaredFields();
for (String includeColumn : includeColumns) {
Optional<Field> includeColumnOptional = Arrays.stream(declaredFields).filter(f -> f.getName().equals(includeColumn))
.findFirst();
if (includeColumnOptional.isPresent()) {
Field field = includeColumnOptional.get();
field.setAccessible(true);
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if (!ObjectUtils.isEmpty(excelProperty)) {
// 列名
String[] columNameArray = excelProperty.value();
headList.add(Arrays.asList(columNameArray));
// 可导出的列
columnList.add(field);
// 保存每列的宽度
ColumnWidth columnWidth = field.getAnnotation(ColumnWidth.class);
columnWidthMap.put(columnWidth == null ? -1 : columnWidth.value(), columnIndex++);
}
}
}
/**
* 2、根据需要导出的列获取需要显示的数据
*/
List<List<Object>> exportDataList = new ArrayList<>();
// 执行函数式接口获取需要导出的数据
List<V> dataCollection = getDataFun.apply(d);
for (V v : dataCollection) {
// 拼装每行的数据
List<Object> dataSubList = new ArrayList<>();
for (Field field : columnList) {
try {
Object columnValue = field.get(v);
dataSubList.add(Optional.ofNullable(columnValue).orElse(""));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
exportDataList.add(dataSubList);
}
// 表格处理策略
ExcelWriterBuilder writerBuilder = EasyExcel.write(outputStream).head(headList);
if (!ObjectUtils.isEmpty(writeHandlerList)) {
writeHandlerList.forEach(writerBuilder::registerWriteHandler);
}
// 类型转换策略
if (ObjectUtils.isEmpty(converterList)) {
writerBuilder.registerConverter(new DataConverter.IntegerConverter());
writerBuilder.registerConverter(new DataConverter.LocalDateConverter());
writerBuilder.registerConverter(new DataConverter.LocalTimeConverter());
writerBuilder.registerConverter(new DataConverter.LocalDateTimeConverter());
} else {
converterList.forEach(writerBuilder::registerConverter);
}
// 创建Sheet页
String sheetName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
// 设置列宽
writeSheet.setColumnWidthMap(columnWidthMap);
// 把数据写入到Sheet页中
ExcelWriter excelWriter = writerBuilder.build();
excelWriter.write(exportDataList, writeSheet);
// 关闭流
excelWriter.finish();
// 关闭输出流
IOUtils.closeQuietly(outputStream);
}
}
三、测试
(一)设置表头和内容策略
/**
* 设置颜色
* @return
*/
private WriteHandler setColor() {
// 头的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 背景设置为白色
headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short)18);
headWriteCellStyle.setWriteFont(headWriteFont);
// 内容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
WriteFont contentWriteFont = new WriteFont();
// 字体大小
contentWriteFont.setFontHeightInPoints((short)15);
contentWriteCellStyle.setWriteFont(contentWriteFont);
// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
return horizontalCellStyleStrategy;
}
(二)设置单元格样式策略
public static class CellHandler implements CellWriteHandler {
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
CellStyle cellStyle = cell.getCellStyle();
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
}
}
(四)实体类
可选择某几种属性进行导出
package com.xiaobai.easyexcel.dynamiccolumn;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
/**
* @author wangtw
* @ClassName DynamicData
* @description: EasyExcel动态列导出
* @date 2024/2/914:59
*/
@AllArgsConstructor
@Builder
@Data
public class DynamicData {
@ExcelProperty("id")
private Integer id;
@ColumnWidth(30)
@ExcelProperty("姓名")
private String realName;
@ColumnWidth(30)
@ExcelProperty("性别")
private String sex;
@ColumnWidth(30)
@ExcelProperty("年龄")
private int age;
@ColumnWidth(50)
@ExcelProperty("单位名称")
private String orgName;
@ColumnWidth(50)
@ExcelProperty("部门名称")
private String deptName;
}
(五)数据准备
private List<DynamicData> getData(String condition) {
List<DynamicData> dynamicDataList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
DynamicData data = DynamicData.builder()
.id(i)
.age(random.nextInt(60))
.sex("男")
.realName("王" + i)
.orgName("单位" + i)
.deptName("部门" + i)
.build();
dynamicDataList.add(data);
}
return dynamicDataList;
}
(六)测试代码(选择orgName、deptName、realName、sex进行导出)
@Test
public void exportTest() {
File file = new File("测试.xlsx");
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
List<String> includeColumns = new ArrayList<>();
includeColumns.add("orgName");
includeColumns.add("deptName");
includeColumns.add("realName");
includeColumns.add("sex");
List<WriteHandler> writeHandlers = new ArrayList<>();
writeHandlers.add(setColor());
writeHandlers.add(new CellHandler());
DynamicColumnExport.export(null, DynamicData.class,
this::getData, outputStream, includeColumns, writeHandlers, null);
}