feat: add permission tree
This commit is contained in:
parent
070ed823a8
commit
f53b3beecb
|
@ -24,6 +24,7 @@ type (
|
|||
GetPermissionStatusByPathReq = permission.GetPermissionStatusByPathReq
|
||||
ListPermissionResp = permission.ListPermissionResp
|
||||
ListPermissionStatusResp = permission.ListPermissionStatusResp
|
||||
MapPermissionStatusResp = permission.MapPermissionStatusResp
|
||||
NoneReq = permission.NoneReq
|
||||
OKResp = permission.OKResp
|
||||
PermissionItem = permission.PermissionItem
|
||||
|
@ -39,11 +40,11 @@ type (
|
|||
ValidationTokenResp = permission.ValidationTokenResp
|
||||
|
||||
PermissionService interface {
|
||||
// ListPermissionStatus 取得狀態
|
||||
// ListPermissionStatus 取得所有權限狀態列表,給前端表演用
|
||||
ListPermissionStatus(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*ListPermissionStatusResp, error)
|
||||
// ListPermission 取得完整權限
|
||||
ListPermission(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*ListPermissionResp, error)
|
||||
// CheckPermissionByRole 透過角色 ID 來檢視權限
|
||||
// MapPermissionStatus 取得所有權限開閉狀態,簡易版,給前端表演用
|
||||
MapPermissionStatus(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*MapPermissionStatusResp, error)
|
||||
// CheckPermissionByRole 透過角色 ID 來檢視權限,後台要通過時真的看這個
|
||||
CheckPermissionByRole(ctx context.Context, in *CheckPermissionByRoleReq, opts ...grpc.CallOption) (*PermissionResp, error)
|
||||
// GetPermissionStatusByPath 透過資源拿取角色的狀態
|
||||
GetPermissionStatusByPath(ctx context.Context, in *GetPermissionStatusByPathReq, opts ...grpc.CallOption) (*PermissionStatusItem, error)
|
||||
|
@ -60,19 +61,19 @@ func NewPermissionService(cli zrpc.Client) PermissionService {
|
|||
}
|
||||
}
|
||||
|
||||
// ListPermissionStatus 取得狀態
|
||||
// ListPermissionStatus 取得所有權限狀態列表,給前端表演用
|
||||
func (m *defaultPermissionService) ListPermissionStatus(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*ListPermissionStatusResp, error) {
|
||||
client := permission.NewPermissionServiceClient(m.cli.Conn())
|
||||
return client.ListPermissionStatus(ctx, in, opts...)
|
||||
}
|
||||
|
||||
// ListPermission 取得完整權限
|
||||
func (m *defaultPermissionService) ListPermission(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*ListPermissionResp, error) {
|
||||
// MapPermissionStatus 取得所有權限開閉狀態,簡易版,給前端表演用
|
||||
func (m *defaultPermissionService) MapPermissionStatus(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*MapPermissionStatusResp, error) {
|
||||
client := permission.NewPermissionServiceClient(m.cli.Conn())
|
||||
return client.ListPermission(ctx, in, opts...)
|
||||
return client.MapPermissionStatus(ctx, in, opts...)
|
||||
}
|
||||
|
||||
// CheckPermissionByRole 透過角色 ID 來檢視權限
|
||||
// CheckPermissionByRole 透過角色 ID 來檢視權限,後台要通過時真的看這個
|
||||
func (m *defaultPermissionService) CheckPermissionByRole(ctx context.Context, in *CheckPermissionByRoleReq, opts ...grpc.CallOption) (*PermissionResp, error) {
|
||||
client := permission.NewPermissionServiceClient(m.cli.Conn())
|
||||
return client.CheckPermissionByRole(ctx, in, opts...)
|
||||
|
|
|
@ -24,6 +24,7 @@ type (
|
|||
GetPermissionStatusByPathReq = permission.GetPermissionStatusByPathReq
|
||||
ListPermissionResp = permission.ListPermissionResp
|
||||
ListPermissionStatusResp = permission.ListPermissionStatusResp
|
||||
MapPermissionStatusResp = permission.MapPermissionStatusResp
|
||||
NoneReq = permission.NoneReq
|
||||
OKResp = permission.OKResp
|
||||
PermissionItem = permission.PermissionItem
|
||||
|
|
|
@ -24,6 +24,7 @@ type (
|
|||
GetPermissionStatusByPathReq = permission.GetPermissionStatusByPathReq
|
||||
ListPermissionResp = permission.ListPermissionResp
|
||||
ListPermissionStatusResp = permission.ListPermissionStatusResp
|
||||
MapPermissionStatusResp = permission.MapPermissionStatusResp
|
||||
NoneReq = permission.NoneReq
|
||||
OKResp = permission.OKResp
|
||||
PermissionItem = permission.PermissionItem
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
-- 一級分類
|
||||
INSERT INTO `permission` (`parent`, `name`, `http_method`, `http_path`, `create_time`, `update_time`)
|
||||
VALUES (0, 'user.info.management', '', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); -- 用戶資訊管理
|
||||
|
||||
# -- 二級分類 用戶資訊管理
|
||||
SET @id := (SELECT id FROM `permission` where name = 'user.info.management');
|
||||
INSERT INTO `permission` (`parent`, `name`, `http_method`, `http_path`, `create_time`, `update_time`)
|
||||
VALUES (@id, 'user.basic.info', '', '', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); -- 用戶基礎資訊查詢
|
||||
|
||||
|
||||
# -- 三級分類 用戶基礎資訊管理-基礎資訊查詢表
|
||||
SET @id := (SELECT id FROM `permission` where name = 'user.basic.info');
|
||||
INSERT INTO `permission` (`parent`, `name`, `http_method`, `http_path`, `create_time`, `update_time`)
|
||||
VALUES (@id, 'user.info.select', 'GET', '/v1/user', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()), -- 查詢
|
||||
(@id, 'user.info.select.plain_code', 'GET', '/v1/user', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()); -- 明碼查詢
|
|
@ -171,7 +171,8 @@ message PermissionStatusItem {
|
|||
int64 parent_id =2;
|
||||
string name =3;
|
||||
PermissionStatus status = 4;
|
||||
bool approval = 5;
|
||||
string type =5;
|
||||
bool approval = 6;
|
||||
}
|
||||
|
||||
message ListPermissionStatusResp {
|
||||
|
@ -210,12 +211,16 @@ message GetPermissionStatusByPathReq {
|
|||
string method = 3;
|
||||
}
|
||||
|
||||
message MapPermissionStatusResp {
|
||||
map<int64,PermissionStatus> data = 1; // permission id : open close
|
||||
}
|
||||
|
||||
service PermissionService {
|
||||
// ListPermissionStatus 取得狀態
|
||||
// ListPermissionStatus 取得所有權限狀態列表,給前端表演用
|
||||
rpc ListPermissionStatus(NoneReq)returns(ListPermissionStatusResp);
|
||||
// ListPermission 取得完整權限
|
||||
rpc ListPermission(NoneReq)returns(ListPermissionResp);
|
||||
// CheckPermissionByRole 透過角色 ID 來檢視權限
|
||||
// ListPermission 一次性取得所有權限表
|
||||
rpc ListPermission(NoneReq)returns(MapPermissionStatusResp);
|
||||
// CheckPermissionByRole 透過角色 ID 來檢視權限,後台要通過時真的看這個
|
||||
rpc CheckPermissionByRole(CheckPermissionByRoleReq)returns(PermissionResp);
|
||||
// GetPermissionStatusByPath 透過資源拿取角色的狀態
|
||||
rpc GetPermissionStatusByPath(GetPermissionStatusByPathReq)returns(PermissionStatusItem);
|
||||
|
|
4
go.mod
4
go.mod
|
@ -7,9 +7,7 @@ require (
|
|||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/open-policy-agent/opa v0.67.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/zeromicro/go-zero v1.7.0
|
||||
go.uber.org/mock v0.4.0
|
||||
|
@ -18,6 +16,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||
github.com/agnivade/levenshtein v1.1.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
|
@ -38,6 +37,7 @@ require (
|
|||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
|
|
|
@ -14,4 +14,8 @@ type Config struct {
|
|||
Expired time.Duration
|
||||
Secret string
|
||||
}
|
||||
// 加上DB結構體
|
||||
DB struct {
|
||||
DsnString string
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,3 +6,36 @@ const (
|
|||
PermissionTypeBackendUser PermissionType = iota + 1
|
||||
PermissionTypeFrontendUser
|
||||
)
|
||||
|
||||
type PermissionTypeCode string
|
||||
|
||||
const (
|
||||
PermissionTypeBackCode PermissionTypeCode = "back"
|
||||
PermissionTypeFrontCode PermissionTypeCode = "front"
|
||||
)
|
||||
|
||||
var permissionMap = map[int64]PermissionTypeCode{
|
||||
1: PermissionTypeFrontCode,
|
||||
2: PermissionTypeBackCode,
|
||||
}
|
||||
|
||||
func ToPermissionTypeCode(code int64) (PermissionTypeCode, bool) {
|
||||
result, ok := permissionMap[code]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return result, true
|
||||
}
|
||||
|
||||
func (t *PermissionTypeCode) ToString() string {
|
||||
return string(*t)
|
||||
}
|
||||
|
||||
type PermissionStatus string
|
||||
type Permissions map[string]PermissionStatus
|
||||
|
||||
const (
|
||||
PermissionStatusOpenCode PermissionStatus = "open"
|
||||
PermissionStatusCloseCode PermissionStatus = "close"
|
||||
)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"ark-permission/internal/domain"
|
||||
"ark-permission/internal/entity"
|
||||
)
|
||||
|
||||
// PermissionTreeManager 定義一組操作權限樹的接口
|
||||
// 這個名稱說明它是專門負責管理和操作權限樹的管理器
|
||||
type PermissionTreeManager interface {
|
||||
// AddPermission 將一個新的權限節點插入到樹中
|
||||
// key 是父節點的ID,value 是要插入的 Permission 資料
|
||||
// 此方法應該能處理節點是否存在於父節點下的情況
|
||||
AddPermission(parentID int64, permission entity.Permission) error
|
||||
// FindPermissionByID 根據權限 ID 查詢樹中的某個節點
|
||||
// 如果節點存在,返回對應的 Permission 資料,否則返回 nil
|
||||
FindPermissionByID(permissionID int64) (*Permission, error)
|
||||
// GetAllParentPermissionIDs 根據傳入的 permissions 列表
|
||||
// 找出每個權限的完整父節點權限 ID 路徑
|
||||
// 例如,如果 B 的父權限是 A,並且給了 B 權限,則返回 A 和 B 的權限 ID
|
||||
GetAllParentPermissionIDs(permissions domain.Permissions) ([]int64, error)
|
||||
// GetAllParentPermissionStatuses 返回給定權限下的所有完整父節點權限狀態
|
||||
// 例如,若給 B 權限,該方法將返回所有與 B 相關的父權限的狀態
|
||||
GetAllParentPermissionStatuses(permissions domain.Permissions) (domain.Permissions, error)
|
||||
// GetRolePermissionTree 根據角色權限找出所有父節點和子節點權限狀態
|
||||
// 角色權限是傳入的一個列表,該方法會根據每個角色的權限,返回所有相關的權限狀態
|
||||
GetRolePermissionTree(rolePermissions []entity.RolePermission) domain.Permissions
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
ID int64 `json:"-"`
|
||||
Name string `json:"name"`
|
||||
HTTPMethod string `json:"http_method"`
|
||||
HTTPPath string `json:"http_path"`
|
||||
Parent *Permission `json:"-"`
|
||||
Children []*Permission `json:"children"`
|
||||
PathIDs []int64 `json:"-"` // full path id
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package entity
|
||||
|
||||
type RolePermission struct {
|
||||
ID int64 `gorm:"column:id"`
|
||||
RoleID int64 `gorm:"column:role_id"`
|
||||
PermissionID int64 `gorm:"column:permission_id"`
|
||||
|
||||
CreateTime int64 `gorm:"column:create_time;autoCreateTime"`
|
||||
UpdateTime int64 `gorm:"column:update_time;autoUpdateTime"`
|
||||
}
|
||||
|
||||
func (c *RolePermission) TableName() string {
|
||||
return "role_permission"
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package permissionservicelogic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"ark-permission/gen_result/pb/permission"
|
||||
"ark-permission/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ListPermissionLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewListPermissionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPermissionLogic {
|
||||
return &ListPermissionLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
// ListPermission 取得完整權限
|
||||
func (l *ListPermissionLogic) ListPermission(in *permission.NoneReq) (*permission.ListPermissionResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &permission.ListPermissionResp{}, nil
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package permissionservicelogic
|
||||
|
||||
import (
|
||||
"ark-permission/internal/domain"
|
||||
ers "code.30cm.net/wanderland/library-go/errors"
|
||||
"context"
|
||||
|
||||
"ark-permission/gen_result/pb/permission"
|
||||
|
@ -23,9 +25,31 @@ func NewListPermissionStatusLogic(ctx context.Context, svcCtx *svc.ServiceContex
|
|||
}
|
||||
}
|
||||
|
||||
// ListPermissionStatus 取得狀態
|
||||
// ListPermissionStatus 取得
|
||||
func (l *ListPermissionStatusLogic) ListPermissionStatus(in *permission.NoneReq) (*permission.ListPermissionStatusResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
// 搜尋所有權限
|
||||
permissions, err := l.svcCtx.Permission.FindAllOpenPermission(l.ctx)
|
||||
if err != nil {
|
||||
return nil, ers.DBError(err.Error())
|
||||
}
|
||||
|
||||
return &permission.ListPermissionStatusResp{}, nil
|
||||
exist := make(map[string]struct{})
|
||||
status := make([]*permission.PermissionStatusItem, 0, len(permissions))
|
||||
for _, v := range permissions {
|
||||
if _, ok := exist[v.Name]; !ok {
|
||||
t, _ := domain.ToPermissionTypeCode(v.Type)
|
||||
status = append(status, &permission.PermissionStatusItem{
|
||||
Id: v.Id, // 權限 ID
|
||||
ParentId: v.Parent.Int64, // 上級權限的ID
|
||||
Name: v.Name, // 權限名稱
|
||||
Status: permission.PermissionStatus(v.Status), // 權限開啟或關閉,判斷時上級權限如果關閉,下級也應該關閉對此人關閉
|
||||
Type: t.ToString(), // 前台權限,還是後台權限,還是其他中台之類的
|
||||
})
|
||||
exist[v.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return &permission.ListPermissionStatusResp{
|
||||
Data: status,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package permissionservicelogic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"ark-permission/gen_result/pb/permission"
|
||||
"ark-permission/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type MapPermissionStatusLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logx.Logger
|
||||
}
|
||||
|
||||
func NewMapPermissionStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MapPermissionStatusLogic {
|
||||
return &MapPermissionStatusLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logx.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
// MapPermissionStatus 取得所有權限開閉狀態,簡易版,給前端表演用
|
||||
func (l *MapPermissionStatusLogic) MapPermissionStatus(in *permission.NoneReq) (*permission.MapPermissionStatusResp, error) {
|
||||
// todo: add your logic here and delete this line
|
||||
|
||||
return &permission.MapPermissionStatusResp{}, nil
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlc"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
|
@ -12,6 +15,7 @@ type (
|
|||
// and implement the added methods in customPermissionModel.
|
||||
PermissionModel interface {
|
||||
permissionModel
|
||||
FindAllOpenPermission(ctx context.Context) ([]*Permission, error)
|
||||
}
|
||||
|
||||
customPermissionModel struct {
|
||||
|
@ -19,9 +23,25 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// NewPermissionModel returns a model for the database table.
|
||||
func NewPermissionModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) PermissionModel {
|
||||
// NewPermissionModel 者裡不用快取版本,因為快取我想要自己控制,也就是 local cache 不想上升至 redis 的層級
|
||||
// 因為我 permission 設計是由 sql 新增,重啟服務就可以重啟,或者是未來可以放一個mq 來同步
|
||||
func NewPermissionModel(conn sqlx.SqlConn) PermissionModel {
|
||||
return &customPermissionModel{
|
||||
defaultPermissionModel: newPermissionModel(conn, c, opts...),
|
||||
defaultPermissionModel: newPermissionModel(conn),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *customPermissionModel) FindAllOpenPermission(ctx context.Context) ([]*Permission, error) {
|
||||
query := fmt.Sprintf("select %s from %s where `status` = ? order by `create_time` asc", permissionRows, m.table)
|
||||
var resp []*Permission
|
||||
err := m.conn.QueryRowsCtx(ctx, &resp, query, 1)
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
return resp, nil
|
||||
case errors.Is(err, sqlc.ErrNotFound):
|
||||
return nil, ErrNotFound
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/stores/builder"
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlc"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
|
@ -20,9 +19,6 @@ var (
|
|||
permissionRows = strings.Join(permissionFieldNames, ",")
|
||||
permissionRowsExpectAutoSet = strings.Join(stringx.Remove(permissionFieldNames, "`id`"), ",")
|
||||
permissionRowsWithPlaceHolder = strings.Join(stringx.Remove(permissionFieldNames, "`id`"), "=?,") + "=?"
|
||||
|
||||
cachePermissionIdPrefix = "cache:permission:id:"
|
||||
cachePermissionNamePrefix = "cache:permission:name:"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -35,7 +31,7 @@ type (
|
|||
}
|
||||
|
||||
defaultPermissionModel struct {
|
||||
sqlc.CachedConn
|
||||
conn sqlx.SqlConn
|
||||
table string
|
||||
}
|
||||
|
||||
|
@ -52,42 +48,30 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
func newPermissionModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultPermissionModel {
|
||||
func newPermissionModel(conn sqlx.SqlConn) *defaultPermissionModel {
|
||||
return &defaultPermissionModel{
|
||||
CachedConn: sqlc.NewConn(conn, c, opts...),
|
||||
table: "`permission`",
|
||||
conn: conn,
|
||||
table: "`permission`",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultPermissionModel) withSession(session sqlx.Session) *defaultPermissionModel {
|
||||
return &defaultPermissionModel{
|
||||
CachedConn: m.CachedConn.WithSession(session),
|
||||
table: "`permission`",
|
||||
conn: sqlx.NewSqlConnFromSession(session),
|
||||
table: "`permission`",
|
||||
}
|
||||
}
|
||||
|
||||
func (m *defaultPermissionModel) Delete(ctx context.Context, id int64) error {
|
||||
data, err := m.FindOne(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
permissionIdKey := fmt.Sprintf("%s%v", cachePermissionIdPrefix, id)
|
||||
permissionNameKey := fmt.Sprintf("%s%v", cachePermissionNamePrefix, data.Name)
|
||||
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
|
||||
return conn.ExecCtx(ctx, query, id)
|
||||
}, permissionIdKey, permissionNameKey)
|
||||
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
|
||||
_, err := m.conn.ExecCtx(ctx, query, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *defaultPermissionModel) FindOne(ctx context.Context, id int64) (*Permission, error) {
|
||||
permissionIdKey := fmt.Sprintf("%s%v", cachePermissionIdPrefix, id)
|
||||
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", permissionRows, m.table)
|
||||
var resp Permission
|
||||
err := m.QueryRowCtx(ctx, &resp, permissionIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error {
|
||||
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", permissionRows, m.table)
|
||||
return conn.QueryRowCtx(ctx, v, query, id)
|
||||
})
|
||||
err := m.conn.QueryRowCtx(ctx, &resp, query, id)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
|
@ -99,15 +83,9 @@ func (m *defaultPermissionModel) FindOne(ctx context.Context, id int64) (*Permis
|
|||
}
|
||||
|
||||
func (m *defaultPermissionModel) FindOneByName(ctx context.Context, name string) (*Permission, error) {
|
||||
permissionNameKey := fmt.Sprintf("%s%v", cachePermissionNamePrefix, name)
|
||||
var resp Permission
|
||||
err := m.QueryRowIndexCtx(ctx, &resp, permissionNameKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
|
||||
query := fmt.Sprintf("select %s from %s where `name` = ? limit 1", permissionRows, m.table)
|
||||
if err := conn.QueryRowCtx(ctx, &resp, query, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Id, nil
|
||||
}, m.queryPrimary)
|
||||
query := fmt.Sprintf("select %s from %s where `name` = ? limit 1", permissionRows, m.table)
|
||||
err := m.conn.QueryRowCtx(ctx, &resp, query, name)
|
||||
switch err {
|
||||
case nil:
|
||||
return &resp, nil
|
||||
|
@ -119,39 +97,17 @@ func (m *defaultPermissionModel) FindOneByName(ctx context.Context, name string)
|
|||
}
|
||||
|
||||
func (m *defaultPermissionModel) Insert(ctx context.Context, data *Permission) (sql.Result, error) {
|
||||
permissionIdKey := fmt.Sprintf("%s%v", cachePermissionIdPrefix, data.Id)
|
||||
permissionNameKey := fmt.Sprintf("%s%v", cachePermissionNamePrefix, data.Name)
|
||||
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?)", m.table, permissionRowsExpectAutoSet)
|
||||
return conn.ExecCtx(ctx, query, data.Parent, data.Name, data.HttpMethod, data.HttpPath, data.Status, data.Type, data.CreateTime, data.UpdateTime)
|
||||
}, permissionIdKey, permissionNameKey)
|
||||
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?)", m.table, permissionRowsExpectAutoSet)
|
||||
ret, err := m.conn.ExecCtx(ctx, query, data.Parent, data.Name, data.HttpMethod, data.HttpPath, data.Status, data.Type, data.CreateTime, data.UpdateTime)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (m *defaultPermissionModel) Update(ctx context.Context, newData *Permission) error {
|
||||
data, err := m.FindOne(ctx, newData.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
permissionIdKey := fmt.Sprintf("%s%v", cachePermissionIdPrefix, data.Id)
|
||||
permissionNameKey := fmt.Sprintf("%s%v", cachePermissionNamePrefix, data.Name)
|
||||
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
|
||||
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, permissionRowsWithPlaceHolder)
|
||||
return conn.ExecCtx(ctx, query, newData.Parent, newData.Name, newData.HttpMethod, newData.HttpPath, newData.Status, newData.Type, newData.CreateTime, newData.UpdateTime, newData.Id)
|
||||
}, permissionIdKey, permissionNameKey)
|
||||
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, permissionRowsWithPlaceHolder)
|
||||
_, err := m.conn.ExecCtx(ctx, query, newData.Parent, newData.Name, newData.HttpMethod, newData.HttpPath, newData.Status, newData.Type, newData.CreateTime, newData.UpdateTime, newData.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *defaultPermissionModel) formatPrimary(primary any) string {
|
||||
return fmt.Sprintf("%s%v", cachePermissionIdPrefix, primary)
|
||||
}
|
||||
|
||||
func (m *defaultPermissionModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error {
|
||||
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", permissionRows, m.table)
|
||||
return conn.QueryRowCtx(ctx, v, query, primary)
|
||||
}
|
||||
|
||||
func (m *defaultPermissionModel) tableName() string {
|
||||
return m.table
|
||||
}
|
||||
|
|
|
@ -22,19 +22,19 @@ func NewPermissionServiceServer(svcCtx *svc.ServiceContext) *PermissionServiceSe
|
|||
}
|
||||
}
|
||||
|
||||
// ListPermissionStatus 取得狀態
|
||||
// ListPermissionStatus 取得所有權限狀態列表,給前端表演用
|
||||
func (s *PermissionServiceServer) ListPermissionStatus(ctx context.Context, in *permission.NoneReq) (*permission.ListPermissionStatusResp, error) {
|
||||
l := permissionservicelogic.NewListPermissionStatusLogic(ctx, s.svcCtx)
|
||||
return l.ListPermissionStatus(in)
|
||||
}
|
||||
|
||||
// ListPermission 取得完整權限
|
||||
func (s *PermissionServiceServer) ListPermission(ctx context.Context, in *permission.NoneReq) (*permission.ListPermissionResp, error) {
|
||||
l := permissionservicelogic.NewListPermissionLogic(ctx, s.svcCtx)
|
||||
return l.ListPermission(in)
|
||||
// MapPermissionStatus 取得所有權限開閉狀態,簡易版,給前端表演用
|
||||
func (s *PermissionServiceServer) MapPermissionStatus(ctx context.Context, in *permission.NoneReq) (*permission.MapPermissionStatusResp, error) {
|
||||
l := permissionservicelogic.NewMapPermissionStatusLogic(ctx, s.svcCtx)
|
||||
return l.MapPermissionStatus(in)
|
||||
}
|
||||
|
||||
// CheckPermissionByRole 透過角色 ID 來檢視權限
|
||||
// CheckPermissionByRole 透過角色 ID 來檢視權限,後台要通過時真的看這個
|
||||
func (s *PermissionServiceServer) CheckPermissionByRole(ctx context.Context, in *permission.CheckPermissionByRoleReq) (*permission.PermissionResp, error) {
|
||||
l := permissionservicelogic.NewCheckPermissionByRoleLogic(ctx, s.svcCtx)
|
||||
return l.CheckPermissionByRole(in)
|
||||
|
|
|
@ -4,10 +4,12 @@ import (
|
|||
"ark-permission/internal/config"
|
||||
"ark-permission/internal/domain/repository"
|
||||
"ark-permission/internal/lib/required"
|
||||
"ark-permission/internal/model"
|
||||
repo "ark-permission/internal/repository"
|
||||
ers "code.30cm.net/wanderland/library-go/errors"
|
||||
"code.30cm.net/wanderland/library-go/errors/code"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
)
|
||||
|
||||
type ServiceContext struct {
|
||||
|
@ -16,6 +18,8 @@ type ServiceContext struct {
|
|||
Validate required.Validate
|
||||
Redis redis.Redis
|
||||
TokenRedisRepo repository.TokenRepository
|
||||
|
||||
Permission model.PermissionModel
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
|
@ -23,8 +27,11 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ers.Scope = code.CloudEPPermission
|
||||
|
||||
sqlConn := sqlx.NewMysql(c.DB.DsnString)
|
||||
|
||||
return &ServiceContext{
|
||||
Config: c,
|
||||
Validate: required.MustValidator(),
|
||||
|
@ -32,5 +39,6 @@ func NewServiceContext(c config.Config) *ServiceContext {
|
|||
TokenRedisRepo: repo.NewTokenRepository(repo.TokenRepositoryParam{
|
||||
Store: newRedis,
|
||||
}),
|
||||
Permission: model.NewPermissionModel(sqlConn),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
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
|
||||
}
|
|
@ -0,0 +1,611 @@
|
|||
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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewPermissionTree(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want *PermissionTree
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
want: &PermissionTree{
|
||||
root: &usecase.Permission{
|
||||
ID: 0,
|
||||
Name: "root",
|
||||
Children: []*usecase.Permission{},
|
||||
},
|
||||
nodes: map[int64]*usecase.Permission{0: {
|
||||
ID: 0,
|
||||
Name: "root",
|
||||
Children: []*usecase.Permission{},
|
||||
}}, // 根節點也加入 nodes 記錄
|
||||
paths: make(map[int64][]int),
|
||||
names: make(map[string][]int64),
|
||||
ids: make(map[int64]string),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tree := NewPermissionTree()
|
||||
// 驗證 Root 的值
|
||||
assert.Equal(t, tree.root.ID, tt.want.root.ID)
|
||||
assert.Equal(t, tree.root.Name, tt.want.root.Name)
|
||||
assert.Equal(t, len(tree.root.Children), len(tt.want.root.Children))
|
||||
|
||||
assert.Equal(t, len(tree.nodes), len(tt.want.nodes))
|
||||
assert.Equal(t, len(tree.paths), len(tt.want.paths))
|
||||
assert.Equal(t, len(tree.ids), len(tt.want.ids))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 測試 AddPermission 函數
|
||||
func TestAddPermission(t *testing.T) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
parentID int64
|
||||
permission entity.Permission
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
parentID: 0,
|
||||
permission: entity.Permission{ID: 2, Name: "new_permission", HTTPPath: "/new", HTTPMethod: "POST"},
|
||||
},
|
||||
{
|
||||
name: "Invalid Parent ID",
|
||||
parentID: 99, // 無效的 parentID
|
||||
permission: entity.Permission{ID: 3, Name: "invalid_parent", HTTPPath: "/invalid", HTTPMethod: "GET"},
|
||||
expectedError: ers.ResourceNotFound("failed to find ID 99"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tree.AddPermission(tt.parentID, tt.permission)
|
||||
|
||||
// 錯誤檢查
|
||||
if !reflect.DeepEqual(err, tt.expectedError) {
|
||||
t.Errorf("expected error %v, got %v", tt.expectedError, err)
|
||||
}
|
||||
|
||||
if tt.expectedError == nil {
|
||||
// 檢查是否已正確插入到 nodes 中
|
||||
node, ok := tree.nodes[tt.permission.ID]
|
||||
if !ok {
|
||||
t.Errorf("expected permission with ID %d to be in nodes map", tt.permission.ID)
|
||||
}
|
||||
|
||||
if node.Name != tt.permission.Name {
|
||||
t.Errorf("expected permission name %s, got %s", tt.permission.Name, node.Name)
|
||||
}
|
||||
|
||||
// 檢查父節點的子節點是否正確加入
|
||||
parentNode, _ := tree.FindPermissionByID(tt.parentID)
|
||||
found := false
|
||||
for _, child := range parentNode.Children {
|
||||
if child.ID == tt.permission.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected permission ID %d to be child of parent ID %d", tt.permission.ID, tt.parentID)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 測試 buildNodePath 函數
|
||||
func TestBuildNodePath(t *testing.T) {
|
||||
// ======== 準備測試 ========
|
||||
tree := NewPermissionTree()
|
||||
// 插入一些節點
|
||||
err := tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission"})
|
||||
assert.NoError(t, err)
|
||||
// ======== 準備測試 ========
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeID int64
|
||||
expectedPath []int
|
||||
}{
|
||||
{
|
||||
name: "Grandchild Permission Path",
|
||||
nodeID: 4,
|
||||
expectedPath: []int{0, 0, 0}, // 根 -> 子 -> 孫節點的索引
|
||||
},
|
||||
{
|
||||
name: "Child Permission 1 Path",
|
||||
nodeID: 2,
|
||||
expectedPath: []int{0, 0}, // 根 -> 子節點的索引
|
||||
},
|
||||
{
|
||||
name: "Child Permission 2 Path",
|
||||
nodeID: 3,
|
||||
expectedPath: []int{0, 1}, // 根 -> 子節點的索引
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 查找要測試的節點
|
||||
node, err := tree.FindPermissionByID(tt.nodeID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find node with ID %d: %v", tt.nodeID, err)
|
||||
}
|
||||
// 構建節點的完整路徑
|
||||
// Grandchild Permission Path 測試孫節點的完整路徑,這應該包含根節點和子節點的索引,結果為 [0, 0],表示它是根節點的第一個子節點的第一個子節點。
|
||||
// Child Permission 1 Path 測試子節點 1 的路徑,應該是 [0],表示它是根節點的第一個子節點。
|
||||
// Child Permission 2 Path 測試子節點 2 的路徑,應該是 [1],表示它是根節點的第二個子節點。
|
||||
tree.buildNodePath(node, tt.nodeID)
|
||||
|
||||
// 從 paths 中獲取構建好的路徑
|
||||
resultPath, ok := tree.paths[tt.nodeID]
|
||||
if !ok {
|
||||
t.Fatalf("path not found for node ID %d", tt.nodeID)
|
||||
}
|
||||
|
||||
// 比較結果路徑與預期路徑
|
||||
if !reflect.DeepEqual(resultPath, tt.expectedPath) {
|
||||
t.Errorf("expected path %v, got %v", tt.expectedPath, resultPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 測試 GetAllParentPermissionIDs 函數
|
||||
func TestGetAllParentPermissionIDs(t *testing.T) {
|
||||
tree := NewPermissionTree()
|
||||
// ======== 準備測試 ========
|
||||
// 添加節點
|
||||
err := tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(3, entity.Permission{ID: 5, Name: "grandchild_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(0, entity.Permission{ID: 6, Name: "root_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(6, entity.Permission{ID: 6, Name: "child_permission_3"})
|
||||
assert.NoError(t, err)
|
||||
// ======== 準備測試 ========
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
permissions domain.Permissions
|
||||
expectedResult []int64
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid permissions with open status",
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedResult: []int64{0, 1, 2, 4}, // 根 -> 子 -> 孫節點
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Valid multiple permissions with open status",
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_2": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedResult: []int64{0, 1, 2, 4, 0, 1, 3, 5}, // 多個權限
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Permission name not found",
|
||||
permissions: domain.Permissions{
|
||||
"unknown_permission": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: fmt.Errorf("permission with name unknown_permission not found"),
|
||||
},
|
||||
{
|
||||
name: "Permission close by parent node",
|
||||
permissions: domain.Permissions{
|
||||
"root_permission_2": domain.PermissionStatusCloseCode,
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tree.GetAllParentPermissionIDs(tt.permissions)
|
||||
|
||||
// 檢查返回結果
|
||||
if !reflect.DeepEqual(result, tt.expectedResult) {
|
||||
t.Errorf("expected %v, got %v", tt.expectedResult, result)
|
||||
}
|
||||
|
||||
// 檢查錯誤
|
||||
if (err == nil && tt.expectedError != nil) || (err != nil && tt.expectedError == nil) {
|
||||
t.Errorf("expected error %v, got %v", tt.expectedError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 測試 GetAllParentPermissionStatuses 函數
|
||||
func TestGetAllParentPermissionStatuses(t *testing.T) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
// 添加節點
|
||||
err := tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(3, entity.Permission{ID: 5, Name: "grandchild_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
permissions domain.Permissions
|
||||
expectedResult domain.Permissions
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid permissions with open status",
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedResult: domain.Permissions{
|
||||
"root_permission": domain.PermissionStatusOpenCode,
|
||||
"child_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Multiple permissions with open status",
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_2": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedResult: domain.Permissions{
|
||||
"root_permission": domain.PermissionStatusOpenCode,
|
||||
"child_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
"child_permission_2": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_2": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Permission name not found",
|
||||
permissions: domain.Permissions{
|
||||
"unknown_permission": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: fmt.Errorf("permission with name unknown_permission not found"),
|
||||
},
|
||||
{
|
||||
name: "Closed permissions are ignored",
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusCloseCode,
|
||||
},
|
||||
expectedResult: domain.Permissions{},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Multiple permissions with close status",
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusCloseCode,
|
||||
},
|
||||
expectedResult: domain.Permissions{},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tree.GetAllParentPermissionStatuses(tt.permissions)
|
||||
|
||||
// 檢查返回結果
|
||||
if !reflect.DeepEqual(result, tt.expectedResult) {
|
||||
t.Errorf("expected %v, got %v", tt.expectedResult, result)
|
||||
}
|
||||
|
||||
// 檢查錯誤
|
||||
if (err == nil && tt.expectedError != nil) || (err != nil && tt.expectedError == nil) {
|
||||
t.Errorf("expected error %v, got %v", tt.expectedError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 測試 isParentPermissionOpen 函數
|
||||
func TestIsParentPermissionOpen(t *testing.T) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
// 添加節點
|
||||
err := tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(2, entity.Permission{ID: 5, Name: "grandchild_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeID int64
|
||||
permissions domain.Permissions
|
||||
expectedOpen bool
|
||||
}{
|
||||
{
|
||||
name: "Parent has open child permission",
|
||||
nodeID: 2,
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedOpen: true,
|
||||
},
|
||||
{
|
||||
name: "Parent has no open child permissions",
|
||||
nodeID: 2,
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusCloseCode,
|
||||
},
|
||||
expectedOpen: false,
|
||||
},
|
||||
{
|
||||
name: "Parent with multiple children, one open",
|
||||
nodeID: 2,
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusCloseCode,
|
||||
"grandchild_permission_2": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedOpen: true,
|
||||
},
|
||||
{
|
||||
name: "Parent with no child permissions in list",
|
||||
nodeID: 3,
|
||||
permissions: domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedOpen: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
node, err := tree.FindPermissionByID(tt.nodeID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to find node with ID %d: %v", tt.nodeID, err)
|
||||
}
|
||||
|
||||
result := tree.isParentPermissionOpen(node, tt.permissions)
|
||||
if result != tt.expectedOpen {
|
||||
t.Errorf("expected %v, got %v", tt.expectedOpen, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 測試 GetRolePermissionTree 函數
|
||||
func TestGetRolePermissionTree(t *testing.T) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
// 添加節點
|
||||
err := tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
assert.NoError(t, err)
|
||||
err = tree.AddPermission(3, entity.Permission{ID: 5, Name: "grandchild_permission_2"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 測試數據
|
||||
tests := []struct {
|
||||
name string
|
||||
rolePermissions []entity.RolePermission
|
||||
expectedResult domain.Permissions
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Single role permission with parent and child",
|
||||
rolePermissions: []entity.RolePermission{
|
||||
{PermissionID: 4}, // grandchild_permission_1
|
||||
},
|
||||
expectedResult: domain.Permissions{
|
||||
"root_permission": domain.PermissionStatusOpenCode,
|
||||
"child_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Multiple role permissions with parents and children",
|
||||
rolePermissions: []entity.RolePermission{
|
||||
{PermissionID: 4}, // grandchild_permission_1
|
||||
{PermissionID: 5}, // grandchild_permission_2
|
||||
},
|
||||
expectedResult: domain.Permissions{
|
||||
"root_permission": domain.PermissionStatusOpenCode,
|
||||
"child_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
"child_permission_2": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_2": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Role permission with no children",
|
||||
rolePermissions: []entity.RolePermission{
|
||||
{PermissionID: 2}, // child_permission_1
|
||||
},
|
||||
expectedResult: domain.Permissions{
|
||||
"root_permission": domain.PermissionStatusOpenCode,
|
||||
"child_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Role permission not found",
|
||||
rolePermissions: []entity.RolePermission{
|
||||
{PermissionID: 99}, // non-existent permission
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: fmt.Errorf("permission with ID %d not found", 99),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tree.GetRolePermissionTree(tt.rolePermissions)
|
||||
|
||||
// 檢查返回結果
|
||||
if !reflect.DeepEqual(result, tt.expectedResult) {
|
||||
t.Errorf("expected %v, got %v", tt.expectedResult, result)
|
||||
}
|
||||
|
||||
// 檢查錯誤
|
||||
if (err == nil && tt.expectedError != nil) || (err != nil && tt.expectedError == nil) {
|
||||
t.Errorf("expected error %v, got %v", tt.expectedError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetAllParentPermissionIDs(b *testing.B) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
// 添加節點
|
||||
tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
tree.AddPermission(3, entity.Permission{ID: 5, Name: "grandchild_permission_2"})
|
||||
|
||||
permissions := domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_2": domain.PermissionStatusOpenCode,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = tree.GetAllParentPermissionIDs(permissions)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetAllParentPermissionStatuses(b *testing.B) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
// 添加節點
|
||||
tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
tree.AddPermission(3, entity.Permission{ID: 5, Name: "grandchild_permission_2"})
|
||||
|
||||
permissions := domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_2": domain.PermissionStatusOpenCode,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = tree.GetAllParentPermissionStatuses(permissions)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsParentPermissionOpen(b *testing.B) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
// 添加節點
|
||||
tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
tree.AddPermission(2, entity.Permission{ID: 5, Name: "grandchild_permission_2"})
|
||||
|
||||
permissions := domain.Permissions{
|
||||
"grandchild_permission_1": domain.PermissionStatusOpenCode,
|
||||
"grandchild_permission_2": domain.PermissionStatusCloseCode,
|
||||
}
|
||||
|
||||
node, _ := tree.FindPermissionByID(2)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tree.isParentPermissionOpen(node, permissions)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetRolePermissionTree(b *testing.B) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
// 添加節點
|
||||
tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
tree.AddPermission(3, entity.Permission{ID: 5, Name: "grandchild_permission_2"})
|
||||
|
||||
rolePermissions := []entity.RolePermission{
|
||||
{PermissionID: 4}, // grandchild_permission_1
|
||||
{PermissionID: 5}, // grandchild_permission_2
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = tree.GetRolePermissionTree(rolePermissions)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildNodePath(b *testing.B) {
|
||||
tree := NewPermissionTree()
|
||||
|
||||
// 添加節點
|
||||
tree.AddPermission(0, entity.Permission{ID: 1, Name: "root_permission"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 2, Name: "child_permission_1"})
|
||||
tree.AddPermission(1, entity.Permission{ID: 3, Name: "child_permission_2"})
|
||||
tree.AddPermission(2, entity.Permission{ID: 4, Name: "grandchild_permission_1"})
|
||||
|
||||
node, _ := tree.FindPermissionByID(4)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tree.buildNodePath(node, 4)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue