陈昶聿
4 天以前 6d52b8631cc608d5c4be5c738939c3a466d411fd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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;
    }
}