346 lines
8.4 KiB
Go
346 lines
8.4 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"
|
|
"fmt"
|
|
"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 RoleRepositoryParam struct {
|
|
Conf *mongo.Conf
|
|
CacheConf cache.CacheConf
|
|
DBOpts []mon.Option
|
|
CacheOpts []cache.Option
|
|
}
|
|
|
|
type RoleRepository struct {
|
|
DB mongo.DocumentDBWithCacheUseCase
|
|
}
|
|
|
|
func NewRoleRepository(param RoleRepositoryParam) repository.RoleRepository {
|
|
e := entity.Role{}
|
|
documentDB, err := mongo.MustDocumentDBWithCache(
|
|
param.Conf,
|
|
e.CollectionName(),
|
|
param.CacheConf,
|
|
param.DBOpts,
|
|
param.CacheOpts,
|
|
)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return &RoleRepository{
|
|
DB: documentDB,
|
|
}
|
|
}
|
|
|
|
// Create 建立角色
|
|
func (repo *RoleRepository) Create(ctx context.Context, role *entity.Role) error {
|
|
if role.ID.IsZero() {
|
|
role.ID = bson.NewObjectID()
|
|
}
|
|
|
|
// 設定時間戳記
|
|
now := time.Now().Unix()
|
|
role.CreateTime = now
|
|
role.UpdateTime = now
|
|
|
|
_, err := repo.DB.GetClient().InsertOne(ctx, role)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 清除相關快取
|
|
repo.clearRoleCache(ctx, role)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update 更新角色
|
|
func (repo *RoleRepository) Update(ctx context.Context, role *entity.Role) error {
|
|
// 更新時間戳記
|
|
role.UpdateTime = time.Now().Unix()
|
|
|
|
filter := bson.M{"_id": role.ID}
|
|
update := bson.M{"$set": role}
|
|
|
|
_, err := repo.DB.GetClient().UpdateOne(ctx, filter, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 清除相關快取
|
|
repo.clearRoleCache(ctx, role)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete 刪除角色 (軟刪除)
|
|
func (repo *RoleRepository) Delete(ctx context.Context, uid string) error {
|
|
now := time.Now().Unix()
|
|
|
|
filter := bson.M{"uid": uid}
|
|
update := bson.M{
|
|
"$set": bson.M{
|
|
"status": domain.RecordDeleted,
|
|
"update_time": now,
|
|
},
|
|
}
|
|
|
|
_, err := repo.DB.GetClient().UpdateOne(ctx, filter, update)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 清除快取
|
|
rk := domain.GetRoleUIDRedisKey(uid)
|
|
_ = repo.DB.DelCache(ctx, rk)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get 取得單一角色 (by ID)
|
|
func (repo *RoleRepository) Get(ctx context.Context, id int64) (*entity.Role, error) {
|
|
var data entity.Role
|
|
rk := domain.GetRoleIDRedisKey(id)
|
|
|
|
// 將 int64 ID 轉換為 ObjectID (假設 ID 可以轉換為 hex)
|
|
// 注意:這裡可能需要根據實際的 ID 生成策略調整
|
|
oid := bson.NewObjectIDFromTimestamp(time.Unix(id, 0))
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
// GetByUID 取得單一角色 (by UID)
|
|
func (repo *RoleRepository) GetByUID(ctx context.Context, uid string) (*entity.Role, error) {
|
|
var data entity.Role
|
|
rk := domain.GetRoleUIDRedisKey(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
|
|
}
|
|
}
|
|
|
|
// GetByUIDs 批量取得角色 (by UIDs)
|
|
func (repo *RoleRepository) GetByUIDs(ctx context.Context, uids []string) ([]*entity.Role, error) {
|
|
var data []*entity.Role
|
|
|
|
filter := bson.M{
|
|
"uid": bson.M{"$in": uids},
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// List 列出所有角色
|
|
func (repo *RoleRepository) List(ctx context.Context, filter repository.RoleFilter) ([]*entity.Role, error) {
|
|
var data []*entity.Role
|
|
|
|
// 建立查詢條件
|
|
bsonFilter := bson.M{}
|
|
|
|
// 如果有指定 ClientID
|
|
if filter.ClientID > 0 {
|
|
bsonFilter["client_id"] = filter.ClientID
|
|
}
|
|
|
|
// 如果有指定 UID
|
|
if filter.UID != "" {
|
|
bsonFilter["uid"] = filter.UID
|
|
}
|
|
|
|
// 如果有指定 Name (模糊搜尋)
|
|
if filter.Name != "" {
|
|
bsonFilter["name"] = bson.M{"$regex": filter.Name, "$options": "i"}
|
|
}
|
|
|
|
// 如果有指定狀態
|
|
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.Role{}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// Page 分頁查詢角色
|
|
func (repo *RoleRepository) Page(ctx context.Context, filter repository.RoleFilter, page, size int) ([]*entity.Role, int64, error) {
|
|
var data []*entity.Role
|
|
|
|
// 建立查詢條件
|
|
bsonFilter := bson.M{}
|
|
|
|
// 如果有指定 ClientID
|
|
if filter.ClientID > 0 {
|
|
bsonFilter["client_id"] = filter.ClientID
|
|
}
|
|
|
|
// 如果有指定 UID
|
|
if filter.UID != "" {
|
|
bsonFilter["uid"] = filter.UID
|
|
}
|
|
|
|
// 如果有指定 Name (模糊搜尋)
|
|
if filter.Name != "" {
|
|
bsonFilter["name"] = bson.M{"$regex": filter.Name, "$options": "i"}
|
|
}
|
|
|
|
// 如果有指定狀態
|
|
if filter.Status != nil {
|
|
bsonFilter["status"] = *filter.Status
|
|
}
|
|
|
|
// 計算總數
|
|
total, err := repo.DB.GetClient().CountDocuments(ctx, bsonFilter)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// 如果沒有資料,直接返回
|
|
if total == 0 {
|
|
return []*entity.Role{}, 0, nil
|
|
}
|
|
|
|
// 計算分頁參數
|
|
skip := int64((page - 1) * size)
|
|
limit := int64(size)
|
|
|
|
// 查詢資料
|
|
findOptions := options.Find().
|
|
SetSkip(skip).
|
|
SetLimit(limit).
|
|
SetSort(bson.M{"create_time": -1}) // 依建立時間降序排列
|
|
|
|
err = repo.DB.GetClient().Find(ctx, &data, bsonFilter, findOptions)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return data, total, nil
|
|
}
|
|
|
|
// Exists 檢查角色是否存在
|
|
func (repo *RoleRepository) 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
|
|
}
|
|
|
|
// clearRoleCache 清除角色相關快取
|
|
func (repo *RoleRepository) clearRoleCache(ctx context.Context, role *entity.Role) {
|
|
// 清除 UID 快取
|
|
if role.UID != "" {
|
|
rk := domain.GetRoleUIDRedisKey(role.UID)
|
|
_ = repo.DB.DelCache(ctx, rk)
|
|
}
|
|
}
|
|
|
|
// NextID 取得下一個角色 ID
|
|
// 使用 MongoDB 的 findOneAndUpdate 原子操作來生成自增 ID
|
|
func (repo *RoleRepository) NextID(ctx context.Context) (int64, error) {
|
|
// 使用一個特殊的文檔來存儲計數器
|
|
filter := bson.M{"_id": "role_counter"}
|
|
update := bson.M{
|
|
"$inc": bson.M{"seq": 1},
|
|
}
|
|
|
|
var result struct {
|
|
ID string `bson:"_id"`
|
|
Seq int64 `bson:"seq"`
|
|
}
|
|
|
|
opts := options.FindOneAndUpdate().
|
|
SetUpsert(true).
|
|
SetReturnDocument(options.After)
|
|
|
|
err := repo.DB.GetClient().FindOneAndUpdate(ctx, &result, filter, update, opts)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to generate next ID: %w", err)
|
|
}
|
|
|
|
return result.Seq, nil
|
|
}
|
|
|
|
// Index20251009002UP 建立 Role 集合的索引
|
|
// 這個函數應該在應用啟動時或數據庫遷移時執行一次
|
|
func (repo *RoleRepository) Index20251009002UP(ctx context.Context) (*mongodriver.Cursor, error) {
|
|
// 1. 唯一索引:角色 UID 必須唯一
|
|
// 等價於 db.role.createIndex({"uid": 1}, {unique: true})
|
|
repo.DB.PopulateIndex(ctx, "uid", 1, true)
|
|
|
|
// 2. 複合唯一索引:同一個 Client 下角色名稱必須唯一
|
|
// 等價於 db.role.createIndex({"client_id": 1, "name": 1}, {unique: true})
|
|
repo.DB.PopulateMultiIndex(ctx, []string{"client_id", "name"}, []int32{1, 1}, true)
|
|
|
|
// 3. 查詢索引:按 Client ID 查詢
|
|
// 等價於 db.role.createIndex({"client_id": 1})
|
|
repo.DB.PopulateIndex(ctx, "client_id", 1, false)
|
|
|
|
// 4. 查詢索引:按狀態查詢
|
|
// 等價於 db.role.createIndex({"status": 1})
|
|
repo.DB.PopulateIndex(ctx, "status", 1, false)
|
|
|
|
// 5. 複合索引:按 Client ID 和狀態查詢(常用組合)
|
|
// 等價於 db.role.createIndex({"client_id": 1, "status": 1})
|
|
repo.DB.PopulateMultiIndex(ctx, []string{"client_id", "status"}, []int32{1, 1}, false)
|
|
|
|
// 6. 文本索引:支持角色名稱模糊搜索
|
|
// 注意:如果已經有文本索引,這個可能會衝突,可以根據需要調整
|
|
// repo.DB.PopulateIndex(ctx, "name", 1, false)
|
|
|
|
// 7. 時間戳索引:用於排序和時間範圍查詢
|
|
// 等價於 db.role.createIndex({"create_time": 1})
|
|
repo.DB.PopulateIndex(ctx, "create_time", 1, false)
|
|
|
|
// 8. 時間戳索引:用於更新時間排序
|
|
// 等價於 db.role.createIndex({"update_time": -1})
|
|
repo.DB.PopulateIndex(ctx, "update_time", -1, false)
|
|
|
|
// 返回所有索引列表
|
|
return repo.DB.GetClient().Indexes().List(ctx)
|
|
}
|