EasyExcel 实现动态表头导入
需求:
实现数据导入,模板的表头可以有固定列和非固定列(即用户可以自定义表头,前端直接进行数据展示)模板如图所示,自定义列用户可以输入自定义的表头,代码实现中固定表头为姓名、身份证号、手机号。
依赖导入
导入easyexcel 依赖、和阿里巴巴fastjson
<!-- excel导出 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.3.2</version> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.26</version> </dependency>
定义导入实体类
定义一个导入实体类,其中固定表头直接定义属性,非固定表头,定义为json字符串
package com.focus.zxowntest.entity; import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; /** * @author zx * @date 2023/9/7 16:03 */ @Data public class Salary { /** * 姓名 */ @ExcelProperty(value = "姓名",index = 0) private String name; /** * 手机号 */ @ExcelProperty(value = "手机号",index = 1) private String phone; /** * 身份证号 */ @ExcelProperty(value = "身份证号",index = 2) private String card; /** * 自定义数据 */ @ExcelIgnore private String json; }
定义监听器
import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.exception.ExcelDataConvertException; import com.alibaba.excel.metadata.data.ReadCellData; import com.alibaba.excel.util.ListUtils; import com.alibaba.fastjson2.JSONObject; import com.focus.zxowntest.entity.Salary; import lombok.extern.slf4j.Slf4j; import java.util.*; import java.util.stream.Collectors; @Slf4j public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> { /** * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 5; private List<Salary> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); private List<String> headers = new ArrayList<>(); // 存储自定义表头 Map<Integer, String> customHeaders = new HashMap<>(); /** * 这里会一行行的返回头 * * @param headMap * @param context */ @Override public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) { for (int i = 3; i < headMap.size(); i++) { String header = headMap.get(i).getStringValue(); if(!"null".equals(header) && !isFixedHeader(header) ){ customHeaders.put(i,header); } } // 如果想转成成 Map<Integer,String> // 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener // 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换 } @Override public void invoke(Map<Integer, String> data, AnalysisContext context) { // 处理读取到的数据逻辑,根据 ExcelRow 中的数据构建对象 // 根据 ExcelRow 中的表头信息构建对应的对象,可以根据实际情况进行修改 // 这里仅作示例,只输出每个人的姓名和身份证号以及其他自定义的属性 Salary salary = new Salary(); StringBuilder sb = new StringBuilder(); salary.setName(data.get(0)); salary.setPhone(data.get(1)); salary.setCard(data.get(2)); JSONObject jsonObject = new JSONObject(); for (int i = 3; i <= data.size()-1; i++) { jsonObject.put(customHeaders.get(i),data.get(i)); } salary.setJson(jsonObject.toJSONString()); //将读取到的数据加入缓存集合 cachedDataList.add(salary); //如果缓存集合数据大于等于设定值,存储数据库 if (cachedDataList.size() >= BATCH_COUNT) { saveData(); //清空集合 cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); log.info("所有数据解析完成!"); } //判断是否是固定表头 private boolean isFixedHeader(String header) { //定义固定表头集合 List<String> list = Arrays.asList("姓名","手机号","身份证号"); return list.contains(header); } /** * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 * * @param exception * @param context * @throws Exception */ @Override public void onException(Exception exception, AnalysisContext context) { log.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); // 如果是某一个单元格的转换异常 能获取到具体行号 // 如果要获取头的信息 配合invokeHeadMap使用 if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData()); } } /** * 加上存储数据库 */ private void saveData() { List<Salary> newList = cachedDataList.stream().collect(Collectors.collectingAndThen( Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Salary::getCard))), ArrayList::new)); if(newList.size()<cachedDataList.size()){ log.error("表中存在{}条重复数据,请检查数据后重新导入!",cachedDataList.size()-newList.size()); } log.info("{}条数据,开始存储数据库!", newList.size()); log.info("存储数据库成功!"); } }
数据文件解析导入
/** * 不创建对象的读 */ @Test public void noModelRead() { String fileName = "D:\\doc\\zxowntest\\src\\main\\resources\\模板数据测试.xlsx"; // 这里 只要,然后读取第一个sheet 同步读取会自动finish EasyExcel.read(fileName, new NoModelDataListener()) .sheet()//默认读取第一个sheet .headRowNumber(2)//定义表头行 .doReadSync(); }