回顾
项目已开源:基于 Golang 的 容器管理系统
- 「容器管理系统」1. 开篇:框架选型和环境搭建
- 「容器管理系统」开发篇:1. 初始化配置和日志监控
- 「容器管理系统」开发篇:2. 封装gin统一返回JSON
- 「容器管理系统」开发篇:3. JWT(JSON Web Token)的应用
- 「容器管理系统」开发篇:4. Gin 如何优雅的使用 struct 的 tag 标签
- 「容器管理系统」开发篇:5. 如何实现 RBAC 权限管理(一)
上节讲述了:
- 什么是
RBAC
? RBAC
的组成RBAC
之间的关系RBAC
的设计模型- 定义
RBAC
数据表
本节将继续接着上节内容,讲述权限二叉树的实现。
为什么要用二叉树?
二叉树(Binary tree
)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分。
什么是二叉树?
二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个节点。
二叉树的基本形态
二叉树是递归定义的,其节点有左右子树之分,逻辑上二叉树有五种基本形态:
- 空二叉树------如下图(a)
- 只有一个根节点的二叉树------如下图(b)
- 只有左子树------如下图(c)
- 只有右子树------如下图(d)
- 完全二叉树------如下图(e)
特殊类型
- 满二叉树:如果一棵二叉树只有度为0的节点和度为2的节点,并且度为0的节点在同一层上,则这棵二叉树为满二叉树
- 完全二叉树:深度为k,有n个节点的二叉树当且仅当其每一个节点都与深度为k的满二叉树中编号从1到n的节点一一对应时,称为完全二叉树
- 完全二叉树的特点是叶子节点只可能出现在层序最大的两层上,并且某个节点的左分支下子孙的最大层序与右分支下子孙的最大层序相等或大1
二叉树的性质
- 性质1: 二叉树的第i层上至多有2i-1(i≥1)个节点
- 性质2: 深度为h的二叉树中至多含有2h-1个节点
- 性质3: 若在任意一棵二叉树中,有n0个叶子节点,有n2个度为2的节点,则必有n0=n2+1
- 性质4: 具有n个节点的满二叉树深为log2n+1
- 性质5: 若对一棵有n个节点的完全二叉树进行顺序编号(1≤i≤n),那么,对于编号为i(i≥1)的节点:
- 当i=1时,该节点为根,它无双亲节点
- 当i>1时,该节点的双亲节点的编号为i/2
- 若2i≤n,则有编号为2i的左节点,否则没有左节点
- 若2i+1≤n,则有编号为2i+1的右节点,否则没有右节点
讲了这么多,光说不练假把式。下面咱们就进入实操。
如何生成二叉树?
这里主要使用
cloud_menu
菜单表来实现,其中最核心的一点就是: 表中的ParentId
字段,有这个字段咱们才可以真正生成权限二叉树,这里就不再贴模型字段了,不懂或者不明白的请看上节知识点.
在 golang
中实现二叉树不需要使用递归来实现。
- 第一步:需要在对应的模型中加入指定属性来接收子节点数据
go
type CloudMenu struct {
...
// 树节点
ChildNode *CloudMenuTree `json:"child_node" gorm:"-"`
}
type CloudMenuTree []*CloudMenu
- 第二步:将从数据库读取的数据赋值到
CloudMenuTree
这个struct
中
这里是因为
CloudMenuTree
定义的是CloudMenu
切片,而CloudMenu
中又有ChildNode
属性指向CloudMenuTree
,因此要使用CloudMenuTree
作为接收的struct
为后续生成二叉树做基础数据的准备!
go
var menuResp models.CloudMenuTree
err := db.D().Select(selectFields).Find(&menuResp).Error
if err != nil {
response.Error(c, constant.ErrorDB, err, constant.ErrorMsg[constant.ErrorDB])
return
}
- 第三步:将基础数据处理成二叉树结构数据
go
// TreeNode 格式化树节点
func (c CloudMenuTree) TreeNode() CloudMenuTree {
if len(c) <= 0 {
return c
}
// 先重组数据:以数据的ID作为外层的key编号,以便下面进行子树的数据组合
TreeMenuData := make(map[uint32]*CloudMenu)
for _, item := range c {
TreeMenuData[item.MenuId] = item
}
var TreeNode CloudMenuTree
for _, val := range TreeMenuData {
if val.ParentId == 0 {
TreeNode = append(TreeNode, val)
continue
}
if p_item, ok := TreeMenuData[val.ParentId]; ok {
if p_item.ChildNode == nil {
p_item.ChildNode = &CloudMenuTree{val}
continue
}
*p_item.ChildNode = append(*p_item.ChildNode, val)
}
}
return TreeNode
}
代码解读:
- 先对基础数据重组:以数据的
ID
作为外层的key
编号的map
数据- 重新初始化一个
CloudMenuTree
避免原CloudMenuTree
的数据会复用- 遍历重组后的基础数据
TreeMenuData
- 判断是否为第一层节点,如果是第一层节点,直接追加到初始化的
CloudMenuTree
中- 如果是子节点,则判断当前的父节点是否存在,如果存在,则需要判断是否已经追加到
ChildNode
子节点数据中,如果父节点的子节点数据为空,则追加到父节点的子节点数据中并跳过当前记录,如果存在,则通过指针改变当前父节点中的子节点数据中的当前记录
到这里就实现了对基础数据生成对应的二叉树就完成了。
结束语
- 为什么要用二叉树?
- 什么是二叉树?
- 二叉树的基本形态、特殊类型、性质
- 如何生成二叉树?