文章目录
- 前言
- 一、需求和效果
- 二、难点和思路
- 三、全部代码
- 踩坑
前言
之前分享的 EasyExcel 批量导入并校验数据,仅支持规则excel,即首行表头,下面对应数据,无合并单元格情况。
 本篇主要解决问题:
- 模板excel 表头不在首行
- 数据项有合并单元格情况
esayexcel版本2.2.7
一、需求和效果

 
二、难点和思路
-  跳过表头前的说明 设置headRowNumber指定表头位置,第三行 
EasyExcel.read(inputStream, EvalTemplateReq.class,
                    listener).extraRead(CellExtraTypeEnum.MERGE).sheet().headRowNumber(3).doRead();
- 合并单元格数据获取放入list
 合并单元格的数据默认是在合并表格的左上第一个格子,其他为null,获取到这个格子的数据,赋值给其他被合并单元格对应的字段中
3.1 开启合并单元格识别
 extraRead(CellExtraTypeEnum.MERGE)开启合并单元格识别
 3.2 获取合并的单元格数据
 EvalExcelDataListener 重写extra方法,获取到被合并的数据,后续处理。extra方法在invoke方法执行,先不要在意要导入的list部分字段是null,后续重新赋值即可。
 extra会从excel头读取,所以要把前2行的数据过滤掉,这里用extra.getFirstRowIndex()>2判断一下
    // 合并单元格
    private final List<CellExtra> extraMergeInfoList = new ArrayList<>();
    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        if (extra.getType() == CellExtraTypeEnum.MERGE) {
            // 处理合并单元格的情况
            int firstRowIndex = extra.getFirstRowIndex();
            if(firstRowIndex>2) {
                extraMergeInfoList.add(extra);
            }
        }
    }
3.3 doAfterAllAnalysed中补全list字段值
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //所有数据解析完毕执行该方法
        // 防止导入空的Excel
        if (context.readRowHolder().getRowIndex() <= 0) {
            throw new ExcelAnalysisException("当前excel无数据!");
        }
        //处理合并单元格
        list = EasyExcelMergeUtil.explainMergeData(list, extraMergeInfoList, 3);
        saveData();
    }
3.4 explainMergeData 补全字段值
 根据单元格左上角标通过反射获取字段并赋值给其他被合并的单元格,角标对应实体类index属性
三、全部代码
- 接收数据实体
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
 * 评估标准
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EvalTemplateReq {
    @ExcelIgnore
    private String id;
    /**
     * 评估项
     */
    @ExcelProperty(value = "评估名称",index = 1)
    @NotBlank(message = "评估名称不能为空")
    private String item;
    /**
     * 指标简称
     */
    @ExcelProperty(value = "指标简称",index =2)
    @NotBlank(message = "指标简称不能为空")
    private String itemShortName;
    /**
     * 指标编码
     */
    @ExcelProperty(value = "指标编码",index = 3)
    @NotBlank(message = "指标编码不能为空")
    private String itemCode;
    /**
     * 指标释义
     */
    @ExcelProperty(value = "指标释义",index = 4)
    @NotBlank(message = "指标释义不能为空")
    private String itemExplain;
    /**
     * 评价要求/分数 json
     */
    @ExcelIgnore
    private List<Requirements> requirementList;
    /**
     * 系统监测类/参演部门上报类
     */
    @ExcelProperty(value = "分类",index = 0)
    @NotBlank(message = "分类不能为空")
    private String type;
    @ExcelProperty(value = "评价要求",index = 5)
    @JsonIgnore
    private String require;
    @ExcelProperty(value = "分数",index = 6)
    @JsonIgnore
    private Integer score;
    @Builder
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Requirements {
        @ExcelIgnore
        private String id;
        /**
         * 评价内容
         */
        @NotBlank(message = "评价要求不能为空")
        private String require;
        /**
         * 分数
         */
        @NotBlank(message = "分数不能为空")
        private Integer score;
    }
}
- service调用
    /**
     * 导入评估标准
     */
    @Override
    public String importEval(MultipartFile file) {
        EvalExcelDataListener listener = new EvalExcelDataListener(evalTemplateDao);
        InputStream inputStream;
        try {
            inputStream = file.getInputStream();
            EasyExcel.read(inputStream, EvalTemplateReq.class,
                    listener).extraRead(CellExtraTypeEnum.MERGE).sheet().headRowNumber(3).doRead();
            return "全部导入成功!";
        } catch (IOException e) {
            throw new BusinessCheckException("Excel 文件流读取失败");
        } catch (ExcelAnalysisException e) {
            return e.getMessage();
        } catch (Exception e) {
            throw new BusinessException("数据导入失败", e);
        }
    }
