temp
This commit is contained in:
452
internal/application/product/product_application_service_impl.go
Normal file
452
internal/application/product/product_application_service_impl.go
Normal file
@@ -0,0 +1,452 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user