From 2451bc425759bd513488d4c123a9680946116e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Thu, 20 Mar 2025 17:11:56 +0800 Subject: [PATCH] feat: product tags --- pkg/domain/entity/product_tag.go | 12 +- pkg/domain/redis.go | 5 + pkg/domain/repository/tags.go | 68 ++++++++- pkg/repository/product_tags.go | 181 ++++++++++++++++++++++++ pkg/repository/product_tags_biinding.go | 81 +++++++++++ 5 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 pkg/repository/product_tags.go create mode 100644 pkg/repository/product_tags_biinding.go diff --git a/pkg/domain/entity/product_tag.go b/pkg/domain/entity/product_tag.go index 93880cd..8ac03b8 100644 --- a/pkg/domain/entity/product_tag.go +++ b/pkg/domain/entity/product_tag.go @@ -5,12 +5,16 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -type ProductTags struct { - ID primitive.ObjectID `bson:"_id,omitempty"` // 專案 ID - Types product.ItemType `bson:"types"` // Tag 類型 - Name string `bson:"name"` +type Tags struct { + ID primitive.ObjectID `bson:"_id,omitempty"` // 專案 ID + Types product.ItemType `bson:"types"` // Tag 類型 + Name string `bson:"name"` // tag 名稱 ShowType product.ShowType `bson:"show_type"` // 顯示筐 Cover *string `bson:"cover,omitempty"` // 封面圖片 UpdatedAt int64 `bson:"updated_at" json:"updated_at"` // 更新時間 CreatedAt int64 `bson:"created_at" json:"created_at"` // 創建時間 } + +func (p *Tags) CollectionName() string { + return "tags" +} diff --git a/pkg/domain/redis.go b/pkg/domain/redis.go index fba0c3f..a8ff107 100644 --- a/pkg/domain/redis.go +++ b/pkg/domain/redis.go @@ -18,6 +18,7 @@ const ( GetProductRedisKey RedisKey = "get" GetProductItemRedisKey RedisKey = "get_item" GetProductStatisticsRedisKey RedisKey = "statistics" + GetTagsRedisKey RedisKey = "tags" ) func GetProductRK(id string) string { @@ -31,3 +32,7 @@ func GetProductItemRK(id string) string { func GetProductStatisticsRK(id string) string { return GetProductStatisticsRedisKey.With(id).ToString() } + +func GetTagsRK(id string) string { + return GetTagsRedisKey.With(id).ToString() +} diff --git a/pkg/domain/repository/tags.go b/pkg/domain/repository/tags.go index 24c7a81..650add0 100644 --- a/pkg/domain/repository/tags.go +++ b/pkg/domain/repository/tags.go @@ -1,7 +1,73 @@ package repository +import ( + "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" + "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product" + "context" + "go.mongodb.org/mongo-driver/mongo" +) + +// TagRepo 定義與 tag (Tags) 資料表相關的 CRUD 與查詢操作 type TagRepo interface { + Base + TagBindingRepo + Index } -type TagBindingRepo interface { +type Base interface { + // Create 新增一筆 Tag 資料 + Create(ctx context.Context, tag *entity.Tags) error + // GetByID 根據 tag 的內部 ID 取得資料 + GetByID(ctx context.Context, id string) (*entity.Tags, error) + // Update 更新現有的 Tag 資料 + Update(ctx context.Context, id string, tag TagModifyParams) error + // Delete 刪除指定 ID 的 Tag 資料 + Delete(ctx context.Context, id string) error + // List 根據查詢條件取得 Tag 資料列表 + // 回傳值分別為資料列表與符合條件的總筆數 + List(ctx context.Context, params TagQueryParams) ([]*entity.Tags, int64, error) +} + +// TagQueryParams 為查詢 Tags 時的參數結構 +type TagQueryParams struct { + Types *product.ItemType // 過濾 Tag 類型 + Name *string // 過濾名稱(部分比對) + ShowType *product.ShowType // 過濾顯示筐 + // 可根據需求增加其他查詢條件,如分頁、排序等 + PageSize int64 + PageIndex int64 +} + +type TagModifyParams struct { + Types *product.ItemType // 過濾 Tag 類型 + Name *string // 過濾名稱(部分比對) + ShowType *product.ShowType // 過濾顯示筐 + Cover *string `bson:"cover,omitempty"` // 封面圖片 +} + +// TagBindingRepo 定義與 tag binding (TagsBindingTable) 資料表相關的操作 +type TagBindingRepo interface { + // BindTag 建立一筆 tag 與其他資料(例如專案)的綁定關係 + BindTag(ctx context.Context, binding *entity.TagsBindingTable) error + // UnbindTag 刪除一筆綁定資料 + UnbindTag(ctx context.Context, tagID, referenceID string) error + // GetBindingsByReference 根據參照 ID 取得所有綁定資料 + GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error) + // ListTagBinding 根據查詢條件取得 tag binding 的資料列表 + // 回傳值分別為資料列表與符合條件的總筆數 + ListTagBinding(ctx context.Context, params TagBindingQueryParams) ([]*entity.TagsBindingTable, int64, error) +} + +// TagBindingQueryParams 為查詢 TagBindingTable 時的參數結構 +type TagBindingQueryParams struct { + ReferenceID *string // 過濾參照 ID + TagID *string // 過濾 Tag ID + // 可根據需求增加其他查詢條件,如分頁、排序等 + PageSize int64 + PageIndex int64 +} + +type Index interface { + IndexTags20250317001UP(ctx context.Context) (*mongo.Cursor, error) + IndexTagsBinding20250317001UP(ctx context.Context) (*mongo.Cursor, error) } diff --git a/pkg/repository/product_tags.go b/pkg/repository/product_tags.go new file mode 100644 index 0000000..766003e --- /dev/null +++ b/pkg/repository/product_tags.go @@ -0,0 +1,181 @@ +package repository + +import ( + "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/repository" + mgo "code.30cm.net/digimon/library-go/mongo" + "context" + "errors" + "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" + "time" +) + +type TagsRepositoryParam struct { + Conf *mgo.Conf + CacheConf cache.CacheConf + DBOpts []mon.Option + CacheOpts []cache.Option +} + +type TagsRepository struct { + Tags mgo.DocumentDBWithCacheUseCase + TageBinding mgo.DocumentDBWithCacheUseCase +} + +func NewTagsRepository(param TagsRepositoryParam) repository.TagRepo { + tags := entity.Tags{} + tagsDB, err := mgo.MustDocumentDBWithCache( + param.Conf, + tags.CollectionName(), + param.CacheConf, + param.DBOpts, + param.CacheOpts, + ) + if err != nil { + panic(err) + } + + tagBinding := entity.TagsBindingTable{} + bindingsDB, err := mgo.MustDocumentDBWithCache( + param.Conf, + tagBinding.CollectionName(), + param.CacheConf, + param.DBOpts, + param.CacheOpts, + ) + if err != nil { + panic(err) + } + + return &TagsRepository{ + Tags: tagsDB, + TageBinding: bindingsDB, + } +} + +func (repo *TagsRepository) Create(ctx context.Context, data *entity.Tags) error { + if data.ID.IsZero() { + now := time.Now().UTC().UnixNano() + data.ID = primitive.NewObjectID() + data.CreatedAt = now + data.UpdatedAt = now + } + rk := domain.GetTagsRK(data.ID.Hex()) + _, err := repo.Tags.InsertOne(ctx, rk, data) + + return err +} + +func (repo *TagsRepository) GetByID(ctx context.Context, id string) (*entity.Tags, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + + var result *entity.Tags + err = repo.Tags.FindOne(ctx, domain.GetTagsRK(id), &result, bson.M{"_id": oid}) + switch { + case err == nil: + return result, nil + case errors.Is(err, mon.ErrNotFound): + return nil, ErrNotFound + default: + return nil, err + } +} + +func (repo *TagsRepository) Update(ctx context.Context, id string, data repository.TagModifyParams) error { + now := time.Now().UTC().UnixNano() + // 動態構建更新內容 + updateFields := bson.M{ + "updated_at": now, // 確保 `updateAt` 總是更新 + } + if data.Name != nil { + updateFields["name"] = *data.Name + } + if data.Types != nil { + updateFields["types"] = *data.Types + } + if data.ShowType != nil { + updateFields["show_type"] = *data.ShowType + } + if data.Cover != nil { + updateFields["cover"] = *data.Cover + } + + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return ErrInvalidObjectID + } + + // 執行更新 + rk := domain.GetTagsRK(id) + _, err = repo.Tags.UpdateOne(ctx, rk, bson.M{"_id": oid}, bson.M{"$set": updateFields}) + if err != nil { + return err + } + + return err +} + +func (repo *TagsRepository) Delete(ctx context.Context, id string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return ErrInvalidObjectID + } + + filter := bson.M{"_id": oid} + _, err = repo.Tags.DeleteOne(ctx, domain.GetTagsRK(id), filter) + if err != nil { + return err + } + + return nil +} + +func (repo *TagsRepository) List(ctx context.Context, params repository.TagQueryParams) ([]*entity.Tags, int64, error) { + // 構建查詢過濾器 + filter := bson.M{} + if params.Name != nil { + filter["name"] = *params.Name + } + if params.Types != nil { + filter["types"] = *params.Types + } + if params.ShowType != nil { + filter["show_type"] = *params.ShowType + } + // 設置排序選項 + opts := options.Find().SetSkip((params.PageIndex - 1) * params.PageSize).SetLimit(params.PageSize) + opts.SetSort(bson.D{{Key: "updated_at", Value: -1}}) + + // 查詢符合條件的總數 + count, err := repo.Tags.GetClient().CountDocuments(ctx, filter) + if err != nil { + return nil, 0, err + } + + // 執行查詢並獲取結果 + var tags []*entity.Tags + err = repo.Tags.GetClient().Find(ctx, &tags, filter, opts) + if err != nil { + return nil, 0, err + } + + return tags, count, nil +} + +func (repo *TagsRepository) IndexTags20250317001UP(ctx context.Context) (*mongo.Cursor, error) { + // 等價於 db.account.createIndex({"create_at": 1}) + repo.Tags.PopulateIndex(ctx, "show_type", 1, false) + repo.Tags.PopulateIndex(ctx, "types", 1, false) + repo.Tags.PopulateIndex(ctx, "name", 1, false) + + return repo.Tags.GetClient().Indexes().List(ctx) +} diff --git a/pkg/repository/product_tags_biinding.go b/pkg/repository/product_tags_biinding.go new file mode 100644 index 0000000..86752aa --- /dev/null +++ b/pkg/repository/product_tags_biinding.go @@ -0,0 +1,81 @@ +package repository + +import ( + "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" + "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository" + "context" + "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" + "time" +) + +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 + } + + _, err := repo.TageBinding.GetClient().InsertOne(ctx, data) + + return err +} + +func (repo *TagsRepository) UnbindTag(ctx context.Context, tagID, referenceID string) error { + filter := bson.M{"tag_id": tagID, "reference_id": referenceID} + _, err := repo.TageBinding.GetClient().DeleteOne(ctx, filter) + if err != nil { + return err + } + + return nil +} + +func (repo *TagsRepository) GetBindingsByReference(ctx context.Context, referenceID string) ([]*entity.TagsBindingTable, error) { + var result []*entity.TagsBindingTable + filter := bson.M{"reference_id": referenceID} + err := repo.TageBinding.GetClient().Find(ctx, &result, filter) + if err != nil { + return nil, err + } + + return result, nil +} + +func (repo *TagsRepository) ListTagBinding(ctx context.Context, params repository.TagBindingQueryParams) ([]*entity.TagsBindingTable, int64, error) { + // 構建查詢過濾器 + filter := bson.M{} + if params.ReferenceID != nil { + filter["reference_id"] = *params.ReferenceID + } + if params.TagID != nil { + filter["tag_id"] = *params.TagID + } + + // 設置排序選項 + opts := options.Find().SetSkip((params.PageIndex - 1) * params.PageSize).SetLimit(params.PageSize) + opts.SetSort(bson.D{{Key: "updated_at", Value: -1}}) + + // 查詢符合條件的總數 + count, err := repo.TageBinding.GetClient().CountDocuments(ctx, filter) + if err != nil { + return nil, 0, err + } + + // 執行查詢並獲取結果 + var tags []*entity.TagsBindingTable + err = repo.TageBinding.GetClient().Find(ctx, &tags, filter, opts) + if err != nil { + return nil, 0, err + } + + return tags, count, nil +} + +func (repo *TagsRepository) IndexTagsBinding20250317001UP(ctx context.Context) (*mongo.Cursor, error) { + //TODO implement me + panic("implement me") +}