前言
这两天遇到了一个读取excel动态表头的需求,具体的业务信息不方便透露,举例来说就是如下图的excel,我需要将其中几列合为bean中的一个属性,放到一个map中。

每个月统计一次年级各班三次周考的各科平均分,然后入库,入库的格式是这样的[↓]

系统里用的组件是EasyExcel,我查阅相关文档后发现并没有针对动态表头的接口(倒是在github里看到有老哥提交了动态表头导出的更新代码,但目前还未被采纳),故而只能硬着头皮自己去实现,谁让我司的产品是个人美心善的姐姐呢。
核心思路就是在监听器中获取到表头信息,然后通过下标索引和反射新建bean的实例。
依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
自定义注解ExcelMultiColumn
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelMultiColumn{
int fromIndex();
int endIndex() default Integer.MAX_VALUE;
}
注解内容的优化还有很多,或许可以支持pattern匹配?
实体类SubjectScore
@Data
public class SubjectScore {
//年级
@ExcelProperty(index = 0)
private String gradeClass;
//科目
@ExcelProperty(index = 1)
private String subject;
//平均分
@ExcelMultiColumn(fromIndex = 2)
private Map<String,Double> avgScores;
}
重写Listener
@Slf4j
public class DynamicHeaderListener extends AnalysisEventListener<LinkedHashMap<Integer, Object>> {
private static final int BATCH_SIZE = 200;
@Getter
private List<SubjectScore> result;
/**
* 表头数据
*/
private Map<Integer, String> headMap;
private final DataConvert<LinkedHashMap<Integer, Object>, SubjectScore> dataConvert;
public DataProcessListener(DataConvert<LinkedHashMap<Integer, Object>, SubjectScore> dataConvert) {
this.result = new LinkedList<>();
this.dataConvert = dataConvert;
}
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
*/
@SneakyThrows
@Override
public void invoke(LinkedHashMap<Integer, Object> data, AnalysisContext analysisContext) {
//根据业务策略进行map2bean
SubjectScore object = dataConvert.convert(data, headMap);
result.add(object);
if (result.size() >= BATCH_SIZE) {
//入库服务,自定义实现
//saveData();
//防止OOM
result = ListUtils.newArrayListWithExpectedSize(BATCH_SIZE);;
}
}
/**
* 所有数据解析完成了
*
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//入库服务,自定义实现
//saveData();
}
}
自定义类型转换器DataConvert
public class DynamicHeaderDataConvert<G> implements DataConvert<LinkedHashMap<Integer, Object>, G> {
private Class<G> clazz;
public DynamicHeaderDataConvert(Class<G> clazz) {
this.clazz = clazz;
}
@SneakyThrows
@Override
public G convert(LinkedHashMap<Integer, Object> data, Map<Integer, String> headMap) {
Field[] fields = clazz.getDeclaredFields();
G object = clazz.newInstance();
int boundary = data.keySet().stream().max(Integer::compareTo).get();
for (Field field : fields) {
Object value;
if (field.getAnnotation(ExcelProperty.class) != null) {
int index;
index = field.getAnnotation(ExcelProperty.class).index();
value = data.get(index);
} else if (field.getAnnotation(ExcelMultiColumn.class) != null) {
int endIndex = Math.min(field.getAnnotation(ExcelMultiColumn.class).endIndex(), boundary);
int fromIndex = Math.max(field.getAnnotation(ExcelMultiColumn.class).fromIndex(), 0);
Map<String, Object> map = new LinkedHashMap<>();
for (int i = fromIndex; i <= endIndex; i++) {
map.put(headMap.get(i), data.get(i));
}
value = map;
} else {
continue;
}
BeanUtils.setProperty(object, field.getName(), value);
}
return object;
}
}
基本效果
@Test
public void readTest1() {
DataProcessListener listener = new DataProcessListener(new DynamicHeaderDataConvert<>(SubjectScore.class));
EasyExcel.read("DynamicHeaderReader.xlsx", listener)
.doReadAll();
listener.getResult();
}

这只是一个简单的实现,可以看到最后的结果中还有合并单元格读取以及入库格式转换的问题没有解决。所以仅仅是用作记录,之后有空再优化。
1554

被折叠的 条评论
为什么被折叠?



