工具篇-EasyExcel-web行列合并导入

本文介绍了如何使用阿里巴巴的EasyExcel库处理包含行列合并数据的Excel文件导入,特别是维护数据的层级结构。通过定义数据实体、监听器和数据处理逻辑,实现了导入时自动平铺合并单元格数据,并保持父子级关系。方法包括在监听器中维护层级关系,然后在控制器和实现类中处理数据并进行数据库操作。
摘要由CSDN通过智能技术生成


前言

本文介绍excel 存在行列合并项的数据,进行web导入。


一、准备工作:

pom引入jar:

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>easyexcel</artifactId>
   <version>3.1.4</version>
</dependency>

二、数据导入:

1.导入思路:

在excel 中的数据是有层级结构的,那么导入的时候就需要通过一种方式将数据的上下级关系进行维护,由于正常导入时,被合并的单元格数据只会在某一条数据中出现,即使多条数据的父类都是一个,但多条数据中改单元格的值只会被放入到第一条数据,后续的数据都为null ,本文通过以下思路进行导入;

  • 将合并单元格的数据完全进行平铺展示,并且一个单元的父类只能有一个:
    在这里插入图片描述

  • 在导入时从第二个指标开始,填充该指标的父类指标值,如上图中 总平面 下的 机动车停车 指标,归属于 机动车停车设置原则 则归属于总平面_机动车停车,将其层级关系进行字段维护;

  • 导入时通过遍历获取层级关系为 总平面 层级关系进行字段 则可以得到 总平面下的所以子级指标,同理,只要遍历 总平面_机动车停车 层级关系进行字段 则可以得到 机动车停车 下的子级指标;

2. 实现:

2.1 定义excel 导入数据实体:

根据自身业务完成定义


import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

@Data
public class ProductAdaptExcelDictQuatoDto implements Serializable {

    @ExcelProperty(value = "序号")
    private Integer rowNum;
    @ExcelProperty(value = "指标类型")
    private String dictType;
    @ExcelProperty(value = "指标所属航道")
    private String channel;
    @ExcelProperty(value = "指标1",converter = CustomStringStringConverter.class)
    private String quato1;
    @ExcelProperty(value = "指标2",converter = CustomStringStringConverter.class)
    private String quato2;
    private String quato2Below;
    @ExcelProperty(value = "指标3",converter = CustomStringStringConverter.class)
    private String quato3;
    private String quato3Below;
    @ExcelProperty(value = "指标4",converter = CustomStringStringConverter.class)
    private String quato4;
    private String quato4Below;
    @ExcelProperty(value = "指标5",converter = CustomStringStringConverter.class)
    private String quato5;
    private String quato5Below;

//    @ExcelProperty(value = "指标6",converter = CustomStringStringConverter.class)
//    private String quato6;
//    private String quato6Below;

    @ExcelProperty(value = "单位")
    private String unit;
    @ExcelProperty(value = "输入方式")
    private String inputType;
    @ExcelProperty(value = "值域")
    private String inputValue;
    @ExcelProperty(value = "指标排序")
    private String order;
    @ExcelProperty(value = "指标备注")
    private String remark;


    private List<ProductAdaptExcelDictQuatoDto> childQuato;





}

  • @ExcelProperty 中 value 对应 excel 表头;
    在这里插入图片描述
  • converter 可以进行数据转换,本例将数据转为字符串,并去掉回车,换行,及前后空格;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ReadConverterContext;
import com.alibaba.excel.converters.WriteConverterContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.WriteCellData;

public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class<?> supportJavaTypeKey() {
        return String.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    /**
     * 这里读的时候会调用
     *
     * @param context
     * @return
     */
    @Override
    public String convertToJavaData(ReadConverterContext<?> context) {
        return context.getReadCellData().getStringValue().trim().replaceAll("[\r\n]", "");
    }

    /**
     * 这里是写的时候会调用 不用管
     *
     * @return
     */
    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
        return new WriteCellData<>(context.getValue());
    }

}

  • quato2Below,quato3Below … 用来维护层级关系

2.2 定义excel 导入listener:


import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.cric.zhongjian.common.core.utils.StringUtils;
import com.cric.zhongjian.system.dto.ProductAdaptExcelDictQuatoDto;

import java.util.ArrayList;
import java.util.List;

public class ProductAdaptDictQuatoExcelListener extends AnalysisEventListener<ProductAdaptExcelDictQuatoDto> {
    private List<ProductAdaptExcelDictQuatoDto> fileDatas = new ArrayList<>(1 << 5);

