对于无限级分类的问题,这里我们使用一种改进前序遍历树的方式来实现。主要解决数据读取上的效率问题。

我们常见的无限分类的做法是通过parent id去寻找分类的归属关系,或者为了查询方便添加path字段以及depth,但是在数据量较大时,想要构建出整棵树的话,在效率上就不大ok了。

所以我们使用一种改进前序遍历树的方式来实现。主要解决数据读取上的效率问题。大概如图所示,数据库中存储的话也是存储node以及node的left,right值,以此来表示node之间的从属关系。

结构如图

表结构

查询:

我们可以通过比较当前节点的右节点和上个节点的右节点来判断是否为父子关系,大致如下:

 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
var pong types.PongResponse
var topRecord orm.Category
var right []int
var nodes []orm.Category

if record := l.svcCtx.Orm.Table("category").Where("title", "顶级栏目").Find(&topRecord).RowsAffected; record == 0 {
    return nil, errors.New("该分类不存在")
}

if err := l.
    svcCtx.
    Orm.
    Table("category").
    Where("lft >= ? and lft <= ?", topRecord.Lft, topRecord.Rgt).
    Order("lft").
    Find(&nodes).
    Error;
    err != nil {
    return nil, err
}

for _, node := range nodes {
    if len(right) > 0 {
        for right[len(right)-1] < node.Lft {
            right = right[:len(right)-1]
        }
    }

    title := node.Title

    if len(right) > 0 {
        title = "|-" + title
    }

    pong.Data = append(pong.Data, map[string]interface{}{
        "id":    node.Id,
        "type":  node.Type,
        "title": strings.Repeat("|-", len(right)) + node.Title,
        "name":  node.Title,
    })

    right = append(right, node.Rgt)
}
return &pong, nil
    
//这里只是通过“|-”来代表从属关系,当然实际应用中可以替换为parent的整个对象

结果如下

 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
{
    "data": [
        {
            "id": 1,
            "name": "顶级栏目",
            "title": "顶级栏目",
            "type": 1
        },
        {
            "id": 8,
            "name": "留言板",
            "title": "|-留言板",
            "type": 1
        },
        {
            "id": 7,
            "name": "人才招聘",
            "title": "|-人才招聘",
            "type": 1
        },
        {
            "id": 6,
            "name": "资料下载",
            "title": "|-资料下载",
            "type": 3
        },
        {
            "id": 5,
            "name": "荣誉资质",
            "title": "|-荣誉资质",
            "type": 1
        },
        {
            "id": 4,
            "name": "公司产品",
            "title": "|-公司产品",
            "type": 2
        },
        {
            "id": 3,
            "name": "新闻",
            "title": "|-新闻",
            "type": 1
        },
        {
            "id": 2,
            "name": "公司简介",
            "title": "|-公司简介",
            "type": 1
        },
        {
            "id": 9,
            "name": "总裁",
            "title": "|-|-总裁",
            "type": 1
        }
    ]
}

插入:

找到其父节点,之后把left和right大于父节点left的节点的左右值加上2,之后再插入本节点,left、right值分别为父节点left+1和+2,可以用一个存储过程来操作:

1
2
3
4
5
6
7
8
9
CREATE PROCEDURE `category_insert_by_parent`(IN pid INT,IN title VARCHAR(20), IN type INT, IN l_order INT, IN pubtime INT) 
BEGIN 
DECLARE myLeft INT; 
SELECT lft into myLeft FROM category WHERE id= pid; 
UPDATE qy_category SET rgt = rgt + 2 WHERE rgt > myLeft; 
UPDATE qy_category SET lft = lft + 2 WHERE lft > myLeft; 
INSERT INTO qy_category(type, title, lft, rgt, lorder, create_time) VALUES(type ,title, myLeft + 1, myLeft + 2, l_order, pubtime); 
commit; 
END

删除:

  1. 得到要删除节点的左右值,并得到他们的差再加一,@mywidth = @rgt - @lft + 1;
  2. 删除左右值在本节点之间的节点
  3. 修改条件为大于本节点右值的所有节点,操作为把他们的左右值都减去@mywidth

####存储过程如下:

1
2
3
4
5
6
7
8
CREATE PROCEDURE `category_delete_by_key`(IN id INT) 
BEGIN 
SELECT @myLeft := lft, @myRight := rgt, @myWidth := rgt - lft + 1 
FROM category 
WHERE id = id; 
DELETE FROM category WHERE lft BETWEEN @myLeft AND @myRight; 
UPDATE nested_category SET rgt = rgt - @myWidth WHERE rgt > @myRight; 
UPDATE nested_category SET lft = lft - @myWidth WHERE lft > @myRight;

雀食!这种方式的增删改都比较繁琐,但是毕竟我们的主要矛盾是查询😁