package repository import ( "backend/pkg/library/mongo" "backend/pkg/permission/domain" "backend/pkg/permission/domain/entity" "backend/pkg/permission/domain/repository" "context" "errors" "strings" "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 PermissionRepositoryParam struct { Conf *mongo.Conf CacheConf cache.CacheConf DBOpts []mon.Option CacheOpts []cache.Option } type PermissionRepository struct { DB mongo.DocumentDBWithCacheUseCase } func NewPermissionRepository(param PermissionRepositoryParam) repository.PermissionRepository { e := entity.Permission{} documentDB, err := mongo.MustDocumentDBWithCache( param.Conf, e.CollectionName(), param.CacheConf, param.DBOpts, param.CacheOpts, ) if err != nil { panic(err) } return &PermissionRepository{ DB: documentDB, } } func (repo *PermissionRepository) FindOne(ctx context.Context, id string) (*entity.Permission, error) { var data entity.Permission rk := domain.GetPermissionIDRedisKey(id) oid, err := bson.ObjectIDFromHex(id) if err != nil { return nil, ErrInvalidObjectID } err = repo.DB.FindOne(ctx, rk, &data, bson.M{"_id": oid}) switch { case err == nil: return &data, nil case errors.Is(err, mon.ErrNotFound): return nil, ErrNotFound default: return nil, err } } func (repo *PermissionRepository) FindByName(ctx context.Context, name string) (*entity.Permission, error) { var data entity.Permission rk := domain.GetPermissionNameRedisKey(name) err := repo.DB.FindOne(ctx, rk, &data, bson.M{"name": name}) switch { case err == nil: return &data, nil case errors.Is(err, mon.ErrNotFound): return nil, ErrNotFound default: return nil, err } } func (repo *PermissionRepository) GetByNames(ctx context.Context, names []string) ([]*entity.Permission, error) { var data []*entity.Permission filter := bson.M{ "name": bson.M{"$in": names}, } err := repo.DB.GetClient().Find(ctx, &data, filter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return nil, ErrNotFound } return nil, err } return data, nil } func (repo *PermissionRepository) FindByHTTP(ctx context.Context, path, method string) (*entity.Permission, error) { var perm entity.Permission filter := bson.M{ "http_path": path, "http_method": strings.ToUpper(method), // 確保大小寫一致 } err := repo.DB.GetClient().FindOne(ctx, &perm, filter) switch { case err == nil: return &perm, nil case errors.Is(err, mon.ErrNotFound): return nil, ErrNotFound default: return nil, err } } func (repo *PermissionRepository) List(ctx context.Context, filter repository.PermissionFilter) ([]*entity.Permission, error) { var data []*entity.Permission // 建立查詢條件 bsonFilter := bson.M{} // 如果有指定類型 if filter.Type != nil { bsonFilter["type"] = *filter.Type } // 如果有指定狀態 if filter.Status != nil { bsonFilter["status"] = *filter.Status } // 如果有指定父 ID if filter.ParentID != nil { if *filter.ParentID == 0 { // 查詢根權限 (沒有父 ID 或父 ID 為空) bsonFilter["$or"] = []bson.M{ {"parent_id": bson.M{"$exists": false}}, {"parent_id": bson.ObjectID{}}, } } else { // 查詢特定父 ID 的子權限 bsonFilter["parent_id"] = *filter.ParentID } } err := repo.DB.GetClient().Find(ctx, &data, bsonFilter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return []*entity.Permission{}, nil } return nil, err } return data, nil } func (repo *PermissionRepository) ListActive(ctx context.Context) ([]*entity.Permission, error) { var data []*entity.Permission // 使用快取查詢啟用的權限 bsonFilter := bson.M{ "status": domain.RecordActive, } err := repo.DB.GetClient().Find(ctx, &data, bsonFilter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return []*entity.Permission{}, nil } return nil, err } return data, nil } func (repo *PermissionRepository) GetChildren(ctx context.Context, parentID int64) ([]*entity.Permission, error) { var data []*entity.Permission // 查詢指定父 ID 的子權限 bsonFilter := bson.M{ "parent_id": parentID, } err := repo.DB.GetClient().Find(ctx, &data, bsonFilter) if err != nil { if errors.Is(err, mon.ErrNotFound) { return []*entity.Permission{}, nil } return nil, err } return data, nil } // Index20251009001UP 建立 Permission 集合的索引 // 這個函數應該在應用啟動時或數據庫遷移時執行一次 func (repo *PermissionRepository) Index20251009001UP(ctx context.Context) (*mongodriver.Cursor, error) { // 1. 唯一索引:權限名稱必須唯一 // 等價於 db.permission.createIndex({"name": 1}, {unique: true}) repo.DB.PopulateIndex(ctx, "name", 1, true) // 2. 複合唯一稀疏索引:HTTP 路徑 + 方法的組合必須唯一(用於 API 權限) // 等價於 db.permission.createIndex({"http_path": 1, "http_method": 1}, {unique: true, sparse: true}) // 注意:sparse: true 表示只對存在這些欄位的文檔建立索引,避免 null 值衝突 repo.DB.PopulateSparseMultiIndex(ctx, []string{"http_path", "http_method"}, []int32{1, 1}, true) // 3. 查詢索引:按狀態查詢(例如 ListActive) // 等價於 db.permission.createIndex({"status": 1}) repo.DB.PopulateIndex(ctx, "status", 1, false) // 4. 查詢索引:按父 ID 查詢(用於獲取子權限) // 等價於 db.permission.createIndex({"parent_id": 1}) repo.DB.PopulateIndex(ctx, "parent_id", 1, false) // 5. 複合索引:按類型和狀態查詢(常用組合) // 等價於 db.permission.createIndex({"type": 1, "status": 1}) repo.DB.PopulateMultiIndex(ctx, []string{"type", "status"}, []int32{1, 1}, false) // 6. 時間戳索引:用於排序和時間範圍查詢 // 等價於 db.permission.createIndex({"create_time": 1}) repo.DB.PopulateIndex(ctx, "create_time", 1, false) // 返回所有索引列表 return repo.DB.GetClient().Indexes().List(ctx) }