测试代码地址: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*/@Overridepublic Class supportJavaTypeKey() {return tClass;}/*** 导入支持的Excel类型* @return*/@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 导入类型转换* @param cellData* @param excelContentProperty* @param globalConfiguration* @return* @throws Exception*/@Overridepublic 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*/@Overridepublic 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 {@Overridepublic void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {}@Overridepublic void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {}@Overridepublic void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {}@Overridepublic 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进行导出)
@Testpublic 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);}