- 合并单元格数据处理util
package com.gsafety.bg.pd.service.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.CellExtra;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.List;
@Slf4j
public class EasyExcelMergeUtil {
    /**
     * 处理合并单元格
     * @param data               解析数据
     * @param extraMergeInfoList 合并单元格信息
     * @param headRowNumber      起始行
     * @return 填充好的解析数据
     */
    public static <T> List<T> explainMergeData(List<T> data, List<CellExtra> extraMergeInfoList, Integer headRowNumber) {
        // 循环所有合并单元格信息
        extraMergeInfoList.forEach(cellExtra -> {
            int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNumber;
            int lastRowIndex = cellExtra.getLastRowIndex() - headRowNumber;
            int firstColumnIndex = cellExtra.getFirstColumnIndex();
            int lastColumnIndex = cellExtra.getLastColumnIndex();
            // 获取初始值
            Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, data);
            // 设置值
            for (int i = firstRowIndex; i <= lastRowIndex; i++) {
                for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {
                    setInitValueToList(initValue, i, j, data);
                }
            }
        });
        return data;
    }
    /**
     * 设置合并单元格的值
     *
     * @param filedValue  值
     * @param rowIndex    行
     * @param columnIndex 列
     * @param data        解析数据
     */
    private static <T> void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {
        if (rowIndex >= data.size()) return;
        T object = data.get(rowIndex);
        for (Field field : object.getClass().getDeclaredFields()) {
            // 提升反射性能,关闭安全检查
            field.setAccessible(true);
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation != null) {
                if (annotation.index() == columnIndex) {
                    try {
                        field.set(object, filedValue);
                        break;
                    } catch (IllegalAccessException e) {
                        log.error("设置合并单元格的值异常:{}", e.getMessage());
                    }
                }
            }
        }
    }
    /**
     * 获取合并单元格的初始值
     * rowIndex对应list的索引
     * columnIndex对应实体内的字段
     *
     * @param firstRowIndex    起始行
     * @param firstColumnIndex 起始列
     * @param data             列数据
     * @return 初始值
     */
    private static <T> Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {
        Object filedValue = null;
        T object = data.get(firstRowIndex);
        for (Field field : object.getClass().getDeclaredFields()) {
            // 提升反射性能,关闭安全检查
            field.setAccessible(true);
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation != null) {
                if (annotation.index() == firstColumnIndex) {
                    try {
                        filedValue = field.get(object);
                        break;
                    } catch (IllegalAccessException e) {
                        log.error("设置合并单元格的初始值异常:{}", e.getMessage());
                    }
                }
            }
        }
        return filedValue;
    }
}
- easyexcel数据监听类
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.metadata.CellExtra;
import com.gsafety.bg.gsdss.common.utils.json.JsonUtil;
import com.gsafety.bg.pd.dao.EvalTemplateDao;
import com.gsafety.bg.pd.model.dto.req.EvalTemplateReq;
import com.gsafety.bg.pd.model.po.EvalTemplatePO;
import java.util.*;
import java.util.stream.Collectors;
public class EvalExcelDataListener extends AnalysisEventListener<EvalTemplateReq> {
    private final Integer LIST_COUNT = 19;
    List<EvalTemplateReq> list = new ArrayList<>(LIST_COUNT);
    // 合并单元格
    private final List<CellExtra> extraMergeInfoList = new ArrayList<>();
    // 由于监听器只能通过new的方式创建,所以可以通过构造器传入dao层对象
    private final EvalTemplateDao dao;
    public EvalExcelDataListener(EvalTemplateDao dao) {
        this.dao = dao;
    }
    @Override
    public void invoke(EvalTemplateReq req, AnalysisContext context) {
        list.add(req);
        if (list.size() > LIST_COUNT) {
            throw new ExcelAnalysisException("当前excel数据量不得大于" + LIST_COUNT + "条!");
        }
    }
    @Override
    public void extra(CellExtra extra, AnalysisContext context) {
        if (extra.getType() == CellExtraTypeEnum.MERGE) {
            // 处理合并单元格的情况
            int firstRowIndex = extra.getFirstRowIndex();
            if(firstRowIndex>2) {
                extraMergeInfoList.add(extra);
            }
        }
    }
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        //所有数据解析完毕执行该方法
        // 防止导入空的Excel
        if (context.readRowHolder().getRowIndex() <= 0) {
            throw new ExcelAnalysisException("当前excel无数据!");
        }
        //处理合并单元格
        list = EasyExcelMergeUtil.explainMergeData(list, extraMergeInfoList, 3);
        saveData();
    }
    protected void saveData() {
        Map<String, List<EvalTemplateReq>> collect = list.stream().collect(Collectors.groupingBy(EvalTemplateReq::getItem));
        List<EvalTemplatePO> pos = new ArrayList<>();
        collect.forEach((k, v) -> {
            List<EvalTemplateReq.Requirements> requirements = v.stream().map(l -> EvalTemplateReq.Requirements.builder()
                    .id(v.indexOf(l) + "")
                    .require(l.getRequire())
                    .score(l.getScore())
                    .build()).collect(Collectors.toList());
            pos.add(EvalTemplatePO.builder()
                    .item(k)
                    .itemCode(collect.get(k).get(0).getItemCode())
                    .itemExplain(collect.get(k).get(0).getItemExplain())
                    .itemShortName(collect.get(k).get(0).getItemShortName())
                    .type(collect.get(k).get(0).getType())
                    .requirements(JsonUtil.of(requirements))
                    .build());
        });
        dao.deleteAll();
        dao.saveAll(pos);
    }
}
踩坑
- extra方法不生效
 extraRead(CellExtraTypeEnum.MERGE)显式指定识别合并单元格数据
 invoke中原校验数据,因为为null导致拦截,不走extra方法。
 执行顺序:invoke->extra->doAfterAllAnalysed
- 指定表头位置后extra获取的第一个数据还是首行的合并单元格数据
 headRowNumber(3)只是指定了读取表头的位置,extra是获取整表的所有合并单元格数据,根据excel模板,要跳过2行,获取从第三行后的合并单元格数据。if(firstRowIndex>2)
参考:https://blog.csdn.net/xhmico/article/details/136905419











![[misc]-流量包-wireshark-icmp](https://i-blog.csdnimg.cn/direct/a7aa333a3e99427b835baf2eb35224b7.png)
![JVM和类加载机制-01[JVM底层]](https://i-blog.csdnimg.cn/direct/277abce8a84746c5899fc19cb0630be2.png)







