部门树怎么构建?

wen python案例 7

本文目录导读:

部门树怎么构建?

  1. 第一步:数据库表设计
  2. 第二步:后端构建树的算法
  3. 第三步:前端如何处理?
  4. 特殊情况与优化技巧
  5. 推荐方案

构建“部门树”(即组织架构树)是后端开发中非常常见的需求,比如在考勤系统、OA系统、权限管理系统中都会用到。

本质上,这是一个将扁平的列表数据,转换为具有层级结构的树形数据的过程。

以下是几种常见的构建方案,从数据库设计到前端处理,分场景讲解。

第一步:数据库表设计

这是基础,通常有两种主流设计方式:

邻接表模式

最常用、最直观,每一条记录只记录自己的直接父级。

CREATE TABLE department (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    parent_id BIGINT DEFAULT NULL,   -- 父级ID,顶级部门为NULL或0
    sort_order INT DEFAULT 0,        -- 排序字段
    -- 其他字段...
    FOREIGN KEY (parent_id) REFERENCES department(id)
);

示例数据: | id | name | parent_id | sort_order | |----|------|-----------|------------| | 1 | 总公司 | NULL | 1 | | 2 | 技术部 | 1 | 1 | | 3 | 产品部 | 1 | 2 | | 4 | 前端组 | 2 | 1 | | 5 | 后端组 | 2 | 2 | | 6 | 设计组 | 3 | 1 |


第二步:后端构建树的算法

假设你已经从数据库查询出了所有部门列表(List<Department>),现在需要将其转换为树结构。

递归构建(简单直观,适合数据量小 / < 几百条)

核心思想:找到根节点,然后递归为每个节点寻找子节点。

Java 伪代码:

// 1. 查询所有部门
List<Department> allDepts = departmentMapper.selectList(null);
// 2. 构建树
public List<DepartmentTree> buildTree(List<Department> allDepts) {
    List<DepartmentTree> trees = new ArrayList<>();
    // 用于快速查找的Map
    Map<Long, DepartmentTree> dtoMap = allDepts.stream()
            .collect(Collectors.toMap(Department::getId, DepartmentTree::new));
    for (Department dept : allDepts) {
        DepartmentTree node = dtoMap.get(dept.getId());
        if (dept.getParentId() == null) {
            // 根节点
            trees.add(node);
        } else {
            // 非根节点,找到父节点并添加
            DepartmentTree parent = dtoMap.get(dept.getParentId());
            if (parent != null) {
                parent.getChildren().add(node);
            }
        }
    }
    // 可选:对每个节点的子节点进行排序
    sortChildren(trees);
    return trees;
}

优点:逻辑简单,只需要一次数据库查询。 缺点:如果部门层级非常深(比如超过100层),递归可能导致栈溢出。

两次遍历 + Map 映射(推荐,最高效)

核心思想:第一次遍历把所有节点放入Map,第二次遍历建立父子关系。

Python 伪代码:

def build_tree(dept_list):
    """
    dept_list: list of dict, 每个dict包含 id, name, parent_id
    return: list of root nodes
    """
    node_map = {}
    roots = []
    # 第一次遍历:创建所有节点,并以id为key存入map
    for dept in dept_list:
        node = {
            'id': dept['id'],
            'name': dept['name'],
            'children': []
        }
        node_map[dept['id']] = node
    # 第二次遍历:建立父子关系
    for dept in dept_list:
        node = node_map[dept['id']]
        parent_id = dept.get('parent_id')
        if parent_id is None or parent_id == 0:
            roots.append(node)
        else:
            parent_node = node_map.get(parent_id)
            if parent_node:
                parent_node['children'].append(node)
    # 排序(可选)
    def sort_tree(nodes):
        for node in nodes:
            if node['children']:
                node['children'].sort(key=lambda x: x.get('sort_order', 0))
                sort_tree(node['children'])
    sort_tree(roots)
    return roots

优点:时间复杂度 O(n),空间换时间,非常快,适合任何深度。


第三步:前端如何处理?

如果你只是需要前端来拼树(比如从后端拿到的数据是平铺的),可以使用JavaScript的reduceMap来实现,思路与后端完全一致:

function buildTree(flatList) {
  const map = {};
  const tree = [];
  // 1. 建立映射
  flatList.forEach(item => {
    map[item.id] = { ...item, children: [] };
  });
  // 2. 构建树
  flatList.forEach(item => {
    const node = map[item.id];
    if (item.parentId === null || item.parentId === 0) {
      tree.push(node);
    } else {
      const parent = map[item.parentId];
      if (parent) {
        parent.children.push(node);
      }
    }
  });
  // 3. 排序(可选)
  const sortChildren = (nodes) => {
    nodes.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
    nodes.forEach(node => {
      if (node.children) sortChildren(node.children);
    });
  };
  sortChildren(tree);
  return tree;
}

特殊情况与优化技巧

  1. 大数据量优化

    • 如果全量数据超过几千条,一次性加载到内存构建可能会慢,建议在数据库层使用递归CTE(PostgreSQL/MySQL 8.0+)。
    • MySQL 8.0+ 递归CTE示例
      WITH RECURSIVE dept_tree AS (
          SELECT id, name, parent_id, 1 AS level
          FROM department
          WHERE parent_id IS NULL
          UNION ALL
          SELECT d.id, d.name, d.parent_id, dt.level + 1
          FROM department d
          INNER JOIN dept_tree dt ON d.parent_id = dt.id
      )
      SELECT * FROM dept_tree;

      这种方式直接在数据库层面拿到带层级的平铺数据,然后后端用Map组装即可。

  2. 树结构校验

    • 防止出现循环依赖:A的父级是B,B的父级是C,C的父级是A,简单办法:构建时记录已访问过的节点ID,如果某节点又是自己的祖先,则报错或断开链路。
  3. 懒加载(按需加载)

    • 如果树非常深(比如10层以上)且节点很多,建议不要一次加载全部,可以只加载顶层,用户点击展开时,再通过接口加载对应子节点。
  4. 路径字段

    • 为了快速查询某个部门下的所有子部门,可以增加一个path字段(如 1/2/4/ 表示总公司->技术部->前端组),这样用LIKE '1/2/%'就能查到所有子孙部门,不用递归。

推荐方案

  • 中小规模(<2000条):使用 两次遍历 + Map 的方式,代码简单,效率高。
  • 大规模:优先使用数据库 递归CTE路径字段 + 后端Map组装。
  • 前端使用:如果数据已经是平铺的,直接用JS的Map法在前端组装,可以减轻后端压力(适用于小树)。

如果你用的是Java,推荐使用Stream API配合Collectors.toMap来做;如果是JavaScript,注意使用Map代替Object来存储节点映射,性能会更好。

抱歉,评论功能暂时关闭!