思路梳理过程
- 以车辆配置信息为例,导入的模版信息如下,我的需求是固定为7个层级,不固定的也没关系
只需导入最后的层级即可,即存在
/广汽本田
、/广汽本田/2018款
、/广汽本田/2018款/车系01
时,只需要导入/广汽本田/2018款/车系01
层级即可
- 把导入的层级数据,根据列进行拆分,把拆分后的数据放入Set中保存,组合成所有层级
把
/广汽本田/2018款/车系01
拆分成/广汽本田
、/广汽本田/2018款
、/广汽本田/2018款/车系01
,分别存入Set中
- 根据Set集合,批量保存数据到数据库中(暂未设置父节点)
- 如序号1的图片所示,层级固定为7级,且顺序严格从左到右;从品牌开始,按顺序查找下一级的数据,按照层级查找子节点,批量更新(设置父节点数据)
代码实现
数据层工具是MyBatis Plus
相关实体
@Data
public class VehicleDictListImportDto {
@ExcelProperty(value = "品牌", order = 0)
private String brand;
@ExcelProperty(value = "年款", order = 1)
private String year;
@ExcelProperty(value = "车系", order = 2)
private String series;
@ExcelProperty(value = "车型", order = 3)
private String configuration;
@ExcelProperty(value = "排量", order = 4)
private String displacement;
@ExcelProperty(value = "外观颜色", order = 5)
private String appearanceColor;
@ExcelProperty(value = "内饰颜色", order = 6)
private String interiorColor;
}
public enum NodeTypeEnum {
BRAND("BRAND", "品牌"),
YEAR("YEAR", "年款"),
SERIES("SERIES", "车系"),
CONFIGURATION("CONFIGURATION", "车型"),
DISPLACEMENT("DISPLACEMENT", "排量"),
APPEARANCE_COLOR("APPEARANCE_COLOR", "外观颜色"),
INTERIOR_COLOR("INTERIOR_COLOR", "内饰颜色");
}
/**
* 树形节点数据
*/
@Data
@TableName(value = "tree_node")
public class TreeNodeEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_UUID, value = "id")
private String id;
/**
* 业务类型
*/
@TableField
private String attrCode;
/**
* 节点名称
*/
@TableField
private String name;
/**
* 节点编码
*/
@TableField
private String code;
/**
* 节点类型
*/
@TableField
private String type;
/**
* 节点排序
*/
@TableField
private Integer orderNum;
/**
* 父节点id
*/
@TableField
private String pId;
@TableField("is_deleted")
private Boolean deleted = false;
/**
* 节点名称层级
*/
@TableField
private String level;
}
Controller
@RequestExcel使用的是EasyExcel的starter,用于把导入的数据转成实体
@RequestMapping(value = "/importExcel", method = {RequestMethod.POST})
public Result<Integer> importExcel(@RequestExcel(ignoreEmptyRow = true) List<VehicleDictListImportDto> importDtoList) {
return Result.ok(treeNodeService.importExcel(importDtoList));
}
Service
@Slf4j
@Service
public class TreeNodeService extends ServiceImpl<TreeNodeMapper, TreeNodeEntity> {
@Resource
private TreeNodeMapper treeNodeMapper;
@Resource
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
public static final String FORWARD_SLASH = "/";
@Transactional(rollbackFor = Exception.class)
public Integer importExcel(List<VehicleDictListImportDto> importDtoList) {
if (importDtoList.size() == 0) {
return 0;
}
// 所有层级
Set<String> levelSet = new HashSet<>();
for (VehicleDictListImportDto importDto : importDtoList) {
String brand = FORWARD_SLASH + importDto.getBrand();
String year = FORWARD_SLASH + importDto.getYear();
String series = FORWARD_SLASH + importDto.getSeries();
String configuration = FORWARD_SLASH + importDto.getConfiguration();
String displacement = FORWARD_SLASH + importDto.getDisplacement();
String appearanceColor = FORWARD_SLASH + importDto.getAppearanceColor();
String interiorColor = FORWARD_SLASH + importDto.getInteriorColor();
levelSet.add(brand);
levelSet.add(brand + year);
levelSet.add(brand + year + series);
levelSet.add(brand + year + series + configuration);
levelSet.add(brand + year + series + configuration + displacement);
levelSet.add(brand + year + series + configuration + displacement + appearanceColor);
levelSet.add(brand + year + series + configuration + displacement + appearanceColor + interiorColor);
}
// 获取当前登陆者
String userId = UserUtils.getUserId();
// 获取执行器
ThreadPoolExecutor threadPoolExecutor = threadPoolTaskExecutor.getThreadPoolExecutor();
// 总level
List<String> levelList = new ArrayList<>(levelSet);
List<TreeNodeEntity> createList = new ArrayList<>();
for (String level : levelList) {
String name = StringUtils.substringAfterLast(level, FORWARD_SLASH);
TreeNodeEntity treeNodeEntity = new TreeNodeEntity();
treeNodeEntity.setAttrCode("NEW_VEHICLE");
treeNodeEntity.setName(name);
treeNodeEntity.setCode(name);
treeNodeEntity.setType(this.getTypeByLevel(level));
treeNodeEntity.setOrderNum(1000);
treeNodeEntity.setLevel(level);
treeNodeEntity.setCreateUser(userId);
treeNodeEntity.setCreateTime(new Date());
treeNodeEntity.setUpdateUser(userId);
treeNodeEntity.setUpdateTime(new Date());
createList.add(treeNodeEntity);
}
int number = 500;
int createListSize = createList.size();
int createThreadSize = (int) Math.ceil(1.0 * createListSize / number);
// 异步批量新增数据
List<CompletableFuture<Void>> createFutureList = new ArrayList<>();
for (int i = 0; i < createThreadSize; i++) {
List<TreeNodeEntity> createSubList = createList.subList(i * number, Math.min(i * number + number, createListSize));
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
log.info("子线程-批量新增-开始:{}", Thread.currentThread().getName());
super.saveBatch(createSubList);
log.info("子线程-批量新增-结束:{}", Thread.currentThread().getName());
}, threadPoolExecutor);
createFutureList.add(future);
}
CompletableFuture<Void> createAllOf = CompletableFuture.allOf(createFutureList.toArray(new CompletableFuture[0]));
createAllOf.join();
log.info("主线程-新增操作已结束");
// 节点类型集合
Map<String, List<TreeNodeEntity>> typeMap = createList.stream()
.collect(Collectors.groupingBy(TreeNodeEntity::getType));
List<TreeNodeEntity> updateList = new ArrayList<>();
NodeTypeEnum[] nodeTypeEnumArray = NodeTypeEnum.values();
for (int i = 0; i < nodeTypeEnumArray.length - 1; i++) {
NodeTypeEnum nodeTypeEnum = nodeTypeEnumArray[i];
List<TreeNodeEntity> pidEntityList = typeMap.get(nodeTypeEnum.getValue());
for (TreeNodeEntity pidEntity : pidEntityList) {
String id = pidEntity.getId();
String level = pidEntity.getLevel();
// 防止找到仅左边相同的数据
List<TreeNodeEntity> entityList = typeMap.get(nodeTypeEnumArray[i + 1].getValue()).stream()
.filter(x -> StringUtils.contains(x.getLevel(), level + FORWARD_SLASH))
.collect(Collectors.toList());
for (TreeNodeEntity entity : entityList) {
entity.setPId(id);
updateList.add(entity);
}
}
}
// 异步批量修改数据
int updateListSize = updateList.size();
int updateThreadSize = (int) Math.ceil(1.0 * updateListSize / number);
List<CompletableFuture<Void>> updateFutureList = new ArrayList<>();
for (int i = 0; i < updateThreadSize; i++) {
List<TreeNodeEntity> updateSubList = updateList.subList(i * number, Math.min(i * number + number, updateListSize));
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
log.info("子线程-批量更新-开始:{}", Thread.currentThread().getName());
super.updateBatchById(updateSubList);
log.info("子线程-批量更新-结束:{}", Thread.currentThread().getName());
}, threadPoolExecutor);
updateFutureList.add(future);
}
CompletableFuture<Void> updateAllOf = CompletableFuture.allOf(updateFutureList.toArray(new CompletableFuture[0]));
updateAllOf.join();
log.info("主线程-更新操作已结束");
return createListSize;
}
/**
* 根据层级获取节点类型
*
* @param level 层级
* @return 节点类型
*/
private String getTypeByLevel(String level) {
String type;
int times = StringUtils.countMatches(level, FORWARD_SLASH);
switch (times) {
case 1:
type = NodeTypeEnum.BRAND.getValue();
break;
case 2:
type = NodeTypeEnum.YEAR.getValue();
break;
case 3:
type = NodeTypeEnum.SERIES.getValue();
break;
case 4:
type = NodeTypeEnum.CONFIGURATION.getValue();
break;
case 5:
type = NodeTypeEnum.DISPLACEMENT.getValue();
break;
case 6:
type = NodeTypeEnum.APPEARANCE_COLOR.getValue();
break;
case 7:
type = NodeTypeEnum.INTERIOR_COLOR.getValue();
break;
default:
throw new BizException("传入的层级不正确,错误的数据为:{}", level);
}
return type;
}
}