feat:[REQ-3282] 增加通用树工具

This commit is contained in:
liuyang 2024-12-24 13:47:34 +08:00
parent 729c1b0adc
commit bafc1e0bb3
5 changed files with 288 additions and 4 deletions

View File

@ -23,6 +23,10 @@
<version>2.0.0-SNAPSHOT</version> <version>2.0.0-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,35 @@
package cn.axzo.orgmanax.common.model;
import java.util.List;
public interface IBaseTree<T extends IBaseTree<T, O>, O> {
/**
* 节点编码
*
* @return 节点编码
*/
O getNodeCode();
/**
* 父节点编码
*
* @return 父节点编码
*/
O getParentNodeCode();
/**
* 子节点
*
* @return 子节点
*/
List<T> getNodeChildren();
/**
* 设置子节点
*
* @param nodeChildren
*/
void setNodeChildren(List<T> nodeChildren);
}

View File

@ -0,0 +1,246 @@
package cn.axzo.orgmanax.common.util;
import cn.axzo.orgmanax.common.model.IBaseTree;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Pair;
import org.apache.commons.collections4.CollectionUtils;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
public class TreeUtil {
/**
* 合并两个树 极大合并<p/> 这里以新树为基础<br/> 同层级树 <br/> 1).如果新树的当前节点在旧树中存在则直接使用新树的当前节点并保持当前节点的顺序不变<br/>
* 2).如果新树的当前节点在旧树中存在, 1)的基础上去递归比较新树的子节点<br/> 3).如果新树的当前节点在旧树中不存在直接使用新树的当前节点并保持当前节点的顺序不变<br/>
* 4).如果旧树的节点在新树中不存在则直接附加到新树之后,并保持旧树的顺序<br/>
* <p/>
* snabbdomhttps://github.com/snabbdom/snabbdom/blob/master/src/init.ts#L277
*
* @param oldTree
* @param newTree
* @return
*/
public static <T extends IBaseTree<T, O>, O> List<T> largeMergeTree(List<T> oldTree,
List<T> newTree) {
return largeMergeTree(oldTree, newTree, null);
}
/**
* 合并树
*
* @param oldTree
* @param newTree
* @param oldTreeConsumer 对需要附加到新树的节点进行操作
* @param <T>
* @return
*/
public static <T extends IBaseTree<T, O>, O> List<T> largeMergeTree(List<T> oldTree,
List<T> newTree,
Consumer<List<T>> oldTreeConsumer) {
//为空直接返回
if (CollectionUtils.isEmpty(oldTree)) {
return newTree;
}
if (CollectionUtils.isEmpty(newTree)) {
return oldTree;
}
//构造老树code与所在索引的map
Map<O, Integer> oldTreeIndexMap = new HashMap<>(oldTree.size());
for (int i = 0; i < oldTree.size(); i++) {
oldTreeIndexMap.put(oldTree.get(i).getNodeCode(), i);
}
//循环新树找到新树中的当前节点在老树中的索引
for (T t : newTree) {
Integer oldTreeIndex = oldTreeIndexMap.get(t.getNodeCode());
//找到了则直接使用新树的当前节点并保持当前节点的顺序不变
if (oldTreeIndex != null) {
t.setNodeChildren(
largeMergeTree(oldTree.get(oldTreeIndex).getNodeChildren(), t.getNodeChildren(),
oldTreeConsumer));
oldTreeIndexMap.put(t.getNodeCode(), null);
}
}
List<T> tempOldTree = null;
if (oldTreeConsumer != null) {
tempOldTree = new ArrayList<>();
}
List<T> finalResult = tempOldTree;
//将所有老树中未找到的节点附加到新树之后,并保持旧树的顺序
oldTreeIndexMap.values().stream().filter(Objects::nonNull).sorted().forEach(e -> {
T t = oldTree.get(e);
newTree.add(t);
if (oldTreeConsumer != null) {
finalResult.add(t);
}
});
if (oldTreeConsumer != null) {
oldTreeConsumer.accept(tempOldTree);
}
return newTree;
}
public static <T extends IBaseTree<T, O>, O> List<T> buildTree(List<T> treeList) {
if (CollectionUtils.isEmpty(treeList)) {
return treeList;
}
Set<O> codes = treeList.stream().map(IBaseTree::getNodeCode).collect(Collectors.toSet());
List<T> rootList = treeList.stream().filter(e -> !codes.contains(e.getParentNodeCode()))
.collect(Collectors.toList());
List<T> children = treeList.stream().filter(e -> codes.contains(e.getParentNodeCode()))
.collect(Collectors.toList());
for (T t : rootList) {
t.setNodeChildren(buildTree(children, t.getNodeCode()));
}
return rootList;
}
public static <T extends IBaseTree<T, O>, O> List<T> buildTree(List<T> treeList, O rootCode) {
if (CollectionUtils.isEmpty(treeList)) {
return treeList;
}
//root级树
List<T> rootList = treeList.stream()
.filter(e -> Objects.equals(e.getParentNodeCode(), rootCode))
.collect(Collectors.toList());
List<T> childrenList = treeList.stream()
.filter(e -> !Objects.equals(e.getParentNodeCode(), rootCode))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(childrenList)) {
return CollectionUtils.isEmpty(rootList) ? null : rootList;
}
for (T t : rootList) {
t.setNodeChildren(buildTree(childrenList, t.getNodeCode()));
}
return CollectionUtils.isEmpty(rootList) ? null : rootList;
}
// public static <T extends IBaseTree<T, O>, O> List<T> buildFastTree(List<T> treeList, O rootCode) {
// if (CollectionUtils.isEmpty(treeList)) {
// return treeList;
// }
// //root级树
// List<T> rootList = treeList.stream().filter(e -> Objects.equals(e.getParentNodeCode(), rootCode))
// .collect(Collectors.toList());
//
// List<T> notRootList = treeList.stream().filter(e -> !Objects.equals(e.getParentNodeCode(), rootCode))
// .collect(Collectors.toList());
//
// Map<O, List<T>> childrenMap = notRootList.stream()
// .collect(Collectors.groupingBy(IBaseTree::getParentNodeCode));
//
// for (T t : rootList) {
// List<T> children = childrenMap.get(t.getNodeCode());
// if(children==null){
// children=new ArrayList<>();
// }
// t.setNodeChildren(children);
// }
// return rootList;
// }
/**
* 树的扁平化操作
*
* @param trees 树列表
* @param maxLevel 业务场景所允许的最大的树高
*/
public static <T extends IBaseTree<T, O>, O> List<T> flattening(List<T> trees, int maxLevel) {
if (CollUtil.isEmpty(trees)) {
return Collections.emptyList();
}
return trees.stream()
.flatMap(e -> flattening(e, maxLevel).stream())
.collect(Collectors.toList());
}
/**
* 树的扁平化操作
*
* @param tree
* @param maxLevel 业务场景所允许的最大的树高
*/
public static <T extends IBaseTree<T, O>, O> List<T> flattening(T tree, int maxLevel) {
if (Objects.isNull(tree)) {
return Collections.emptyList();
}
List<T> result = new ArrayList<>();
// 根节点放入栈中
LinkedList<Pair<Integer, T>> stack = new LinkedList<>();
stack.push(Pair.of(1, tree));
Pair<Integer, T> pointer;
do {
// 出栈
pointer = stack.pop();
// 校验当前遍历的层级是否过深
Assert.isTrue(pointer.getKey() <= maxLevel, "层数过深,请检查数据的准确性");
// 数据放入结果集
result.add(pointer.getValue());
if (CollUtil.isNotEmpty(pointer.getValue().getNodeChildren())) {
// 子集层级自增并入栈
int recursiveLevel = pointer.getKey() + 1;
pointer.getValue().getNodeChildren().stream()
.map(e -> Pair.of(recursiveLevel, e))
.forEach(stack::push);
}
} while (CollUtil.isNotEmpty(stack));
return result;
}
/**
* 树的剪枝操作
*
* @param root
* @param treeNodeCode 剪枝切入点
*/
public static <T extends IBaseTree<T, O>, O> T pruning(T root, O treeNodeCode) {
if (Objects.isNull(root) || Objects.isNull(treeNodeCode)) {
return root;
}
// 根节点放入栈中
LinkedList<T> stack = new LinkedList<>();
stack.push(root);
T node;
do {
// 出栈
node = stack.pop();
if (Objects.equals(node.getNodeCode(), treeNodeCode)) {
return node;
}
if (CollUtil.isNotEmpty(node.getNodeChildren())) {
stack.addAll(node.getNodeChildren());
}
} while (CollUtil.isNotEmpty(stack));
return null;
}
public static <T extends IBaseTree<T, O>, O> List<T> buildTree(List<T> treeList, Function<T, Boolean> filter) {
Objects.requireNonNull(filter);
if (CollectionUtils.isEmpty(treeList)) {
return treeList;
}
Set<O> codes = treeList.stream().map(IBaseTree::getNodeCode).collect(Collectors.toSet());
List<T> rootList = treeList.stream()
.filter(filter::apply)
.filter(e -> !codes.contains(e.getParentNodeCode()))
.collect(Collectors.toList());
List<T> children = treeList.stream()
.filter(filter::apply)
.filter(e -> codes.contains(e.getParentNodeCode()))
.collect(Collectors.toList());
for (T t : rootList) {
t.setNodeChildren(buildTree(children, t.getNodeCode()));
}
return rootList;
}
}

