package repository import ( "backend/pkg/library/mongo" "backend/pkg/permission/domain" "backend/pkg/permission/domain/entity" "backend/pkg/permission/domain/repository" "context" "errors" "time" "github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/mon" "go.mongodb.org/mongo-driver/v2/bson" mongodriver "go.mongodb.org/mongo-driver/v2/mongo" ) type RolePermissionRepositoryParam struct { Conf *mongo.Conf CacheConf cache.CacheConf DBOpts []mon.Option CacheOpts []cache.Option } type RolePermissionRepository struct { DB mongo.DocumentDBWithCacheUseCase } func NewRolePermissionRepository(param RolePermissionRepositoryParam) repository.RolePermissionRepository { e := entity.RolePermission{} documentDB, err := mongo.MustDocumentDBWithCache( param.Conf, e.CollectionName(), param.CacheConf, param.DBOpts, param.CacheOpts, ) if err != nil { panic(err) } return &RolePermissionRepository{ DB: documentDB, } } // Create 建立角色權限關聯 func (repo *RolePermissionRepository) Create(ctx context.Context, roleID int64, permissionIDs []int64) error { if len(permissionIDs) == 0 { return nil } now := time.Now().Unix() // 將 int64 轉換為 ObjectID roleOID := bson.NewObjectIDFromTimestamp(time.Unix(roleID, 0)) // 批量建立角色權限關聯 documents := make([]interface{}, 0, len(permissionIDs)) for _, permissionID := range permissionIDs { permOID := bson.NewObjectIDFromTimestamp(time.Unix(permissionID, 0)) rp := &entity.RolePermission{ ID: bson.NewObjectID(), RoleID: roleOID, PermissionID: permOID, } rp.CreateTime = now rp.UpdateTime = now documents = append(documents, rp) } _, err := repo.DB.GetClient().InsertMany(ctx, documents) if err != nil { return err } // 清除快取 repo.clearRolePermissionCache(ctx, roleID) return nil } // Update 更新角色權限關聯 (先刪除再建立) func (repo *RolePermissionRepository) Update(ctx context.Context, roleID int64, permissionIDs []int64) error { // 先刪除該角色的所有權限關聯 err := repo.Delete(ctx, roleID) if err != nil { return err } // 再建立新的關聯 return repo.Create(ctx, roleID, permissionIDs) } // Delete 刪除角色的所有權限 func (repo *RolePermissionRepository) Delete(ctx context.Context, roleID int64) error { // 將 int64 轉換為 ObjectID roleOID := bson.NewObjectIDFromTimestamp(time.Unix(roleID, 0)) filter := bson.M{"role_id": roleOID} _, err := repo.DB.GetClient().DeleteMany(ctx, filter) if err != nil { return err } // 清除快取 repo.clearRolePermissionCache(ctx, roleID) return nil } // GetByRoleID 取得角色的所有權限關聯 func (repo *RolePermissionRepository) GetByRoleID(ctx context.Context, roleID int64) ([]*entity.RolePermission, error) { var data []*entity.RolePermission // 將 int64 轉換為 ObjectID roleOID := bson.NewObjectIDFromTimestamp(time.Unix(roleID, 0)) filter := bson.M{"role_id": roleOID} err := repo.DB.GetClient().Find(ctx, &data, filter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return []*entity.RolePermission{}, nil } return nil, err } return data, nil } // GetByRoleIDs 批量取得多個角色的權限關聯 (優化 N+1 查詢) func (repo *RolePermissionRepository) GetByRoleIDs(ctx context.Context, roleIDs []int64) (map[int64][]*entity.RolePermission, error) { if len(roleIDs) == 0 { return make(map[int64][]*entity.RolePermission), nil } var data []*entity.RolePermission // 將 int64 轉換為 ObjectID roleOIDs := make([]bson.ObjectID, 0, len(roleIDs)) oidToInt64 := make(map[string]int64) // 用於反向映射 for _, roleID := range roleIDs { roleOID := bson.NewObjectIDFromTimestamp(time.Unix(roleID, 0)) roleOIDs = append(roleOIDs, roleOID) oidToInt64[roleOID.Hex()] = roleID } filter := bson.M{ "role_id": bson.M{"$in": roleOIDs}, } err := repo.DB.GetClient().Find(ctx, &data, filter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return make(map[int64][]*entity.RolePermission), nil } return nil, err } // 將結果按 roleID (int64) 分組 result := make(map[int64][]*entity.RolePermission) for _, rp := range data { // 將 ObjectID 轉回 int64 roleIDInt64 := oidToInt64[rp.RoleID.Hex()] result[roleIDInt64] = append(result[roleIDInt64], rp) } return result, nil } // GetByPermissionIDs 根據權限 ID 取得所有角色關聯 func (repo *RolePermissionRepository) GetByPermissionIDs(ctx context.Context, permissionIDs []int64) ([]*entity.RolePermission, error) { if len(permissionIDs) == 0 { return []*entity.RolePermission{}, nil } var data []*entity.RolePermission // 將 int64 轉換為 ObjectID permOIDs := make([]bson.ObjectID, 0, len(permissionIDs)) for _, permID := range permissionIDs { permOID := bson.NewObjectIDFromTimestamp(time.Unix(permID, 0)) permOIDs = append(permOIDs, permOID) } filter := bson.M{ "permission_id": bson.M{"$in": permOIDs}, } err := repo.DB.GetClient().Find(ctx, &data, filter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return []*entity.RolePermission{}, nil } return nil, err } return data, nil } // GetRolesByPermission 根據權限 ID 取得所有角色 ID func (repo *RolePermissionRepository) GetRolesByPermission(ctx context.Context, permissionID int64) ([]int64, error) { var data []*entity.RolePermission // 將 int64 轉換為 ObjectID permOID := bson.NewObjectIDFromTimestamp(time.Unix(permissionID, 0)) filter := bson.M{"permission_id": permOID} err := repo.DB.GetClient().Find(ctx, &data, filter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return []int64{}, nil } return nil, err } // 提取所有 roleID 並轉換回 int64 roleIDs := make([]int64, 0, len(data)) for _, rp := range data { // 將 ObjectID 轉換回 int64 (取 timestamp) roleIDInt64 := rp.RoleID.Timestamp().Unix() roleIDs = append(roleIDs, roleIDInt64) } return roleIDs, nil } // clearRolePermissionCache 清除角色權限關聯快取 func (repo *RolePermissionRepository) clearRolePermissionCache(ctx context.Context, roleID int64) { rk := domain.GetRolePermissionRedisKey(roleID) _ = repo.DB.DelCache(ctx, rk) } // Index20251009003UP 建立 RolePermission 集合的索引 // 這個函數應該在應用啟動時或數據庫遷移時執行一次 func (repo *RolePermissionRepository) Index20251009003UP(ctx context.Context) (*mongodriver.Cursor, error) { // 1. 複合唯一索引:角色 ID + 權限 ID 的組合必須唯一(避免重複關聯) // 等價於 db.role_permission.createIndex({"role_id": 1, "permission_id": 1}, {unique: true}) repo.DB.PopulateMultiIndex(ctx, []string{"role_id", "permission_id"}, []int32{1, 1}, true) // 2. 查詢索引:按角色 ID 查詢(用於獲取某角色的所有權限) // 等價於 db.role_permission.createIndex({"role_id": 1}) repo.DB.PopulateIndex(ctx, "role_id", 1, false) // 3. 查詢索引:按權限 ID 查詢(用於獲取擁有某權限的所有角色) // 等價於 db.role_permission.createIndex({"permission_id": 1}) repo.DB.PopulateIndex(ctx, "permission_id", 1, false) // 4. 複合索引:按權限 ID 和狀態查詢 // 等價於 db.role_permission.createIndex({"permission_id": 1, "status": 1}) repo.DB.PopulateMultiIndex(ctx, []string{"permission_id", "status"}, []int32{1, 1}, false) // 5. 時間戳索引:用於排序和時間範圍查詢 // 等價於 db.role_permission.createIndex({"create_time": 1}) repo.DB.PopulateIndex(ctx, "create_time", 1, false) // 返回所有索引列表 return repo.DB.GetClient().Indexes().List(ctx) }