package repository import ( "context" "errors" "fmt" "time" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository" mgo "code.30cm.net/digimon/library-go/mongo" "github.com/zeromicro/go-zero/core/stores/cache" "github.com/zeromicro/go-zero/core/stores/mon" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type ProductItemRepositoryParam struct { Conf *mgo.Conf CacheConf cache.CacheConf DBOpts []mon.Option CacheOpts []cache.Option } type ProductItemRepository struct { DB mgo.DocumentDBWithCacheUseCase } func NewProductItemRepository(param ProductItemRepositoryParam) repository.ProductItemRepository { e := entity.ProductItems{} documentDB, err := mgo.MustDocumentDBWithCache( param.Conf, e.CollectionName(), param.CacheConf, param.DBOpts, param.CacheOpts, ) if err != nil { panic(err) } return &ProductItemRepository{ DB: documentDB, } } func (repo *ProductItemRepository) Insert(ctx context.Context, items []entity.ProductItems) error { now := time.Now().UTC().UnixNano() docs := make([]any, 0, len(items)) for i := range items { if items[i].ID.IsZero() { items[i].ID = primitive.NewObjectID() items[i].CreatedAt = now items[i].UpdatedAt = now } docs = append(docs, items[i]) } if len(docs) == 0 { return nil } _, err := repo.DB.GetClient().InsertMany(ctx, docs) return err } func (repo *ProductItemRepository) FindByID(ctx context.Context, id string) (*entity.ProductItems, error) { oid, err := primitive.ObjectIDFromHex(id) if err != nil { return nil, ErrInvalidObjectID } var data entity.ProductItems rk := domain.GetProductItemRK(id) 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 *ProductItemRepository) Delete(ctx context.Context, ids []string) error { if len(ids) == 0 { return nil } objectIDs := make([]primitive.ObjectID, 0, len(ids)) cacheKeys := make([]string, 0, len(ids)) for _, id := range ids { oid, err := primitive.ObjectIDFromHex(id) if err != nil { return ErrInvalidObjectID } objectIDs = append(objectIDs, oid) cacheKeys = append(cacheKeys, domain.GetProductItemRK(id)) } // 刪除多筆 Mongo 資料 filter := bson.M{"_id": bson.M{"$in": objectIDs}} _, err := repo.DB.GetClient().DeleteMany(ctx, filter) // 可選:刪除 Redis 快取(每個 ID 都清除) _ = repo.DB.DelCache(ctx, cacheKeys...) return err } func (repo *ProductItemRepository) Update(ctx context.Context, id string, param *repository.ProductUpdateItem) error { // 將 `id` 轉換為 MongoDB 的 ObjectID objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return ErrInvalidObjectID } // 構建更新文檔 setFields := bson.M{} // 檢查並添加需要更新的欄位 if param.Name != nil { setFields["name"] = *param.Name } if param.Description != nil { setFields["description"] = *param.Description } if param.ShortDescription != nil { setFields["short_description"] = *param.ShortDescription } if param.Price != nil { setFields["price"] = *param.Price } if param.Stock != nil { setFields["stock"] = *param.Stock } if param.IsUnLimit != nil { setFields["is_un_limit"] = *param.IsUnLimit } if param.IsFree != nil { setFields["is_free"] = *param.IsFree } if param.SKU != nil { setFields["sku"] = *param.SKU } if param.TimeSeries != nil { setFields["time_series"] = *param.TimeSeries } if len(param.Media) > 0 { setFields["media"] = param.Media } if param.CustomFields != nil { setFields["custom_fields"] = param.CustomFields } if param.Freight != nil { setFields["freight"] = param.Freight } // 如果沒有任何需要更新的內容,直接返回 if len(setFields) == 0 { return fmt.Errorf("no fields to update") } // 執行更新操作 filter := bson.M{"_id": objectID} rk := domain.GetProductItemRK(id) _, err = repo.DB.UpdateOne(ctx, rk, filter, bson.M{ "$set": setFields, }) if err != nil { return fmt.Errorf("failed to update product item: %w", err) } return nil } func (repo *ProductItemRepository) DeleteByReferenceID(ctx context.Context, id string) error { // 1. 查詢所有符合 reference_id 的項目,只取 _id 欄位 filter := bson.M{"reference_id": id} projection := bson.M{"_id": 1} opts := options.Find().SetProjection(projection) var items []entity.ProductItems err := repo.DB.GetClient().Find(ctx, &items, filter, opts) if err != nil { return err } // 2. 刪除這些 ID 對應的 Redis 快取 cacheKeys := make([]string, 0, len(items)) for _, item := range items { cacheKeys = append(cacheKeys, domain.GetProductItemRK(item.ID.Hex())) } if len(cacheKeys) > 0 { _ = repo.DB.DelCache(ctx, cacheKeys...) } // 3. 刪除 DB 中的資料 delFilter := bson.M{"reference_id": id} _, err = repo.DB.GetClient().DeleteMany(ctx, delFilter) if err != nil { return err } return nil } func (repo *ProductItemRepository) ListProductItem(ctx context.Context, param repository.ProductItemQueryParams) ([]entity.ProductItems, int64, error) { // 構建查詢過濾器 filter := bson.M{} ids := make([]primitive.ObjectID, 0, len(param.ItemID)) for _, item := range param.ItemID { oid, err := primitive.ObjectIDFromHex(item) if err != nil { continue } ids = append(ids, oid) } // 添加篩選條件 if len(param.ItemID) > 0 { filter["_id"] = bson.M{"$in": ids} } if param.ReferenceID != nil { filter["reference_id"] = *param.ReferenceID } if param.IsFree != nil { filter["is_free"] = *param.IsFree } if param.Status != nil { filter["status"] = *param.Status } // 設置排序選項 opts := options.Find().SetSkip((param.PageIndex - 1) * param.PageSize).SetLimit(param.PageSize) opts.SetSort(bson.D{{Key: "created_at", Value: -1}}) // 查詢符合條件的總數 count, err := repo.DB.GetClient().CountDocuments(ctx, filter) if err != nil { return nil, 0, err } // 執行查詢並獲取結果 var products []entity.ProductItems err = repo.DB.GetClient().Find(ctx, &products, filter, opts) if err != nil { return nil, 0, err } return products, count, nil } func (repo *ProductItemRepository) UpdateStatus(ctx context.Context, id string, status product.ItemStatus) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return ErrInvalidObjectID } filter := bson.M{"_id": objectID} update := bson.M{"$set": bson.M{"status": status}} rk := domain.GetProductItemRK(id) _, err = repo.DB.UpdateOne(ctx, rk, filter, update) if err != nil { return fmt.Errorf("failed to update status: %w", err) } return nil } func (repo *ProductItemRepository) IncSalesCount(ctx context.Context, id string, count int64) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return ErrInvalidObjectID } filter := bson.M{"_id": objectID} update := bson.M{"$inc": bson.M{"sales_count": count}} rk := domain.GetProductItemRK(id) _, err = repo.DB.UpdateOne(ctx, rk, filter, update) if err != nil { return fmt.Errorf("failed to decrease stock: %w", err) } return nil } func (repo *ProductItemRepository) DecSalesCount(ctx context.Context, id string, count int64) error { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { return ErrInvalidObjectID } filter := bson.M{"_id": objectID, "sales_count": bson.M{"$gte": count}} update := bson.M{"$inc": bson.M{"sales_count": -count}} rk := domain.GetProductItemRK(id) _, err = repo.DB.UpdateOne(ctx, rk, filter, update) if err != nil { return fmt.Errorf("failed to decrease stock: %w", err) } return nil } func (repo *ProductItemRepository) GetSalesCount(ctx context.Context, ids []string) ([]repository.ProductItemSalesCount, error) { objectIDs := make([]primitive.ObjectID, 0, len(ids)) for _, id := range ids { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { continue } objectIDs = append(objectIDs, objectID) } if len(objectIDs) == 0 { return nil, ErrInvalidObjectID } result := make([]entity.ProductItems, 0, len(ids)) filter := bson.M{"_id": bson.M{"$in": objectIDs}} err := repo.DB.GetClient().Find(ctx, &result, filter) if err != nil { return nil, err } stockMap := make([]repository.ProductItemSalesCount, 0, len(result)) for _, item := range result { stockMap = append(stockMap, repository.ProductItemSalesCount{ ID: item.ID.Hex(), Count: item.SalesCount, }) } return stockMap, nil } func (repo *ProductItemRepository) Index20250317001UP(ctx context.Context) (*mongo.Cursor, error) { repo.DB.PopulateIndex(ctx, "reference_id", 1, false) repo.DB.PopulateIndex(ctx, "status", 1, false) return repo.DB.GetClient().Indexes().List(ctx) }