java
复制代码
package org.whh.excel.util;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.whh.excel.annotation.Excel;
import org.whh.excel.converter.CellStyleInfo;
import org.whh.excel.converter.ExcelConverter;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 通用Excel导入导出工具类
* 基于注解自动处理多级数据结构的导入导出
* 支持@ExcelColumn、@ExcelObject、@ExcelConvert注解
*
* @author whh
*/
@Slf4j
public class ExcelUtils {
// ========== 表头样式全局配置 ==========
// 修改这些配置即可改变所有Excel导出的表头样式
/**
* 表头背景颜色配置
* 常用颜色选项:
* - IndexedColors.DARK_BLUE (深蓝色,商务风格)
* - IndexedColors.BLUE (蓝色)
* - IndexedColors.LIGHT_BLUE (浅蓝色)
* - IndexedColors.GREEN (绿色)
* - IndexedColors.DARK_GREEN (深绿色)
* - IndexedColors.GREY_50_PERCENT (灰色)
* - IndexedColors.ORANGE (橙色)
* - IndexedColors.RED (红色)
*/
private static final IndexedColors HEADER_BACKGROUND_COLOR = IndexedColors.AUTOMATIC;
/**
* 表头字体颜色配置
* 常用颜色选项:
* - IndexedColors.WHITE (白色,适合深色背景)
* - IndexedColors.BLACK (黑色,适合浅色背景)
* - IndexedColors.BLUE (蓝色)
* - IndexedColors.RED (红色)
* - IndexedColors.GREEN (绿色)
*/
private static final IndexedColors HEADER_FONT_COLOR = IndexedColors.BLACK;
/**
* 表头字体大小配置 (推荐范围: 10-16)
*/
private static final short HEADER_FONT_SIZE = 12;
/**
* 表头是否加粗配置
* true: 加粗显示 (推荐)
* false: 正常字体
*/
private static final boolean HEADER_FONT_BOLD = true;
/**
* 表头边框样式配置
* - BorderStyle.THIN (细边框,推荐)
* - BorderStyle.MEDIUM (中等边框)
* - BorderStyle.THICK (粗边框)
* - BorderStyle.NONE (无边框)
*/
private static final BorderStyle HEADER_BORDER_STYLE = BorderStyle.THIN;
/**
* 表头边框颜色配置
* 常用:IndexedColors.BLACK (黑色边框)
*/
private static final IndexedColors HEADER_BORDER_COLOR = IndexedColors.BLACK;
/**
* 导出数据到Excel(通用方法)
*/
public static <T> void exportExcel(List<T> data, Class<T> clazz, String fileName, HttpServletResponse response) {
// 清理样式缓存,避免不同工作簿间的样式冲突
clearStyleCache();
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet();
// 解析实体类结构
List<FieldInfo> allFields = parseEntityStructure(clazz);
// 创建表头
createHeader(sheet, allFields);
// 填充数据
int currentRow = 1;
for (T item : data) {
currentRow = fillMultiLevelData(sheet, item, allFields, currentRow);
}
// 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));
// 设置缓存控制,避免代理服务器问题
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
workbook.write(response.getOutputStream());
log.info("Excel导出成功,文件名: {}, 数据量: {} 条", fileName, data.size());
} catch (org.springframework.web.context.request.async.AsyncRequestNotUsableException e) {
// 客户端取消下载或连接中断,这是正常情况,不需要报错
log.info("客户端连接中断 (可能是多线程下载工具如迅雷等导致): {}", e.getMessage());
} catch (java.io.IOException e) {
if (e.getMessage() != null && (e.getMessage().contains("你的主机中的软件中止了一个已建立的连接") || e.getMessage().contains("Connection reset") || e.getMessage().contains("Broken pipe"))) {
// 网络连接中断,通常是客户端问题(多线程下载工具常见)
log.info("客户端网络连接中断 (通常是多线程下载工具如迅雷等的正常行为,文件仍会正确下载): {}", e.getMessage());
} else {
log.error("Excel导出时发生IO异常", e);
throw new RuntimeException("Excel导出失败", e);
}
} catch (org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException e) {
// 检查是否是因为客户端中断导致的POI异常
Throwable cause = e.getCause();
if (cause instanceof org.apache.poi.openxml4j.exceptions.OpenXML4JException) {
// 进一步检查根本原因
String message = e.getMessage();
if (message != null && (message.contains("failed to be saved in the stream") || message.contains("Fail to save"))) {
log.info("Excel文件保存过程中客户端连接中断 (通常是多线程下载工具导致,文件会正确下载): {}", message);
return; // 不抛出异常,因为这是正常的多线程下载行为
}
}
log.error("Excel导出失败", e);
throw new RuntimeException("Excel导出失败", e);
} catch (Exception e) {
log.error("Excel导出失败", e);
throw new RuntimeException("Excel导出失败", e);
}
}
/**
* 导入Excel数据(通用方法)
*/
public static <T> List<T> importExcel(InputStream inputStream, Class<T> clazz) {
List<T> result;
// 保存原始的压缩率限制
double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio();
try {
// 临时调整压缩率限制,允许处理包含大量样式的Excel文件
// 从默认的 0.01 调整到 0.005,以支持样式丰富的Excel文件
ZipSecureFile.setMinInflateRatio(0.005);
try (Workbook workbook = WorkbookFactory.create(inputStream)) {
Sheet sheet = workbook.getSheetAt(0);
// 解析实体类结构
List<FieldInfo> allFields = parseEntityStructure(clazz);
// 读取数据并构建对象
result = parseMultiLevelData(sheet, clazz, allFields);
log.info("Excel导入成功,共导入 {} 条记录", result.size());
}
} catch (Exception e) {
log.error("Excel导入失败", e);
throw new RuntimeException("Excel导入失败", e);
} finally {
// 恢复原始的压缩率限制
ZipSecureFile.setMinInflateRatio(originalMinInflateRatio);
log.debug("已恢复POI安全设置,压缩率限制: {}", originalMinInflateRatio);
}
return result;
}
/**
* 解析实体类结构,获取所有层级的字段信息
*/
private static <T> List<FieldInfo> parseEntityStructure(Class<T> clazz) {
List<FieldInfo> allFields = new ArrayList<>();
collectAllFields(clazz, allFields, 0);
// 按层级和索引排序
allFields.sort((a, b) -> {
if (a.level != b.level) {
return Integer.compare(a.level, b.level);
}
return Integer.compare(a.columnIndex, b.columnIndex);
});
// 重新分配列索引
int currentIndex = 0;
for (FieldInfo fieldInfo : allFields) {
if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
fieldInfo.columnIndex = currentIndex++;
}
}
return allFields;
}
/**
* 递归收集所有字段信息
*/
private static void collectAllFields(Class<?> clazz, List<FieldInfo> allFields, int level) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Excel excel = field.getAnnotation(Excel.class);
if (excel != null) {
FieldInfo fieldInfo = new FieldInfo();
fieldInfo.field = field;
fieldInfo.excel = excel;
fieldInfo.level = level;
fieldInfo.fieldType = excel.fieldType();
if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
fieldInfo.columnIndex = excel.index() != -1 ? excel.index() : Integer.MAX_VALUE;
}
// 初始化转换器
if (excel.converter() != Excel.DefaultConverter.class) {
try {
@SuppressWarnings("unchecked") ExcelConverter<Object, Object> converter = (ExcelConverter<Object, Object>) excel.converter().getDeclaredConstructor().newInstance();
fieldInfo.converter = converter;
} catch (Exception e) {
log.warn("初始化转换器失败: {}", excel.converter().getName());
}
}
allFields.add(fieldInfo);
// 如果是对象或集合类型,递归收集子字段
if (fieldInfo.fieldType == Excel.FieldType.OBJECT || fieldInfo.fieldType == Excel.FieldType.COLLECTION) {
Class<?> actualType = getActualType(field);
if (actualType != Object.class) {
collectAllFields(actualType, allFields, level + 1);
}
}
}
}
}
/**
* 获取字段的实际类型
*/
private static Class<?> getActualType(Field field) {
Excel excel = field.getAnnotation(Excel.class);
if (excel != null && excel.type() != Object.class) {
return excel.type();
}
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) genericType;
Type[] actualTypeArguments = paramType.getActualTypeArguments();
if (actualTypeArguments.length > 0) {
return (Class<?>) actualTypeArguments[0];
}
}
return field.getType();
}
/**
* 创建表头
*/
private static void createHeader(Sheet sheet, List<FieldInfo> allFields) {
Row headerRow = sheet.createRow(0);
// 创建表头样式
CellStyle headerStyle = createHeaderStyle(sheet.getWorkbook());
for (FieldInfo fieldInfo : allFields) {
if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
Cell cell = headerRow.createCell(fieldInfo.columnIndex);
String headerName = "";
if (fieldInfo.excel != null && StringUtils.isNotBlank(fieldInfo.excel.name())) {
headerName = fieldInfo.excel.name();
} else {
headerName = fieldInfo.field.getName();
}
cell.setCellValue(headerName);
// 应用表头样式
cell.setCellStyle(headerStyle);
// 设置列宽
if (fieldInfo.excel != null && fieldInfo.excel.width() > 0) {
sheet.setColumnWidth(fieldInfo.columnIndex, fieldInfo.excel.width() * 256);
} else {
sheet.setColumnWidth(fieldInfo.columnIndex, 20 * 256);
}
}
}
}
/**
* 创建表头样式
*/
private static CellStyle createHeaderStyle(Workbook workbook) {
CellStyle headerStyle = workbook.createCellStyle();
// 创建字体(使用全局配置)
Font headerFont = workbook.createFont();
headerFont.setBold(HEADER_FONT_BOLD); // 使用配置的加粗设置
headerFont.setFontHeightInPoints(HEADER_FONT_SIZE); // 使用配置的字体大小
headerFont.setColor(HEADER_FONT_COLOR.getIndex()); // 使用配置的字体颜色
// 将字体应用到样式
headerStyle.setFont(headerFont);
// 设置背景色(使用全局配置)
headerStyle.setFillForegroundColor(HEADER_BACKGROUND_COLOR.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置对齐方式
headerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
headerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
// 设置边框(使用全局配置)
headerStyle.setBorderTop(HEADER_BORDER_STYLE);
headerStyle.setBorderBottom(HEADER_BORDER_STYLE);
headerStyle.setBorderLeft(HEADER_BORDER_STYLE);
headerStyle.setBorderRight(HEADER_BORDER_STYLE);
// 设置边框颜色(使用全局配置)
headerStyle.setTopBorderColor(HEADER_BORDER_COLOR.getIndex());
headerStyle.setBottomBorderColor(HEADER_BORDER_COLOR.getIndex());
headerStyle.setLeftBorderColor(HEADER_BORDER_COLOR.getIndex());
headerStyle.setRightBorderColor(HEADER_BORDER_COLOR.getIndex());
return headerStyle;
}
// 样式缓存,避免创建重复样式(线程安全)
private static final Map<String, CellStyle> styleCache = new ConcurrentHashMap<>();
/**
* 清理样式缓存
* 在每次导出开始时调用,避免不同工作簿间的样式冲突
*/
private static void clearStyleCache() {
styleCache.clear();
}
/**
* 创建或获取单元格样式(支持动态颜色和样式缓存)
*/
private static CellStyle createCellStyle(Workbook workbook, FieldInfo fieldInfo, Object value) {
// 默认颜色(从注解获取)
IndexedColors fontColor = fieldInfo.excel.fontColor();
IndexedColors backgroundColor = fieldInfo.excel.backgroundColor();
// 如果有转换器,尝试获取动态样式
if (fieldInfo.converter != null) {
try {
CellStyleInfo styleInfo = fieldInfo.converter.getCellStyle(value, fieldInfo.excel.params());
if (styleInfo != null) {
// 动态样式优先级高于注解样式
if (styleInfo.hasFontColor()) {
fontColor = styleInfo.getFontColor();
}
if (styleInfo.hasBackgroundColor()) {
backgroundColor = styleInfo.getBackgroundColor();
}
}
} catch (Exception e) {
log.warn("获取动态样式失败: {}", e.getMessage());
}
}
// 检查是否需要创建样式
boolean needStyle = fontColor != IndexedColors.AUTOMATIC || backgroundColor != IndexedColors.AUTOMATIC;
// 只有在需要时才创建样式
if (!needStyle) {
return null;
}
// 创建样式缓存键
String cacheKey = fontColor.name() + "_" + backgroundColor.name();
// 先查缓存
CellStyle cachedStyle = styleCache.get(cacheKey);
if (cachedStyle != null) {
return cachedStyle;
}
CellStyle cellStyle = workbook.createCellStyle();
// 应用字体样式
if (fontColor != IndexedColors.AUTOMATIC) {
Font font = workbook.createFont();
font.setColor(fontColor.getIndex());
cellStyle.setFont(font);
}
// 应用背景样式
if (backgroundColor != IndexedColors.AUTOMATIC) {
cellStyle.setFillForegroundColor(backgroundColor.getIndex());
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
}
// 缓存样式
styleCache.put(cacheKey, cellStyle);
return cellStyle;
}
/**
* 填充多级数据
*/
private static int fillMultiLevelData(Sheet sheet, Object rootItem, List<FieldInfo> allFields, int startRow) {
try {
// 展开为平面数据行
List<Map<String, Object>> dataRows = expandToFlatRows(rootItem, allFields);
// 填充每行数据
for (int i = 0; i < dataRows.size(); i++) {
Row row = sheet.createRow(startRow + i);
Map<String, Object> rowData = dataRows.get(i);
for (FieldInfo fieldInfo : allFields) {
if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
Cell cell = row.createCell(fieldInfo.columnIndex);
// 使用层级特定的唯一键获取值
String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
Object value = rowData.get(uniqueKey);
String cellValue = convertToExcelValue(value, fieldInfo);
cell.setCellValue(cellValue);
// 应用单元格样式(支持注解样式和动态样式)
CellStyle cellStyle = createCellStyle(sheet.getWorkbook(), fieldInfo, value);
if (cellStyle != null) {
cell.setCellStyle(cellStyle);
}
}
}
}
// 创建合并单元格
if (dataRows.size() > 1) {
createMergeRegions(sheet, rootItem, allFields, startRow, dataRows.size());
}
return startRow + dataRows.size();
} catch (Exception e) {
log.error("填充多级数据失败", e);
return startRow + 1;
}
}
/**
* 展开对象为平面数据行
*/
private static List<Map<String, Object>> expandToFlatRows(Object item, List<FieldInfo> allFields) {
List<Map<String, Object>> result = new ArrayList<>();
try {
expandObjectToRows(item, new HashMap<>(), allFields, result);
} catch (Exception e) {
log.warn("展开对象失败", e);
}
return result.isEmpty() ? List.of(new HashMap<>()) : result;
}
/**
* 递归展开对象到行数据
*/
private static void expandObjectToRows(Object item, Map<String, Object> parentData, List<FieldInfo> allFields, List<Map<String, Object>> result) throws Exception {
Map<String, Object> currentData = new HashMap<>(parentData);
List<Object> childItems = new ArrayList<>();
// 收集当前层级的字段值
for (FieldInfo fieldInfo : allFields) {
if (fieldInfo.field.getDeclaringClass().isAssignableFrom(item.getClass())) {
Object value = fieldInfo.field.get(item);
if (fieldInfo.fieldType == Excel.FieldType.COLLECTION && value instanceof Collection) {
childItems.addAll((Collection<?>) value);
} else if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
// 使用层级+字段名作为唯一键,避免不同层级的同名字段相互覆盖
String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
currentData.put(uniqueKey, value); // 使用唯一键存储,避免覆盖
}
}
}
// 如果有子对象,递归处理
if (!childItems.isEmpty()) {
for (Object childItem : childItems) {
expandObjectToRows(childItem, currentData, allFields, result);
}
} else {
result.add(currentData);
}
}
/**
* 创建合并单元格(支持任意多级数据结构)
*/
private static void createMergeRegions(Sheet sheet, Object rootItem, List<FieldInfo> allFields, int startRow, int totalRows) {
try {
// 构建层级结构树
HierarchyNode rootNode = buildHierarchyTree(rootItem, allFields, startRow);
// 递归创建所有层级的合并区域
createMergeRegionsRecursively(sheet, rootNode, allFields);
} catch (Exception e) {
log.warn("创建合并单元格失败", e);
}
}
/**
* 构建层级结构树(支持任意层级)
*/
private static HierarchyNode buildHierarchyTree(Object rootItem, List<FieldInfo> allFields, int startRow) {
HierarchyNode rootNode = new HierarchyNode();
rootNode.item = rootItem;
rootNode.level = 0;
rootNode.startRow = startRow;
// 递归构建层级树
buildHierarchyTreeRecursively(rootNode, allFields);
return rootNode;
}
/**
* 递归构建层级结构树
*/
private static void buildHierarchyTreeRecursively(HierarchyNode node, List<FieldInfo> allFields) {
try {
List<Object> childItems = new ArrayList<>();
// 找到当前对象的子对象集合
for (FieldInfo fieldInfo : allFields) {
if (fieldInfo.field.getDeclaringClass().isAssignableFrom(node.item.getClass()) && fieldInfo.fieldType == Excel.FieldType.COLLECTION) {
Object value = fieldInfo.field.get(node.item);
if (value instanceof Collection) {
childItems.addAll((Collection<?>) value);
}
break; // 每个对象只处理一个子集合
}
}
// 为每个子对象创建节点
int currentRow = node.startRow;
for (Object childItem : childItems) {
HierarchyNode childNode = new HierarchyNode();
childNode.item = childItem;
childNode.level = node.level + 1;
childNode.startRow = currentRow;
childNode.parent = node;
node.children.add(childNode);
// 递归构建子节点
buildHierarchyTreeRecursively(childNode, allFields);
// 计算当前子节点占用的行数
childNode.rowCount = calculateNodeRowCount(childNode);
currentRow += childNode.rowCount;
}
// 如果没有子节点,则为叶子节点,占用1行
if (childItems.isEmpty()) {
node.rowCount = 1;
} else {
// 有子节点时,行数为所有子节点行数之和
node.rowCount = node.children.stream().mapToInt(child -> child.rowCount).sum();
}
} catch (Exception e) {
log.warn("构建层级树失败", e);
node.rowCount = 1;
}
}
/**
* 计算节点占用的行数
*/
private static int calculateNodeRowCount(HierarchyNode node) {
if (node.children.isEmpty()) {
return 1;
}
return node.children.stream().mapToInt(child -> child.rowCount).sum();
}
/**
* 递归创建所有层级的合并区域
*/
private static void createMergeRegionsRecursively(Sheet sheet, HierarchyNode node, List<FieldInfo> allFields) {
try {
// 为当前层级的字段创建合并区域
if (node.rowCount > 1) {
List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.level == node.level).toList();
for (FieldInfo fieldInfo : currentLevelFields) {
CellRangeAddress mergeRegion = new CellRangeAddress(node.startRow, node.startRow + node.rowCount - 1, fieldInfo.columnIndex, fieldInfo.columnIndex);
sheet.addMergedRegion(mergeRegion);
// 设置居中样式
setCellStyle(sheet, node.startRow, fieldInfo.columnIndex);
}
}
// 递归处理子节点
for (HierarchyNode child : node.children) {
createMergeRegionsRecursively(sheet, child, allFields);
}
} catch (Exception e) {
log.warn("递归创建合并区域失败", e);
}
}
/**
* 层级节点类(支持任意层级的数据结构)
*/
private static class HierarchyNode {
Object item; // 当前对象
int level; // 层级深度 (0=根层级)
int startRow; // 起始行号
int rowCount; // 占用行数
HierarchyNode parent; // 父节点
List<HierarchyNode> children = new ArrayList<>(); // 子节点列表
}
/**
* 设置单元格样式(保留已有样式,只添加居中对齐)
*/
private static void setCellStyle(Sheet sheet, int row, int col) {
try {
Row sheetRow = sheet.getRow(row);
if (sheetRow != null) {
Cell cell = sheetRow.getCell(col);
if (cell != null) {
CellStyle existingStyle = cell.getCellStyle();
CellStyle newStyle = sheet.getWorkbook().createCellStyle();
// 如果已有样式,复制其属性
if (existingStyle != null && existingStyle.getIndex() != 0) {
// 复制颜色和字体属性
newStyle.cloneStyleFrom(existingStyle);
}
// 设置居中对齐
newStyle.setAlignment(HorizontalAlignment.CENTER);
newStyle.setVerticalAlignment(VerticalAlignment.CENTER);
cell.setCellStyle(newStyle);
}
}
} catch (Exception e) {
log.warn("设置单元格样式失败", e);
}
}
/**
* 转换值为Excel显示格式
*/
private static String convertToExcelValue(Object value, FieldInfo fieldInfo) {
if (value == null) {
return fieldInfo.excel != null ? fieldInfo.excel.defaultValue() : "";
}
// 使用转换器
if (fieldInfo.converter != null) {
try {
Object converted = fieldInfo.converter.convertToExcel(value, fieldInfo.excel.params());
return String.valueOf(converted);
} catch (Exception e) {
log.warn("字段转换失败: {}", fieldInfo.field.getName());
}
}
// 日期格式化
if (value instanceof Date && fieldInfo.excel != null && StringUtils.isNotBlank(fieldInfo.excel.dateFormat())) {
SimpleDateFormat sdf = new SimpleDateFormat(fieldInfo.excel.dateFormat());
return sdf.format((Date) value);
}
return String.valueOf(value);
}
/**
* 解析Excel数据为对象列表
*/
private static <T> List<T> parseMultiLevelData(Sheet sheet, Class<T> clazz, List<FieldInfo> allFields) {
List<T> result = new ArrayList<>();
try {
if (sheet.getPhysicalNumberOfRows() <= 1) {
log.warn("Excel文件没有数据行");
return result;
}
// 读取表头
Row headerRow = sheet.getRow(0);
Map<String, Integer> headerMap = parseHeader(headerRow, allFields);
// 读取所有数据行
List<Map<String, Object>> rowDataList = readAllRows(sheet, headerMap, allFields);
if (rowDataList.isEmpty()) {
log.warn("没有读取到任何数据");
return result;
}
// 构建多级对象结构
result = buildMultiLevelObjects(rowDataList, clazz, allFields);
} catch (Exception e) {
log.error("解析Excel数据失败", e);
throw new RuntimeException("解析Excel数据失败", e);
}
return result;
}
/**
* 解析Excel表头
*/
private static Map<String, Integer> parseHeader(Row headerRow, List<FieldInfo> allFields) {
Map<String, Integer> headerMap = new HashMap<>();
if (headerRow != null) {
// 按层级排序字段,确保低层级的字段优先映射
List<FieldInfo> sortedFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.excel != null).sorted((a, b) -> Integer.compare(a.level, b.level)).toList();
// 记录每个列名已经被哪些层级使用过
Map<String, Set<Integer>> usedLevels = new HashMap<>();
for (int i = 0; i < headerRow.getPhysicalNumberOfCells(); i++) {
Cell cell = headerRow.getCell(i);
if (cell != null) {
String headerName = cell.getStringCellValue();
// 按层级顺序找到第一个未映射的匹配字段
for (FieldInfo fieldInfo : sortedFields) {
if (headerName.equals(fieldInfo.excel.name())) {
String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
// 检查这个字段是否已经被映射过
if (!headerMap.containsKey(uniqueKey)) {
// 记录此列名在此层级的使用
usedLevels.computeIfAbsent(headerName, k -> new HashSet<>()).add(fieldInfo.level);
headerMap.put(uniqueKey, i);
break;
}
}
}
}
}
}
log.debug("解析表头完成,映射关系: {}", headerMap);
return headerMap;
}
/**
* 读取所有数据行
*/
private static List<Map<String, Object>> readAllRows(Sheet sheet, Map<String, Integer> headerMap, List<FieldInfo> allFields) {
List<Map<String, Object>> rowDataList = new ArrayList<>();
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
Map<String, Object> rowData = new HashMap<>();
boolean hasData = false;
// 读取每个字段的值
for (FieldInfo fieldInfo : allFields) {
if (fieldInfo.fieldType == Excel.FieldType.COLUMN && fieldInfo.excel != null) {
String fieldName = fieldInfo.field.getName();
// 使用层级特定的唯一键查找列索引
String uniqueKey = "L" + fieldInfo.level + "_" + fieldName;
Integer colIndex = headerMap.get(uniqueKey);
if (colIndex != null) {
Cell cell = row.getCell(colIndex);
Object value = getCellValue(cell, fieldInfo);
// 使用层级特定的唯一键存储,避免同名字段覆盖
rowData.put(uniqueKey, value);
if (value != null && !value.toString().trim().isEmpty()) {
hasData = true;
}
}
}
}
if (hasData) {
rowData.put("_rowIndex", i);
rowDataList.add(rowData);
}
}
// 填补合并单元格的空值问题
fillMergedCellValues(rowDataList, allFields);
return rowDataList;
}
/**
* 填补合并单元格的空值问题
* 严格按层级和子对象组隔离,只在同层级同对象组内填补空值
*/
private static void fillMergedCellValues(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields) {
if (rowDataList.isEmpty()) return;
// 按层级分组字段
Map<Integer, List<String>> levelFields = new HashMap<>();
for (FieldInfo fieldInfo : allFields) {
if (fieldInfo.fieldType == Excel.FieldType.COLUMN && fieldInfo.excel != null) {
String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
levelFields.computeIfAbsent(fieldInfo.level, k -> new ArrayList<>()).add(uniqueKey);
}
}
// 找到最大层级(叶子节点层级)
int maxLevel = levelFields.keySet().stream().mapToInt(Integer::intValue).max().orElse(0);
// 按层级逐级处理,严格隔离
for (int currentLevel = 0; currentLevel < maxLevel; currentLevel++) {
List<String> currentLevelFieldKeys = levelFields.get(currentLevel);
if (currentLevelFieldKeys == null || currentLevelFieldKeys.isEmpty()) continue;
// 获取当前层级的关键字段
List<String> currentLevelKeyFields = getKeyFieldNames(allFields, currentLevel);
// 按当前层级的对象分组
Map<String, List<Map<String, Object>>> currentLevelGroups = new LinkedHashMap<>();
for (Map<String, Object> rowData : rowDataList) {
String levelKey = buildObjectKey(rowData, currentLevelKeyFields, currentLevel);
currentLevelGroups.computeIfAbsent(levelKey, k -> new ArrayList<>()).add(rowData);
}
// 为当前层级的每个对象组分别填补空值
for (Map.Entry<String, List<Map<String, Object>>> levelGroup : currentLevelGroups.entrySet()) {
String levelKey = levelGroup.getKey();
List<Map<String, Object>> levelRowData = levelGroup.getValue();
for (String fieldKey : currentLevelFieldKeys) {
// 检查当前层级对象组内是否有空值需要填补
boolean hasEmptyValue = false;
Object masterValue = null;
// 先找到第一个非空值作为主值
for (Map<String, Object> rowData : levelRowData) {
Object currentValue = rowData.get(fieldKey);
if (currentValue != null && !currentValue.toString().trim().isEmpty()) {
if (masterValue == null) {
masterValue = currentValue;
}
} else {
hasEmptyValue = true;
}
}
// 只有同时满足"有主值"和"有空值"的条件才进行填充
if (masterValue != null && hasEmptyValue) {
int filledCount = 0;
for (Map<String, Object> rowData : levelRowData) {
Object currentValue = rowData.get(fieldKey);
if (currentValue == null || currentValue.toString().trim().isEmpty()) {
rowData.put(fieldKey, masterValue);
filledCount++;
}
}
}
}
}
}
}
/**
* 获取单元格值,支持合并单元格
*/
private static Object getCellValue(Cell cell, FieldInfo fieldInfo) {
if (cell == null) return null;
try {
// 检查是否为合并单元格
Sheet sheet = cell.getSheet();
int rowIndex = cell.getRowIndex();
int colIndex = cell.getColumnIndex();
// 查找合并区域
for (CellRangeAddress range : sheet.getMergedRegions()) {
if (range.isInRange(rowIndex, colIndex)) {
// 如果是合并单元格,获取合并区域的第一个单元格的值
Row firstRow = sheet.getRow(range.getFirstRow());
if (firstRow != null) {
Cell firstCell = firstRow.getCell(range.getFirstColumn());
if (firstCell != null && firstCell != cell) {
return getCellValue(firstCell, fieldInfo);
}
}
}
}
// 普通单元格处理
switch (cell.getCellType()) {
case STRING:
String stringValue = cell.getStringCellValue();
return convertCellValue(stringValue, fieldInfo);
case NUMERIC:
double numericValue = cell.getNumericCellValue();
// 如果字段类型是Date,尝试转换为日期
if (fieldInfo.field.getType() == Date.class) {
return cell.getDateCellValue();
} else if (fieldInfo.field.getType() == Integer.class || fieldInfo.field.getType() == int.class) {
return (int) numericValue;
}
return numericValue;
case BOOLEAN:
return cell.getBooleanCellValue();
default:
return null;
}
} catch (Exception e) {
log.warn("读取单元格值失败: {}", e.getMessage());
return null;
}
}
/**
* 转换单元格值
*/
private static Object convertCellValue(String value, FieldInfo fieldInfo) {
if (value == null || value.trim().isEmpty()) return null;
Class<?> fieldType = fieldInfo.field.getType();
try {
// 如果有自定义转换器,先尝试转换
if (fieldInfo.converter != null) {
return fieldInfo.converter.convertFromExcel(value, new String[0]);
}
// 根据字段类型转换
if (fieldType == String.class) {
return value;
} else if (fieldType == Integer.class || fieldType == int.class) {
return Integer.parseInt(value);
} else if (fieldType == Long.class || fieldType == long.class) {
return Long.parseLong(value);
} else if (fieldType == Double.class || fieldType == double.class) {
return Double.parseDouble(value);
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
return Boolean.parseBoolean(value);
} else if (fieldType == Date.class) {
// 尝试解析日期
SimpleDateFormat[] formats = {new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), new SimpleDateFormat("yyyy-MM-dd"), new SimpleDateFormat("yyyy/MM/dd")};
for (SimpleDateFormat format : formats) {
try {
return format.parse(value);
} catch (Exception ignored) {
}
}
}
return value;
} catch (Exception e) {
log.warn("转换单元格值失败: {} -> {}", value, fieldType.getSimpleName());
return value;
}
}
/**
* 构建多级对象结构
*/
private static <T> List<T> buildMultiLevelObjects(List<Map<String, Object>> rowDataList, Class<T> clazz, List<FieldInfo> allFields) {
List<T> result = new ArrayList<>();
try {
// 找到最大层级深度
int maxLevel = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN).mapToInt(f -> f.level).max().orElse(0);
log.debug("最大层级深度: {}", maxLevel);
// 按层级分组数据
Map<String, List<Map<String, Object>>> groupedData = groupDataByHierarchy(rowDataList, allFields);
// 构建根对象
for (Map.Entry<String, List<Map<String, Object>>> entry : groupedData.entrySet()) {
T rootObject = buildSingleObject(entry.getValue(), clazz, allFields, 0);
if (rootObject != null) {
result.add(rootObject);
}
}
} catch (Exception e) {
log.error("构建多级对象失败", e);
}
return result;
}
/**
* 按层级分组数据(支持任意级别)
*/
private static Map<String, List<Map<String, Object>>> groupDataByHierarchy(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields) {
Map<String, List<Map<String, Object>>> groupedData = new LinkedHashMap<>();
// 获取根层级的关键字段组合
List<String> rootKeyFieldNames = getKeyFieldNames(allFields, 0);
for (Map<String, Object> rowData : rowDataList) {
// 构建根对象的唯一标识
String key = buildObjectKey(rowData, rootKeyFieldNames, 0);
groupedData.computeIfAbsent(key, k -> new ArrayList<>()).add(rowData);
}
return groupedData;
}
/**
* 获取指定层级的关键字段名称列表
*/
private static List<String> getKeyFieldNames(List<FieldInfo> allFields, int level) {
List<String> keyFieldNames = new ArrayList<>();
// 获取当前层级的所有字段
List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.level == level).toList();
// 收集所有标记为isKey=true的字段
for (FieldInfo field : currentLevelFields) {
Excel excel = field.excel;
if (excel != null && excel.isKey()) {
keyFieldNames.add(field.field.getName());
}
}
// 如果没有找到任何关键字段,使用第一个字段作为默认关键字段
if (keyFieldNames.isEmpty() && !currentLevelFields.isEmpty()) {
keyFieldNames.add(currentLevelFields.get(0).field.getName());
}
return keyFieldNames;
}
/**
* 构建对象的唯一标识键
*/
private static String buildObjectKey(Map<String, Object> rowData, List<String> keyFieldNames, int level) {
StringBuilder key = new StringBuilder();
for (String fieldName : keyFieldNames) {
// 使用层级特定的唯一键获取值
String uniqueKey = "L" + level + "_" + fieldName;
Object value = rowData.get(uniqueKey);
key.append(value != null ? value.toString() : "null").append("|");
}
return key.toString();
}
/**
* 构建单个对象
*/
private static <T> T buildSingleObject(List<Map<String, Object>> rowDataList, Class<T> clazz, List<FieldInfo> allFields, int level) {
try {
T object = clazz.getDeclaredConstructor().newInstance();
// 设置当前层级的字段值
Map<String, Object> representativeRow = findRepresentativeRow(rowDataList, allFields, level);
List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.level == level).toList();
for (FieldInfo fieldInfo : currentLevelFields) {
// 使用层级特定的唯一键,避免同名字段覆盖问题
String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
Object value = representativeRow.get(uniqueKey);
if (value != null) {
fieldInfo.field.set(object, value);
}
}
List<FieldInfo> childCollectionFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLLECTION && f.level == level).toList();
for (FieldInfo collectionField : childCollectionFields) {
Class<?> elementType = getActualType(collectionField.field);
// 按子对象分组
Map<String, List<Map<String, Object>>> childGroups = groupChildData(rowDataList, allFields, level + 1);
List<Object> childObjects = new ArrayList<>();
for (List<Map<String, Object>> childRowData : childGroups.values()) {
Object childObject = buildSingleObject(childRowData, elementType, allFields, level + 1);
if (childObject != null) {
childObjects.add(childObject);
}
}
collectionField.field.set(object, childObjects);
}
return object;
} catch (Exception e) {
log.error("构建对象失败: {}", e.getMessage());
return null;
}
}
/**
* 找到代表性的行数据(用于获取当前层级的字段值)
* 对于每个字段,优先选择有值的行数据
*/
private static Map<String, Object> findRepresentativeRow(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields, int level) {
if (rowDataList.isEmpty()) {
return new HashMap<>();
}
if (rowDataList.size() == 1) {
return rowDataList.get(0);
}
// 获取当前层级的所有字段
List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.level == level).toList();
// 创建一个组合的代表性行数据
Map<String, Object> representativeRow = new HashMap<>(rowDataList.get(0));
// 对于每个字段,找到第一个非空值
for (FieldInfo fieldInfo : currentLevelFields) {
String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
Object currentValue = representativeRow.get(uniqueKey);
// 如果当前值是空的,尝试从其他行中找到非空值
if (currentValue == null || currentValue.toString().trim().isEmpty()) {
for (Map<String, Object> rowData : rowDataList) {
Object value = rowData.get(uniqueKey);
if (value != null && !value.toString().trim().isEmpty()) {
representativeRow.put(uniqueKey, value);
break;
}
}
}
}
return representativeRow;
}
/**
* 分组子数据(支持任意级别)
*/
private static Map<String, List<Map<String, Object>>> groupChildData(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields, int childLevel) {
Map<String, List<Map<String, Object>>> childGroups = new LinkedHashMap<>();
// 获取子层级的关键字段组合
List<String> childKeyFieldNames = getKeyFieldNames(allFields, childLevel);
for (Map<String, Object> rowData : rowDataList) {
// 构建子对象的唯一标识
String key = buildObjectKey(rowData, childKeyFieldNames, childLevel);
childGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(rowData);
}
return childGroups;
}
/**
* 字段信息类
*/
private static class FieldInfo {
Field field;
Excel excel;
ExcelConverter<Object, Object> converter;
int level;
int columnIndex;
Excel.FieldType fieldType;
}
}