feat:add tree

This commit is contained in:
王性驊 2025-02-28 09:39:42 +08:00
parent eb85982ef1
commit dbdc777ed4
13 changed files with 380 additions and 181 deletions

View File

@ -26,7 +26,7 @@ import (
type Role struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"` // 角色名稱
UID string `bson:"uid"`
UID string `bson:"uid"` // 限定角色的管理範圍:例如,只有 UID 對應的使用者可以修改或刪除這個角色。
ClientID string `bson:"client_id"`
Status permission.Status `bson:"status"` // 例如 1: 啟用, 0: 停用
CreateAt int64 `bson:"create_at"`

View File

@ -7,4 +7,4 @@ const (
FrontendUser
)
const AdminRoleUID = "GodDog"
const AdminRoleUID = "GD1000001" // 管理員 GodDog 1000001

View File

@ -10,8 +10,8 @@ import (
type RolePermissionRepository interface {
Get(ctx context.Context, roleID string) ([]*entity.RolePermission, error)
GetByPermissionID(ctx context.Context, permissionIDs []string) ([]*entity.RolePermission, error)
Create(ctx context.Context, entity entity.RolePermission) error
Delete(ctx context.Context, roleID string, permission string) error
Create(ctx context.Context, entity []entity.RolePermission) error
Delete(ctx context.Context, roleID string, permissions []string) error
RolePermissionIndex
}

View File

@ -3,5 +3,5 @@ package usecase
import "fmt"
var (
NotFoundError = fmt.Errorf("permission not found")
ErrNotFound = fmt.Errorf("permission not found")
)

View File

@ -10,12 +10,13 @@ import (
type PermissionUseCase interface {
Insert(ctx context.Context, req CreatePermissionReq) error
Del(ctx context.Context, id string) error
Update(ctx context.Context, req UpdatePermissionReq) error
Get(ctx context.Context, id string) (entity.Permission, error)
List(ctx context.Context, param ListParam) ([]entity.Permission, int64, error)
All(ctx context.Context, status *permission.Status)
Update(ctx context.Context, id string, req UpdatePermissionReq) error
All(ctx context.Context, status *permission.Status) ([]entity.Permission, error)
// FilterAll 用樹的結構,使得付節點若關閉,子節點也不會顯示
FilterAll(ctx context.Context) ([]entity.Permission, error)
// Get(ctx context.Context, id string) (entity.Permission, error)
// List(ctx context.Context, param ListParam) ([]entity.Permission, int64, error)
}
type CreatePermissionReq struct {

View File

@ -10,19 +10,20 @@ type RoleUseCase interface {
List(ctx context.Context, param ListQuery) ([]Role, int64, error)
All(ctx context.Context, clientID *string) ([]Role, error)
GetByID(ctx context.Context, id string) (*Role, error)
GetByUID(ctx context.Context, uid string) (*Role, error)
GetByUID(ctx context.Context, uid string) (*Role, error) // -> 限定角色的管理範圍:查詢誰建立的,用某個角色管理,目前用不到
Create(ctx context.Context, role CreateRoleReq) error
Update(ctx context.Context, id string, data CreateRoleReq) error
Delete(ctx context.Context, id string) error
}
type ListQuery struct {
PageSize int64 // 必填
PageIndex int64 // 必填
ClientID *string
UID *string
Name *string
Status *permission.Status
PageSize int64 // 必填
PageIndex int64 // 必填
ClientID *string
UID *string
Name *string
Status *permission.Status
Permission []string
}
type Role struct {

View File

@ -7,12 +7,12 @@ import (
)
type RolePermissionUseCase interface {
Get(ctx context.Context, roleID string) (permission.Permissions, error)
GetByRoleUID(ctx context.Context, uid string) (permission.Permissions, error)
GetByUser(ctx context.Context, uid string) (UserPermission, error)
Get(ctx context.Context, roleID string) (permission.Permissions, error) // -> role 有哪些Permission
Create(ctx context.Context, roleID string, permissions permission.Permissions) error
Delete(ctx context.Context, roleID string, permissions permission.Permissions) error
List(ctx context.Context, req ListQuery) (RoleResp, error)
//GetByRoleUID(ctx context.Context, uid string) (permission.Permissions, error)
//GetByUser(ctx context.Context, uid string) (UserPermission, error)
}
type UserPermission struct {
@ -26,6 +26,6 @@ type UserRoleCountResp struct {
}
type RoleResp struct {
List []UserRoleCountResp `json:"list"`
Total int64 `json:"total"`
Roles []Role
Total int64 `json:"total"`
}

View File

@ -59,26 +59,40 @@ func (repo *RolePermissionRepository) GetByPermissionID(ctx context.Context, per
return result, nil
}
func (repo *RolePermissionRepository) Create(ctx context.Context, role entity.RolePermission) error {
if role.ID.IsZero() {
now := time.Now().UTC().UnixNano()
role.ID = primitive.NewObjectID()
role.CreateAt = now
role.UpdateAt = now
func (repo *RolePermissionRepository) Create(ctx context.Context, roles []entity.RolePermission) error {
if len(roles) == 0 {
return nil // 如果 roles 是空的,則不執行任何操作
}
_, err := repo.DB.GetClient().InsertOne(ctx, role)
now := time.Now().UTC().UnixNano()
// 將 []entity.RolePermission 轉換為 []interface{}
var roleInterfaces []interface{}
for i := range roles {
if roles[i].ID.IsZero() {
roles[i].ID = primitive.NewObjectID()
roles[i].CreateAt = now
roles[i].UpdateAt = now
}
roleInterfaces = append(roleInterfaces, roles[i])
}
_, err := repo.DB.GetClient().InsertMany(ctx, roleInterfaces)
return err
}
func (repo *RolePermissionRepository) Delete(ctx context.Context, roleID string, permission string) error {
filter := bson.M{
"role_id": roleID,
"permission_id": permission,
func (repo *RolePermissionRepository) Delete(ctx context.Context, roleID string, permissions []string) error {
if len(permissions) == 0 {
return nil // 如果 permissions 為空,則不執行刪除操作
}
_, err := repo.DB.GetClient().DeleteOne(ctx, filter)
filter := bson.M{
"role_id": roleID,
"permission_id": bson.M{"$in": permissions}, // 使用 $in 刪除多個 permission_id
}
_, err := repo.DB.GetClient().DeleteMany(ctx, filter)
if err != nil {
return err
}

View File

@ -74,7 +74,7 @@ func TestRolePermissionRepository_Create(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := repo.Create(context.Background(), tc.input)
err := repo.Create(context.Background(), []entity.RolePermission{tc.input})
if tc.expectErr {
assert.Error(t, err, "應該返回錯誤")
@ -97,7 +97,7 @@ func TestRolePermissionRepository_Delete(t *testing.T) {
PermissionID: "perm_1",
}
err = repo.Create(context.Background(), existingRolePermission)
err = repo.Create(context.Background(), []entity.RolePermission{existingRolePermission})
assert.NoError(t, err, "應該成功插入測試資料")
testCases := []struct {
@ -122,7 +122,7 @@ func TestRolePermissionRepository_Delete(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := repo.Delete(context.Background(), tc.roleID, tc.permission)
err := repo.Delete(context.Background(), tc.roleID, []string{tc.permission})
if tc.expectErr {
assert.Error(t, err, "應該返回錯誤")
@ -158,7 +158,7 @@ func TestRolePermissionRepository_GetByPermissionID(t *testing.T) {
}
for _, rp := range existingRolePermissions {
err := repo.Create(context.Background(), rp)
err := repo.Create(context.Background(), []entity.RolePermission{rp})
assert.NoError(t, err, "應該成功插入測試資料")
}
@ -227,7 +227,7 @@ func TestRolePermissionRepository_Get(t *testing.T) {
}
for _, rp := range existingRolePermissions {
err := repo.Create(context.Background(), rp)
err := repo.Create(context.Background(), []entity.RolePermission{rp})
assert.NoError(t, err, "應該成功插入測試資料")
}

View File

@ -1 +1,98 @@
package usecase
import (
"context"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
)
type PermissionUseCaseParam struct {
permissionRepository repository.PermissionRepository
}
type PermissionUseCase struct {
PermissionUseCaseParam
}
func NewPermissionUseCase(param PermissionUseCaseParam) usecase.PermissionUseCase {
return &PermissionUseCase{
PermissionUseCaseParam: param,
}
}
func (use *PermissionUseCase) Insert(ctx context.Context, req usecase.CreatePermissionReq) error {
insert := entity.Permission{
Name: req.Name,
HTTPMethod: req.HTTPMethod,
HTTPPath: req.HTTPPath,
Status: req.Status,
Type: req.Type,
}
if req.Parent != nil {
insert.Parent = *req.Parent
}
err := use.permissionRepository.Insert(ctx, insert)
if err != nil {
return err
}
return nil
}
func (use *PermissionUseCase) Del(ctx context.Context, id string) error {
err := use.Del(ctx, id)
if err != nil {
return err
}
return nil
}
func (use *PermissionUseCase) Update(ctx context.Context, id string, req usecase.UpdatePermissionReq) error {
update := repository.UpdatePermission{
Name: req.Name,
HTTPMethod: req.HTTPMethod,
HTTPPath: req.HTTPPath,
Status: req.Status,
Type: req.Type,
}
err := use.permissionRepository.Update(ctx, id, update)
if err != nil {
return err
}
return nil
}
func (use *PermissionUseCase) All(ctx context.Context, status *permission.Status) ([]entity.Permission, error) {
all, err := use.permissionRepository.GetAll(ctx, status)
if err != nil {
return nil, err
}
return all, nil
}
func (use *PermissionUseCase) FilterAll(ctx context.Context) ([]entity.Permission, error) {
all, err := use.permissionRepository.GetAll(ctx, nil)
if err != nil {
return nil, err
}
nodes, err := GeneratePermissionTree(all).filterOpenNodes()
if err != nil {
return nil, err
}
result := make([]entity.Permission, 0, len(nodes))
for _, item := range nodes {
result = append(result, item)
}
return result, nil
}

View File

@ -3,6 +3,8 @@ 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"
@ -26,6 +28,8 @@ type PermissionNode struct {
Children []*PermissionNode
}
const rootName = "root"
// GeneratePermissionTree 根據扁平權限資料建立樹,並掛在 dummy root 下
func GeneratePermissionTree(permissions []entity.Permission) *PermissionTree {
tree := &PermissionTree{
@ -45,7 +49,7 @@ func GeneratePermissionTree(permissions []entity.Permission) *PermissionTree {
// 2. 建立 dummy root 節點
tree.root = &PermissionNode{
Data: entity.Permission{ID: primitive.NewObjectID(), Name: "root"},
Data: entity.Permission{ID: primitive.NewObjectID(), Name: rootName},
Children: []*PermissionNode{},
}
@ -125,106 +129,93 @@ func (tree *PermissionTree) filterOpenNodes() (map[string]entity.Permission, err
return result, nil
}
//// getFullParentPermissionIDs
//// 根據傳入的權限狀態 (Permissions) 回傳完整的權限 ID 列表,包含所有祖先。
//// 如果某個節點為非葉節點,則會檢查其子節點是否有啟用,否則該節點不會被展開。
//func (tree *PermissionTree) getFullParentPermissionIDs(permissions permission.Permissions) ([]string, error) {
// tree.mu.RLock()
// defer tree.mu.RUnlock()
//
// exist := make(map[string]bool)
// var ids []string
//
// for name, status := range permissions {
// if status != permission.OpenPermission {
// continue
// }
// idList, ok := tree.nameToIDs[name]
// if !ok {
// return nil, NotFoundError
// }
// for _, pid := range idList {
// node, exists := tree.nodes[pid]
// if !exists || node == nil {
// return nil, NotFoundError
// }
// // 如果為父節點,檢查其子節點是否有啟用,若都關閉則不展開
// if len(node.Children) > 0 {
// var childOpen bool
// for _, child := range node.Children {
// if childStatus, ok := permissions[child.Data.Name]; ok && childStatus == permission.OpenPermission {
// childOpen = true
// break
// }
// }
// if !childOpen {
// continue
// }
// }
// // 將該節點及所有祖先(直到 dummy root不包括 dummy root加入結果
// for cur := node; cur != nil && cur.Data.Name != "root"; cur = cur.Parent {
// if !exist[cur.Data.ID.Hex()] {
// ids = append(ids, cur.Data.ID.Hex())
// exist[cur.Data.ID.Hex()] = true
// }
// }
// }
// }
//
// return ids, nil
//}
// getFullParentPermission 根據 role permission 找出完整 path permission status
func (tree *PermissionTree) getFullParentPermission(rolePermissions []*entity.RolePermission) permission.Permissions {
status := permission.Permissions{}
//// getFullParentPermissionStatus
//// 根據傳入的權限狀態 (Permissions) 回傳完整的權限狀態,包含所有祖先的名稱設為啟用。
//func (tree *PermissionTree) getFullParentPermissionStatus(permissions permission.Permissions) (permission.Permissions, error) {
// tree.mu.RLock()
// defer tree.mu.RUnlock()
//
// result := make(permission.Permissions)
// exist := make(map[string]bool)
//
// for name, status := range permissions {
// if status != permission.OpenPermission {
// continue
// }
// idList, ok := tree.nameToIDs[name]
// if !ok {
// return nil, NotFoundError
// }
// for _, pid := range idList {
// node, exists := tree.nodes[pid]
// if !exists || node == nil {
// return nil, NotFoundError
// }
// // 將該節點及所有祖先標記為啟用
// for cur := node; cur != nil && cur.Data.Name != "root"; cur = cur.Parent {
// if !exist[cur.Data.ID.Hex()] {
// result[cur.Data.Name] = permission.OpenPermission
// exist[cur.Data.ID.Hex()] = true
// }
// }
// }
// }
//
// return result, nil
//}
//
//// getFullParentPermission
//// 根據角色權限 (RolePermission) 列表,回傳完整的權限狀態(名稱->狀態),包含所有祖先
//func (tree *PermissionTree) getFullParentPermission(rolePermissions []entity.RolePermission) permission.Permissions {
// tree.mu.RLock()
// defer tree.mu.RUnlock()
//
// result := make(permission.Permissions)
// for _, rp := range rolePermissions {
// node, ok := tree.nodes[rp.PermissionID]
// if !ok || node == nil {
// continue
// }
// // 將該節點及所有祖先設為啟用
// for cur := node; cur != nil && cur.Data.Name != "root"; cur = cur.Parent {
// result[cur.Data.Name] = permission.OpenPermission
// }
// }
// return result
//}
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
}

View File

@ -191,7 +191,6 @@ func (use *RoleUseCase) Update(ctx context.Context, id string, data usecase.Crea
err := use.roleRepository.Update(ctx, repository.UpdateReq{
ID: id,
Name: &data.Name,
UID: &data.UID,
ClientID: &data.ClientID,
Status: &data.Status,
})

View File

@ -3,12 +3,17 @@ package usecase
import (
"context"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/permission"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/repository"
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase"
)
type RolePermissionUseCaseParam struct {
//rolePermissionRepository repository.RolePermissionRepository
rolePermissionRepository repository.RolePermissionRepository
permissionRepository repository.PermissionRepository
roleRepository repository.RoleRepository
userRoleRepository repository.UserRoleRepository
}
type RolePermissionUseCase struct {
@ -21,53 +26,144 @@ func NewRolePermissionUseCase(param RolePermissionUseCaseParam) usecase.RolePerm
}
}
// Get 拿到這個 Role ID 底下有哪些權限是開的
func (use *RolePermissionUseCase) Get(ctx context.Context, roleID string) (permission.Permissions, error) {
//rolePermissions, err := use.rolePermissionRepository.Get(ctx, roleID)
//if err != nil && !errors.Is(err, repository.ErrRecordNotFound) {
// return nil, usecase.InternalError{Err: fmt.Errorf("permissionRepo.Get error: %w", err)}
//}
//
//permissions, err := uc.PermissionRepo.All(ctx)
//if err != nil && !errors.Is(err, repository.ErrRecordNotFound) {
// return nil, usecase.InternalError{Err: fmt.Errorf("permissionRepo.AllStatus error: %w", err)}
//}
//
//// TODO cache http://jira.logintt.com:8080/browse/ESC-2046
//return GeneratePermissionTree(permissions).getFullParentPermission(rolePermissions), nil
//get, err := use.rolePermissionRepository.Get(ctx, roleID)
//if err != nil {
// return nil, err
//}
//
//permissions := make(permission.Permissions, len(get))
//for _, item := range get {
// permissions[item.PermissionID] = item.
//}
//
return permission.Permissions{}, nil
}
// 拿到這個 role 底下有哪些 Permission
rolePermission, err := use.rolePermissionRepository.Get(ctx, roleID)
if err != nil {
return nil, err
}
p, err := use.permissionRepository.GetAll(ctx, nil) // -> 開的關的 permission 都會拿到
if err != nil {
return nil, err
}
func (use *RolePermissionUseCase) GetByRoleUID(ctx context.Context, uid string) (permission.Permissions, error) {
//TODO implement me
panic("implement me")
}
func (use *RolePermissionUseCase) GetByUser(ctx context.Context, uid string) (usecase.UserPermission, error) {
//TODO implement me
panic("implement me")
return GeneratePermissionTree(p).getFullParentPermission(rolePermission), nil
}
func (use *RolePermissionUseCase) Create(ctx context.Context, roleID string, permissions permission.Permissions) error {
//TODO implement me
panic("implement me")
// 如果加了一個,要把上面的節點都加入才可以
permissionIDs, err := use.getPermissionIDs(ctx, permissions)
if err != nil {
return err
}
insert := make([]entity.RolePermission, 0, len(permissions))
for _, permissionID := range permissionIDs {
insert = append(insert, entity.RolePermission{
RoleID: roleID,
PermissionID: permissionID,
})
}
err = use.rolePermissionRepository.Create(ctx, insert)
if err != nil {
return err
}
return nil
}
func (use *RolePermissionUseCase) getPermissionIDs(ctx context.Context, setPermissions permission.Permissions) ([]string, error) {
p, err := use.permissionRepository.GetAll(ctx, nil)
if err != nil {
return nil, err
}
return GeneratePermissionTree(p).getFullParentPermissionIDs(setPermissions)
}
func (use *RolePermissionUseCase) Delete(ctx context.Context, roleID string, permissions permission.Permissions) error {
//TODO implement me
panic("implement me")
// 如果加了一個,要把上面的節點都加入才可以
permissionIDs, err := use.getPermissionIDs(ctx, permissions)
if err != nil {
return err
}
del := make([]string, 0, len(permissions))
for _, permissionID := range permissionIDs {
del = append(del, permissionID)
}
err = use.rolePermissionRepository.Delete(ctx, roleID, del)
if err != nil {
return err
}
return nil
}
func (use *RolePermissionUseCase) List(ctx context.Context, req usecase.ListQuery) (usecase.RoleResp, error) {
//TODO implement me
panic("implement me")
roles, total, err := use.roleRepository.List(ctx, repository.ListQuery{
PageIndex: req.PageIndex,
PageSize: req.PageSize,
ClientID: req.ClientID,
UID: req.UID,
Name: req.Name,
Status: req.Status,
})
if err != nil {
return usecase.RoleResp{}, err
}
result := make([]usecase.Role, 0, len(roles))
for _, item := range roles {
result = append(result, usecase.Role{
ID: item.ID.Hex(),
UID: item.UID,
Name: item.Name,
Status: item.Status,
ClientID: item.ClientID,
})
}
return usecase.RoleResp{
Total: total,
Roles: result,
}, nil
}
//// GetByRoleUID 拿到這個 UID 底下有哪些權限是開的
//func (use *RolePermissionUseCase) GetByRoleUID(ctx context.Context, uid string) (permission.Permissions, error) {
// permissions := make(permission.Permissions)
//
// // admin權限
// if uid == permission.AdminRoleUID {
// data, err := use.permissionRepository.GetAll(ctx, nil)
// if err != nil {
// return nil, err
// }
//
// for _, v := range data {
// permissions[v.Name] = permission.OpenPermission
// }
// } else {
// role, err := use.roleRepository.GetByUID(ctx, uid)
// if err != nil {
// return nil, err
// }
//
// permissions, err = use.Get(ctx, role.ID.Hex())
// if err != nil {
// return nil, err
// }
// }
//
// return permissions, nil
//}
//
//func (use *RolePermissionUseCase) GetByUser(ctx context.Context, uid string) (usecase.UserPermission, error) {
// userRole, err := use.userRoleRepository.GetByUserID(ctx, uid)
// if err != nil {
// return usecase.UserPermission{}, err
// }
//
// p, err := use.Get(ctx, userRole.RoleID)
// if err != nil {
// return usecase.UserPermission{}, err
// }
//
// return usecase.UserPermission{
// RoleID: userRole.RoleID,
// Permissions: p,
// }, nil
//}