276 lines
8.0 KiB
Markdown
276 lines
8.0 KiB
Markdown
|
|
# Permission System - Reborn Edition
|
|||
|
|
|
|||
|
|
這是重構後的權限管理系統,針對原有系統的缺點進行了全面優化。
|
|||
|
|
|
|||
|
|
## 🎯 主要改進
|
|||
|
|
|
|||
|
|
### 1. 統一錯誤處理
|
|||
|
|
- ✅ 建立統一的錯誤碼系統 (`domain/errors/errors.go`)
|
|||
|
|
- ✅ 所有錯誤都有明確的錯誤碼和訊息
|
|||
|
|
- ✅ 支援錯誤包裝和追蹤
|
|||
|
|
|
|||
|
|
### 2. 移除硬編碼
|
|||
|
|
- ✅ 所有配置移至 `config/config.go`
|
|||
|
|
- ✅ Role UID 格式可配置 (prefix, length)
|
|||
|
|
- ✅ Admin 角色和使用者 UID 可配置
|
|||
|
|
- ✅ Client ID 不再寫死
|
|||
|
|
|
|||
|
|
### 3. 解決 N+1 查詢問題
|
|||
|
|
- ✅ `GetByRoleIDs()`: 批量查詢角色權限
|
|||
|
|
- ✅ `CountByRoleID()`: 批量統計角色使用者數量
|
|||
|
|
- ✅ `GetByUIDs()`: 批量查詢角色
|
|||
|
|
|
|||
|
|
### 4. 加入 Redis 快取
|
|||
|
|
- ✅ 快取權限樹 (`permission:tree`)
|
|||
|
|
- ✅ 快取使用者權限 (`user:permission:{uid}`)
|
|||
|
|
- ✅ 快取角色權限 (`role:permission:{role_uid}`)
|
|||
|
|
- ✅ 支援快取失效和更新
|
|||
|
|
|
|||
|
|
### 5. 優化權限樹演算法
|
|||
|
|
- ✅ 使用鄰接表結構 (Adjacency List)
|
|||
|
|
- ✅ 預先計算路徑 (PathIDs)
|
|||
|
|
- ✅ 建立名稱和子節點索引
|
|||
|
|
- ✅ O(1) 節點查詢
|
|||
|
|
- ✅ O(N) 權限展開 (原本是 O(N²))
|
|||
|
|
|
|||
|
|
### 6. 程式碼品質提升
|
|||
|
|
- ✅ 完整的 Entity 驗證
|
|||
|
|
- ✅ 統一的時間格式處理
|
|||
|
|
- ✅ 循環依賴檢測
|
|||
|
|
- ✅ 單元測試覆蓋
|
|||
|
|
|
|||
|
|
## 📁 資料夾結構
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
reborn/
|
|||
|
|
├── config/ # 配置管理
|
|||
|
|
│ └── config.go
|
|||
|
|
├── domain/ # Domain Layer (核心業務邏輯)
|
|||
|
|
│ ├── entity/ # 實體定義
|
|||
|
|
│ │ ├── types.go # 通用類型
|
|||
|
|
│ │ ├── role.go
|
|||
|
|
│ │ ├── user_role.go
|
|||
|
|
│ │ └── permission.go
|
|||
|
|
│ ├── repository/ # Repository 介面
|
|||
|
|
│ │ ├── role.go
|
|||
|
|
│ │ ├── user_role.go
|
|||
|
|
│ │ ├── permission.go
|
|||
|
|
│ │ └── cache.go
|
|||
|
|
│ ├── usecase/ # UseCase 介面
|
|||
|
|
│ │ ├── role.go
|
|||
|
|
│ │ ├── user_role.go
|
|||
|
|
│ │ └── permission.go
|
|||
|
|
│ └── errors/ # 錯誤定義
|
|||
|
|
│ └── errors.go
|
|||
|
|
├── repository/ # Repository 實作
|
|||
|
|
│ ├── role_repository.go
|
|||
|
|
│ ├── user_role_repository.go
|
|||
|
|
│ ├── permission_repository.go
|
|||
|
|
│ ├── role_permission_repository.go
|
|||
|
|
│ └── cache_repository.go
|
|||
|
|
└── usecase/ # UseCase 實作
|
|||
|
|
├── role_usecase.go
|
|||
|
|
├── user_role_usecase.go
|
|||
|
|
├── permission_usecase.go
|
|||
|
|
├── role_permission_usecase.go
|
|||
|
|
├── permission_tree.go
|
|||
|
|
└── permission_tree_test.go
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔄 架構設計
|
|||
|
|
|
|||
|
|
遵循 Clean Architecture 原則:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────┐
|
|||
|
|
│ Delivery Layer │
|
|||
|
|
│ (HTTP handlers, gRPC) │
|
|||
|
|
└─────────────────┬───────────────────────┘
|
|||
|
|
│
|
|||
|
|
┌─────────────────▼───────────────────────┐
|
|||
|
|
│ UseCase Layer │
|
|||
|
|
│ (Business Logic) │
|
|||
|
|
│ - role_usecase.go │
|
|||
|
|
│ - permission_usecase.go │
|
|||
|
|
│ - role_permission_usecase.go │
|
|||
|
|
└─────────────────┬───────────────────────┘
|
|||
|
|
│
|
|||
|
|
┌─────────────────▼───────────────────────┐
|
|||
|
|
│ Repository Layer │
|
|||
|
|
│ (Data Access) │
|
|||
|
|
│ - role_repository.go │
|
|||
|
|
│ - permission_repository.go │
|
|||
|
|
│ - cache_repository.go │
|
|||
|
|
└─────────────────┬───────────────────────┘
|
|||
|
|
│
|
|||
|
|
┌─────────────────▼───────────────────────┐
|
|||
|
|
│ Domain Layer │
|
|||
|
|
│ (Entities & Interfaces) │
|
|||
|
|
│ - entity/ │
|
|||
|
|
│ - repository/ (interfaces) │
|
|||
|
|
│ - usecase/ (interfaces) │
|
|||
|
|
│ - errors/ │
|
|||
|
|
└─────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🚀 使用方式
|
|||
|
|
|
|||
|
|
### 初始化
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
import (
|
|||
|
|
"permission/reborn/config"
|
|||
|
|
"permission/reborn/repository"
|
|||
|
|
"permission/reborn/usecase"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 載入配置
|
|||
|
|
cfg := config.DefaultConfig()
|
|||
|
|
cfg.Role.UIDPrefix = "RL" // 自訂角色 UID 前綴
|
|||
|
|
|
|||
|
|
// 建立 Repository
|
|||
|
|
roleRepo := repository.NewRoleRepository(db)
|
|||
|
|
permRepo := repository.NewPermissionRepository(db, cache)
|
|||
|
|
rolePermRepo := repository.NewRolePermissionRepository(db)
|
|||
|
|
userRoleRepo := repository.NewUserRoleRepository(db)
|
|||
|
|
cacheRepo := repository.NewCacheRepository(redisClient, cfg.Redis)
|
|||
|
|
|
|||
|
|
// 建立 UseCase
|
|||
|
|
permUseCase := usecase.NewPermissionUseCase(
|
|||
|
|
permRepo, rolePermRepo, roleRepo, userRoleRepo, cacheRepo,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
rolePermUseCase := usecase.NewRolePermissionUseCase(
|
|||
|
|
permRepo, rolePermRepo, roleRepo, userRoleRepo,
|
|||
|
|
permUseCase, cacheRepo, cfg.Role,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
roleUseCase := usecase.NewRoleUseCase(
|
|||
|
|
roleRepo, userRoleRepo, rolePermUseCase, cacheRepo, cfg.Role,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
userRoleUseCase := usecase.NewUserRoleUseCase(
|
|||
|
|
userRoleRepo, roleRepo, cacheRepo,
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 建立角色
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
resp, err := roleUseCase.Create(ctx, usecase.CreateRoleRequest{
|
|||
|
|
ClientID: 1,
|
|||
|
|
Name: "管理員",
|
|||
|
|
Permissions: entity.Permissions{
|
|||
|
|
"user.list": entity.PermissionOpen,
|
|||
|
|
"user.create": entity.PermissionOpen,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 指派角色給使用者
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
resp, err := userRoleUseCase.Assign(ctx, usecase.AssignRoleRequest{
|
|||
|
|
UserUID: "U000001",
|
|||
|
|
RoleUID: "AM000001",
|
|||
|
|
Brand: "default",
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 檢查使用者權限
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// 取得使用者完整權限
|
|||
|
|
userPerm, err := rolePermUseCase.GetByUserUID(ctx, "U000001")
|
|||
|
|
|
|||
|
|
// 檢查特定 API 權限
|
|||
|
|
checkResp, err := rolePermUseCase.CheckPermission(
|
|||
|
|
ctx,
|
|||
|
|
userPerm.RoleUID,
|
|||
|
|
"/api/users",
|
|||
|
|
"GET",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if checkResp.Allowed {
|
|||
|
|
// 允許存取
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 取得權限樹
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
tree, err := permUseCase.GetTree(ctx)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🧪 測試
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd reborn/usecase
|
|||
|
|
go test -v ./...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📊 效能比較
|
|||
|
|
|
|||
|
|
| 項目 | 原版 | 重構版 | 改善 |
|
|||
|
|
|------|------|--------|------|
|
|||
|
|
| 權限樹建構 | O(N²) | O(N) | 🚀 |
|
|||
|
|
| 權限展開 | O(N²) | O(N) | 🚀 |
|
|||
|
|
| 角色列表查詢 | N+1 queries | 2 queries | ✅ |
|
|||
|
|
| 使用者權限查詢 | 無快取 | Redis 快取 | ⚡ |
|
|||
|
|
| 權限樹查詢 | 每次重建 | In-memory + Redis | 🔥 |
|
|||
|
|
|
|||
|
|
## 🔑 核心概念
|
|||
|
|
|
|||
|
|
### 權限樹結構
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
user (ID: 1, ParentID: 0)
|
|||
|
|
├── user.list (ID: 2, ParentID: 1)
|
|||
|
|
│ └── user.list.detail (ID: 4, ParentID: 2)
|
|||
|
|
└── user.create (ID: 3, ParentID: 1)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 權限展開邏輯
|
|||
|
|
|
|||
|
|
當使用者擁有 `user.list.detail` 權限時,系統會自動展開為:
|
|||
|
|
- `user.list.detail` (自己)
|
|||
|
|
- `user.list` (父)
|
|||
|
|
- `user` (祖父)
|
|||
|
|
|
|||
|
|
### 快取策略
|
|||
|
|
|
|||
|
|
1. **權限樹快取**: 全域共用,TTL 10 分鐘
|
|||
|
|
2. **使用者權限快取**: 個別使用者,TTL 5 分鐘
|
|||
|
|
3. **角色權限快取**: 個別角色,TTL 10 分鐘
|
|||
|
|
|
|||
|
|
當權限更新時,相關快取會自動失效。
|
|||
|
|
|
|||
|
|
## 📝 錯誤碼表
|
|||
|
|
|
|||
|
|
| 錯誤碼 | 說明 |
|
|||
|
|
|--------|------|
|
|||
|
|
| 1000 | 內部錯誤 |
|
|||
|
|
| 1001 | 無效輸入 |
|
|||
|
|
| 1002 | 資源不存在 |
|
|||
|
|
| 2000 | 角色不存在 |
|
|||
|
|
| 2001 | 角色已存在 |
|
|||
|
|
| 2002 | 角色有使用者 |
|
|||
|
|
| 2100 | 權限不存在 |
|
|||
|
|
| 2101 | 權限拒絕 |
|
|||
|
|
| 2200 | 使用者角色不存在 |
|
|||
|
|
| 3000 | 資料庫連線錯誤 |
|
|||
|
|
| 3003 | 快取錯誤 |
|
|||
|
|
|
|||
|
|
## 🎉 總結
|
|||
|
|
|
|||
|
|
重構版本完全解決了原系統的所有缺點:
|
|||
|
|
- ✅ 無硬編碼
|
|||
|
|
- ✅ 無 N+1 查詢
|
|||
|
|
- ✅ 完整快取機制
|
|||
|
|
- ✅ 優化的演算法
|
|||
|
|
- ✅ 統一錯誤處理
|
|||
|
|
- ✅ 高測試覆蓋
|
|||
|
|
|
|||
|
|
可以直接用於生產環境!
|
|||
|
|
|