This commit is contained in:
2026-04-21 22:36:48 +08:00
commit 488c695fdf
748 changed files with 266838 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
package services
import (
"context"
"hyapi-server/internal/domains/product/entities"
"hyapi-server/internal/domains/product/repositories"
"go.uber.org/zap"
)
// ProductApiConfigService 产品API配置领域服务接口
type ProductApiConfigService interface {
// 根据产品ID获取API配置
GetApiConfigByProductID(ctx context.Context, productID string) (*entities.ProductApiConfig, error)
// 根据产品代码获取API配置
GetApiConfigByProductCode(ctx context.Context, productCode string) (*entities.ProductApiConfig, error)
// 批量获取产品API配置
GetApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductApiConfig, error)
// 创建产品API配置
CreateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error
// 更新产品API配置
UpdateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error
// 删除产品API配置
DeleteApiConfig(ctx context.Context, configID string) error
// 检查产品API配置是否存在
ExistsByProductID(ctx context.Context, productID string) (bool, error)
}
// ProductApiConfigServiceImpl 产品API配置领域服务实现
type ProductApiConfigServiceImpl struct {
apiConfigRepo repositories.ProductApiConfigRepository
logger *zap.Logger
}
// NewProductApiConfigService 创建产品API配置领域服务
func NewProductApiConfigService(
apiConfigRepo repositories.ProductApiConfigRepository,
logger *zap.Logger,
) ProductApiConfigService {
return &ProductApiConfigServiceImpl{
apiConfigRepo: apiConfigRepo,
logger: logger,
}
}
// GetApiConfigByProductID 根据产品ID获取API配置
func (s *ProductApiConfigServiceImpl) GetApiConfigByProductID(ctx context.Context, productID string) (*entities.ProductApiConfig, error) {
s.logger.Debug("获取产品API配置", zap.String("product_id", productID))
config, err := s.apiConfigRepo.FindByProductID(ctx, productID)
if err != nil {
s.logger.Error("获取产品API配置失败", zap.Error(err), zap.String("product_id", productID))
return nil, err
}
return config, nil
}
// GetApiConfigByProductCode 根据产品代码获取API配置
func (s *ProductApiConfigServiceImpl) GetApiConfigByProductCode(ctx context.Context, productCode string) (*entities.ProductApiConfig, error) {
s.logger.Debug("根据产品代码获取API配置", zap.String("product_code", productCode))
config, err := s.apiConfigRepo.FindByProductCode(ctx, productCode)
if err != nil {
s.logger.Error("根据产品代码获取API配置失败", zap.Error(err), zap.String("product_code", productCode))
return nil, err
}
return config, nil
}
// GetApiConfigsByProductIDs 批量获取产品API配置
func (s *ProductApiConfigServiceImpl) GetApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductApiConfig, error) {
s.logger.Debug("批量获取产品API配置", zap.Strings("product_ids", productIDs))
configs, err := s.apiConfigRepo.FindByProductIDs(ctx, productIDs)
if err != nil {
s.logger.Error("批量获取产品API配置失败", zap.Error(err), zap.Strings("product_ids", productIDs))
return nil, err
}
return configs, nil
}
// CreateApiConfig 创建产品API配置
func (s *ProductApiConfigServiceImpl) CreateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error {
s.logger.Debug("创建产品API配置", zap.String("product_id", config.ProductID))
// 检查是否已存在配置
exists, err := s.apiConfigRepo.ExistsByProductID(ctx, config.ProductID)
if err != nil {
s.logger.Error("检查产品API配置是否存在失败", zap.Error(err), zap.String("product_id", config.ProductID))
return err
}
if exists {
return entities.NewValidationError("产品API配置已存在")
}
// 验证配置
if err := config.Validate(); err != nil {
s.logger.Error("产品API配置验证失败", zap.Error(err), zap.String("product_id", config.ProductID))
return err
}
// 保存配置
err = s.apiConfigRepo.Create(ctx, *config)
if err != nil {
s.logger.Error("创建产品API配置失败", zap.Error(err), zap.String("product_id", config.ProductID))
return err
}
s.logger.Info("产品API配置创建成功", zap.String("product_id", config.ProductID))
return nil
}
// UpdateApiConfig 更新产品API配置
func (s *ProductApiConfigServiceImpl) UpdateApiConfig(ctx context.Context, config *entities.ProductApiConfig) error {
s.logger.Debug("更新产品API配置", zap.String("config_id", config.ID))
// 验证配置
if err := config.Validate(); err != nil {
s.logger.Error("产品API配置验证失败", zap.Error(err), zap.String("config_id", config.ID))
return err
}
// 更新配置
err := s.apiConfigRepo.Update(ctx, *config)
if err != nil {
s.logger.Error("更新产品API配置失败", zap.Error(err), zap.String("config_id", config.ID))
return err
}
s.logger.Info("产品API配置更新成功", zap.String("config_id", config.ID))
return nil
}
// DeleteApiConfig 删除产品API配置
func (s *ProductApiConfigServiceImpl) DeleteApiConfig(ctx context.Context, configID string) error {
s.logger.Debug("删除产品API配置", zap.String("config_id", configID))
err := s.apiConfigRepo.Delete(ctx, configID)
if err != nil {
s.logger.Error("删除产品API配置失败", zap.Error(err), zap.String("config_id", configID))
return err
}
s.logger.Info("产品API配置删除成功", zap.String("config_id", configID))
return nil
}
// ExistsByProductID 检查产品API配置是否存在
func (s *ProductApiConfigServiceImpl) ExistsByProductID(ctx context.Context, productID string) (bool, error) {
return s.apiConfigRepo.ExistsByProductID(ctx, productID)
}

