This commit is contained in:
2025-07-20 20:53:26 +08:00
parent 83bf9aea7d
commit 8ad1d7288e
158 changed files with 18156 additions and 13188 deletions

View File

@@ -3,6 +3,7 @@ package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -12,7 +13,7 @@ type Product struct {
Name string `gorm:"type:varchar(100);not null" comment:"产品名称"`
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
Description string `gorm:"type:text" comment:"产品简介"`
Content string `gorm:"type:longtext" comment:"产品内容"`
Content string `gorm:"type:text" comment:"产品内容"`
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
Price float64 `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
@@ -32,6 +33,14 @@ type Product struct {
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (p *Product) BeforeCreate(tx *gorm.DB) error {
if p.ID == "" {
p.ID = uuid.New().String()
}
return nil
}
// IsValid 检查产品是否有效
func (p *Product) IsValid() bool {
return p.DeletedAt.Time.IsZero() && p.IsEnabled

View File

@@ -3,6 +3,7 @@ package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -12,22 +13,26 @@ type ProductCategory struct {
Name string `gorm:"type:varchar(100);not null" comment:"分类名称"`
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"分类编号"`
Description string `gorm:"type:text" comment:"分类描述"`
ParentID *string `gorm:"type:varchar(36)" comment:"父分类ID"`
Level int `gorm:"default:1" comment:"分类层级"`
Sort int `gorm:"default:0" comment:"排序"`
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
IsVisible bool `gorm:"default:true" comment:"是否展示"`
// 关联关系
Parent *ProductCategory `gorm:"foreignKey:ParentID" comment:"父分类"`
Children []ProductCategory `gorm:"foreignKey:ParentID" comment:"子分类"`
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (pc *ProductCategory) BeforeCreate(tx *gorm.DB) error {
if pc.ID == "" {
pc.ID = uuid.New().String()
}
return nil
}
// IsValid 检查分类是否有效
func (pc *ProductCategory) IsValid() bool {
return pc.DeletedAt.Time.IsZero() && pc.IsEnabled
@@ -38,27 +43,6 @@ func (pc *ProductCategory) IsVisibleToUser() bool {
return pc.IsValid() && pc.IsVisible
}
// IsRoot 检查是否为根分类
func (pc *ProductCategory) IsRoot() bool {
return pc.ParentID == nil || *pc.ParentID == ""
}
// IsLeaf 检查是否为叶子分类
func (pc *ProductCategory) IsLeaf() bool {
return len(pc.Children) == 0
}
// GetFullPath 获取完整分类路径
func (pc *ProductCategory) GetFullPath() string {
if pc.IsRoot() {
return pc.Name
}
if pc.Parent != nil {
return pc.Parent.GetFullPath() + " > " + pc.Name
}
return pc.Name
}
// Enable 启用分类
func (pc *ProductCategory) Enable() {
pc.IsEnabled = true

View File

@@ -3,6 +3,7 @@ package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -11,11 +12,11 @@ type ProductDocumentation struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"文档ID"`
ProductID string `gorm:"type:varchar(36);not null;uniqueIndex" comment:"产品ID"`
Title string `gorm:"type:varchar(200);not null" comment:"文档标题"`
Content string `gorm:"type:longtext;not null" comment:"文档内容"`
UsageGuide string `gorm:"type:longtext" comment:"使用指南"`
APIDocs string `gorm:"type:longtext" comment:"API文档"`
Examples string `gorm:"type:longtext" comment:"使用示例"`
FAQ string `gorm:"type:longtext" comment:"常见问题"`
Content string `gorm:"type:text;not null" comment:"文档内容"`
UsageGuide string `gorm:"type:text" comment:"使用指南"`
APIDocs string `gorm:"type:text" comment:"API文档"`
Examples string `gorm:"type:text" comment:"使用示例"`
FAQ string `gorm:"type:text" comment:"常见问题"`
Version string `gorm:"type:varchar(20);default:'1.0'" comment:"文档版本"`
Published bool `gorm:"default:false" comment:"是否已发布"`
@@ -27,6 +28,14 @@ type ProductDocumentation struct {
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (pd *ProductDocumentation) BeforeCreate(tx *gorm.DB) error {
if pd.ID == "" {
pd.ID = uuid.New().String()
}
return nil
}
// IsValid 检查文档是否有效
func (pd *ProductDocumentation) IsValid() bool {
return pd.DeletedAt.Time.IsZero()

View File

@@ -3,25 +3,15 @@ package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// SubscriptionStatus 订阅状态枚举
type SubscriptionStatus string
const (
SubscriptionStatusActive SubscriptionStatus = "active" // 活跃
SubscriptionStatusInactive SubscriptionStatus = "inactive" // 非活跃
SubscriptionStatusExpired SubscriptionStatus = "expired" // 已过期
SubscriptionStatusCanceled SubscriptionStatus = "canceled" // 已取消
)
// Subscription 订阅实体
type Subscription struct {
ID string `gorm:"primaryKey;type:varchar(36)" comment:"订阅ID"`
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
Status SubscriptionStatus `gorm:"type:varchar(20);not null;default:'active'" comment:"订阅状态"`
Price float64 `gorm:"type:decimal(10,2);not null" comment:"订阅价格"`
APIUsed int64 `gorm:"default:0" comment:"已使用API调用次数"`
@@ -33,6 +23,14 @@ type Subscription struct {
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (s *Subscription) BeforeCreate(tx *gorm.DB) error {
if s.ID == "" {
s.ID = uuid.New().String()
}
return nil
}
// IsValid 检查订阅是否有效
func (s *Subscription) IsValid() bool {
return s.DeletedAt.Time.IsZero()
@@ -43,16 +41,6 @@ func (s *Subscription) IncrementAPIUsage(count int64) {
s.APIUsed += count
}
// Activate 激活订阅
func (s *Subscription) Activate() {
s.Status = SubscriptionStatusActive
}
// Deactivate 停用订阅
func (s *Subscription) Deactivate() {
s.Status = SubscriptionStatusInactive
}
// ResetAPIUsage 重置API使用次数
func (s *Subscription) ResetAPIUsage() {
s.APIUsed = 0

View File

@@ -13,21 +13,13 @@ type ProductCategoryRepository interface {
// 基础查询方法
FindByCode(ctx context.Context, code string) (*entities.ProductCategory, error)
FindByParentID(ctx context.Context, parentID *string) ([]*entities.ProductCategory, error)
FindRootCategories(ctx context.Context) ([]*entities.ProductCategory, error)
FindVisible(ctx context.Context) ([]*entities.ProductCategory, error)
FindEnabled(ctx context.Context) ([]*entities.ProductCategory, error)
// 复杂查询方法
ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) ([]*entities.ProductCategory, int64, error)
GetCategoryTree(ctx context.Context) ([]*entities.ProductCategory, error)
// 业务查询方法
FindCategoriesByLevel(ctx context.Context, level int) ([]*entities.ProductCategory, error)
FindCategoryPath(ctx context.Context, categoryID string) ([]*entities.ProductCategory, error)
// 统计方法
CountByParent(ctx context.Context, parentID *string) (int64, error)
CountEnabled(ctx context.Context) (int64, error)
CountVisible(ctx context.Context) (int64, error)
}

View File

@@ -4,8 +4,6 @@ package queries
type ListCategoriesQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
ParentID *string `json:"parent_id"`
Level *int `json:"level"`
IsEnabled *bool `json:"is_enabled"`
IsVisible *bool `json:"is_visible"`
SortBy string `json:"sort_by"`

View File

@@ -1,14 +1,11 @@
package queries
import "tyapi-server/internal/domains/product/entities"
// ListSubscriptionsQuery 订阅列表查询
type ListSubscriptionsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
ProductID string `json:"product_id"`
Status entities.SubscriptionStatus `json:"status"`
Keyword string `json:"keyword"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
}
@@ -21,11 +18,9 @@ type GetSubscriptionQuery struct {
// GetUserSubscriptionsQuery 获取用户订阅查询
type GetUserSubscriptionsQuery struct {
UserID string `json:"user_id"`
Status *entities.SubscriptionStatus `json:"status"`
}
// GetProductSubscriptionsQuery 获取产品订阅查询
type GetProductSubscriptionsQuery struct {
ProductID string `json:"product_id"`
Status *entities.SubscriptionStatus `json:"status"`
}

View File

@@ -10,25 +10,16 @@ import (
// SubscriptionRepository 订阅仓储接口
type SubscriptionRepository interface {
interfaces.Repository[entities.Subscription]
// 基础查询方法
FindByUserID(ctx context.Context, userID string) ([]*entities.Subscription, error)
FindByProductID(ctx context.Context, productID string) ([]*entities.Subscription, error)
FindByUserAndProduct(ctx context.Context, userID, productID string) (*entities.Subscription, error)
FindActive(ctx context.Context) ([]*entities.Subscription, error)
// 复杂查询方法
ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) ([]*entities.Subscription, int64, error)
FindUserActiveSubscriptions(ctx context.Context, userID string) ([]*entities.Subscription, error)
FindExpiredSubscriptions(ctx context.Context) ([]*entities.Subscription, error)
// 业务查询方法
FindSubscriptionsByStatus(ctx context.Context, status entities.SubscriptionStatus) ([]*entities.Subscription, error)
FindSubscriptionsByDateRange(ctx context.Context, startDate, endDate string) ([]*entities.Subscription, error)
// 统计方法
CountByUser(ctx context.Context, userID string) (int64, error)
CountByProduct(ctx context.Context, productID string) (int64, error)
CountByStatus(ctx context.Context, status entities.SubscriptionStatus) (int64, error)
CountActive(ctx context.Context) (int64, error)
}
}

View File

@@ -0,0 +1,185 @@
package services
import (
"context"
"errors"
"fmt"
"strings"
"go.uber.org/zap"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
)
// ProductManagementService 产品管理领域服务
// 负责产品的基本管理操作,包括创建、查询、更新等
type ProductManagementService struct {
productRepo repositories.ProductRepository
categoryRepo repositories.ProductCategoryRepository
logger *zap.Logger
}
// NewProductManagementService 创建产品管理领域服务
func NewProductManagementService(
productRepo repositories.ProductRepository,
categoryRepo repositories.ProductCategoryRepository,
logger *zap.Logger,
) *ProductManagementService {
return &ProductManagementService{
productRepo: productRepo,
categoryRepo: categoryRepo,
logger: logger,
}
}
// CreateProduct 创建产品
func (s *ProductManagementService) CreateProduct(ctx context.Context, product *entities.Product) (*entities.Product, error) {
// 验证产品信息
if err := s.ValidateProduct(product); err != nil {
return nil, err
}
// 验证产品编号唯一性
if err := s.ValidateProductCode(product.Code, ""); err != nil {
return nil, err
}
// 创建产品
createdProduct, err := s.productRepo.Create(ctx, *product)
if err != nil {
s.logger.Error("创建产品失败", zap.Error(err))
return nil, fmt.Errorf("创建产品失败: %w", err)
}
s.logger.Info("产品创建成功",
zap.String("product_id", createdProduct.ID),
zap.String("product_name", createdProduct.Name),
)
return &createdProduct, nil
}
// GetProductByID 根据ID获取产品
func (s *ProductManagementService) GetProductByID(ctx context.Context, productID string) (*entities.Product, error) {
product, err := s.productRepo.GetByID(ctx, productID)
if err != nil {
return nil, fmt.Errorf("产品不存在: %w", err)
}
return &product, nil
}
// GetProductWithCategory 获取产品及其分类信息
func (s *ProductManagementService) GetProductWithCategory(ctx context.Context, productID string) (*entities.Product, error) {
product, err := s.productRepo.GetByID(ctx, productID)
if err != nil {
return nil, fmt.Errorf("产品不存在: %w", err)
}
// 加载分类信息
if product.CategoryID != "" {
category, err := s.categoryRepo.GetByID(ctx, product.CategoryID)
if err == nil {
product.Category = &category
}
}
return &product, nil
}
// UpdateProduct 更新产品
func (s *ProductManagementService) UpdateProduct(ctx context.Context, product *entities.Product) error {
// 验证产品信息
if err := s.ValidateProduct(product); err != nil {
return err
}
// 验证产品编号唯一性(排除自己)
if err := s.ValidateProductCode(product.Code, product.ID); err != nil {
return err
}
if err := s.productRepo.Update(ctx, *product); err != nil {
s.logger.Error("更新产品失败", zap.Error(err))
return fmt.Errorf("更新产品失败: %w", err)
}
s.logger.Info("产品更新成功",
zap.String("product_id", product.ID),
zap.String("product_name", product.Name),
)
return nil
}
// DeleteProduct 删除产品
func (s *ProductManagementService) DeleteProduct(ctx context.Context, productID string) error {
if err := s.productRepo.Delete(ctx, productID); err != nil {
s.logger.Error("删除产品失败", zap.Error(err))
return fmt.Errorf("删除产品失败: %w", err)
}
s.logger.Info("产品删除成功", zap.String("product_id", productID))
return nil
}
// GetVisibleProducts 获取可见产品列表
func (s *ProductManagementService) GetVisibleProducts(ctx context.Context) ([]*entities.Product, error) {
return s.productRepo.FindVisible(ctx)
}
// GetEnabledProducts 获取启用产品列表
func (s *ProductManagementService) GetEnabledProducts(ctx context.Context) ([]*entities.Product, error) {
return s.productRepo.FindEnabled(ctx)
}
// GetProductsByCategory 根据分类获取产品
func (s *ProductManagementService) GetProductsByCategory(ctx context.Context, categoryID string) ([]*entities.Product, error) {
return s.productRepo.FindByCategoryID(ctx, categoryID)
}
// ValidateProduct 验证产品
func (s *ProductManagementService) ValidateProduct(product *entities.Product) error {
if product == nil {
return errors.New("产品不能为空")
}
if strings.TrimSpace(product.Name) == "" {
return errors.New("产品名称不能为空")
}
if strings.TrimSpace(product.Code) == "" {
return errors.New("产品编号不能为空")
}
if product.Price < 0 {
return errors.New("产品价格不能为负数")
}
// 验证分类是否存在
if product.CategoryID != "" {
category, err := s.categoryRepo.GetByID(context.Background(), product.CategoryID)
if err != nil {
return fmt.Errorf("产品分类不存在: %w", err)
}
if !category.IsValid() {
return errors.New("产品分类已禁用或删除")
}
}
return nil
}
// ValidateProductCode 验证产品编号唯一性
func (s *ProductManagementService) ValidateProductCode(code string, excludeID string) error {
if strings.TrimSpace(code) == "" {
return errors.New("产品编号不能为空")
}
existingProduct, err := s.productRepo.FindByCode(context.Background(), code)
if err == nil && existingProduct != nil && existingProduct.ID != excludeID {
return errors.New("产品编号已存在")
}
return nil
}

View File

@@ -1,151 +0,0 @@
package services
import (
"errors"
"fmt"
"strings"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
)
// ProductService 产品领域服务
type ProductService struct {
productRepo repositories.ProductRepository
categoryRepo repositories.ProductCategoryRepository
subscriptionRepo repositories.SubscriptionRepository
}
// NewProductService 创建产品领域服务
func NewProductService(
productRepo repositories.ProductRepository,
categoryRepo repositories.ProductCategoryRepository,
subscriptionRepo repositories.SubscriptionRepository,
) *ProductService {
return &ProductService{
productRepo: productRepo,
categoryRepo: categoryRepo,
subscriptionRepo: subscriptionRepo,
}
}
// ValidateProduct 验证产品
func (s *ProductService) ValidateProduct(product *entities.Product) error {
if product == nil {
return errors.New("产品不能为空")
}
if strings.TrimSpace(product.Name) == "" {
return errors.New("产品名称不能为空")
}
if strings.TrimSpace(product.Code) == "" {
return errors.New("产品编号不能为空")
}
if product.Price < 0 {
return errors.New("产品价格不能为负数")
}
// 验证分类是否存在
if product.CategoryID != "" {
category, err := s.categoryRepo.GetByID(nil, product.CategoryID)
if err != nil {
return fmt.Errorf("产品分类不存在: %w", err)
}
if !category.IsValid() {
return errors.New("产品分类已禁用或删除")
}
}
return nil
}
// ValidateProductCode 验证产品编号唯一性
func (s *ProductService) ValidateProductCode(code string, excludeID string) error {
if strings.TrimSpace(code) == "" {
return errors.New("产品编号不能为空")
}
existingProduct, err := s.productRepo.FindByCode(nil, code)
if err == nil && existingProduct != nil && existingProduct.ID != excludeID {
return errors.New("产品编号已存在")
}
return nil
}
// CanUserSubscribeProduct 检查用户是否可以订阅产品
func (s *ProductService) CanUserSubscribeProduct(userID string, productID string) (bool, error) {
// 检查产品是否存在且可订阅
product, err := s.productRepo.GetByID(nil, productID)
if err != nil {
return false, fmt.Errorf("产品不存在: %w", err)
}
if !product.CanBeSubscribed() {
return false, errors.New("产品不可订阅")
}
// 检查用户是否已有该产品的订阅
existingSubscription, err := s.subscriptionRepo.FindByUserAndProduct(nil, userID, productID)
if err == nil && existingSubscription != nil {
return false, errors.New("用户已有该产品的订阅")
}
return true, nil
}
// GetProductWithCategory 获取产品及其分类信息
func (s *ProductService) GetProductWithCategory(productID string) (*entities.Product, error) {
product, err := s.productRepo.GetByID(nil, productID)
if err != nil {
return nil, fmt.Errorf("产品不存在: %w", err)
}
// 加载分类信息
if product.CategoryID != "" {
category, err := s.categoryRepo.GetByID(nil, product.CategoryID)
if err == nil {
product.Category = &category
}
}
return &product, nil
}
// GetVisibleProducts 获取可见产品列表
func (s *ProductService) GetVisibleProducts() ([]*entities.Product, error) {
return s.productRepo.FindVisible(nil)
}
// GetEnabledProducts 获取启用产品列表
func (s *ProductService) GetEnabledProducts() ([]*entities.Product, error) {
return s.productRepo.FindEnabled(nil)
}
// GetProductsByCategory 根据分类获取产品
func (s *ProductService) GetProductsByCategory(categoryID string) ([]*entities.Product, error) {
return s.productRepo.FindByCategoryID(nil, categoryID)
}
// GetProductStats 获取产品统计信息
func (s *ProductService) GetProductStats() (map[string]int64, error) {
stats := make(map[string]int64)
total, err := s.productRepo.CountByCategory(nil, "")
if err == nil {
stats["total"] = total
}
enabled, err := s.productRepo.CountEnabled(nil)
if err == nil {
stats["enabled"] = enabled
}
visible, err := s.productRepo.CountVisible(nil)
if err == nil {
stats["visible"] = visible
}
return stats, nil
}

View File

@@ -0,0 +1,144 @@
package services
import (
"context"
"errors"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
)
// ProductSubscriptionService 产品订阅领域服务
// 负责产品订阅相关的业务逻辑,包括订阅验证、订阅管理等
type ProductSubscriptionService struct {
productRepo repositories.ProductRepository
subscriptionRepo repositories.SubscriptionRepository
logger *zap.Logger
}
// NewProductSubscriptionService 创建产品订阅领域服务
func NewProductSubscriptionService(
productRepo repositories.ProductRepository,
subscriptionRepo repositories.SubscriptionRepository,
logger *zap.Logger,
) *ProductSubscriptionService {
return &ProductSubscriptionService{
productRepo: productRepo,
subscriptionRepo: subscriptionRepo,
logger: logger,
}
}
// CanUserSubscribeProduct 检查用户是否可以订阅产品
func (s *ProductSubscriptionService) CanUserSubscribeProduct(ctx context.Context, userID string, productID string) (bool, error) {
// 检查产品是否存在且可订阅
product, err := s.productRepo.GetByID(ctx, productID)
if err != nil {
return false, fmt.Errorf("产品不存在: %w", err)
}
if !product.CanBeSubscribed() {
return false, errors.New("产品不可订阅")
}
// 检查用户是否已有该产品的订阅
existingSubscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, productID)
if err == nil && existingSubscription != nil {
return false, errors.New("用户已有该产品的订阅")
}
return true, nil
}
// CreateSubscription 创建订阅
func (s *ProductSubscriptionService) CreateSubscription(ctx context.Context, userID, productID string) (*entities.Subscription, error) {
// 检查是否可以订阅
canSubscribe, err := s.CanUserSubscribeProduct(ctx, userID, productID)
if err != nil {
return nil, err
}
if !canSubscribe {
return nil, errors.New("无法订阅该产品")
}
// 获取产品信息以获取价格
product, err := s.productRepo.GetByID(ctx, productID)
if err != nil {
return nil, fmt.Errorf("产品不存在: %w", err)
}
// 创建订阅
subscription := &entities.Subscription{
UserID: userID,
ProductID: productID,
Price: product.Price,
}
createdSubscription, err := s.subscriptionRepo.Create(ctx, *subscription)
if err != nil {
s.logger.Error("创建订阅失败", zap.Error(err))
return nil, fmt.Errorf("创建订阅失败: %w", err)
}
s.logger.Info("订阅创建成功",
zap.String("subscription_id", createdSubscription.ID),
zap.String("user_id", userID),
zap.String("product_id", productID),
)
return &createdSubscription, nil
}
// GetUserSubscriptions 获取用户订阅列表
func (s *ProductSubscriptionService) GetUserSubscriptions(ctx context.Context, userID string) ([]*entities.Subscription, error) {
return s.subscriptionRepo.FindByUserID(ctx, userID)
}
// GetSubscriptionByID 根据ID获取订阅
func (s *ProductSubscriptionService) GetSubscriptionByID(ctx context.Context, subscriptionID string) (*entities.Subscription, error) {
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
if err != nil {
return nil, fmt.Errorf("订阅不存在: %w", err)
}
return &subscription, nil
}
// CancelSubscription 取消订阅
func (s *ProductSubscriptionService) CancelSubscription(ctx context.Context, subscriptionID string) error {
// 由于订阅实体没有状态字段,这里直接删除订阅
if err := s.subscriptionRepo.Delete(ctx, subscriptionID); err != nil {
s.logger.Error("取消订阅失败", zap.Error(err))
return fmt.Errorf("取消订阅失败: %w", err)
}
s.logger.Info("订阅取消成功",
zap.String("subscription_id", subscriptionID),
)
return nil
}
// GetProductStats 获取产品统计信息
func (s *ProductSubscriptionService) GetProductStats(ctx context.Context) (map[string]int64, error) {
stats := make(map[string]int64)
total, err := s.productRepo.CountByCategory(ctx, "")
if err == nil {
stats["total"] = total
}
enabled, err := s.productRepo.CountEnabled(ctx)
if err == nil {
stats["enabled"] = enabled
}
visible, err := s.productRepo.CountVisible(ctx)
if err == nil {
stats["visible"] = visible
}
return stats, nil
}