From 90c11f60f9ba313acbfd76de66f3b4022bc8b270 Mon Sep 17 00:00:00 2001 From: lishuyan <1206770390@qq.com> Date: Sun, 20 Jul 2025 12:57:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(core)=EF=BC=9A=E2=9C=A8=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20=E6=89=A9=E5=B1=95=20hutool=20TreeUtil=20=E5=B0=81?= =?UTF-8?q?=E8=A3=85=E6=A0=91=E6=9E=84=E5=BB=BA=E7=9A=84=20TreeBuildUtils?= =?UTF-8?q?=20=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C=E5=85=B6=E4=B8=AD?= =?UTF-8?q?=E5=8C=85=E6=8B=AC=E6=89=A9=E5=B1=95=E7=9A=84=EF=BC=88=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=A0=91=E5=BD=A2=E7=BB=93=E6=9E=84=E3=80=81=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E5=A4=9A=E6=A0=B9=E8=8A=82=E7=82=B9=E7=9A=84=E6=A0=91?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=88=E6=94=AF=E6=8C=81=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E9=A1=B6=E7=BA=A7=E8=8A=82=E7=82=B9=EF=BC=89=EF=BC=89=E7=AD=89?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starter/core/util/TreeBuildUtils.java | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 continew-starter-core/src/main/java/top/continew/starter/core/util/TreeBuildUtils.java diff --git a/continew-starter-core/src/main/java/top/continew/starter/core/util/TreeBuildUtils.java b/continew-starter-core/src/main/java/top/continew/starter/core/util/TreeBuildUtils.java new file mode 100644 index 00000000..67a10a46 --- /dev/null +++ b/continew-starter-core/src/main/java/top/continew/starter/core/util/TreeBuildUtils.java @@ -0,0 +1,161 @@ +package top.continew.starter.core.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.tree.Tree; +import cn.hutool.core.lang.tree.TreeNodeConfig; +import cn.hutool.core.lang.tree.TreeUtil; +import cn.hutool.core.lang.tree.parser.NodeParser; +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 扩展 hutool TreeUtil 封装树构建 + * + * @author Lion Li + * @author lishuyan + */ +public class TreeBuildUtils extends TreeUtil { + + private TreeBuildUtils() { + } + + /** + * 构建树形结构 + * + * @param 输入节点的类型 + * @param 节点ID的类型 + * @param list 节点列表,其中包含了要构建树形结构的所有节点 + * @param nodeParser 解析器,用于将输入节点转换为树节点 + * @return 构建好的树形结构列表 + */ + public static List> build(List list, NodeParser nodeParser) { + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + K k = ReflectUtil.invoke(list.get(0), CharSequenceUtil.genGetter("parentId")); + return TreeUtil.build(list, k, TreeNodeConfig.DEFAULT_CONFIG, nodeParser); + } + + /** + * 构建树形结构 + * + * @param 输入节点的类型 + * @param 节点ID的类型 + * @param parentId 顶级节点 + * @param list 节点列表,其中包含了要构建树形结构的所有节点 + * @param nodeParser 解析器,用于将输入节点转换为树节点 + * @return 构建好的树形结构列表 + */ + public static List> build(List list, K parentId, NodeParser nodeParser) { + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return TreeUtil.build(list, parentId, TreeNodeConfig.DEFAULT_CONFIG, nodeParser); + } + + /** + * 构建树形结构 + * + * @param 输入节点的类型 + * @param 节点ID的类型 + * @param list 节点列表,其中包含了要构建树形结构的所有节点 + * @param parentId 顶级节点 + * @param treeNodeConfig 树节点配置 + * @param nodeParser 解析器,用于将输入节点转换为树节点 + * @return 构建好的树形结构列表 + */ + public static List> build(List list, K parentId, TreeNodeConfig treeNodeConfig, NodeParser nodeParser) { + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + return TreeUtil.build(list, parentId, treeNodeConfig, nodeParser); + } + + /** + * 构建多根节点的树结构(支持多个顶级节点) + * + * @param list 原始数据列表 + * @param getId 获取节点 ID 的方法引用,例如:node -> node.getId() + * @param getParentId 获取节点父级 ID 的方法引用,例如:node -> node.getParentId() + * @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点 + * @param 原始数据类型(如实体类、DTO 等) + * @param 节点 ID 类型(如 Long、String) + * @return 构建完成的树形结构(可能包含多个顶级根节点) + */ + public static List> buildMultiRoot(List list, Function getId, Function getParentId, NodeParser parser) { + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + Set rootParentIds = CollUtils.mapToSet(list, getParentId); + rootParentIds.removeAll(CollUtils.mapToSet(list, getId)); + // 构建每一个根 parentId 下的树,并合并成最终结果列表 + return rootParentIds.stream() + .flatMap(rootParentId -> TreeUtil.build(list, rootParentId, parser).stream()) + .collect(Collectors.toList()); + } + + /** + * 构建多根节点的树结构(支持多个顶级节点) + * + * @param 原始数据类型(如实体类、DTO 等) + * @param 节点 ID 类型(如 Long、String) + * @param list 原始数据列表 + * @param getId 获取节点 ID 的方法引用,例如:node -> node.getId() + * @param getParentId 获取节点父级 ID 的方法引用,例如:node -> node.getParentId() + * @param treeNodeConfig 树节点配置 + * @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点 + * @return 构建完成的树形结构(可能包含多个顶级根节点) + */ + public static List> buildMultiRoot(List list, Function getId, Function getParentId, + TreeNodeConfig treeNodeConfig, NodeParser parser) { + if (CollUtil.isEmpty(list)) { + return CollUtil.newArrayList(); + } + Set rootParentIds = CollUtils.mapToSet(list, getParentId); + rootParentIds.removeAll(CollUtils.mapToSet(list, getId)); + // 构建每一个根 parentId 下的树,并合并成最终结果列表 + return rootParentIds.stream() + .flatMap(rootParentId -> TreeUtil.build(list, rootParentId, treeNodeConfig, parser).stream()) + .collect(Collectors.toList()); + } + + /** + * 获取节点列表中所有节点的叶子节点 + * + * @param 节点ID的类型 + * @param nodes 节点列表 + * @return 包含所有叶子节点的列表 + */ + public static List> getLeafNodes(List> nodes) { + if (CollUtil.isEmpty(nodes)) { + return CollUtil.newArrayList(); + } + return nodes.stream() + .flatMap(TreeBuildUtils::extractLeafNodes) + .collect(Collectors.toList()); + } + + /** + * 获取指定节点下的所有叶子节点 + * + * @param 节点ID的类型 + * @param node 要查找叶子节点的根节点 + * @return 包含所有叶子节点的列表 + */ + private static Stream> extractLeafNodes(Tree node) { + if (!node.hasChild()) { + return Stream.of(node); + } else { + // 递归调用,获取所有子节点的叶子节点 + return node.getChildren().stream() + .flatMap(TreeBuildUtils::extractLeafNodes); + } + } + +}