package usecase import ( "ark-permission/internal/domain" "ark-permission/internal/domain/usecase" "ark-permission/internal/entity" ers "code.30cm.net/wanderland/library-go/errors" "fmt" "sort" ) // NewPermissionTree 創建新的權限樹 func NewPermissionTree() *PermissionTree { // 插入虛擬跟節點,讓其他人都放在這個底下,行為一致,無需處理邊界 root := &usecase.Permission{ ID: 0, // 根節點 ID 通常設為 0 或其他標示符號 Name: "root", // 虛擬根節點名稱 Children: []*usecase.Permission{}, } return &PermissionTree{ root: root, nodes: map[int64]*usecase.Permission{0: root}, // 根節點也加入 nodes 記錄 paths: make(map[int64][]int), names: make(map[string][]int64), ids: make(map[int64]string), } } // PermissionTree 將初始化與方法分開 // 不考慮使用其樹型結構原因是,在我們的的場景下, // 因為深度不超過 100 層,且資料量在 300 到 500 筆之間,至多不超過一萬筆 // (考慮到一萬條權限已很複雜) 讀取操作是主要需求,而寫入與刪除操作相對較少, // 這樣的場景適合空間換時間的策略。 // 優點: // 1. 空間換時間: // • 使用 map 來保存節點和它們的路徑、名稱等,可以在 O(1) 的時間複雜度內查找節點、名稱、路徑等資料,非常適合頻繁讀取的場景。 // • 雖然消耗了更多的空間(儲存多個映射),但這樣的空間開銷在你的資料量規模(300-500 筆)下是可以接受的。 // 2. 高效查找: // • 查找節點(nodes map)、路徑(paths map)和名稱(names map)都是透過直接查找 map 實現的,這會極大提升查找效率,尤其是在讀取大量資料的情況下。 // • map 在 Golang 中的查找操作具有平均 O(1) 的時間複雜度,非常適合大量查找的場景。 // 3. 樹結構較小,寫入與刪除頻率較低: // • 由於你的資料量不大,且寫入和刪除操作較少,這樣的設計不會過度影響性能。即使有少量的寫入或刪除操作,更新 map 的開銷也是可以接受的。 // 為何設計 names map[string][]int64 // 雖然目前sql 層面來說 name 是唯一的 // 這是因為在某些情況下,權限名稱並不是唯一的,可能會有多個權限使用同一個名稱。例如: // • 你可能在多個模組中使用相同的權限名稱,例如 “Read” 或 “Write”,但它們的實際權限 ID 是不同的。 // • 使用 map[string][]int64 結構來儲存這些同名權限,可以快速查找所有對應的權限 ID,並有效管理不同模組的相同名稱權限。 type PermissionTree struct { root *usecase.Permission // 根節點,表示權限樹的最頂層,所有其他權限都從這裡派生 nodes map[int64]*usecase.Permission // 透過 permission ID 快速查找權限節點,允許 O(1) 查找 paths map[int64][]int // 每個權限節點的完整路徑,儲存節點 ID 對應的索引路徑,方便快速查找() names map[string][]int64 // 權限名稱對應權限 ID,支持多個同名權限,允許快速根據名稱查找所有相關 ID ids map[int64]string // 權限 ID 對應權限名稱,允許根據權限 ID 反向查找權限名稱 } // AddPermission 插入新的權限節點 func (tree *PermissionTree) AddPermission(parentID int64, value entity.Permission) error { node := &usecase.Permission{ ID: value.ID, Name: value.Name, HTTPPath: value.HTTPPath, HTTPMethod: value.HTTPMethod, Children: []*usecase.Permission{}, } // 查找父節點,找不到回傳錯誤。 parentNode, err := tree.FindPermissionByID(parentID) if err != nil { return ers.ResourceNotFound(err.Error()) } parentNode.Children = append(parentNode.Children, node) node.Parent = parentNode // 設置路徑 ID if node.Parent.ID >= 0 { node.PathIDs = append(node.Parent.PathIDs, node.Parent.ID) } // 更新樹結構中的數據 tree.nodes[value.ID] = node // 節點加入紀錄 tree.names[value.Name] = append(tree.names[value.Name], value.ID) tree.ids[value.ID] = value.Name // 計算完整路徑 tree.buildNodePath(node, value.ID) return nil } // buildNodePath 構建節點的完整路徑 // paths 是一個 map 結構,儲存每個權限節點的完整路徑 // key 是節點的權限 ID (int64) // value 是從根節點到該節點的索引路徑 ([]int), // 每個 int 值代表該節點在其父節點的 Children 列表中的索引位置。 // // 例如: // 假設有以下的權限樹結構: // root (ID: 1) // ├── child_permission_1 (ID: 2) // │ └── grandchild_permission_1 (ID: 4) // └── child_permission_2 (ID: 3) // // 那麼: // paths[4] = []int{0, 0} // 表示 grandchild_permission_1 的完整路徑: // - 它是根節點的第一個子節點(索引 0) // - 它的父節點(child_permission_1)也是根節點的第一個子節點(索引 0) // // 類似的: // paths[2] = []int{0} // 表示 child_permission_1 是根節點的第一個子節點。 func (tree *PermissionTree) buildNodePath(node *usecase.Permission, insertID int64) { // 找出該node完整的path路徑 var path []int for { if node.Parent == nil { sort.SliceStable(path, func(_, _ int) bool { return true }) tree.paths[insertID] = path break } for i, v := range node.Parent.Children { if node.ID == v.ID { path = append(path, i) node = node.Parent } } } } // FindPermissionByID 根據 ID 查找權限節點 // 如果權限節點存在,返回對應的 Permission 資料 func (tree *PermissionTree) FindPermissionByID(permissionID int64) (*usecase.Permission, error) { // 直接從 nodes map 中查找對應的節點,O(1) 時間複雜度 node, ok := tree.nodes[permissionID] if !ok { return nil, fmt.Errorf("failed to find ID %d", permissionID) } // 返回找到的節點 return node, nil } // GetAllParentPermissionIDs 返回所有父節點權限 ID func (tree *PermissionTree) GetAllParentPermissionIDs(permissions domain.Permissions) ([]int64, error) { exist := make(map[int64]bool) // 用於記錄已處理的權限 ID var ids []int64 for name, status := range permissions { if status != domain.PermissionStatusOpenCode { continue // 只處理開啟狀態的權限 } // 根據名稱查找對應的權限 ID 列表 pIDs, ok := tree.names[name] if !ok { return nil, ers.ResourceNotFound(fmt.Sprintf("permission with name %s not found", name)) } for _, pID := range pIDs { // 檢查是否已經處理過這個權限 ID if exist[pID] { continue } node, err := tree.FindPermissionByID(pID) if err != nil { return nil, err } // 如果該節點有子節點且父節點未開啟,則跳過該節點 if len(node.Children) > 0 && !tree.isParentPermissionOpen(node, permissions) { continue } // 加入該節點的父節點路徑和自身 ID ids = append(ids, node.PathIDs...) ids = append(ids, pID) // 將所有相關的 ID 記錄為已處理 for _, id := range append(node.PathIDs, pID) { exist[id] = true } } } return ids, nil } // GetAllParentPermissionStatuses 返回所有父節點的權限狀態 // 如果一個權限開啟,返回所有相關父節點的狀態 func (tree *PermissionTree) GetAllParentPermissionStatuses(permissions domain.Permissions) (domain.Permissions, error) { // 用來存儲已經處理過的節點,避免重複處理 exist := make(map[int64]bool) // 用來存儲返回的所有權限狀態 resultStatuses := make(domain.Permissions) // 遍歷每個權限 for name, status := range permissions { // 只處理已開啟的權限 if status != domain.PermissionStatusOpenCode { continue } // 根據權限名稱查找對應的權限 ID 列表 pIDs, ok := tree.names[name] if !ok { return nil, ers.ResourceNotFound(fmt.Sprintf("permission with name %s not found", name)) } // 遍歷所有權限 ID for _, pID := range pIDs { // 檢查是否已處理過這個 ID if exist[pID] { continue } node, err := tree.FindPermissionByID(pID) if err != nil { return nil, err } // 處理該節點的父節點路徑和自身 ID allIDs := append(node.PathIDs, pID) for _, id := range allIDs { // 避免重複處理 if exist[id] { continue } if permissionName, exists := tree.ids[id]; exists { // 設置權限狀態為已開啟 resultStatuses[permissionName] = domain.PermissionStatusOpenCode exist[id] = true } } } } return resultStatuses, nil } // isParentPermissionOpen 檢查父節點的權限是否已開啟 func (tree *PermissionTree) isParentPermissionOpen(node *usecase.Permission, permissions domain.Permissions) bool { for _, child := range node.Children { // 檢查子節點是否有開啟的權限 if status, ok := permissions[child.Name]; ok && status == domain.PermissionStatusOpenCode { return true } } return false } // GetRolePermissionTree 根據角色權限找出所有父節點和子節點權限狀態 // 角色權限是傳入的一個列表,該方法會根據每個角色的權限,返回所有相關的權限狀態 func (tree *PermissionTree) GetRolePermissionTree(rolePermissions []entity.RolePermission) (domain.Permissions, error) { // 初始化用來存儲所有權限狀態的 map resultStatuses := make(domain.Permissions) // 初始化用來記錄已處理的權限 ID,避免重複處理 exist := make(map[int64]bool) // 遍歷所有角色的權限 for _, rolePermission := range rolePermissions { // 根據權限 ID 找到對應的節點 node, err := tree.FindPermissionByID(rolePermission.PermissionID) if err != nil { return nil, fmt.Errorf("permission with ID %d not found: %w", rolePermission.PermissionID, err) } // 將該節點及其父節點的權限狀態加入 resultStatuses allIDs := append(node.PathIDs, rolePermission.PermissionID) // 包含所有父節點和當前節點 for _, id := range allIDs { if !exist[id] { // 檢查是否已經處理過 if permissionName, ok := tree.ids[id]; ok { // 設置權限狀態為已開啟(或者根據 rolePermission 狀態設置) resultStatuses[permissionName] = domain.PermissionStatusOpenCode exist[id] = true // 記錄該 ID 已處理過 } } } // 遍歷該節點的所有子節點,並設置權限狀態 for _, child := range node.Children { if !exist[child.ID] { if permissionName, ok := tree.ids[child.ID]; ok { resultStatuses[permissionName] = domain.PermissionStatusOpenCode exist[child.ID] = true } } } } return resultStatuses, nil }