easyexcel初次尝试
交代业务背景:
我需要去读取excel中合并单元格的数据记录,如果是导出格式这里应该没有,因为我觉得生成excel文件相对来说比较容易,也许初生牛犊不怕虎吧。
下面是我借鉴的博客:
作者:mybabe0312
业务需求:
讲解一下具体的需要:我需要拿到用户信息和他每天需要吃饭的次数,用于给这个用户预定饭,不然他就得饿着了。所以这里明确的是:时间2021/7/1是动态的,然后就是下面的早餐、午餐、晚餐也是动态的,也就意味着我不知道他要预定几天的的,每天的餐段又是多少,这是未知,也是这个的难点。
如果你刚刚好看到了这篇文章,而你也有类似的问题,你可以借鉴我的思路,完成你的需求啦。我用了一天才弄懂这个,有点笨。。。
我添加的maven依赖
<!--excel 读写操作 主要的-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
其他依赖就不给你了,自己动手丰衣足食
我读取到的数据
这个是合并单元格的属性 如果你的合并单元格和我的不同,你也可以根据这个来设置你自己的规则,这叫做举一反三哦:
代码展示
实体类 主要信息记录
@Data
@EqualsAndHashCode(callSuper = false)
public class StudentExcel {
/**
* 用户姓名
*/
private String name;
/**
* 用户工号
*/
private String employeeNo;
/**
* 用户报餐信息
*/
private List<PeriodInfoDTO> list;
}
实体类 记录日期对应餐段的数据
@Data
public class PeriodInfoDTO {
/**
* 日期 2021-07-15
*/
private Date time;
/**
* 餐段名称
*/
private String periodName;
/**
* 报餐次数
*/
private Integer count;
}
业务实现代码 main方法内的可以提出来具体实现
package com.demo.utils;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.CellExtra;
import com.demo.entity.excel.PeriodInfoDTO;
import com.demo.entity.excel.StudentExcel;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 描述:监听
*
* @author XxX
* @date 2021-07-15 16:58
*/
@Component
public class StudentExcelListener extends AnalysisEventListener<Map<Integer, String>> {
/**
* Integer:第几行数据index, Map<Integer:第几个单元格的index, String:值> index 从0开始
*/
private final LinkedHashMap<Integer, Map<Integer, String>> dataMap = new LinkedHashMap<>();
/**
* 合并单元格的附加属性
*/
private final List<CellExtra> extraMergeInfoList = new ArrayList<>();
/**
* 最终结果存放的地方
*/
private List<StudentExcel> resultList = new ArrayList<>();
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
//读取到的每行数据,其key是以0开始的索引
dataMap.put(context.readRowHolder().getRowIndex(),data) ;
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
this.explainMergeData(dataMap, extraMergeInfoList);
}
/**
* 我制定的规则
*
* @param data 读取excel的数据存储地方
* @param extraMergeInfoList 何必单元格的附加属性
*/
private void explainMergeData(LinkedHashMap<Integer, Map<Integer, String>> data, List<CellExtra> extraMergeInfoList) {
if (CollectionUtils.isEmpty(extraMergeInfoList)) {
return;
}
//和单元格的时间格式需要对应 因为需要转译
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
//第一行和第二行数据,这是必定存在的标题头
Map<Integer, String> title1 = data.get(0);
Map<Integer, String> title2 = data.get(1);
//获取日期和餐段信息
Map<Integer, PeriodInfoDTO> periodInfoMap = new HashMap<>();
//只需要合并行的记录 合并列的我不需要
Map<Integer, CellExtra> extraMap = extraMergeInfoList.stream()
//想要过滤得到日期的单元格附加属性
.filter(p -> (p.getFirstColumnIndex() != 0 || p.getLastColumnIndex() != 0) && (!p.getFirstColumnIndex().equals(p.getLastColumnIndex())))
.collect(Collectors.toMap(CellExtra::getColumnIndex, Function.identity()));
PeriodInfoDTO periodInfo;
for (Map.Entry<Integer, String> elm : title1.entrySet()) {
if (StringUtils.isBlank(elm.getValue())) {
continue;
}
CellExtra extra = extraMap.get(elm.getKey());
if (extra != null) {
String date = elm.getValue();
//包含这个列 表示获取的是日期 要去第二行找
int firstColumnIndex = extra.getFirstColumnIndex();
int lastColumnIndex = extra.getLastColumnIndex();
while (firstColumnIndex <= lastColumnIndex) {
periodInfo = new PeriodInfoDTO();
periodInfo.setTime(DateUtil.parse(date, sdf));
periodInfo.setPeriodName(title2.get(firstColumnIndex));
periodInfoMap.put(firstColumnIndex, periodInfo);
firstColumnIndex++;
}
}
}
StudentExcel student;
PeriodInfoDTO newDTO;
PeriodInfoDTO periodInfoDTO;
List<PeriodInfoDTO> list;
List<StudentExcel> studentExcels = new ArrayList<>(data.size());
for (Map.Entry<Integer, Map<Integer, String>> elm : data.entrySet()) {
if (elm.getKey() <= 1) {
continue;
}
student = new StudentExcel();
list = new ArrayList<>();
for (Map.Entry<Integer, String> elm1 : elm.getValue().entrySet()) {
switch (elm1.getKey()) {
case 1:
student.setName(elm1.getValue());
break;
case 2:
student.setEmployeeNo(elm1.getValue());
break;
default:
periodInfoDTO = periodInfoMap.get(elm1.getKey());
if (periodInfoDTO != null) {
newDTO = new PeriodInfoDTO();
newDTO.setTime(periodInfoDTO.getTime());
newDTO.setPeriodName(periodInfoDTO.getPeriodName());
newDTO.setCount(Integer.valueOf(elm1.getValue()));
list.add(newDTO);
}
break;
}
}
student.setList(list);
studentExcels.add(student);
}
this.resultList = studentExcels;
}
/**
* 区分extra是什么类型
*
* @param extra 单元格附加属性 这一块没仔细研究呀
* @param context 内容 a context is the main anchorage point of a excel reader
*/
@Override
public void extra(CellExtra extra, AnalysisContext context) {
switch (extra.getType()){
case MERGE:{
//合并单元格
extraMergeInfoList.add(extra);
break;
}
// HYPERLINK 超链接 COMMENT 评论
default: {
break;
}
}
}
/**
* 某行的数据解析失败
* */
@Override
public void onException(Exception exception, AnalysisContext context) {
System.err.println("解析失败,但是继续解析下一行: " + exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
//这里直接抛出异常 提示哪行数据问题,方便定位
System.err.println("第{}行,第{}列解析异常" + excelDataConvertException.getRowIndex() +
excelDataConvertException.getColumnIndex());
}
}
public static void main(String[] args) {
//读取的表格的绝对路径
String fileName3 = "/Downloads/c1.xlsx" ;
StudentExcelListener readListener = new StudentExcelListener();
EasyExcel.read(fileName3, readListener)
// 需要读取合并单元格信息 默认不读取 需要指定
.extraRead(CellExtraTypeEnum.MERGE)
//.registerConverter(new EmptyConverter()) //默认:DefaultConverterLoader#loadDefaultReadConverter()
.ignoreEmptyRow(true)
.autoTrim(true)
//默认从第一行开始读 需要指定
.headRowNumber(0)
.autoCloseStream(true)
//.sheet("读取指定的sheet")
.sheet()
.doRead();
List<StudentExcel> resultList = readListener.resultList;
System.out.println("你好");
}
}
结束语
这里是最后了,主要难点就是怎么取获取数据和合并单元格的属性,其实一开始什么都不知道,到后面的一知半解,但是这还不够,这一次算是初次学习和总结,收获了很多,但是也看到了自己对java的了解远远不够,继续加油!!
最后提醒一下哈,主要的规则根据参数来界定和拼接你需要的数据记录,这算是一个不是很好的小demo,但是可以给你一点小见解。重点关注-> explainMergeData方法哦!