diff --git a/go.mod b/go.mod index 76f1669..89e1de8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module code.30cm.net/digimon/app-cloudep-product-service go 1.24.0 require ( + code.30cm.net/digimon/library-go/errs v1.2.14 code.30cm.net/digimon/library-go/mongo v0.0.9 github.com/alicebob/miniredis/v2 v2.34.0 github.com/shopspring/decimal v1.4.0 diff --git a/go.sum b/go.sum index d8f6c5c..cef40c6 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +code.30cm.net/digimon/library-go/errs v1.2.14 h1:Un9wcIIjjJW8D2i0ISf8ibzp9oNT4OqLsaSKW0T4RJU= +code.30cm.net/digimon/library-go/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o= code.30cm.net/digimon/library-go/mongo v0.0.9 h1:fPciIE5B85tXpLg8aeVQqKVbLnfpVAk9xbMu7pE2tVw= code.30cm.net/digimon/library-go/mongo v0.0.9/go.mod h1:KBVKz/Ci5IheI77BgZxPUeKkaGvDy8fV8EDHSCOLIO4= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= diff --git a/pkg/domain/repository/tags.go b/pkg/domain/repository/tags.go index 5e50940..81fcd07 100644 --- a/pkg/domain/repository/tags.go +++ b/pkg/domain/repository/tags.go @@ -48,8 +48,8 @@ type TagModifyParams struct { // TagBindingRepo 定義與 tag binding (TagsBindingTable) 資料表相關的操作 type TagBindingRepo interface { - // BindTag 建立一筆 tag 與其他資料(例如專案)的綁定關係 - BindTag(ctx context.Context, binding *entity.TagsBindingTable) error + // BindTags 建立一筆 tag 與其他資料(例如專案)的綁定關係 + BindTags(ctx context.Context, binding []*entity.TagsBindingTable) error // UnbindTag 刪除一筆綁定資料 UnbindTag(ctx context.Context, tagID, referenceID string) error // GetBindingsByReference 根據參照 ID 取得所有綁定資料 diff --git a/pkg/repository/product_tags_biinding.go b/pkg/repository/product_tags_biinding.go index a24c5c8..b2224bc 100644 --- a/pkg/repository/product_tags_biinding.go +++ b/pkg/repository/product_tags_biinding.go @@ -12,15 +12,22 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" ) -func (repo *TagsRepository) BindTag(ctx context.Context, data *entity.TagsBindingTable) error { - if data.ID.IsZero() { - now := time.Now().UTC().UnixNano() - data.ID = primitive.NewObjectID() - data.CreatedAt = now - data.UpdatedAt = now - } +func (repo *TagsRepository) BindTags(ctx context.Context, data []*entity.TagsBindingTable) error { + now := time.Now().UTC().UnixNano() + docs := make([]any, 0, len(data)) - _, err := repo.TageBinding.GetClient().InsertOne(ctx, data) + for i := range data { + if data[i].ID.IsZero() { + data[i].ID = primitive.NewObjectID() + data[i].CreatedAt = now + data[i].UpdatedAt = now + } + docs = append(docs, data[i]) + } + if len(docs) == 0 { + return nil + } + _, err := repo.TageBinding.GetClient().InsertMany(ctx, docs) return err } diff --git a/pkg/repository/product_tags_test.go b/pkg/repository/product_tags_test.go index 7cf657f..a11f261 100644 --- a/pkg/repository/product_tags_test.go +++ b/pkg/repository/product_tags_test.go @@ -545,7 +545,7 @@ func TestBindTag(t *testing.T) { for _, tc := range tests { tc := tc // capture range variable t.Run(tc.name, func(t *testing.T) { - err := repo.BindTag(ctx, tc.inputBinding) + err := repo.BindTags(ctx, []*entity.TagsBindingTable{tc.inputBinding}) if tc.expectError { require.Error(t, err) } else { @@ -581,11 +581,7 @@ func TestGetBindingsByReference(t *testing.T) { } // 插入資料 - err = repo.BindTag(ctx, binding1) - require.NoError(t, err) - err = repo.BindTag(ctx, binding2) - require.NoError(t, err) - err = repo.BindTag(ctx, binding3) + err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding1, binding2, binding3}) require.NoError(t, err) tests := []struct { @@ -665,14 +661,8 @@ func TestListTagBinding(t *testing.T) { UpdatedAt: 2500, } - // 插入綁定資料 - err = repo.BindTag(ctx, binding1) - require.NoError(t, err) - err = repo.BindTag(ctx, binding2) - require.NoError(t, err) - err = repo.BindTag(ctx, binding3) - require.NoError(t, err) - err = repo.BindTag(ctx, binding4) + // 插入資料 + err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding1, binding2, binding3, binding4}) require.NoError(t, err) // 測試案例 @@ -767,7 +757,7 @@ func TestUnbindTag(t *testing.T) { ReferenceID: "ref-001", TagID: "tag-001", } - err = repo.BindTag(ctx, binding) + err = repo.BindTags(ctx, []*entity.TagsBindingTable{binding}) require.NoError(t, err) tests := []struct { diff --git a/pkg/repository/product_test.go b/pkg/repository/product_test.go index 964c041..622f53f 100644 --- a/pkg/repository/product_test.go +++ b/pkg/repository/product_test.go @@ -68,6 +68,7 @@ func SetupTestProductRepository(db string) (repository.ProductRepository, func() func TestListProduct(t *testing.T) { model, tearDown, err := SetupTestProductRepository("testDB") defer tearDown() + fmt.Println("ddddddddddddddddddddd", err.Error()) assert.NoError(t, err) now := time.Now() diff --git a/pkg/usecase/product.go b/pkg/usecase/product.go index 98832b4..5db24ea 100644 --- a/pkg/usecase/product.go +++ b/pkg/usecase/product.go @@ -4,7 +4,13 @@ import ( "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase" + "code.30cm.net/digimon/app-cloudep-product-service/pkg/utils" + "code.30cm.net/digimon/library-go/errs" "context" + "github.com/zeromicro/go-zero/core/logx" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readconcern" ) type ProductUseCaseParam struct { @@ -25,19 +31,130 @@ func MustProductUseCase(param ProductUseCaseParam) usecase.ProductUseCase { } func (use *ProductUseCase) Create(ctx context.Context, product *usecase.Product) error { - //use.ProductRepo.Transaction() - insert := &entity.Product{ - UID: *product.UID, - Title: *product.Title, - ShortTitle: product.ShortTitle, + // 資料前處理:準備 entity.Product 實體 + insert := &entity.Product{} + + if product.UID != nil { + insert.UID = *product.UID + } + if product.Title != nil { + insert.Title = *product.Title + } + if product.IsPublished != nil { + insert.IsPublished = *product.IsPublished + } + if product.Category != nil { + insert.Category = *product.Category + } + if product.ShortTitle != nil { + insert.ShortTitle = product.ShortTitle + } + if product.Details != nil { + insert.Details = product.Details + } + if product.ShortDescription != nil { + insert.ShortDescription = *product.ShortDescription + } + if product.Slug != nil { + insert.Slug = product.Slug + } + if product.Amount != 0 { + insert.Amount = product.Amount + } + if product.StartTime != nil { + st := utils.Rfc3339ToUnix(utils.ToValue(product.StartTime)) + insert.StartTime = &st + } + if product.EndTime != nil { + et := utils.Rfc3339ToUnix(utils.ToValue(product.EndTime)) + insert.EndTime = &et + } + if len(product.Media) > 0 { + medias := make([]entity.Media, 0, len(product.Media)) + for _, m := range product.Media { + medias = append(medias, entity.Media{ + Sort: m.Sort, + URL: m.URL, + Type: m.Type, + }) + } + insert.Media = medias + } + if len(product.CustomFields) > 0 { + cf := make([]entity.CustomFields, 0, len(product.CustomFields)) + for _, field := range product.CustomFields { + cf = append(cf, entity.CustomFields{ + Key: field.Key, + Value: field.Value, + }) + } + insert.CustomFields = cf } - err := use.ProductRepo.Insert(ctx, insert) + // Transaction 設定:只做必要寫入 + opts := options.Transaction().SetReadConcern(readconcern.Local()) + err := use.ProductRepo.Transaction(ctx, func(sessCtx mongo.SessionContext) (any, error) { + // 插入 Product + if err := use.ProductRepo.Insert(sessCtx, insert); err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "req", Value: product}, + {Key: "func", Value: "ProductRepo.Insert"}, + {Key: "err", Value: err.Error()}, + }, + "failed to create product") + + return nil, e + } + + // 插入 Product 統計資料 + if err := use.ProductStatisticsRepo.Create(sessCtx, &entity.ProductStatistics{ + ProductID: insert.ID.Hex(), + Orders: 0, + OrdersUpdateTime: 0, + AverageRating: 0, + AverageRatingUpdateTime: 0, + FansCount: 0, + FansCountUpdateTime: 0, + }); err != nil { + e := errs.DBErrorL(logx.WithContext(ctx), + []logx.LogField{ + {Key: "ProductID", Value: insert.ID.Hex()}, + {Key: "func", Value: "ProductStatisticsRepo.Create"}, + {Key: "err", Value: err.Error()}, + }, + "failed to create product statistics") + + return nil, e + } + + // 過濾 Tag + // 綁定 Tags + //for _, tag := range product.Tags { + // if err := use.TagBinding.BindTag(sessCtx, &entity.TagsBindingTable{ + // ReferenceID: insert.ID.Hex(), + // TagID: tag, + // }); err != nil { + // _ = errs.DBErrorL(logx.WithContext(ctx), + // []logx.LogField{ + // {Key: "ReferenceID", Value: insert.ID.Hex()}, + // {Key: "TagID", Value: tag}, + // {Key: "func", Value: "TagBinding.BindTag"}, + // {Key: "err", Value: err.Error()}, + // }, "") + // + // continue + // } + //} + + return nil, nil + }, opts) + if err != nil { return err } - //TODO implement me - panic("implement me") + + return nil } func (use *ProductUseCase) Update(ctx context.Context, id string, product *usecase.Product) error { diff --git a/pkg/utils/times.go b/pkg/utils/times.go new file mode 100644 index 0000000..36a5a81 --- /dev/null +++ b/pkg/utils/times.go @@ -0,0 +1,28 @@ +package utils + +import "time" + +func UnixToRfc3339(t int64) string { + res := time.Unix(0, t).UTC() + + return res.Format(time.RFC3339) +} + +func Rfc3339ToUnix(rfc3339 string) int64 { + // 解析 RFC3339 格式的時間 + t, err := time.Parse(time.RFC3339, rfc3339) + if err != nil { + return 0 + } + + // 轉換為 Unix Nano (納秒) + return t.UTC().UnixNano() +} + +func ToValue[T any](ptr *T) T { + if ptr == nil { + var zero T + return zero + } + return *ptr +}