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" "go.mongodb.org/mongo-driver/v2/mongo/options" ) type UserRoleRepositoryParam struct { Conf *mongo.Conf CacheConf cache.CacheConf DBOpts []mon.Option CacheOpts []cache.Option } type UserRoleRepository struct { DB mongo.DocumentDBWithCacheUseCase } func NewUserRoleRepository(param UserRoleRepositoryParam) repository.UserRoleRepository { e := entity.UserRole{} documentDB, err := mongo.MustDocumentDBWithCache( param.Conf, e.CollectionName(), param.CacheConf, param.DBOpts, param.CacheOpts, ) if err != nil { panic(err) } return &UserRoleRepository{ DB: documentDB, } } // Create 建立使用者角色 func (repo *UserRoleRepository) Create(ctx context.Context, userRole *entity.UserRole) error { if userRole.ID.IsZero() { userRole.ID = bson.NewObjectID() } // 設定時間戳記 now := time.Now().Unix() userRole.CreateTime = now userRole.UpdateTime = now _, err := repo.DB.GetClient().InsertOne(ctx, userRole) if err != nil { return err } // 清除相關快取 repo.clearUserRoleCache(ctx, userRole.UID) return nil } // Update 更新使用者角色 func (repo *UserRoleRepository) Update(ctx context.Context, uid, roleID string) (*entity.UserRole, error) { now := time.Now().Unix() filter := bson.M{"uid": uid} update := bson.M{ "$set": bson.M{ "role_id": roleID, "update_time": now, }, } // 設置選項:返回更新後的文檔 opts := options.FindOneAndUpdate().SetReturnDocument(options.After) var result entity.UserRole err := repo.DB.GetClient().FindOneAndUpdate(ctx, &result, filter, update, opts) if err != nil { if errors.Is(err, mon.ErrNotFound) { return nil, ErrNotFound } return nil, err } // 清除快取 repo.clearUserRoleCache(ctx, uid) return &result, nil } // Delete 刪除使用者角色 func (repo *UserRoleRepository) Delete(ctx context.Context, uid string) error { filter := bson.M{"uid": uid} _, err := repo.DB.GetClient().DeleteOne(ctx, filter) if err != nil { return err } // 清除快取 repo.clearUserRoleCache(ctx, uid) return nil } // Get 取得使用者角色 func (repo *UserRoleRepository) Get(ctx context.Context, uid string) (*entity.UserRole, error) { var data entity.UserRole rk := domain.GetUserRoleUIDRedisKey(uid) err := repo.DB.FindOne(ctx, rk, &data, bson.M{"uid": uid}) switch { case err == nil: return &data, nil case errors.Is(err, mon.ErrNotFound): return nil, ErrNotFound default: return nil, err } } // GetByRoleID 根據角色 ID 取得所有使用者 func (repo *UserRoleRepository) GetByRoleID(ctx context.Context, roleID string) ([]*entity.UserRole, error) { var data []*entity.UserRole filter := bson.M{"role_id": roleID} err := repo.DB.GetClient().Find(ctx, &data, filter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return []*entity.UserRole{}, nil } return nil, err } return data, nil } // List 列出所有使用者角色 func (repo *UserRoleRepository) List(ctx context.Context, filter repository.UserRoleFilter) ([]*entity.UserRole, error) { var data []*entity.UserRole // 建立查詢條件 bsonFilter := bson.M{} // 如果有指定 Brand if filter.Brand != "" { bsonFilter["brand"] = filter.Brand } // 如果有指定 RoleID if filter.RoleID != "" { bsonFilter["role_id"] = filter.RoleID } // 如果有指定狀態 if filter.Status != nil { bsonFilter["status"] = *filter.Status } err := repo.DB.GetClient().Find(ctx, &data, bsonFilter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return []*entity.UserRole{}, nil } return nil, err } return data, nil } // CountByRoleID 統計每個角色的使用者數量 func (repo *UserRoleRepository) CountByRoleID(ctx context.Context, roleIDs []string) (map[string]int, error) { if len(roleIDs) == 0 { return make(map[string]int), nil } // 使用 MongoDB aggregation pipeline 進行分組統計 pipeline := []bson.M{ { "$match": bson.M{ "role_id": bson.M{"$in": roleIDs}, }, }, { "$group": bson.M{ "_id": "$role_id", "count": bson.M{"$sum": 1}, }, }, } cursor, err := repo.DB.GetClient().Collection.Aggregate(ctx, pipeline) if err != nil { return nil, err } defer cursor.Close(ctx) // 解析結果 result := make(map[string]int) for cursor.Next(ctx) { var item struct { ID string `bson:"_id"` Count int `bson:"count"` } if err := cursor.Decode(&item); err != nil { continue } result[item.ID] = item.Count } // 確保所有傳入的 roleID 都有對應的計數(沒有使用者的角色計數為 0) for _, roleID := range roleIDs { if _, exists := result[roleID]; !exists { result[roleID] = 0 } } return result, nil } // Exists 檢查使用者是否已有角色 func (repo *UserRoleRepository) Exists(ctx context.Context, uid string) (bool, error) { count, err := repo.DB.GetClient().CountDocuments(ctx, bson.M{"uid": uid}) if err != nil { return false, err } return count > 0, nil } // clearUserRoleCache 清除使用者角色相關快取 func (repo *UserRoleRepository) clearUserRoleCache(ctx context.Context, uid string) { if uid != "" { rk := domain.GetUserRoleUIDRedisKey(uid) _ = repo.DB.DelCache(ctx, rk) } } // Index20251009004UP 建立 UserRole 集合的索引 // 這個函數應該在應用啟動時或數據庫遷移時執行一次 func (repo *UserRoleRepository) Index20251009004UP(ctx context.Context) (*mongodriver.Cursor, error) { // 1. 唯一索引:使用者 UID 必須唯一(一個使用者只能有一個角色) // 等價於 db.user_role.createIndex({"uid": 1}, {unique: true}) repo.DB.PopulateIndex(ctx, "uid", 1, true) // 2. 查詢索引:按角色 ID 查詢(用於獲取某角色的所有使用者) // 等價於 db.user_role.createIndex({"role_id": 1}) repo.DB.PopulateIndex(ctx, "role_id", 1, false) // 3. 查詢索引:按 Brand 查詢 // 等價於 db.user_role.createIndex({"brand": 1}) repo.DB.PopulateIndex(ctx, "brand", 1, false) // 4. 查詢索引:按狀態查詢 // 等價於 db.user_role.createIndex({"status": 1}) repo.DB.PopulateIndex(ctx, "status", 1, false) // 5. 複合索引:按 Brand 和角色 ID 查詢(常用組合) // 等價於 db.user_role.createIndex({"brand": 1, "role_id": 1}) repo.DB.PopulateMultiIndex(ctx, []string{"brand", "role_id"}, []int32{1, 1}, false) // 6. 複合索引:按 Brand 和狀態查詢 // 等價於 db.user_role.createIndex({"brand": 1, "status": 1}) repo.DB.PopulateMultiIndex(ctx, []string{"brand", "status"}, []int32{1, 1}, false) // 7. 時間戳索引:用於排序和時間範圍查詢 // 等價於 db.user_role.createIndex({"create_time": 1}) repo.DB.PopulateIndex(ctx, "create_time", 1, false) // 返回所有索引列表 return repo.DB.GetClient().Indexes().List(ctx) }