分类树

一般的分类树状结构有两种方式:

  • 一种是adjacency list,也就是是id,parent_id这种形式。
  • 另一种是nested set,即左右值的形式。

左右值形式查询起来比较高效,无需递归等,推荐使用,但是没有pid形式简单直观。

无限极分类树的使用场景:菜单、组织架构等

效率对比:递归效率低,引用方式效率高。

GO递归实现无限极系统菜单

实现无限级菜单

 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
// 递归获取父节点下的菜单,roleMenuIds:当前用户角色被授权的菜单权限id,不传则返回全部菜单
var menu []model_account.Menu // 定义全局变量
func GetMenu(parentId uint64, roleMenuIds []uint64) []*form.MenuTree {
    if menu == nil {
        model_account.Menu{}.Model().Order("`order` asc").Find(&menu) // 一次性取出全部菜单,只需执行一次sql
    }

    var menuTreeList = make([]*form.MenuTree, 0)
    for _, v := range menu {
        if v.ParentId != parentId { // 关键一步:符合条件的才往下走
            continue;
        }
        child := GetMenu(v.Id, roleMenuIds)
        node := &form.MenuTree{
            Id:    v.Id,
            Title: v.Title,
            Slug:  v.Slug,
            Icon:  v.Icon,
            Uri:   v.Uri,
        }
        node.Children = child

        // 处理用户角色可查看的菜单
        if len(roleMenuIds) > 0 {
            var flag bool
            for _, vv := range roleMenuIds {
                if vv == v.Id {
                    flag = true
                }
            }

            // 当前菜单无权限且子菜单为空
            if !flag && len(node.Children) <= 0 {
                continue
            }

            // 处理父级菜单存在而子级菜单不存在的情况
            if flag && node.Uri == "" && len(node.Children) <= 0 {
                continue
            }
        }

        menuTreeList = append(menuTreeList, node)
    }

    return menuTreeList
}

php递归实现无限极分类树

 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
<?php

// 采用引用方式:生成无限极分类树『tip:仅可用于数据量少的场景』

$arr = array(
    array("id" => 1 , "pid" => 0 , 'cat' => '栏目一'),
    array("id" => 2 , "pid" => 0 , 'cat' => '栏目二'),
    array("id" => 3 , "pid" => 1 , 'cat' => '栏目三'),
    array("id" => 4 , "pid" => 2 , 'cat' => '栏目四'),
    array("id" => 5 , "pid" => 1 , 'cat' => '栏目五'),
    array("id" => 6 , "pid" => 5 , 'cat' => '栏目六'),
    array("id" => 7 , "pid" => 5 , 'cat' => '栏目七'),
    array("id" => 8 , "pid" => 6 , 'cat' => '栏目八'),
    array("id" => 9 , "pid" => 1 , 'cat' => '栏目九'),
    array("id" => 10 , "pid" => 0 , 'cat' => '栏目十'),
    array("id" => 11 , "pid" => 10 , 'cat' => '栏目十一'),
    array("id" => 12 , "pid" => 11 , 'cat' => '栏目十二'),
    array("id" => 13 , "pid" => 2 , 'cat' => '栏目十三'),
    array("id" => 14, "pid" => 13 , 'cat' => '栏目十四')
);

function make_tree($arr, $pidInit=0)
{
    $refer = array();
    $tree = array();
    foreach ($arr as $k => $v) {
        $refer[$v['id']] = &$arr[$k];  // 创建主键的数组引用
    }

    foreach ($arr as $k => $v) {
        $pid = $v['pid'];   // 获取当前分类的父级id
        if ($pid == $pidInit) {
            $tree[] = &$arr[$k];   // 获取规定的栏目及子栏目{$pidInit}
        } elseif (isset($refer[$pid])) {
            $refer[$pid]['subcat'][] = &$arr[$k];  // 如果存在父级栏目,则添加进父级栏目的子栏目数组中
        }
    }

    return $tree;
}

$cat = make_tree($arr, 0);
print_r($cat);

另类的无限极分类树设计

 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
# 部门无限极分类案例的建表sql
CREATE TABLE `t_department` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT COMMENT '部门ID',
  `name` varchar(45) NOT NULL DEFAULT '""' COMMENT '部门名称',
  `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门信息表';

CREATE TABLE `t_department_tree` (
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
    `children_id` SMALLINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '子节点ID',
    `parent_id` SMALLINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父节点ID',
    `depth`SMALLINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '深度',
    PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='部门关系树,children_id=parent_id则为初始节点';

INSERT INTO `t_department` (`id`, `name`, `created_time`, `updated_time`)
VALUES
(1, '拓展与加盟中心', '2018-12-19 18:16:10', '2018-12-19 18:17:01'),
(2, '产品中心', '2018-12-19 18:16:10', '2018-12-19 18:17:02'),
(3, '运营中心', '2018-12-19 18:16:10', '2018-12-19 18:17:03'),
(4, '增值与服务中心', '2018-12-19 18:16:10', '2018-12-19 18:17:04'),
(5, '后端支持', '2018-12-19 18:16:10', '2018-12-19 18:17:04'),
(6, '运营管理部', '2019-06-14 12:38:58.000', '2019-06-14 12:38:58.000'),
(7, '客服管理部', '2019-06-14 12:39:11.000', '2019-06-14 12:39:11.000');

INSERT INTO `t_department_tree` (`children_id`,`parent_id`,`depth`)
VALUES
(1,1,0),
(2,2,0),
(3,3,0),
(4,4,0),
(5,5,0),
(6,6,0),
(6,3,1),
(7,3,1),
(7,7,0);

左右值无限极分类

参考文章

  • 优点:查询效率高,排序也很方便
  • 缺点:编辑是个麻烦