228 lines
6.0 KiB
Go
228 lines
6.0 KiB
Go
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)
|
||
}
|