app-cloudep-permission-server/pkg/usecase/permission_tree.go

223 lines
5.9 KiB
Go
Raw Normal View History

package usecase
import (
"sync"
"code.30cm.net/digimon/library-go/errs"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// PermissionTree 用來管理權限節點,包含快速查詢 map 與輔助 map例如名稱對應的 ID 列表)
type PermissionTree struct {
// dummy root 節點
root *PermissionNode
// permission id => node
nodes map[string]*PermissionNode
// permission name => permission id
names map[string][]string
mu sync.RWMutex // 保護樹的並發存取
}
type PermissionNode struct {
Data entity.Permission
Parent *PermissionNode
Children []*PermissionNode
}
const rootName = "root"
// GeneratePermissionTree 根據扁平權限資料建立樹,並掛在 dummy root 下
func GeneratePermissionTree(permissions []entity.Permission) *PermissionTree {
tree := &PermissionTree{
nodes: make(map[string]*PermissionNode),
names: make(map[string][]string),
}
// 1. 建立所有節點
for _, perm := range permissions {
node := &PermissionNode{
Data: perm,
Children: []*PermissionNode{},
}
tree.nodes[perm.ID.Hex()] = node
tree.names[perm.Name] = append(tree.names[perm.Name], perm.ID.Hex())
}
// 2. 建立 dummy root 節點
tree.root = &PermissionNode{
Data: entity.Permission{ID: primitive.NewObjectID(), Name: rootName},
Children: []*PermissionNode{},
}
// 3. 建立父子連結:若找不到父節點或 Parent 為 0則掛在 dummy root 下
for _, node := range tree.nodes {
if node.Data.Parent == "" {
node.Parent = tree.root
tree.root.Children = append(tree.root.Children, node)
} else if parent, ok := tree.nodes[node.Data.Parent]; ok {
node.Parent = parent
parent.Children = append(parent.Children, node)
} else {
// 若父節點不存在,預設掛在 dummy root 下
node.Parent = tree.root
tree.root.Children = append(tree.root.Children, node)
}
}
return tree
}
// getNode 輔助函數:根據 ID 從樹中查找節點
func (tree *PermissionTree) getNode(id string) *PermissionNode {
tree.mu.RLock()
defer tree.mu.RUnlock()
return tree.nodes[id]
}
//nolint:unused
func (tree *PermissionTree) put(node entity.Permission) {
parentNode := tree.getNode(node.Parent)
if parentNode == nil {
parentNode = tree.root
}
thisNode := &PermissionNode{
Data: node,
Parent: parentNode,
Children: make([]*PermissionNode, 0),
}
parentNode.Children = append(parentNode.Children, thisNode)
tree.names[node.Name] = append(tree.names[node.Name], node.ID.Hex())
tree.nodes[node.ID.Hex()] = thisNode
}
// filterOpenNodes 走訪整棵樹,列出有被打開的節點(父節點沒開,則底下的都不會開)
// 如果某個節點為非葉節點,則會檢查其子節點是否有啟用,否則該節點不會被展開。
// [permissionID] entity.Permission
func (tree *PermissionTree) filterOpenNodes() (map[string]entity.Permission, error) {
tree.mu.RLock()
defer tree.mu.RUnlock()
result := make(map[string]entity.Permission)
// dfs 為內部閉包,可存取 result
// 返回值 bool 表示目前節點或其子孫中是否存在有效 open 節點
var dfs func(node *PermissionNode) bool
dfs = func(node *PermissionNode) bool {
// 若本身狀態非 open則整個分支不展開
if node.Data.Status != permission.Open {
return false
}
// 節點本身是 open不論子節點狀態如何先將該節點加入結果
result[node.Data.ID.Hex()] = node.Data
// 遞迴處理子節點
for _, child := range node.Children {
dfs(child)
}
return true
}
// 從 dummy root 的 Children 開始走訪dummy root 本身不納入結果)
for _, child := range tree.root.Children {
dfs(child)
}
return result, nil
}
// getFullParentPermission 根據 role permission 找出完整 path permission status
func (tree *PermissionTree) getFullParentPermission(rolePermissions []*entity.RolePermission) permission.Permissions {
status := permission.Permissions{}
for _, v := range rolePermissions {
node := tree.getNode(v.PermissionID)
if node == nil {
return nil
}
if _, ok := status[node.Data.Name]; ok {
continue
}
status[node.Data.Name] = permission.StatusCode(node.Data.Status.String())
// 往上找node(父)
for node.Parent != nil {
np := node.Parent
if np == nil || np.Data.Name == rootName {
break
}
parent := tree.getNode(np.Data.ID.Hex())
if parent == nil {
continue
}
status[parent.Data.Name] = permission.StatusCode(parent.Data.Status.String())
}
}
return status
}
// getFullParentPermissionIDs 根據 permissions 找出所有完整 permission
// 比如B的父權限是A給B權限就要回傳A與B權限id
func (tree *PermissionTree) getFullParentPermissionIDs(permissions permission.Permissions) ([]string, error) {
exist := make(map[string]bool)
ids := make([]string, 0)
for name, status := range permissions {
if status != permission.OpenPermission {
continue
}
pIDs, ok := tree.names[name]
if !ok {
return nil, errs.ResourceNotFound("failed to get node")
}
for _, pID := range pIDs {
node := tree.getNode(pID)
if node == nil {
return nil, errs.ResourceNotFound("failed to get node")
}
// 如果有子node代表不是最底層權限而是父node就必須檢查此父node底下子node權限是否有開啟
if len(node.Children) > 0 {
var can bool
for _, ch := range node.Children {
if v, ok := permissions[ch.Data.Name]; ok && v == permission.OpenPermission {
can = true
break
}
}
if !can {
continue
}
}
for node.Parent != nil {
np := node.Parent
if np == nil || np.Data.Name == rootName {
break
}
if _, ok := exist[np.Data.ID.Hex()]; !ok {
ids = append(ids, np.Data.ID.Hex())
exist[np.Data.ID.Hex()] = true
}
}
}
}
return ids, nil
}