package com.ruoyi.common.utils;
|
|
import com.ruoyi.common.core.domain.entity.SysDept;
|
|
import java.util.*;
|
import java.util.function.BiConsumer;
|
import java.util.function.Function;
|
import java.util.stream.Collectors;
|
|
/**
|
* 树结构工具类:根据 parentId 构造树、一次性获取所有叶子节点。
|
* 核心方法为泛型实现,id / parentId / children 通过函数注入,可复用于任何节点类型。
|
*/
|
public class TreeUtils {
|
|
private TreeUtils() {
|
}
|
|
/* ================== 通用方法 ================== */
|
|
/**
|
* 根据 parentId 构造树,O(n) 一次遍历。
|
* 根节点为「父Id不在集合中」的节点;若不存在根节点(如全是自引用的环)则原样返回。
|
*
|
* @param nodes 节点列表
|
* @param idGetter 取节点id
|
* @param parentIdGetter 取父节点id
|
* @param childrenSetter 设置子节点列表
|
*/
|
public static <T, K> List<T> buildTree(
|
List<T> nodes,
|
Function<T, K> idGetter,
|
Function<T, K> parentIdGetter,
|
BiConsumer<T, List<T>> childrenSetter) {
|
if (nodes == null || nodes.isEmpty()) {
|
return new ArrayList<>();
|
}
|
// 按父Id分组,一次遍历拿到每个节点的子列表
|
Map<K, List<T>> byParent = nodes.stream().collect(Collectors.groupingBy(parentIdGetter));
|
for (T node : nodes) {
|
childrenSetter.accept(node, byParent.getOrDefault(idGetter.apply(node), new ArrayList<>()));
|
}
|
Set<K> ids = nodes.stream().map(idGetter).filter(Objects::nonNull).collect(Collectors.toSet());
|
List<T> roots = nodes.stream()
|
.filter(n -> !ids.contains(parentIdGetter.apply(n)))
|
.collect(Collectors.toList());
|
return roots.isEmpty() ? nodes : roots;
|
}
|
|
/**
|
* 一次性获取所有叶子节点:即「id 不出现在任何节点的 parentId 中」的节点,O(n) 一次遍历。
|
*
|
* @param nodes 节点列表
|
* @param idGetter 取节点id
|
* @param parentIdGetter 取父节点id
|
*/
|
public static <T, K> List<T> getLeafNodes(
|
List<T> nodes,
|
Function<T, K> idGetter,
|
Function<T, K> parentIdGetter) {
|
if (nodes == null || nodes.isEmpty()) {
|
return new ArrayList<>();
|
}
|
Set<K> parentIds = nodes.stream()
|
.map(parentIdGetter)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
return nodes.stream()
|
.filter(n -> !parentIds.contains(idGetter.apply(n)))
|
.collect(Collectors.toList());
|
}
|
|
/* ================== SysDept 便捷方法 ================== */
|
|
/**
|
* 根据 SysDept.parentId 构造部门树
|
*/
|
public static List<SysDept> buildDeptTree(List<SysDept> depts) {
|
// 按 deptId 去重,防止 sys_user_dept 一对多关联产生重复的父节点,导致构建部门树时出现问题
|
depts = distinctByDeptId(depts);
|
return buildTree(depts, SysDept::getDeptId, SysDept::getParentId, SysDept::setChildren);
|
}
|
|
/**
|
* 从扁平部门列表一次性获取所有叶子部门:即「deptId 不出现在任何节点的 parentId 中」的节点。
|
* 入参应为扁平列表(不是 buildDeptTree 的返回值),无需先建树。
|
* 会先按 deptId 去重,避免 join 产生的重复部门变成重复叶子。
|
*/
|
public static List<SysDept> getLeafDepts(List<SysDept> depts) {
|
return getLeafNodes(distinctByDeptId(depts), SysDept::getDeptId, SysDept::getParentId);
|
}
|
|
/**
|
* 按 deptId 对部门列表去重,保留首次出现的节点。
|
* 查询结果通过 dept_code 关联 sys_user_dept,同一部门可能出现多条记录(重复的父节点),
|
* 构建部门树前需去重,避免重复节点导致树结构异常。
|
*
|
* @param list 部门列表,允许为 null
|
* @return 去重后的部门列表;入参为 null 或空时原样返回
|
*/
|
private static List<SysDept> distinctByDeptId(List<SysDept> list) {
|
if (list == null || list.isEmpty()) {
|
return list;
|
}
|
Set<Long> deptIdSet = new HashSet<>();
|
List<SysDept> distinctList = new ArrayList<>(list.size());
|
for (SysDept dept : list) {
|
Long deptId = dept.getDeptId();
|
// deptId 为空时保留,避免误合并不同部门
|
if (deptId == null || deptIdSet.add(deptId)) {
|
distinctList.add(dept);
|
}
|
}
|
return distinctList;
|
}
|
}
|