View File

@@ -0,0 +1,128 @@
package services
import (
"context"
"errors"
"fmt"
"hyapi-server/internal/domains/product/entities"
"hyapi-server/internal/domains/product/repositories"
)
// ProductDocumentationService 产品文档服务
type ProductDocumentationService struct {
docRepo repositories.ProductDocumentationRepository
productRepo repositories.ProductRepository
}
// NewProductDocumentationService 创建文档服务实例
func NewProductDocumentationService(
docRepo repositories.ProductDocumentationRepository,
productRepo repositories.ProductRepository,
) *ProductDocumentationService {
return &ProductDocumentationService{
docRepo: docRepo,
productRepo: productRepo,
}
}
// CreateDocumentation 创建文档
func (s *ProductDocumentationService) CreateDocumentation(ctx context.Context, productID string, doc *entities.ProductDocumentation) error {
// 验证产品是否存在
product, err := s.productRepo.GetByID(ctx, productID)
if err != nil {
return fmt.Errorf("产品不存在: %w", err)
}
if !product.IsValid() {
return errors.New("产品已禁用或删除")
}
// 检查是否已存在文档
existingDoc, err := s.docRepo.FindByProductID(ctx, productID)
if err == nil && existingDoc != nil {
return errors.New("该产品已存在文档")
}
// 设置产品ID
doc.ProductID = productID
// 验证文档完整性
if err := doc.Validate(); err != nil {
return fmt.Errorf("文档验证失败: %w", err)
}
// 创建文档
return s.docRepo.Create(ctx, doc)
}
// UpdateDocumentation 更新文档
func (s *ProductDocumentationService) UpdateDocumentation(ctx context.Context, id string, requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes string) error {
// 查找现有文档
doc, err := s.docRepo.FindByID(ctx, id)
if err != nil {
return fmt.Errorf("文档不存在: %w", err)
}
// 使用实体的更新方法
err = doc.UpdateDocumentation(requestURL, requestMethod, basicInfo, requestParams, responseFields, responseExample, errorCodes)
if err != nil {
return fmt.Errorf("文档更新失败: %w", err)
}
// 保存更新
return s.docRepo.Update(ctx, doc)
}
// GetDocumentation 获取文档
func (s *ProductDocumentationService) GetDocumentation(ctx context.Context, id string) (*entities.ProductDocumentation, error) {
return s.docRepo.FindByID(ctx, id)
}
// GetDocumentationByProductID 通过产品ID获取文档
func (s *ProductDocumentationService) GetDocumentationByProductID(ctx context.Context, productID string) (*entities.ProductDocumentation, error) {
return s.docRepo.FindByProductID(ctx, productID)
}
// DeleteDocumentation 删除文档
func (s *ProductDocumentationService) DeleteDocumentation(ctx context.Context, id string) error {
_, err := s.docRepo.FindByID(ctx, id)
if err != nil {
return fmt.Errorf("文档不存在: %w", err)
}
return s.docRepo.Delete(ctx, id)
}
// GetDocumentationWithProduct 获取文档及其关联的产品信息
func (s *ProductDocumentationService) GetDocumentationWithProduct(ctx context.Context, id string) (*entities.ProductDocumentation, error) {
doc, err := s.docRepo.FindByID(ctx, id)
if err != nil {
return nil, err
}
// 加载产品信息
product, err := s.productRepo.GetByID(ctx, doc.ProductID)
if err != nil {
return nil, fmt.Errorf("获取产品信息失败: %w", err)
}
doc.Product = &product
return doc, nil
}
// GetDocumentationsByProductIDs 批量获取文档
func (s *ProductDocumentationService) GetDocumentationsByProductIDs(ctx context.Context, productIDs []string) ([]*entities.ProductDocumentation, error) {
return s.docRepo.FindByProductIDs(ctx, productIDs)
}
// UpdateDocumentationEntity 更新文档实体用于更新PDFFilePath等字段
func (s *ProductDocumentationService) UpdateDocumentationEntity(ctx context.Context, doc *entities.ProductDocumentation) error {
// 验证文档是否存在
_, err := s.docRepo.FindByID(ctx, doc.ID)
if err != nil {
return fmt.Errorf("文档不存在: %w", err)
}
// 保存更新
return s.docRepo.Update(ctx, doc)
}

