1.需求场景描述
现在有个业务需要按照指定模板上传选择题,并进行入表处理,使用easyExcel进行文件上传并读取数据,其中涉及合并单元数据读取问题,这里简单记录一下实现过程,希望对有同样需求的同学有所帮助.下面贴一下文件上传的模板:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1bf2512533389bec8fc63365084b3a56.png)
其中每个选择题的题干部分是合并单元格,每个选择题的四个固定选项分别占一行。入表之前期望读取的数据格式为:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d356fd2d2c1b8c0e4cf8ca21f8d417f2.png)
2.问题分析与实现方案
对于此种场景,根据easy excel读取原理,每行读取完成都支持进行监听回调(可自定义监听器执行invoke中获取所有行数读取信息),所有行数读取完成之后将组装数据进行输出(所有行数读取完成会执行自定义监听器中doAfterAllAnalysed)。按照一般读取操作发现都是按照合并前的行数进行读取。以展示模板信息为例说讲,表格中8行数据,就是进行了8次读取,对于合并单元格所在的列,只有第一行进行了实体类属性数据映射,对于合并过的单元格对应的实体类属性值均为null。以下为代码调试展示说明:
每道题第一行读取的实体类属性值:
![](https://i-blog.csdnimg.cn/blog_migrate/8bca5f728c380c10aa9531f352d06d1a.png)
每道题第一行之外读取的实体类属性值:
![](https://i-blog.csdnimg.cn/blog_migrate/10cc869953d55e34f2864e536b063d9b.png)
所以可以将每行读取完成之后的数据进行组装即可,这里添加了一下目标数据格式类:
ExamInfo.java
下面贴一下案例代码:
easy excel文件读取类(与模板文件对应):
public class ExamMsg {
@ExcelProperty("题目描述")
private String answer;
@ExcelProperty("考试答案")
private String stem;
@ExcelProperty("考试答案分析")
private String analyse;
@ExcelProperty("题目单项分数")
private Integer singleScore;
@ExcelProperty("选项")
private String choiceType;
@ExcelProperty("选项内容")
private String optionContent;
// 省略get/set
}
目标数据组装类(入表之前的最终数据格式):
public class ExamInfo {
private String answer;
private String stem;
private String analyse;
private Integer singleScore;
private List<EaxmChoiceInfo> eaxmChoiceInfoList=new ArrayList<>();
// 省略get/set
}
每个选择题的四个选择项实体类:
public class EaxmChoiceInfo {
private String choiceType;
private String optionContent;
// 省略get/set
}
自定义监听器
public class ExamListener implements ReadListener<ExamMsg> {
// 自定义消费者函数接口用于自定义监听器中数据组装
private final Consumer<List<ExamInfo>> consumer;
public ExamListener(Consumer<List<ExamInfo>> consumer) {
this.consumer = consumer;
}
// easy excel读取参数
private List<ExamMsg> examMsgList=new ArrayList<>();
// 封装读取对象
private List<ExamInfo> examInfoList=new ArrayList<>();
// 每行读取完成之后会执行
@Override
public void invoke(ExamMsg data, AnalysisContext context) throws IllegalAccessException {
// 按照格式组装数据
if(data.getStem() != null){
ExamInfo examInfo = new ExamInfo();
BeanUtils.copyProperties(data,examInfo);
examInfo.getEaxmChoiceInfoList().add(new EaxmChoiceInfo(data.getChoiceType(),data.getOptionContent()));
examInfoList.add(examInfo);
}else {
// 倒序添加选择题信息,只对最后一个进行添加选项数据信息
examInfoList.get(examInfoList.size() - 1).getEaxmChoiceInfoList().add(new EaxmChoiceInfo(data.getChoiceType(),data.getOptionContent()));
}
}
// 每行读取完成之后执行
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if (CollectionUtils.isNotEmpty(examInfoList)) {
consumer.accept(examInfoList);
}
}
}
文件读取测试类:
public class PersonalExcel {
public static void main(String[] args) {
List<ExamInfo> examInfoList=new ArrayList<>();
String fileName = "E:\\项目汇总\\选择题录入模板" + ".xlsx";
EasyExcel.read(fileName, ExamMsg.class, new ExamListener(examInfos -> {
for (ExamInfo examInfo : examInfos) {
examInfoList.add(examInfo);
}
})).sheet().doRead();
System.out.println(examInfoList);
}
}
控制台打印读取数据:
[ExamInfo{answer='新冠肺炎起源于哪一年?', stem='A', analyse='经验得知', singleScore=2,
eaxmChoiceInfoList=[EaxmChoiceInfo{choiceType='A', optionContent='2019'},
EaxmChoiceInfo{choiceType='B', optionContent='2022'},
EaxmChoiceInfo{choiceType='C', optionContent='2021'},
EaxmChoiceInfo{choiceType='D', optionContent='2003'}]},
ExamInfo{answer='刘备的最好的朋友是谁?', stem='B', analyse='猜的', singleScore=2,
eaxmChoiceInfoList=[EaxmChoiceInfo{choiceType='A', optionContent='关羽'},
EaxmChoiceInfo{choiceType='B', optionContent='诸葛亮'},
EaxmChoiceInfo{choiceType='C', optionContent='张飞'}, EaxmChoiceInfo{choiceType='D', optionContent='曹操'}]}]
以上为处理合并单元格文件读取的实现方案,如果对你有帮助欢迎点赞收藏!
更多easy excel实战文章:
Easy excel实战:读取文件自定义转化器不生效问题处理方案
EasyExcel实战:实现导入文件参数校验并记录异常信息