关于使用EasyExcel读取动态表头的记录

前言

这两天遇到了一个读取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();
    }

在这里插入图片描述

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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值