View File

@@ -0,0 +1,437 @@
package services
import (
"context"
"errors"
"fmt"
"strings"
"go.uber.org/zap"
"hyapi-server/internal/application/product/dto/commands"
"hyapi-server/internal/domains/product/entities"
"hyapi-server/internal/domains/product/repositories"
"hyapi-server/internal/domains/product/repositories/queries"
"hyapi-server/internal/shared/interfaces"
)
// ProductManagementService 产品管理领域服务
// 负责产品的基本管理操作,包括创建、查询、更新等
type ProductManagementService struct {
productRepo repositories.ProductRepository
categoryRepo repositories.ProductCategoryRepository
subCategoryRepo repositories.ProductSubCategoryRepository
logger *zap.Logger
}
// NewProductManagementService 创建产品管理领域服务
func NewProductManagementService(
productRepo repositories.ProductRepository,
categoryRepo repositories.ProductCategoryRepository,
subCategoryRepo repositories.ProductSubCategoryRepository,
logger *zap.Logger,
) *ProductManagementService {
return &ProductManagementService{
productRepo: productRepo,
categoryRepo: categoryRepo,
subCategoryRepo: subCategoryRepo,
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
}
func (s *ProductManagementService) GetProductByCode(ctx context.Context, productCode string) (*entities.Product, error) {
product, err := s.productRepo.FindByCode(ctx, productCode)
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
}
}
// 如果是组合包,加载子产品信息
if product.IsPackage {
packageItems, err := s.productRepo.GetPackageItems(ctx, productID)
if err == nil {
product.PackageItems = packageItems
}
}
return &product, nil
}
// GetProductByOldIDWithCategory 根据旧ID获取产品及其分类信息
func (s *ProductManagementService) GetProductByOldIDWithCategory(ctx context.Context, oldID string) (*entities.Product, error) {
product, err := s.productRepo.FindByOldID(ctx, oldID)
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
}
}
// 如果是组合包,加载子产品信息
if product.IsPackage {
packageItems, err := s.productRepo.GetPackageItems(ctx, product.ID)
if err == nil {
product.PackageItems = packageItems
}
}
return product, nil
}
// GetPackageItems 获取组合包项目列表
func (s *ProductManagementService) GetPackageItems(ctx context.Context, packageID string) ([]*entities.ProductPackageItem, error) {
packageItems, err := s.productRepo.GetPackageItems(ctx, packageID)
if err != nil {
s.logger.Error("获取组合包项目失败", zap.Error(err))
return nil, fmt.Errorf("获取组合包项目失败: %w", err)
}
return packageItems, nil
}
// CreatePackageItem 创建组合包项目
func (s *ProductManagementService) CreatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error {
if err := s.productRepo.CreatePackageItem(ctx, packageItem); err != nil {
s.logger.Error("创建组合包项目失败", zap.Error(err))
return fmt.Errorf("创建组合包项目失败: %w", err)
}
s.logger.Info("组合包项目创建成功",
zap.String("package_id", packageItem.PackageID),
zap.String("product_id", packageItem.ProductID),
)
return nil
}
// GetPackageItemByID 根据ID获取组合包项目
func (s *ProductManagementService) GetPackageItemByID(ctx context.Context, itemID string) (*entities.ProductPackageItem, error) {
packageItem, err := s.productRepo.GetPackageItemByID(ctx, itemID)
if err != nil {
return nil, fmt.Errorf("组合包项目不存在: %w", err)
}
return packageItem, nil
}
// UpdatePackageItem 更新组合包项目
func (s *ProductManagementService) UpdatePackageItem(ctx context.Context, packageItem *entities.ProductPackageItem) error {
if err := s.productRepo.UpdatePackageItem(ctx, packageItem); err != nil {
s.logger.Error("更新组合包项目失败", zap.Error(err))
return fmt.Errorf("更新组合包项目失败: %w", err)
}
s.logger.Info("组合包项目更新成功",
zap.String("item_id", packageItem.ID),
zap.String("package_id", packageItem.PackageID),
)
return nil
}
// DeletePackageItem 删除组合包项目
func (s *ProductManagementService) DeletePackageItem(ctx context.Context, itemID string) error {
if err := s.productRepo.DeletePackageItem(ctx, itemID); err != nil {
s.logger.Error("删除组合包项目失败", zap.Error(err))
return fmt.Errorf("删除组合包项目失败: %w", err)
}
s.logger.Info("组合包项目删除成功", zap.String("item_id", itemID))
return nil
}
// UpdatePackageItemsBatch 批量更新组合包子产品
func (s *ProductManagementService) UpdatePackageItemsBatch(ctx context.Context, packageID string, items []commands.PackageItemData) error {
// 删除现有的所有子产品
if err := s.productRepo.DeletePackageItemsByPackageID(ctx, packageID); err != nil {
s.logger.Error("删除现有组合包子产品失败", zap.Error(err))
return fmt.Errorf("删除现有组合包子产品失败: %w", err)
}
// 创建新的子产品项目
for _, item := range items {
packageItem := &entities.ProductPackageItem{
PackageID: packageID,
ProductID: item.ProductID,
SortOrder: item.SortOrder,
}
if err := s.productRepo.CreatePackageItem(ctx, packageItem); err != nil {
s.logger.Error("创建组合包子产品失败", zap.Error(err))
return fmt.Errorf("创建组合包子产品失败: %w", err)
}
}
s.logger.Info("批量更新组合包子产品成功",
zap.String("package_id", packageID),
zap.Int("item_count", len(items)),
)
return 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.IsNegative() {
return errors.New("产品价格不能为负数")
}
if product.CostPrice.IsNegative() {
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("产品分类已禁用或删除")
}
}
// 验证二级分类是否存在(如果设置了二级分类)
if product.SubCategoryID != nil && *product.SubCategoryID != "" {
subCategory, err := s.subCategoryRepo.GetByID(context.Background(), *product.SubCategoryID)
if err != nil {
return fmt.Errorf("产品二级分类不存在: %w", err)
}
if !subCategory.IsValid() {
return errors.New("产品二级分类已禁用或删除")
}
// 验证二级分类是否属于指定的一级分类
if subCategory.CategoryID != product.CategoryID {
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
}
// ListProducts 获取产品列表(支持筛选和分页)
func (s *ProductManagementService) ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.Product, int64, error) {
// 构建查询条件
query := &queries.ListProductsQuery{
Page: options.Page,
PageSize: options.PageSize,
SortBy: options.Sort,
SortOrder: options.Order,
}
// 应用筛选条件
if keyword, ok := filters["keyword"].(string); ok && keyword != "" {
query.Keyword = keyword
}
if categoryID, ok := filters["category_id"].(string); ok && categoryID != "" {
query.CategoryID = categoryID
}
if isEnabled, ok := filters["is_enabled"].(bool); ok {
query.IsEnabled = &isEnabled
}
if isVisible, ok := filters["is_visible"].(bool); ok {
query.IsVisible = &isVisible
}
if isPackage, ok := filters["is_package"].(bool); ok {
query.IsPackage = &isPackage
}
// 调用仓储层获取产品列表
products, total, err := s.productRepo.ListProducts(ctx, query)
if err != nil {
s.logger.Error("获取产品列表失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取产品列表失败: %w", err)
}
s.logger.Info("产品列表查询成功",
zap.Int("count", len(products)),
zap.Int64("total", total),
zap.Int("page", options.Page),
zap.Int("page_size", options.PageSize),
)
return products, total, nil
}
// ListProductsWithSubscriptionStatus 获取产品列表(包含订阅状态)
func (s *ProductManagementService) ListProductsWithSubscriptionStatus(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.Product, map[string]bool, int64, error) {
// 构建查询条件
query := &queries.ListProductsQuery{
Page: options.Page,
PageSize: options.PageSize,
SortBy: options.Sort,
SortOrder: options.Order,
}
// 应用筛选条件
if keyword, ok := filters["keyword"].(string); ok && keyword != "" {
query.Keyword = keyword
}
if categoryID, ok := filters["category_id"].(string); ok && categoryID != "" {
query.CategoryID = categoryID
}
if isEnabled, ok := filters["is_enabled"].(bool); ok {
query.IsEnabled = &isEnabled
}
if isVisible, ok := filters["is_visible"].(bool); ok {
query.IsVisible = &isVisible
}
if isPackage, ok := filters["is_package"].(bool); ok {
query.IsPackage = &isPackage
}
if userID, ok := filters["user_id"].(string); ok && userID != "" {
query.UserID = userID
}
if isSubscribed, ok := filters["is_subscribed"].(bool); ok {
query.IsSubscribed = &isSubscribed
}
// 调用仓储层获取产品列表(包含订阅状态)
products, subscriptionStatusMap, total, err := s.productRepo.ListProductsWithSubscriptionStatus(ctx, query)
if err != nil {
s.logger.Error("获取产品列表失败", zap.Error(err))
return nil, nil, 0, fmt.Errorf("获取产品列表失败: %w", err)
}
s.logger.Info("产品列表查询成功",
zap.Int("count", len(products)),
zap.Int64("total", total),
zap.Int("page", options.Page),
zap.Int("page_size", options.PageSize),
zap.String("user_id", query.UserID),
)
return products, subscriptionStatusMap, total, nil
}

