283 lines
7.0 KiB
Go
283 lines
7.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"
|
|||
|
|
"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)
|
|||
|
|
}
|