352 lines
8.9 KiB
Go
352 lines
8.9 KiB
Go
|
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)
|
||
|
}
|