    @Override
    public void invoke(ProductAdaptExcelDictQuatoDto data, AnalysisContext context) {
       // 层级关系维护
        if (StringUtils.hasText(data.getQuato2())){
            data.setQuato2Below(String.format("%s_child",data.getQuato1()));
        }
        if (StringUtils.hasText(data.getQuato3())){
            data.setQuato3Below(String.format("%s_%s_child",data.getQuato1(),data.getQuato2()));
        }
        if (StringUtils.hasText(data.getQuato4())){
            data.setQuato4Below(String.format("%s_%s_%s_child",data.getQuato1(),data.getQuato2(),data.getQuato3()));
        }
        if (StringUtils.hasText(data.getQuato5())){
            data.setQuato5Below(String.format("%s_%s_%s_%s_child",data.getQuato1(),data.getQuato2(),data.getQuato3(),data.getQuato4()));
        }
//        if (StringUtils.hasText(data.getQuato6())){
//            data.setQuato6Below(String.format("%s_%s_%s_%s__%s_child",data.getQuato1(),data.getQuato2(),data.getQuato3(),data.getQuato4(),data.getQuato5()));
//        }
        fileDatas.add(data);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 解析完成后的操作
    }

    public List<ProductAdaptExcelDictQuatoDto> getFileDatas() {
        return fileDatas;
    }
}

2.3 数据导入处理:

2.3.1 controller:
/**
 * 初始化指标字典
 *
 * @param file
 * @return
 */
@ResponseBody
@PostMapping("dictQuato")
public AjaxResult initDictQuato(@RequestParam("file") MultipartFile file,
                                     @RequestParam("tentCode") @Valid @NotEmpty String tentCode,
                                @RequestParam("lastQuatoLevel") @Valid @NotEmpty String  lastQuatoLevel) {

    InitProductAdaptDataReqDto reqDto = new InitProductAdaptDataReqDto();
    reqDto.setFile(file).setTentCode(tentCode).setLastQuatoLevel(lastQuatoLevel);
    return AjaxResult.success(initDataService.initDictQuato(reqDto));
}
2.3.1 impl:
@Override
public Map initDictQuato(InitProductAdaptDataReqDto reqDto) {
   MultipartFile file = reqDto.getFile();
   Map<String, Object> mapData = new HashMap<>(1 << 2);
   mapData.put("success", true);
   mapData.put("msg", "");
   if (ilegalFile(file)) {
       mapData.put("success", false);
       mapData.put("msg", "文件非法");
       return mapData;
   }

   // read
   List<ProductAdaptExcelDictQuatoDto> fileData = null;
   try {
       fileData = getDictQuatoFileData(file);
   } catch (Exception e) {
       mapData.put("success", false);
       mapData.put("msg", "文件数据读取异常:" + ExceptionFormatUtil.buildErrorMessage(e));
       return mapData;
   }
   List<Map<String, Object>> listMap = new ArrayList<>(1 << 8);
   fileData.stream().forEach(e -> {
       listMap.add(JSONObject.parseObject(JSONObject.toJSONString(e), Map.class));
   });

   // deal 对读取到的数据进行处理,根据自身业务处理
   Map<String, List<ProductAdaptExcelDictQuatoDto>> mapLeve1 = fileData.stream().collect(Collectors.groupingBy(ProductAdaptExcelDictQuatoDto::getQuato1, LinkedHashMap::new, Collectors.toList()));
   // 获取一级 指标
   List<ProductAdaptExcelDictQuatoDto> leve1 = new ArrayList<>(1 << 8);
   mapLeve1.entrySet().stream().forEach(e -> {
       e.getValue().sort(Comparator.comparingInt(ProductAdaptExcelDictQuatoDto::getRowNum));
       leve1.add(e.getValue().get(0));
   });
   // 按照一级 指标读取的顺序排序,保证和excel 指标顺序一直
   leve1.sort(Comparator.comparingInt(ProductAdaptExcelDictQuatoDto::getRowNum));
   // 指标排序
   AtomicInteger sort = new AtomicInteger(5);
   // 指标id
   AtomicInteger idAtomic = new AtomicInteger(1);
   List<QuatoDto> currentRow = leve1.stream().map(e ->
           new QuatoDto(e.getRowNum(), idAtomic.getAndIncrement(), null, e.getQuato1(), new ArrayList<>(1 << 6), 1, e.getQuato1(), sort.getAndAdd(5))).collect(Collectors.toList());

	// 递归数据处理
   dealDictQuatoFileData(currentRow, listMap, fileData, reqDto, idAtomic);
	// 处理完成则 currentRow 保存了所有层级数据,根据自身业务对currentRow 数据的数据库导入
	// 根据自身业务自行处理
	// 处理思路:
	// 1 递归遍历currentRow 
	// 2 插入和更新的判断:数据通过 QuatoDto 下的quatoNameAppend(层级指标汇总值) 和 原有数据对比如果存在则更新,否则插入
	// 3 当有数据需要插入后,在对子级数据 父级id 进行更新
	
   return mapData;
}

