backend/pkg/permission/repository/user_role.go

283 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}