马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
接口功能
通用
支持一对一数据结构导出
支持一对多数据结构导出
支持多层嵌套数据结构导出
支持单元格主动归并
原文来自:
新增及修复
基于我自己的利用场景,新增并能修复一下功能:
- 修复了只有单条一对一数据无法导出标题;
- 修复了多层嵌套数据结构中存在某个嵌套聚集为空无法导出标题;
- 修复了只有一条一对多数据导出时报错标题;
完备源码分析
自界说注解:
- @MyExcelProperty 作用到导出字段上面,表现这是Excel的一列
- @MyExcelCollection 表现一个聚集,紧张针对一对多的导出,比如一个老师对应多个科目,科目就可以用聚集表现
- @MyExcelEntity 表现一个还存在嵌套关系的导出实体(我的利用场景中并未用到该解释)
property为根本数据范例的注解类
- package com.example.excel.annotation;
- import java.lang.annotation.*;
- @Inherited
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyExcelProperty {
- String name() default "";
- int index() default Integer.MAX_VALUE;
- }
复制代码 property为聚集的注解类
- package com.example.excel.annotation;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyExcelCollection {
- String name();
- }
复制代码 property为实体对象的注解类
- package com.example.excel.annotation;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyExcelEntity {
- String name() default "";
- }
复制代码 导出参数类
- package com.example.excel.excel;
- import lombok.Data;
- import lombok.experimental.Accessors;
- @Data
- @Accessors(chain = true)
- public class ExportParam {
- private String fieldName;
- private String columnName;
- private Integer order;
- }
复制代码 导出实体类
- package com.example.excel.excel;
- import lombok.Data;
- import java.util.List;
- import java.util.Map;
- @Data
- public class ExportEntity {
- /**
- * 导出参数
- */
- private List<ExportParam> exportParams;
- /**
- * 平铺后的数据
- */
- private List<Map<String,Object>> data;
- /**
- * 每列的合并行数组
- */
- private Map<String, List<Integer[]>> mergeRowMap;
- }
复制代码 导出工具类
- package com.example.excel.excel;
- import com.alibaba.excel.EasyExcel;
- import com.example.excel.annotation.MyExcelCollection;
- import com.example.excel.annotation.MyExcelEntity;
- import com.example.excel.annotation.MyExcelProperty;
- import com.google.common.base.Preconditions;
- import com.google.common.collect.Lists;
- import jakarta.servlet.http.HttpServletResponse;
- import org.apache.commons.collections4.CollectionUtils;
- import org.apache.commons.lang3.StringUtils;
- import java.io.IOException;
- import java.lang.reflect.Field;
- import java.lang.reflect.ParameterizedType;
- import java.net.URLEncoder;
- import java.util.*;
- import java.util.stream.Collectors;
- public class MyExcelUtil {
- private final String DELIMITER = ".";
- /**
- * @param sourceData 导出数据源
- * @param fileName 导出文件名
- * @param response
- */
- public void exportData(Collection<Object> sourceData, String sheetName, HttpServletResponse response) {
- if (CollectionUtils.isEmpty(sourceData))
- return;
- // 将源数据平铺成List<Map<String, Object>>类型
- ExportEntity exportEntity = flattenObject(sourceData);
- // 获取要导出的列字段
- List<String> columnList = exportEntity.getExportParams().stream()
- .sorted(Comparator.comparing(ExportParam::getOrder))
- .map(ExportParam::getFieldName)
- .collect(Collectors.toList());
- // 获取导出数据集
- List<List<Object>> exportDataList = new ArrayList<>();
- for (Map<String, Object> objectMap : exportEntity.getData()) {
- List<Object> data = new ArrayList<>();
- columnList.stream().forEach(columnName -> {
- data.add(objectMap.get(columnName));
- });
- exportDataList.add(data);
- }
- List<List<String>> headList = columnList.stream().map(Lists::newArrayList).collect(Collectors.toList());
- try {
- response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
- response.setCharacterEncoding("utf-8");
- WriteCellStyle[] cellStyles = cellStyleStrategy();
- EasyExcel.write(response.getOutputStream()).sheet(sheetName)
- .head(headList)
- .registerWriteHandler(new MergeStrategy(exportEntity))
- .registerWriteHandler(new HeadHandler(exportEntity))
- .registerWriteHandler(new HorizontalCellStyleStrategy(cellStyle[0], cellStyle[1]))
- .doWrite(exportDataList);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- /**
- * 获取导出数据:导出字段-值
- * 合并区间map:导出字段-[start, end]
- *
- * @param sourceData
- * @return
- */
- public ExportEntity flattenObject(Collection<Object> sourceData) {
- Map<String, List<Integer[]>> mergeMap = new HashMap<>();
- // 判断源数据class
- Class aClass = sourceData.stream().findFirst().get().getClass();
- // 获取导出字段
- List<ExportParam> exportParams = getAllExportField(aClass, "");
- Preconditions.checkArgument(!exportParams.isEmpty(), "export field not found !");
- // 获取导出数据:导出字段-值 合并区间map:导出字段-[start, end]
- List<Map<String, Object>> target = new ArrayList<>();
- Integer startRow = 0;
- for (Object sourceDataLine : sourceData) {
- List<Map<String, Object>> flatData = flattenObject(sourceDataLine, "", startRow, mergeMap);
- startRow += flatData.size();
- target.addAll(flatData);
- }
- // 包装为导出实体
- ExportEntity exportEntity = new ExportEntity();
- exportEntity.setExportParams(exportParams);
- exportEntity.setData(target);
- exportEntity.setMergeRowMap(mergeMap);
- return exportEntity;
- }
- /**
- * @param data 数据行
- * @param preNode 上一层级字段名
- * @param startRow 当前数据行所在行数
- * @param mergeMap 各字段合并行数组
- * @return
- */
- private List<Map<String, Object>> flattenObject(Object data, String preNode, Integer startRow,
- Map<String, List<Integer[]>> mergeMap) {
- List<Map<String, Object>> flatList = new ArrayList<>();
- if (null == data)
- return flatList;
- // 获取不为null的excel导出字段
- Class<?> aClass = data.getClass();
- List<Field> collectionFields = new ArrayList<>();
- List<Field> entityFields = new ArrayList<>();
- List<Field> propertyFields = new ArrayList<>();
- for (Field field : aClass.getDeclaredFields()) {
- Object value = getFieldValue(field, data);
- if (null == value)
- continue;
- if (isCollection(field)) {
- collectionFields.add(field);
- } else if (isEntity(field)) {
- entityFields.add(field);
- } else if (isProperty(field)) {
- propertyFields.add(field);
- }
- }
- // 对被@MyExcelCollection和@MyExcelEntity注解修饰的field继续flat
- List<Map<String, Object>> entityFlatData = flattenEntityFields(entityFields, data, preNode, startRow, mergeMap);
- List<List<Map<String, Object>>> collectionFlatData = flattenCollectionFields(collectionFields, data, preNode,
- startRow, mergeMap);
- Map<String, Object> objectMap = Collections.emptyMap();
- if (collectionFields.isEmpty() && entityFields.isEmpty()) {
- objectMap = flattenPropertyFields(propertyFields, data, preNode);
- }
- // 解决当前层级collecting和entity属性对应的值都是null时,导致property属性也被自动忽略,无法导出的问题
- if (collectionFlatData.isEmpty() && entityFlatData.isEmpty()) {
- objectMap = flattenPropertyFields(propertyFields, data, preNode);
- }
- if (!objectMap.isEmpty()) {
- flatList.add(objectMap);
- }
- // 当前层级所有平铺列表
- List<List<Map<String, Object>>> allFlatData = Lists.newArrayList();
- if (!entityFlatData.isEmpty()) {
- allFlatData.add(entityFlatData);
- }
- if (!collectionFlatData.isEmpty()) {
- allFlatData.addAll(collectionFlatData);
- }
- List<Map<String, Object>> mergeList = mergeList(data, allFlatData, propertyFields, preNode, startRow, mergeMap);
- if (!mergeList.isEmpty()) {
- flatList.addAll(mergeList);
- }
- return flatList;
- }
- /**
- * flat被@MyExcelEntity注解修饰的field的平铺结果和合并列表
- *
- * @return
- */
- private List<Map<String, Object>> flattenEntityFields(List<Field> entityFields, Object data, String preNode,
- Integer startRow, Map<String, List<Integer[]>> mergeMap) {
- List<Map<String, Object>> entityFlatData = Lists.newArrayList();
- for (Field entityField : entityFields) {
- entityFlatData = flattenObject(getFieldValue(entityField, data),
- buildPreNode(preNode, entityField.getName()), startRow, mergeMap);
- }
- return entityFlatData;
- }
- /**
- * flat被@MyExcelCcollection注解修饰的field的平铺结果和合并列表
- *
- * @return
- */
- private List<List<Map<String, Object>>> flattenCollectionFields(List<Field> collectionFields, Object data,
- String preNode, Integer startRow, Map<String, List<Integer[]>> mergeMap) {
- List<List<Map<String, Object>>> collectionFlatData = Lists.newArrayList();
- for (Field collectionField : collectionFields) {
- Collection collectionValue = (Collection) getFieldValue(collectionField, data);
- // 当前集合字段平铺而成的数据列表
- List<Map<String, Object>> collectionObjectValue = new ArrayList<>();
- // 间隔行数
- Integer row = 0;
- for (Object value : collectionValue) {
- List<Map<String, Object>> flatData = flattenObject(value,
- buildPreNode(preNode, collectionField.getName()), startRow + row, mergeMap);
- if (!flatData.isEmpty()) {
- collectionObjectValue.addAll(flatData);
- // 下条数据的起始间隔行
- row += flatData.size();
- }
- }
- if (!collectionObjectValue.isEmpty()) {
- collectionFlatData.add(collectionObjectValue);
- }
- }
- return collectionFlatData;
- }
- /**
- * flat被@MyExcelProperty注解修饰的field的平铺结果和合并列表
- *
- * @return
- */
- private Map<String, Object> flattenPropertyFields(List<Field> propertyFields, Object data, String preNode) {
- Map<String, Object> flatMap = new HashMap<>();
- for (Field field : propertyFields) {
- flatMap.put(buildPreNode(preNode, field.getName()), getFieldValue(field, data));
- }
- return flatMap;
- }
- /**
- * 返回待合并的集合 层级字段名:[start, end]
- *
- * @return
- */
- private List<Map<String, Object>> mergeList(Object data, List<List<Map<String, Object>>> allFlatData,
- List<Field> propertyFields, String preNode, Integer startRow, Map<String, List<Integer[]>> mergeMap) {
- List<Map<String, Object>> flatList = new ArrayList<>();
- // 当前层级下多个集合字段的最大长度即为当前层级其他字段的合并行数
- Integer maxSize = 0;
- for (List<Map<String, Object>> list : allFlatData) {
- maxSize = Math.max(maxSize, list.size());
- }
- // 记录集合同层级其他字段的合并行数
- if (maxSize > 0) {
- buildMerge(propertyFields, preNode, startRow, startRow + maxSize, mergeMap);
- }
- // 需要合并
- if (maxSize > 1) {
- // 重新构建平铺的list
- List<Map<String, Object>> mergeFlatData = new ArrayList<>(maxSize);
- for (int i = 0; i < maxSize; i++) {
- mergeFlatData.add(new HashMap<>());
- }
- for (List<Map<String, Object>> flatData : allFlatData) {
- for (int i = 0; i < flatData.size(); i++) {
- Map<String, Object> flatMap = new HashMap<>();
- flatMap.putAll(flatData.get(i));
- // 添加同层级字段值
- if (CollectionUtils.isNotEmpty(propertyFields)) {
- for (Field field : propertyFields) {
- flatMap.put(buildPreNode(preNode, field.getName()), getFieldValue(field, data));
- }
- }
- mergeFlatData.get(i).putAll(flatMap);
- }
- }
- flatList.addAll(mergeFlatData);
- } else {
- // 解决只有单行数据时,前节点无法flat的问题
- Map<String, Object> result = new HashMap<>();
- for (List<Map<String, Object>> list : allFlatData) {
- for (Field propertyField : propertyFields) {
- result.put(buildPreNode(preNode, propertyField.getName()), getFieldValue(propertyField, data));
- }
- result.putAll(li.get(0));
- }
- if (!result.isEmpty()) {
- flatList.add(result);
- }
- }
- return flatList;
- }
- /**
- * @param mergeFields 需要合并的字段
- * @param preNode 上一层级字段名
- * @param mergeStartRow 合并开始行
- * @param mergeEndRow 合并结束行
- * @param mergeMap 各字段合并行数组
- */
- private void buildMerge(List<Field> mergeFields, String preNode, Integer mergeStartRow, Integer mergeEndRow,
- Map<String, List<Integer[]>> mergeMap) {
- for (int k = 0; k < mergeFields.size(); k++) {
- String fieldName = buildPreNode(preNode, mergeFields.get(k).getName());
- List<Integer[]> list = mergeMap.get(fieldName);
- if (null == list) {
- mergeMap.put(fieldName, new ArrayList<>());
- }
- Integer[] rowInterval = new Integer[2];
- // 合并开始行
- rowInterval[0] = mergeStartRow;
- // 合并结束行
- rowInterval[1] = mergeEndRow;
- mergeMap.get(fieldName).add(rowInterval);
- }
- }
- /**
- * 获取导出参数
- *
- * @param clazz 导出类
- * @param preNode 上一层级字段名
- * @return
- */
- private List<ExportParam> getAllExportField(Class<?> clazz, String preNode) {
- List<ExportParam> exportFields = new ArrayList<>();
- // 遍历类对象中声明的所有field
- for (Field declaredField : clazz.getDeclaredFields()) {
- // 检查field是否被注解
- MyExcelProperty myExcelProperty = declaredField.getDeclaredAnnotation(MyExcelProperty.class);
- MyExcelEntity myExcelEntity = declaredField.getDeclaredAnnotation(MyExcelEntity.class);
- MyExcelCollection myExcelCollection = declaredField.getDeclaredAnnotation(MyExcelCollection.class);
- // 构建层级关系,比如:lineConfigs.msgConfigs.msgName
- String fieldName = buildPreNode(preNode, declaredField.getName());
- // @MyExcelProperty注解的字段直接作为输出列
- if (null != myExcelProperty) {
- ExportParam exportParam = new ExportParam()
- .setFieldName(fieldName)
- .setColumnName(myExcelProperty.name())
- .setOrder(myExcelProperty.index());
- exportFields.add(exportParam);
- }
- // @MyExcelEntity注解的字段继续遍历
- else if (null != myExcelEntity) {
- exportFields.addAll(getAllExportField(declaredField.getType(), fieldName));
- }
- // @MyExcelCollection注解的字段继续遍历
- else if (null != myExcelCollection) {
- boolean isCollection = Collection.class.isAssignableFrom(declaredField.getType());
- if (!isCollection)
- continue;
- ParameterizedType pt = (ParameterizedType) declaredField.getGenericType();
- Class<?> clz = (Class<?>) pt.getActualTypeArguments()[0];
- exportFields.addAll(getAllExportField(clz, fieldName));
- }
- }
- return exportFields;
- }
- /**
- * 返回某个字段是否被@MyExccelProperty注解
- *
- * @return
- */
- private boolean isProperty(Field field) {
- return null != field.getDeclaredAnnotation(MyExcelProperty.class) ? true : false;
- }
- /**
- * 返回某个字段是否被@MyExccelEntity注解
- *
- * @return
- */
- private boolean isEntity(Field field) {
- return null != field.getDeclaredAnnotation(MyExcelEntity.class) ? true : false;
- }
- /**
- * 返回某个字段是否被@MyExccelCollection注解
- *
- * @return
- */
- private boolean isCollection(Field field) {
- boolean isCollection = Collection.class.isAssignableFrom(field.getType());
- return isCollection && null != field.getAnnotation(MyExcelCollection.class) ? true : false;
- }
- /**
- * @param preNode 上一层级字段名
- * @param fieldName 当前字段名
- * @return
- */
- private String buildPreNode(String preNode, String fieldName) {
- StringBuffer sb = new StringBuffer();
- return StringUtils.isEmpty(preNode) ? fieldName
- : sb.append(preNode).append(DELIMITER).append(fieldName).toString();
- }
- /**
- * 返回对象中某个字段的值
- *
- * @return
- */
- private Object getFieldValue(Field field, Object sourceObject) {
- Object fieldValue;
- try {
- // 允许访问private属性
- field.setAccessible(true);
- fieldValue = field.get(sourceObject);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- return fieldValue;
- }
- /**
- * 单元格样式
- *
- * @return
- */
- public WriteCellStyle[] cellStyleStrategy() {
- WriteCellStyle[] styles = new WriteCellStyle[2];
- WriteFont titleFont = new WriteFont();
- titleFont.setFontName("Arial");
- titleFont.setFontHeightInPoints((short) 12);
- titleFont.setBold(true);
- WriteCellStyle headCellStyle = new WriteCellStyle();
- headCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
- headCellStyle/setHorizontalAlignment(HorzontalAlignment.CENTER);
- headCellStyle.setWriteFOnt(titleFont);
- styles[0] = headCellStyle;
- WriteCellStyle cellStyle = new WriteCellStyle();
- cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
- cellStyle/setHorizontalAlignment(HorzontalAlignment.CENTER);
- styles[1] = cellStyle;
- return styles;
- }
- }
复制代码 单元格归并战略
- package com.example.excel.excel;
- import com.alibaba.excel.metadata.Head;
- import com.alibaba.excel.write.merge.AbstractMergeStrategy;
- import org.apache.poi.ss.usermodel.Cell;
- import org.apache.poi.ss.usermodel.Sheet;
- import org.apache.poi.ss.util.CellRangeAddress;
- import org.springframework.util.CollectionUtils;
- import java.util.List;
- public class MergeStrategy extends AbstractMergeStrategy {
- private ExportEntity exportEntity;
- public MergeStrategy(ExportEntity exportEntity) {
- this.exportEntity = exportEntity;
- }
- @Override
- protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
- //mergeMap需要加上head的行数
- String fieldName = head.getHeadNameList().get(0);
- List<Integer[]> mergeRow = exportEntity.getMergeRowMap().get(fieldName);
- if (CollectionUtils.isEmpty(mergeRow)) return;
- int currentColumnIndex = cell.getColumnIndex();
- int currentRowIndex = cell.getRowIndex();
- //表头所占行数
- int headHeight = head.getHeadNameList().size();
- for (int i = 0; i < mergeRow.size(); i++) {
- if (currentRowIndex == mergeRow.get(i)[1] + headHeight - 1) {
- // 解决单行数据导出时报错:merge region A2 must contains 2 or more cells
- if(mergeRow.get(i)[1] - mergeRow.get(i)[0] == 1){
- continue;
- }
- sheet.addMergedRegion(new CellRangeAddress(mergeRow.get(i)[0] + headHeight, mergeRow.get(i)[1] + headHeight - 1,
- currentColumnIndex, currentColumnIndex));
- }
- }
- }
- }
复制代码 表头导出处理处罚
- ```java
- package com.example.excel.excel;
- import com.alibaba.excel.metadata.Head;
- import com.alibaba.excel.metadata.data.WriteCellData;
- import com.alibaba.excel.write.handler.CellWriteHandler;
- import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
- import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
- import org.apache.poi.ss.usermodel.Cell;
- import java.util.List;
- import java.util.Map;
- import java.util.stream.Collectors;
- public class HeadHandler implements CellWriteHandler {
- private ExportEntity exportEntity;
- public HeadHandler(ExportEntity exportEntity) {
- this.exportEntity = exportEntity;
- }
- @Override
- public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
- List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
- if (!isHead) return;
- List<String> headNameList = head.getHeadNameList();
- Map<String, String> collect = exportEntity.getExportParams().stream().collect(Collectors.toMap(ExportParam::getFieldName, ExportParam::getColumnName));
- cell.setCellValue(collect.get(headNameList.get(0)));
- }
- }
复制代码 导出示例
被导出实体
多层嵌套,且属性中有聚集的类
- public class UserConfigEnntity{
- @MyExcelProperty(name = "学生用户名称")
- private String userName;
- @MyExcelCollection(name = "班级配置")
- private List<ClassConfigEntity> classConfigs;
- }
- public class ClassConfigEntity{
- @MyExcelProperty(name = "班级号")
- private String classNumber;
- @MyExcelCollection(name = "科目配置")
- private List<SubConfigEntity> subConfigs;
- }
- public class SubConfigEntity{
- @MyExcelProperty(name = "科目名称")
- private String subName;
- @MyExcelCollection(name = "时间配置")
- private List<SubTimeConfigEntity> subTimeConfigs;
- }
- public class SubTimeConfigEntity{
- @MyExcelProperty(name = "时间")
- private String subTime;
- @MyExcelCollection(name = "地点配置")
- private List<AddConfigEntity> addConfigs;
- }
- public class RtuConfigEntity{
- @MyExcelProperty(name = "地点")
- private String addName;
- }
复制代码 接口调用
- @RestController
- @ReqeutMapping("/userConfig/userConfig")
- public class CompanyConfigController{
- @AutoWired
- private UserConfigService userConfigService;
-
- @PostMappinng("/export")
- public void expooort(HttpServletResponse response){
- List<UserConfigEnntity> list = userConfigService.queryAll();
- new MyExcelUtil().exportData(list, "学生用户配置内容数据", resposne);
- }
- }
复制代码 结果展示
|