package product import ( "context" "errors" "fmt" "tyapi-server/internal/application/product/dto/commands" appQueries "tyapi-server/internal/application/product/dto/queries" "tyapi-server/internal/application/product/dto/responses" "tyapi-server/internal/domains/product/entities" "tyapi-server/internal/domains/product/repositories" repoQueries "tyapi-server/internal/domains/product/repositories/queries" "tyapi-server/internal/domains/product/services" "go.uber.org/zap" ) // ProductApplicationServiceImpl 产品应用服务实现 type ProductApplicationServiceImpl struct { productRepo repositories.ProductRepository categoryRepo repositories.ProductCategoryRepository subscriptionRepo repositories.SubscriptionRepository productService *services.ProductService logger *zap.Logger } // NewProductApplicationService 创建产品应用服务 func NewProductApplicationService( productRepo repositories.ProductRepository, categoryRepo repositories.ProductCategoryRepository, subscriptionRepo repositories.SubscriptionRepository, productService *services.ProductService, logger *zap.Logger, ) ProductApplicationService { return &ProductApplicationServiceImpl{ productRepo: productRepo, categoryRepo: categoryRepo, subscriptionRepo: subscriptionRepo, productService: productService, logger: logger, } } // CreateProduct 创建产品 func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error { // 1. 参数验证 if err := s.validateCreateProduct(cmd); err != nil { return err } // 2. 验证产品编号唯一性 if err := s.productService.ValidateProductCode(cmd.Code, ""); err != nil { return err } // 3. 创建产品实体 product := &entities.Product{ Name: cmd.Name, Code: cmd.Code, Description: cmd.Description, Content: cmd.Content, CategoryID: cmd.CategoryID, Price: cmd.Price, IsEnabled: cmd.IsEnabled, IsVisible: cmd.IsVisible, IsPackage: cmd.IsPackage, } // 4. 设置SEO信息 product.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords) // 5. 调用领域服务验证 if err := s.productService.ValidateProduct(product); err != nil { return err } // 6. 保存到仓储 if _, err := s.productRepo.Create(ctx, *product); err != nil { s.logger.Error("创建产品失败", zap.Error(err)) return fmt.Errorf("创建产品失败: %w", err) } s.logger.Info("创建产品成功", zap.String("id", product.ID), zap.String("name", product.Name)) return nil } // UpdateProduct 更新产品 func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error { // 1. 获取现有产品 existingProduct, err := s.productRepo.GetByID(ctx, cmd.ID) if err != nil { return fmt.Errorf("产品不存在: %w", err) } // 2. 参数验证 if err := s.validateUpdateProduct(cmd); err != nil { return err } // 3. 验证产品编号唯一性(如果修改了编号) if cmd.Code != "" && cmd.Code != existingProduct.Code { if err := s.productService.ValidateProductCode(cmd.Code, cmd.ID); err != nil { return err } existingProduct.Code = cmd.Code } // 4. 更新字段 if cmd.Name != "" { existingProduct.Name = cmd.Name } if cmd.Description != "" { existingProduct.Description = cmd.Description } if cmd.Content != "" { existingProduct.Content = cmd.Content } if cmd.CategoryID != "" { existingProduct.CategoryID = cmd.CategoryID } if cmd.Price >= 0 { existingProduct.Price = cmd.Price } if cmd.IsEnabled != nil { existingProduct.IsEnabled = *cmd.IsEnabled } if cmd.IsVisible != nil { existingProduct.IsVisible = *cmd.IsVisible } if cmd.IsPackage != nil { existingProduct.IsPackage = *cmd.IsPackage } // 5. 更新SEO信息 if cmd.SEOTitle != "" || cmd.SEODescription != "" || cmd.SEOKeywords != "" { existingProduct.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords) } // 6. 调用领域服务验证 if err := s.productService.ValidateProduct(&existingProduct); err != nil { return err } // 7. 保存到仓储 if err := s.productRepo.Update(ctx, existingProduct); err != nil { s.logger.Error("更新产品失败", zap.Error(err)) return fmt.Errorf("更新产品失败: %w", err) } s.logger.Info("更新产品成功", zap.String("id", existingProduct.ID)) return nil } // DeleteProduct 删除产品 func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error { // 1. 检查产品是否存在 product, err := s.productRepo.GetByID(ctx, cmd.ID) if err != nil { return fmt.Errorf("产品不存在: %w", err) } // 2. 检查是否有活跃订阅 subscriptions, err := s.subscriptionRepo.FindByProductID(ctx, cmd.ID) if err == nil && len(subscriptions) > 0 { return errors.New("产品存在订阅,无法删除") } // 3. 删除产品 if err := s.productRepo.Delete(ctx, cmd.ID); err != nil { s.logger.Error("删除产品失败", zap.Error(err)) return fmt.Errorf("删除产品失败: %w", err) } s.logger.Info("删除产品成功", zap.String("id", cmd.ID), zap.String("name", product.Name)) return nil } // ListProducts 获取产品列表 func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query *appQueries.ListProductsQuery) (*responses.ProductListResponse, error) { // 构建仓储查询 repoQuery := &repoQueries.ListProductsQuery{ Keyword: query.Keyword, CategoryID: query.CategoryID, MinPrice: query.MinPrice, MaxPrice: query.MaxPrice, IsEnabled: query.IsEnabled, IsVisible: query.IsVisible, IsPackage: query.IsPackage, Page: query.Page, PageSize: query.PageSize, SortBy: query.SortBy, SortOrder: query.SortOrder, } // 调用仓储 products, total, err := s.productRepo.ListProducts(ctx, repoQuery) if err != nil { s.logger.Error("获取产品列表失败", zap.Error(err)) return nil, fmt.Errorf("获取产品列表失败: %w", err) } // 转换为响应对象 items := make([]responses.ProductInfoResponse, len(products)) for i := range products { items[i] = *s.convertToProductInfoResponse(products[i]) } return &responses.ProductListResponse{ Total: total, Page: query.Page, Size: query.PageSize, Items: items, }, nil } // GetProductsByIDs 根据ID列表获取产品 func (s *ProductApplicationServiceImpl) GetProductsByIDs(ctx context.Context, query *appQueries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error) { products, err := s.productRepo.GetByIDs(ctx, query.IDs) if err != nil { s.logger.Error("获取产品列表失败", zap.Error(err)) return nil, fmt.Errorf("获取产品列表失败: %w", err) } // 转换为响应对象 items := make([]*responses.ProductInfoResponse, len(products)) for i := range products { items[i] = s.convertToProductInfoResponse(&products[i]) } return items, nil } // GetSubscribableProducts 获取可订阅的产品 func (s *ProductApplicationServiceImpl) GetSubscribableProducts(ctx context.Context, query *appQueries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error) { products, err := s.productRepo.FindSubscribableProducts(ctx, query.UserID) if err != nil { s.logger.Error("获取可订阅产品失败", zap.Error(err)) return nil, fmt.Errorf("获取可订阅产品失败: %w", err) } // 转换为响应对象 items := make([]*responses.ProductInfoResponse, len(products)) for i := range products { items[i] = s.convertToProductInfoResponse(products[i]) } return items, nil } // GetProductByID 根据ID获取产品 func (s *ProductApplicationServiceImpl) GetProductByID(ctx context.Context, query *appQueries.GetProductQuery) (*responses.ProductInfoResponse, error) { var product *entities.Product var err error if query.ID != "" { p, err := s.productRepo.GetByID(ctx, query.ID) if err != nil { return nil, fmt.Errorf("产品不存在: %w", err) } product = &p } else if query.Code != "" { product, err = s.productRepo.FindByCode(ctx, query.Code) if err != nil { return nil, fmt.Errorf("产品不存在: %w", err) } } else { return nil, errors.New("产品ID或编号不能为空") } // 转换为响应对象 response := s.convertToProductInfoResponse(product) // 加载分类信息 if product.CategoryID != "" { category, err := s.categoryRepo.GetByID(ctx, product.CategoryID) if err == nil { response.Category = s.convertToCategoryInfoResponse(&category) } } return response, nil } // EnableProduct 启用产品 func (s *ProductApplicationServiceImpl) EnableProduct(ctx context.Context, cmd *commands.EnableProductCommand) error { product, err := s.productRepo.GetByID(ctx, cmd.ID) if err != nil { return fmt.Errorf("产品不存在: %w", err) } product.Enable() 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("id", cmd.ID)) return nil } // DisableProduct 禁用产品 func (s *ProductApplicationServiceImpl) DisableProduct(ctx context.Context, cmd *commands.DisableProductCommand) error { product, err := s.productRepo.GetByID(ctx, cmd.ID) if err != nil { return fmt.Errorf("产品不存在: %w", err) } product.Disable() 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("id", cmd.ID)) return nil } // ShowProduct 显示产品 func (s *ProductApplicationServiceImpl) ShowProduct(ctx context.Context, cmd *commands.ShowProductCommand) error { product, err := s.productRepo.GetByID(ctx, cmd.ID) if err != nil { return fmt.Errorf("产品不存在: %w", err) } product.Show() 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("id", cmd.ID)) return nil } // HideProduct 隐藏产品 func (s *ProductApplicationServiceImpl) HideProduct(ctx context.Context, cmd *commands.HideProductCommand) error { product, err := s.productRepo.GetByID(ctx, cmd.ID) if err != nil { return fmt.Errorf("产品不存在: %w", err) } product.Hide() 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("id", cmd.ID)) return nil } // UpdateProductSEO 更新产品SEO信息 func (s *ProductApplicationServiceImpl) UpdateProductSEO(ctx context.Context, cmd *commands.UpdateProductSEOCommand) error { product, err := s.productRepo.GetByID(ctx, cmd.ID) if err != nil { return fmt.Errorf("产品不存在: %w", err) } product.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords) if err := s.productRepo.Update(ctx, product); err != nil { s.logger.Error("更新产品SEO失败", zap.Error(err)) return fmt.Errorf("更新产品SEO失败: %w", err) } s.logger.Info("更新产品SEO成功", zap.String("id", cmd.ID)) return nil } // GetProductStats 获取产品统计 func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error) { stats, err := s.productService.GetProductStats() if err != nil { s.logger.Error("获取产品统计失败", zap.Error(err)) return nil, fmt.Errorf("获取产品统计失败: %w", err) } return &responses.ProductStatsResponse{ TotalProducts: stats["total"], EnabledProducts: stats["enabled"], VisibleProducts: stats["visible"], PackageProducts: 0, // 需要单独统计 }, nil } // 私有方法 func (s *ProductApplicationServiceImpl) validateCreateProduct(cmd *commands.CreateProductCommand) error { if cmd.Name == "" { return errors.New("产品名称不能为空") } if cmd.Code == "" { return errors.New("产品编号不能为空") } if cmd.CategoryID == "" { return errors.New("产品分类不能为空") } if cmd.Price < 0 { return errors.New("产品价格不能为负数") } return nil } func (s *ProductApplicationServiceImpl) validateUpdateProduct(cmd *commands.UpdateProductCommand) error { if cmd.ID == "" { return errors.New("产品ID不能为空") } if cmd.Price < 0 { return errors.New("产品价格不能为负数") } return nil } func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse { return &responses.ProductInfoResponse{ ID: product.ID, Name: product.Name, Code: product.Code, Description: product.Description, Content: product.Content, CategoryID: product.CategoryID, Price: product.Price, IsEnabled: product.IsEnabled, IsVisible: product.IsVisible, IsPackage: product.IsPackage, SEOTitle: product.SEOTitle, SEODescription: product.SEODescription, SEOKeywords: product.SEOKeywords, CreatedAt: product.CreatedAt, UpdatedAt: product.UpdatedAt, } } func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *entities.ProductCategory) *responses.CategoryInfoResponse { return &responses.CategoryInfoResponse{ ID: category.ID, Name: category.Name, Code: category.Code, Description: category.Description, ParentID: category.ParentID, Level: category.Level, Sort: category.Sort, IsEnabled: category.IsEnabled, IsVisible: category.IsVisible, CreatedAt: category.CreatedAt, UpdatedAt: category.UpdatedAt, } }