// 递归遍历--父子级指标处理
private void dealDictQuatoFileData(List<QuatoDto> currentData, List<Map<String, Object>> listMap, List<ProductAdaptExcelDictQuatoDto> allData,
                             InitProductAdaptDataReqDto reqDto, AtomicInteger idAtomic) {

   if (CollectionUtils.isEmpty(currentData)) {
       return;
   }
   for (QuatoDto e : currentData) {
       // 当前指标
       Map<String, Object> mapData = listMap.get(e.getRowNum() - 1);
		// 当前指标--下级指标属性
       String childQuatoBelow = getChildQuatoBelow(mapData, e.getCurrentQuatoIndex());

       Integer finalCurrentQuatoIndex = e.getCurrentQuatoIndex() + 1;
       // 获取当前指标的--下级指标
       List<QuatoDto> nextChildRow = listMap.stream().filter(f -> {
           if (null == f.get(String.format("quato%dBelow", finalCurrentQuatoIndex))) {
               return false;
           }
           String q1 = f.get(String.format("quato%dBelow", finalCurrentQuatoIndex)).toString();
           return q1.equals(childQuatoBelow);

       }).map(ff -> {
           QuatoDto oneDto = new QuatoDto((Integer) ff.get("rowNum"), null, null, ff.get(String.format("quato%d", finalCurrentQuatoIndex)) + ""
                   , new ArrayList<>(1 << 6), e.getCurrentQuatoIndex() + 1, getQuatoNameAppend(ff, finalCurrentQuatoIndex), 0);
           return oneDto;
       }).collect(Collectors.toList());
       if (CollectionUtils.isEmpty(nextChildRow)) {
           continue;
       }


       // 下级指标如果有重复,需要去重
       Map<String, List<QuatoDto>> map1 = nextChildRow.stream().collect(Collectors.groupingBy(QuatoDto::getQuatoName, LinkedHashMap::new, Collectors.toList()));
       List<QuatoDto> nextChild = new ArrayList<>(map1.keySet().size());
       AtomicInteger sort = new AtomicInteger(5);
       map1.entrySet().stream().forEach(gg -> {
           if (CollectionUtils.isEmpty(gg.getValue())) {
               return;
           }
           gg.getValue().get(0).setSort(sort.getAndAdd(5));
           nextChild.add(gg.getValue().get(0));

       });
       // 子级指标id 及父级id 设置
       nextChild.stream().forEach(gg -> {
           gg.setId(idAtomic.getAndIncrement()).setParentId(e.getId());
       });
       // 子级指标设置
       e.setChild(nextChild);
       // 递归处理
       dealDictQuatoFileData(nextChild, listMap, allData, reqDto, idAtomic);
   }

}
private String getQuatoNameAppend(Map<String, Object> ff, Integer finalCurrentQuatoIndex) {
    StringBuilder quatoName = new StringBuilder();
    for (Integer i = 1; i < finalCurrentQuatoIndex + 1; i++) {
        quatoName.append(ff.get(String.format("quato%d", i)));
        if (i < finalCurrentQuatoIndex) {
            quatoName.append("_");
        }
    }
    return quatoName.toString();
}

@Data
@AllArgsConstructor
static class QuatoDto {
	// 指标对应excel 的行号
   private Integer rowNum;
   //  指标的id
   private Integer id;
   // 指标的父级id
   private Integer parentId;
   // 指标名称
   private String quatoName;
   // 子级指标
   private List<QuatoDto> child;
   // 当前指标的层级  
   private Integer currentQuatoIndex;
   // 当前指标 父子级指标:父指标_子指标_子指标。。。
   private String quatoNameAppend;
   // 当前指标的排序号
   private Integer sort;
}


private String getChildQuatoBelow(Map<String, Object> mapData, Integer currentQuatoIndex) {
    if (1 == currentQuatoIndex) {
        return String.format("%s_%s", mapData.get("quato" + currentQuatoIndex), "child");
    }
    if (2 == currentQuatoIndex) {
        return String.format("%s_%s_%s", mapData.get("quato1"), mapData.get("quato2"), "child");
    }
    if (3 == currentQuatoIndex) {
        return String.format("%s_%s_%s_%s", mapData.get("quato1"), mapData.get("quato2"), mapData.get("quato3"), "child");
    }
    if (4 == currentQuatoIndex) {
        return String.format("%s_%s_%s_%s_%s", mapData.get("quato1"), mapData.get("quato2"), mapData.get("quato3")
                , mapData.get("quato4"), "child");
    }
    if (5 == currentQuatoIndex) {
        return String.format("%s_%s_%s_%s_%s_%s", mapData.get("quato1"), mapData.get("quato2"), mapData.get("quato3")
                , mapData.get("quato4"), mapData.get("quato5"), "child");
    }
    return "";
}


private List<ProductAdaptExcelDictQuatoDto> getDictQuatoFileData(MultipartFile file) throws IOException {
   // 创建 Excel 读取监听器
   ProductAdaptDictQuatoExcelListener listener = new ProductAdaptDictQuatoExcelListener();
   // 读取 Excel 文件
   EasyExcel.read(file.getInputStream(), ProductAdaptExcelDictQuatoDto.class, listener).extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();
   return listener.getFileDatas();
}

总结

本文通过EasyExcel将有层级关系的Excel 数据进行导入;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值