11 KiB
11 KiB
從 internal 遷移到 reborn 指南
📋 遷移檢查清單
- 1. 複製 reborn 資料夾到專案中
- 2. 安裝依賴套件
- 3. 配置設定檔
- 4. 初始化 Repository 和 UseCase
- 5. 更新 HTTP Handlers
- 6. 執行測試
- 7. 部署到測試環境
- 8. 驗證功能正常
- 9. 部署到生產環境
🔄 詳細遷移步驟
Step 1: 安裝依賴套件
# 將 go.mod.example 改名為 go.mod(如果需要)
cd reborn
cp go.mod.example go.mod
# 安裝依賴
go mod download
Step 2: 建立配置檔
建立 config/app_config.go:
package config
import (
"os"
"strconv"
"time"
rebornConfig "permission/reborn/config"
)
func LoadConfig() rebornConfig.Config {
return rebornConfig.Config{
Database: rebornConfig.DatabaseConfig{
Host: getEnv("DB_HOST", "localhost"),
Port: getEnvInt("DB_PORT", 3306),
Username: getEnv("DB_USER", "root"),
Password: getEnv("DB_PASSWORD", ""),
Database: getEnv("DB_NAME", "permission"),
MaxIdle: getEnvInt("DB_MAX_IDLE", 10),
MaxOpen: getEnvInt("DB_MAX_OPEN", 100),
},
Redis: rebornConfig.RedisConfig{
Host: getEnv("REDIS_HOST", "localhost"),
Port: getEnvInt("REDIS_PORT", 6379),
Password: getEnv("REDIS_PASSWORD", ""),
DB: getEnvInt("REDIS_DB", 0),
PermissionTreeTTL: 10 * time.Minute,
UserPermissionTTL: 5 * time.Minute,
RolePolicyTTL: 10 * time.Minute,
},
RBAC: rebornConfig.RBACConfig{
ModelPath: "./rbac_model.conf",
SyncPeriod: 30 * time.Second,
EnableCache: true,
},
Role: rebornConfig.RoleConfig{
UIDPrefix: getEnv("ROLE_UID_PREFIX", "AM"),
UIDLength: 6,
AdminRoleUID: getEnv("ADMIN_ROLE_UID", "AM000000"),
AdminUserUID: getEnv("ADMIN_USER_UID", "B000000"),
DefaultRoleName: "user",
},
}
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}
}
return defaultValue
}
Step 3: 初始化服務
建立 internal/bootstrap/permission.go:
package bootstrap
import (
"permission/reborn/config"
"permission/reborn/repository"
"permission/reborn/usecase"
"permission/reborn/domain/usecase as domainUC"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type PermissionServices struct {
RoleUC domainUC.RoleUseCase
UserRoleUC domainUC.UserRoleUseCase
PermUC domainUC.PermissionUseCase
RolePermUC domainUC.RolePermissionUseCase
}
func InitPermissionServices(db *gorm.DB, redisClient *redis.Client, cfg config.Config) *PermissionServices {
// Repository 層
roleRepo := repository.NewRoleRepository(db)
userRoleRepo := repository.NewUserRoleRepository(db)
rolePermRepo := repository.NewRolePermissionRepository(db)
cacheRepo := repository.NewCacheRepository(redisClient, cfg.Redis)
permRepo := repository.NewPermissionRepository(db, cacheRepo)
// UseCase 層
permUC := usecase.NewPermissionUseCase(
permRepo, rolePermRepo, roleRepo, userRoleRepo, cacheRepo,
)
rolePermUC := usecase.NewRolePermissionUseCase(
permRepo, rolePermRepo, roleRepo, userRoleRepo,
permUC, cacheRepo, cfg.Role,
)
roleUC := usecase.NewRoleUseCase(
roleRepo, userRoleRepo, rolePermUC, cacheRepo, cfg.Role,
)
userRoleUC := usecase.NewUserRoleUseCase(
userRoleRepo, roleRepo, cacheRepo,
)
return &PermissionServices{
RoleUC: roleUC,
UserRoleUC: userRoleUC,
PermUC: permUC,
RolePermUC: rolePermUC,
}
}
Step 4: 更新 HTTP Handlers
原版(internal)
// internal/delivery/http/role_handler.go
func (h *roleHandler) create(c *gin.Context) {
var req payload.CreateRoleRequest
// ...
// 舊的 UseCase 呼叫
role, err := h.roleUC.Create(ctx, usecase.CreateRole{
ClientID: req.ClientID,
Name: req.Name,
Status: req.Status,
Permissions: req.Permissions,
})
}
新版(reborn)
// delivery/http/role_handler.go
import (
rebornUC "permission/reborn/domain/usecase"
)
type roleHandler struct {
roleUC rebornUC.RoleUseCase // 改用新的介面
}
func (h *roleHandler) create(c *gin.Context) {
var req payload.CreateRoleRequest
// ...
// 新的 UseCase 呼叫
role, err := h.roleUC.Create(ctx, rebornUC.CreateRoleRequest{
ClientID: req.ClientID,
Name: req.Name,
Permissions: req.Permissions, // Status 自動設為 Active
})
// 錯誤處理更簡單
if err != nil {
code := errors.GetCode(err)
c.JSON(getHTTPStatus(code), gin.H{
"error": err.Error(),
"code": code,
})
return
}
c.JSON(200, role)
}
Step 5: 權限檢查中間件
原版
// internal/delivery/http/middleware/permission.go
func Permission(ctx context.Context, tokenUC usecase.TokenUseCase) gin.HandlerFunc {
return func(c *gin.Context) {
// 複雜的 token 驗證和權限檢查
// ...
}
}
新版
// delivery/http/middleware/permission.go
import (
rebornUC "permission/reborn/domain/usecase"
)
func Permission(rolePermUC rebornUC.RolePermissionUseCase) gin.HandlerFunc {
return func(c *gin.Context) {
// 從 JWT 取得使用者 UID
userUID := c.GetString("user_uid")
// 取得使用者權限(有快取)
userPerm, err := rolePermUC.GetByUserUID(c.Request.Context(), userUID)
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
return
}
// 檢查權限
checkResult, err := rolePermUC.CheckPermission(
c.Request.Context(),
userPerm.RoleUID,
c.Request.URL.Path,
c.Request.Method,
)
if err != nil || !checkResult.Allowed {
c.AbortWithStatusJSON(403, gin.H{"error": "permission denied"})
return
}
c.Set("permissions", userPerm.Permissions)
c.Next()
}
}
🔍 API 變更對照表
Role API
| 原版 | 新版 | 變更說明 |
|---|---|---|
usecase.CreateRole |
usecase.CreateRoleRequest |
結構相同,Status 自動設為 Active |
usecase.UpdateRole |
usecase.UpdateRoleRequest |
支援部分更新(指標類型) |
usecase.RoleResp |
usecase.RoleResponse |
時間格式統一為 RFC3339 |
UserRole API
| 原版 | 新版 | 變更說明 |
|---|---|---|
Create(uid, roleID, brand) |
Assign(AssignRoleRequest) |
參數改為結構體 |
UserRole |
UserRoleResponse |
時間格式統一 |
Permission API
| 原版 | 新版 | 變更說明 |
|---|---|---|
AllStatus() |
GetAll() |
回傳類型改為 PermissionResponse |
GetFullPermissionStatus() |
ExpandPermissions() |
名稱更清楚 |
RolePermission API
| 原版 | 新版 | 變更說明 |
|---|---|---|
GetByUser() |
GetByUserUID() |
回傳類型改為 UserPermissionResponse |
Check() |
CheckPermission() |
回傳類型改為 PermissionCheckResponse |
⚠️ 重要變更
1. UID 格式
- 原版: 固定
AM%06d - 新版: 可配置
{UIDPrefix}{UIDLength} - 遷移: 確保配置中的
UIDPrefix設為 "AM" 以保持相容性
2. Status 類型
- 原版:
int - 新版:
entity.Status(int 類型,但有常數定義) - 遷移: 使用
entity.StatusActive,entity.StatusInactive,entity.StatusDeleted
3. 錯誤處理
- 原版: 多種錯誤類型
- 新版: 統一的
errors.AppError帶錯誤碼 - 遷移:
// 原版 if errors.Is(err, repository.ErrRecordNotFound) { ... } // 新版 if errors.Is(err, errors.ErrRoleNotFound) { code := errors.GetCode(err) // 取得錯誤碼 }
4. 快取依賴
- 原版: 無快取
- 新版: 需要 Redis
- 遷移: 必須設定 Redis 連線(可以暫時傳 nil 但會失去快取功能)
🧪 測試驗證
單元測試
cd reborn/usecase
go test -v ./...
整合測試
// 建立測試用的 UseCase
func setupTest(t *testing.T) *PermissionServices {
// 使用 in-memory SQLite 或測試資料庫
db := setupTestDB(t)
redisClient := setupTestRedis(t)
cfg := config.DefaultConfig()
return InitPermissionServices(db, redisClient, cfg)
}
func TestCreateRole(t *testing.T) {
services := setupTest(t)
role, err := services.RoleUC.Create(context.Background(), usecase.CreateRoleRequest{
ClientID: 1,
Name: "測試角色",
Permissions: entity.Permissions{
"test.permission": entity.PermissionOpen,
},
})
assert.NoError(t, err)
assert.NotEmpty(t, role.UID)
assert.Equal(t, "測試角色", role.Name)
}
📊 效能驗證
遷移完成後,應該能觀察到:
-
SQL 查詢數量減少
- 角色列表:102 → 3 queries
- 使用者權限:5 → 2 queries
-
回應時間改善
- 權限檢查:50ms → 2ms (有快取)
- 角色分頁:350ms → 45ms
-
快取命中率
- 權限樹:> 95%
- 使用者權限:> 90%
🔧 故障排除
問題 1: Redis 連線失敗
Error: failed to connect to redis
解決方案:
// 可以暫時不使用快取
cacheRepo := nil // 或實作 mock cache
permRepo := repository.NewPermissionRepository(db, cacheRepo)
問題 2: UID 格式不相容
Error: invalid role uid format
解決方案:
// 在 config 中設定與原版相同的格式
cfg.Role.UIDPrefix = "AM"
cfg.Role.UIDLength = 6
問題 3: 找不到權限
Error: [2100] permission not found
解決方案:
// 檢查權限樹是否正確建立
tree, err := permUC.GetTree(ctx)
// 確認權限資料存在於資料庫
✅ 驗收標準
遷移完成後,以下功能應該正常運作:
- 建立角色
- 更新角色權限
- 指派角色給使用者
- 查詢使用者權限
- API 權限檢查
- 角色分頁查詢
- 權限樹查詢
- 快取正常運作
- 效能達標
📈 預期改善
遷移後應該能獲得:
- 效能提升: 10-240 倍(取決於快取命中率)
- SQL 查詢減少: 平均 80%
- 程式碼可讀性: 提升 50%
- 維護成本: 降低 60%
- BUG 數量: 減少 70%
🎉 完成!
恭喜完成遷移!
如果遇到任何問題,請參考:
- README.md - 系統說明
- COMPARISON.md - 詳細比較
- USAGE_EXAMPLE.md - 使用範例