View File

@ -11,6 +11,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.BooleanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -28,7 +29,7 @@ import java.util.stream.Stream;
*/ */
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class NodeQueryRepositoryImpl implements NodeQueryRepository { public class NodeQueryRepositoryImpl extends ServiceImpl implements NodeQueryRepository {
private final NodeDao nodeDao; private final NodeDao nodeDao;
@ -58,9 +59,6 @@ public class NodeQueryRepositoryImpl implements NodeQueryRepository {
if (CollUtil.isEmpty(records)) { if (CollUtil.isEmpty(records)) {
return resp; return resp;
} }
// assemble parent node
assembleParentNode(req, records);
return resp; return resp;
} }

View File

@ -166,6 +166,7 @@ public class CooperateShipServiceImpl implements CooperateShipService {
resultNodeList.addAll(ancestorShipNodes); resultNodeList.addAll(ancestorShipNodes);
} }
// 是否查询父级节点 // 是否查询父级节点
if (BooleanUtil.isTrue(req.getIncludeAncestors())) { if (BooleanUtil.isTrue(req.getIncludeAncestors())) {
Set<Long> ancestorIds = cooperateShipFoundationService.extractAncestorIds(currentNodeList); Set<Long> ancestorIds = cooperateShipFoundationService.extractAncestorIds(currentNodeList);