View File

@@ -0,0 +1,350 @@
package services
import (
"context"
"errors"
"fmt"
"time"
"gorm.io/gorm"
"go.uber.org/zap"
"hyapi-server/internal/domains/product/entities"
"hyapi-server/internal/domains/product/repositories"
"hyapi-server/internal/domains/product/repositories/queries"
"hyapi-server/internal/shared/interfaces"
"github.com/shopspring/decimal"
)
// 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,
}
}
// UserSubscribedProductByCode 查找用户已订阅的产品
func (s *ProductSubscriptionService) UserSubscribedProductByCode(ctx context.Context, userID string, productCode string) (*entities.Subscription, error) {
product, err := s.productRepo.FindByCode(ctx, productCode)
if err != nil {
return nil, err
}
subscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, product.ID)
if err != nil {
return nil, err
}
return subscription, nil
}
// GetUserSubscribedProduct 查找用户已订阅的产品
func (s *ProductSubscriptionService) GetUserSubscribedProduct(ctx context.Context, userID string, productID string) (*entities.Subscription, error) {
subscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, productID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return subscription, nil
}
// 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,
UIComponentPrice: product.UIComponentPrice,
}
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
}
// ListSubscriptions 获取订阅列表
func (s *ProductSubscriptionService) ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) ([]*entities.Subscription, int64, error) {
return s.subscriptionRepo.ListSubscriptions(ctx, query)
}
// 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
}
func (s *ProductSubscriptionService) SaveSubscription(ctx context.Context, subscription *entities.Subscription) error {
exists, err := s.subscriptionRepo.Exists(ctx, subscription.ID)
if err != nil {
return fmt.Errorf("检查订阅是否存在失败: %w", err)
}
if exists {
return s.subscriptionRepo.Update(ctx, *subscription)
} else {
_, err := s.subscriptionRepo.Create(ctx, *subscription)
if err != nil {
return fmt.Errorf("创建订阅失败: %w", err)
}
return nil
}
}
// IncrementSubscriptionAPIUsage 增加订阅API使用次数使用乐观锁带重试机制
func (s *ProductSubscriptionService) IncrementSubscriptionAPIUsage(ctx context.Context, subscriptionID string, increment int64) error {
const maxRetries = 3
const baseDelay = 10 * time.Millisecond
for attempt := 0; attempt < maxRetries; attempt++ {
// 使用乐观锁直接更新数据库
err := s.subscriptionRepo.IncrementAPIUsageWithOptimisticLock(ctx, subscriptionID, increment)
if err == nil {
// 更新成功
if attempt > 0 {
s.logger.Info("订阅API使用次数更新成功重试后",
zap.String("subscription_id", subscriptionID),
zap.Int64("increment", increment),
zap.Int("retry_count", attempt))
} else {
s.logger.Info("订阅API使用次数更新成功",
zap.String("subscription_id", subscriptionID),
zap.Int64("increment", increment))
}
return nil
}
// 检查是否是版本冲突错误
if errors.Is(err, gorm.ErrRecordNotFound) {
// 版本冲突,等待后重试
if attempt < maxRetries-1 {
delay := time.Duration(attempt+1) * baseDelay
s.logger.Debug("订阅版本冲突,准备重试",
zap.String("subscription_id", subscriptionID),
zap.Int("attempt", attempt+1),
zap.Duration("delay", delay))
time.Sleep(delay)
continue
}
// 最后一次重试失败
s.logger.Error("订阅不存在或版本冲突,重试次数已用完",
zap.String("subscription_id", subscriptionID),
zap.Int("max_retries", maxRetries),
zap.Error(err))
return fmt.Errorf("订阅不存在或已被其他操作修改(重试%d次后失败: %w", maxRetries, err)
}
// 其他错误直接返回,不重试
s.logger.Error("更新订阅API使用次数失败",
zap.String("subscription_id", subscriptionID),
zap.Int64("increment", increment),
zap.Error(err))
return fmt.Errorf("更新订阅API使用次数失败: %w", err)
}
return fmt.Errorf("更新失败,已重试%d次", maxRetries)
}
// GetSubscriptionStats 获取订阅统计信息
func (s *ProductSubscriptionService) GetSubscriptionStats(ctx context.Context) (map[string]interface{}, error) {
stats := make(map[string]interface{})
// 获取总订阅数
totalSubscriptions, err := s.subscriptionRepo.Count(ctx, interfaces.CountOptions{})
if err != nil {
s.logger.Error("获取订阅总数失败", zap.Error(err))
return nil, fmt.Errorf("获取订阅总数失败: %w", err)
}
stats["total_subscriptions"] = totalSubscriptions
// 获取总收入
totalRevenue, err := s.subscriptionRepo.GetTotalRevenue(ctx)
if err != nil {
s.logger.Error("获取总收入失败", zap.Error(err))
return nil, fmt.Errorf("获取总收入失败: %w", err)
}
stats["total_revenue"] = totalRevenue
return stats, nil
}
// GetUserSubscriptionStats 获取用户订阅统计信息
func (s *ProductSubscriptionService) GetUserSubscriptionStats(ctx context.Context, userID string) (map[string]interface{}, error) {
stats := make(map[string]interface{})
// 获取用户订阅数
userSubscriptions, err := s.subscriptionRepo.FindByUserID(ctx, userID)
if err != nil {
s.logger.Error("获取用户订阅失败", zap.Error(err))
return nil, fmt.Errorf("获取用户订阅失败: %w", err)
}
// 计算用户总收入
var totalRevenue float64
for _, subscription := range userSubscriptions {
totalRevenue += subscription.Price.InexactFloat64()
}
stats["total_subscriptions"] = int64(len(userSubscriptions))
stats["total_revenue"] = totalRevenue
return stats, nil
}
// UpdateSubscriptionPrice 更新订阅价格
func (s *ProductSubscriptionService) UpdateSubscriptionPrice(ctx context.Context, subscriptionID string, newPrice float64) error {
// 获取订阅
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
if err != nil {
return fmt.Errorf("订阅不存在: %w", err)
}
// 更新价格
subscription.Price = decimal.NewFromFloat(newPrice)
subscription.Version++ // 增加版本号
// 保存更新
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
s.logger.Error("更新订阅价格失败", zap.Error(err))
return fmt.Errorf("更新订阅价格失败: %w", err)
}
s.logger.Info("订阅价格更新成功",
zap.String("subscription_id", subscriptionID),
zap.Float64("new_price", newPrice))
return nil
}
// UpdateSubscriptionPriceWithUIComponent 更新订阅价格和UI组件价格
func (s *ProductSubscriptionService) UpdateSubscriptionPriceWithUIComponent(ctx context.Context, subscriptionID string, newPrice float64, newUIComponentPrice float64) error {
// 获取订阅
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
if err != nil {
return fmt.Errorf("订阅不存在: %w", err)
}
// 更新价格
subscription.Price = decimal.NewFromFloat(newPrice)
subscription.UIComponentPrice = decimal.NewFromFloat(newUIComponentPrice)
subscription.Version++ // 增加版本号
// 保存更新
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
s.logger.Error("更新订阅价格失败", zap.Error(err))
return fmt.Errorf("更新订阅价格失败: %w", err)
}
s.logger.Info("订阅价格更新成功",
zap.String("subscription_id", subscriptionID),
zap.Float64("new_price", newPrice),
zap.Float64("new_ui_component_price", newUIComponentPrice))
return nil
}