EasyExcel-单字段多关键字匹配Bug
目标:
利用EasyExcel提供的@ExcelProperty 注解完成对单一字段的适配性映射(说人话就是假设业务表中有一个Field是`id`,但在表格中对应的列名可能是:ID、id、主键、唯一性标识...)。
代码:
@Getter
@Setter
@EqualsAndHashCode
@ExcelIgnoreUnannotated
public class DemoEntity {
@ExcelProperty({"ID", "id", "主键", "唯一性标识"})
private Long id;
} Path dataPath = Paths.get("/path/to/file")
.resolve("xxx.csv")
.normalize();
List<DemoEntity> objects = EasyExcelFactory.read(dataPath.toFile())
.sheet()
.head(DemoEntity.class)
.headRowNumber(1)
.doReadSync();表现:
在com.alibaba:easyexcel:4.0.3 版本中使用时,无法正确匹配所有可能出现的字段(只能匹配到最后一个关键字)。
原因:
在com.alibaba.excel.read.processor.DefaultAnalysisEventProcessor#buildHead 中生成Head缓存时第146行存在问题,这里仅获取了headNameList.get(headNameList.size() - 1) 作为关键字进行了匹配。
private void buildHead(AnalysisContext analysisContext, Map<Integer, ReadCellData<?>> cellDataMap) {
// Rule out empty head, and then take the largest column
if (MapUtils.isNotEmpty(cellDataMap)) {
cellDataMap.entrySet()
.stream()
.filter(entry -> CellDataTypeEnum.EMPTY != entry.getValue().getType())
.forEach(entry -> analysisContext.readSheetHolder().setMaxNotEmptyDataHeadSize(entry.getKey()));
}
if (!HeadKindEnum.CLASS.equals(analysisContext.currentReadHolder().excelReadHeadProperty().getHeadKind())) {
return;
}
Map<Integer, String> dataMap = ConverterUtils.convertToStringMap(cellDataMap, analysisContext);
ExcelReadHeadProperty excelHeadPropertyData = analysisContext.readSheetHolder().excelReadHeadProperty();
Map<Integer, Head> headMapData = excelHeadPropertyData.getHeadMap();
Map<Integer, Head> tmpHeadMap = new HashMap<Integer, Head>(headMapData.size() * 4 / 3 + 1);
for (Map.Entry<Integer, Head> entry : headMapData.entrySet()) {
Head headData = entry.getValue();
if (headData.getForceIndex() || !headData.getForceName()) {
tmpHeadMap.put(entry.getKey(), headData);
continue;
}
List<String> headNameList = headData.getHeadNameList();
String headName = headNameList.get(headNameList.size() - 1); // 👈 这里有问题
for (Map.Entry<Integer, String> stringEntry : dataMap.entrySet()) {
if (stringEntry == null) {
continue;
}
String headString = stringEntry.getValue();
Integer stringKey = stringEntry.getKey();
if (StringUtils.isEmpty(headString)) {
continue;
}
if (analysisContext.currentReadHolder().globalConfiguration().getAutoTrim()) {
headString = headString.trim();
}
if (headName.equals(headString)) {
headData.setColumnIndex(stringKey);
tmpHeadMap.put(stringKey, headData);
break;
}
}
}
excelHeadPropertyData.setHeadMap(tmpHeadMap);
}临时解决方案:
在自己的项目中创建同名同包的类DefaultAnalysisEventProcessor 并使用如下代码
package com.alibaba.excel.read.processor;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.enums.HeadKindEnum;
import com.alibaba.excel.enums.RowTypeEnum;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.exception.ExcelAnalysisStopException;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.excel.read.metadata.holder.ReadSheetHolder;
import com.alibaba.excel.read.metadata.property.ExcelReadHeadProperty;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.StringUtils;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Analysis event
*
* @author Cikaros
* @date 2026/2/6 19:35
*/
public class DefaultAnalysisEventProcessor implements AnalysisEventProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAnalysisEventProcessor.class);
@Override
public void extra(AnalysisContext analysisContext) {
dealExtra(analysisContext);
}
@Override
public void endRow(AnalysisContext analysisContext) {
if (RowTypeEnum.EMPTY.equals(analysisContext.readRowHolder().getRowType())) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Empty row!");
}
if (analysisContext.readWorkbookHolder().getIgnoreEmptyRow()) {
return;
}
}
dealData(analysisContext);
}
@Override
public void endSheet(AnalysisContext analysisContext) {
ReadSheetHolder readSheetHolder = analysisContext.readSheetHolder();
if (BooleanUtils.isTrue(readSheetHolder.getEnded())) {
return;
}
readSheetHolder.setEnded(Boolean.TRUE);
for (ReadListener<?> readListener : analysisContext.currentReadHolder().readListenerList()) {
readListener.doAfterAllAnalysed(analysisContext);
}
}
private void dealExtra(AnalysisContext analysisContext) {
for (ReadListener<?> readListener : analysisContext.currentReadHolder().readListenerList()) {
try {
readListener.extra(analysisContext.readSheetHolder().getCellExtra(), analysisContext);
} catch (Exception e) {
onException(analysisContext, e);
break;
}
if (!readListener.hasNext(analysisContext)) {
throw new ExcelAnalysisStopException();
}
}
}
private void onException(AnalysisContext analysisContext, Exception e) {
for (ReadListener<?> readListenerException : analysisContext.currentReadHolder().readListenerList()) {
try {
readListenerException.onException(e, analysisContext);
} catch (RuntimeException re) {
throw re;
} catch (Exception e1) {
throw new ExcelAnalysisException(e1.getMessage(), e1);
}
}
}
private void dealData(AnalysisContext analysisContext) {
ReadRowHolder readRowHolder = analysisContext.readRowHolder();
Map<Integer, ReadCellData<?>> cellDataMap = (Map) readRowHolder.getCellMap();
readRowHolder.setCurrentRowAnalysisResult(cellDataMap);
int rowIndex = readRowHolder.getRowIndex();
int currentHeadRowNumber = analysisContext.readSheetHolder().getHeadRowNumber();
boolean isData = rowIndex >= currentHeadRowNumber;
// Last head column
if (!isData && currentHeadRowNumber == rowIndex + 1) {
buildHead(analysisContext, cellDataMap);
}
// Now is data
for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) {
try {
if (isData) {
readListener.invoke(readRowHolder.getCurrentRowAnalysisResult(), analysisContext);
} else {
readListener.invokeHead(cellDataMap, analysisContext);
}
} catch (Exception e) {
onException(analysisContext, e);
break;
}
if (!readListener.hasNext(analysisContext)) {
throw new ExcelAnalysisStopException();
}
}
}
private void buildHead(AnalysisContext analysisContext, Map<Integer, ReadCellData<?>> cellDataMap) {
// Rule out empty head, and then take the largest column
if (MapUtils.isNotEmpty(cellDataMap)) {
cellDataMap.entrySet()
.stream()
.filter(entry -> CellDataTypeEnum.EMPTY != entry.getValue().getType())
.forEach(entry -> analysisContext.readSheetHolder().setMaxNotEmptyDataHeadSize(entry.getKey()));
}
if (!HeadKindEnum.CLASS.equals(analysisContext.currentReadHolder().excelReadHeadProperty().getHeadKind())) {
return;
}
Map<Integer, String> dataMap = ConverterUtils.convertToStringMap(cellDataMap, analysisContext);
ExcelReadHeadProperty excelHeadPropertyData = analysisContext.readSheetHolder().excelReadHeadProperty();
Map<Integer, Head> headMapData = excelHeadPropertyData.getHeadMap();
Map<Integer, Head> tmpHeadMap = new HashMap<Integer, Head>(headMapData.size() * 4 / 3 + 1);
for (Map.Entry<Integer, Head> entry : headMapData.entrySet()) {
Head headData = entry.getValue();
if (headData.getForceIndex() || !headData.getForceName()) {
tmpHeadMap.put(entry.getKey(), headData);
continue;
}
List<String> headNameList = headData.getHeadNameList();
Function<Map.Entry<?, String>, String> mapVal = Map.Entry::getValue;
Map<String, Integer> cacheMap;
if (analysisContext.currentReadHolder().globalConfiguration().getAutoTrim()) {
mapVal = mapVal.andThen(String::trim);
}
cacheMap = dataMap.entrySet().stream()
.filter(Objects::nonNull)
.filter(item -> !StringUtils.isEmpty(item.getValue()))
.collect(Collectors.toMap(mapVal, Map.Entry::getKey));
for (String headName : headNameList) {
if (cacheMap.containsKey(headName)) {
Integer index = cacheMap.get(headName);
headData.setColumnIndex(index);
tmpHeadMap.put(index, headData);
break;
}
}
}
excelHeadPropertyData.setHeadMap(tmpHeadMap);
}
}
EasyExcel-单字段多关键字匹配Bug
https://blog.cikaros.cn/archives/easyexcel-dan-zi-duan-duo-guan-jian-zi-pi-pei-bug