文章目录
前言
本文介绍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 数据进行导入;