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

@@ -2,6 +2,7 @@ package product
import (
"context"
"errors"
"fmt"
"tyapi-server/internal/application/product/dto/commands"
"tyapi-server/internal/application/product/dto/queries"
@@ -9,7 +10,6 @@ import (
"tyapi-server/internal/domains/product/entities"
"tyapi-server/internal/domains/product/repositories"
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
"tyapi-server/internal/shared/interfaces"
"go.uber.org/zap"
)
@@ -30,114 +30,92 @@ func NewCategoryApplicationService(
logger: logger,
}
}
// CreateCategory 创建分类
func (s *CategoryApplicationServiceImpl) CreateCategory(ctx context.Context, cmd *commands.CreateCategoryCommand) error {
// 1. 参数验证
if err := s.validateCreateCategory(cmd); err != nil {
return err
}
// 2. 检查父分类是否存在
if cmd.ParentID != nil && *cmd.ParentID != "" {
_, err := s.categoryRepo.GetByID(ctx, *cmd.ParentID)
if err != nil {
return fmt.Errorf("父分类不存在: %w", err)
}
// 2. 验证分类编号唯一性
if err := s.validateCategoryCode(cmd.Code, ""); err != nil {
return err
}
// 3. 创建分类实体
category := entities.ProductCategory{
category := &entities.ProductCategory{
Name: cmd.Name,
Code: cmd.Code,
Description: cmd.Description,
ParentID: cmd.ParentID,
Level: cmd.Level,
Sort: cmd.Sort,
IsEnabled: cmd.IsEnabled,
IsVisible: cmd.IsVisible,
}
// 4. 保存到仓储
_, err := s.categoryRepo.Create(ctx, category)
createdCategory, err := s.categoryRepo.Create(ctx, *category)
if err != nil {
s.logger.Error("创建分类失败", zap.Error(err))
s.logger.Error("创建分类失败", zap.Error(err), zap.String("code", cmd.Code))
return fmt.Errorf("创建分类失败: %w", err)
}
s.logger.Info("创建分类成功", zap.String("name", cmd.Name))
s.logger.Info("创建分类成功", zap.String("id", createdCategory.ID), zap.String("code", cmd.Code))
return nil
}
// UpdateCategory 更新分类
func (s *CategoryApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd *commands.UpdateCategoryCommand) error {
// 1. 获取现有分类
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
// 1. 参数验证
if err := s.validateUpdateCategory(cmd); err != nil {
return err
}
// 2. 获取现有分类
existingCategory, err := s.categoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
// 2. 更新字段
if cmd.Name != "" {
category.Name = cmd.Name
// 3. 验证分类编号唯一性(排除当前分类)
if err := s.validateCategoryCode(cmd.Code, cmd.ID); err != nil {
return err
}
if cmd.Code != "" {
category.Code = cmd.Code
}
if cmd.Description != "" {
category.Description = cmd.Description
}
if cmd.ParentID != nil {
category.ParentID = cmd.ParentID
}
if cmd.Level > 0 {
category.Level = cmd.Level
}
if cmd.Sort > 0 {
category.Sort = cmd.Sort
}
if cmd.IsEnabled != nil {
category.IsEnabled = *cmd.IsEnabled
}
if cmd.IsVisible != nil {
category.IsVisible = *cmd.IsVisible
}
// 3. 保存到仓储
if err := s.categoryRepo.Update(ctx, category); err != nil {
s.logger.Error("更新分类失败", zap.Error(err))
// 4. 更新分类信息
existingCategory.Name = cmd.Name
existingCategory.Code = cmd.Code
existingCategory.Description = cmd.Description
existingCategory.Sort = cmd.Sort
existingCategory.IsEnabled = cmd.IsEnabled
existingCategory.IsVisible = cmd.IsVisible
// 5. 保存到仓储
if err := s.categoryRepo.Update(ctx, existingCategory); err != nil {
s.logger.Error("更新分类失败", zap.Error(err), zap.String("id", cmd.ID))
return fmt.Errorf("更新分类失败: %w", err)
}
s.logger.Info("更新分类成功", zap.String("id", cmd.ID))
s.logger.Info("更新分类成功", zap.String("id", cmd.ID), zap.String("code", cmd.Code))
return nil
}
// DeleteCategory 删除分类
func (s *CategoryApplicationServiceImpl) DeleteCategory(ctx context.Context, cmd *commands.DeleteCategoryCommand) error {
// 1. 检查分类是否存在
_, err := s.categoryRepo.GetByID(ctx, cmd.ID)
existingCategory, err := s.categoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
// 2. 检查是否有子分类
children, err := s.categoryRepo.FindByParentID(ctx, &cmd.ID)
if err != nil {
s.logger.Error("检查子分类失败", zap.Error(err))
return fmt.Errorf("检查子分类失败: %w", err)
}
if len(children) > 0 {
return fmt.Errorf("分类下有子分类,无法删除")
}
// 2. 检查是否有产品(可选,根据业务需求决定)
// 这里可以添加检查逻辑,如果有产品则不允许删除
// 3. 删除分类
if err := s.categoryRepo.Delete(ctx, cmd.ID); err != nil {
s.logger.Error("删除分类失败", zap.Error(err))
s.logger.Error("删除分类失败", zap.Error(err), zap.String("id", cmd.ID))
return fmt.Errorf("删除分类失败: %w", err)
}
s.logger.Info("删除分类成功", zap.String("id", cmd.ID))
s.logger.Info("删除分类成功", zap.String("id", cmd.ID), zap.String("code", existingCategory.Code))
return nil
}
@@ -158,16 +136,6 @@ func (s *CategoryApplicationServiceImpl) GetCategoryByID(ctx context.Context, qu
// 转换为响应对象
response := s.convertToCategoryInfoResponse(&category)
// 加载父分类信息
if category.ParentID != nil && *category.ParentID != "" {
parent, err := s.categoryRepo.GetByID(ctx, *category.ParentID)
if err == nil {
parentResponse := s.convertToCategoryInfoResponse(&parent)
response.Parent = parentResponse
}
}
return response, nil
}
@@ -177,8 +145,6 @@ func (s *CategoryApplicationServiceImpl) ListCategories(ctx context.Context, que
repoQuery := &repoQueries.ListCategoriesQuery{
Page: query.Page,
PageSize: query.PageSize,
ParentID: query.ParentID,
Level: query.Level,
IsEnabled: query.IsEnabled,
IsVisible: query.IsVisible,
SortBy: query.SortBy,
@@ -206,212 +172,13 @@ func (s *CategoryApplicationServiceImpl) ListCategories(ctx context.Context, que
}, nil
}
// EnableCategory 启用分类
func (s *CategoryApplicationServiceImpl) EnableCategory(ctx context.Context, cmd *commands.EnableCategoryCommand) error {
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
category.IsEnabled = true
if err := s.categoryRepo.Update(ctx, category); err != nil {
s.logger.Error("启用分类失败", zap.Error(err))
return fmt.Errorf("启用分类失败: %w", err)
}
s.logger.Info("启用分类成功", zap.String("id", cmd.ID))
return nil
}
// DisableCategory 禁用分类
func (s *CategoryApplicationServiceImpl) DisableCategory(ctx context.Context, cmd *commands.DisableCategoryCommand) error {
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
category.IsEnabled = false
if err := s.categoryRepo.Update(ctx, category); err != nil {
s.logger.Error("禁用分类失败", zap.Error(err))
return fmt.Errorf("禁用分类失败: %w", err)
}
s.logger.Info("禁用分类成功", zap.String("id", cmd.ID))
return nil
}
// ShowCategory 显示分类
func (s *CategoryApplicationServiceImpl) ShowCategory(ctx context.Context, cmd *commands.ShowCategoryCommand) error {
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
category.IsVisible = true
if err := s.categoryRepo.Update(ctx, category); err != nil {
s.logger.Error("显示分类失败", zap.Error(err))
return fmt.Errorf("显示分类失败: %w", err)
}
s.logger.Info("显示分类成功", zap.String("id", cmd.ID))
return nil
}
// HideCategory 隐藏分类
func (s *CategoryApplicationServiceImpl) HideCategory(ctx context.Context, cmd *commands.HideCategoryCommand) error {
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
category.IsVisible = false
if err := s.categoryRepo.Update(ctx, category); err != nil {
s.logger.Error("隐藏分类失败", zap.Error(err))
return fmt.Errorf("隐藏分类失败: %w", err)
}
s.logger.Info("隐藏分类成功", zap.String("id", cmd.ID))
return nil
}
// MoveCategory 移动分类
func (s *CategoryApplicationServiceImpl) MoveCategory(ctx context.Context, cmd *commands.MoveCategoryCommand) error {
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("分类不存在: %w", err)
}
// 检查目标父分类是否存在
if cmd.ParentID != nil && *cmd.ParentID != "" {
_, err := s.categoryRepo.GetByID(ctx, *cmd.ParentID)
if err != nil {
return fmt.Errorf("目标父分类不存在: %w", err)
}
}
category.ParentID = cmd.ParentID
if cmd.Sort > 0 {
category.Sort = cmd.Sort
}
if err := s.categoryRepo.Update(ctx, category); err != nil {
s.logger.Error("移动分类失败", zap.Error(err))
return fmt.Errorf("移动分类失败: %w", err)
}
s.logger.Info("移动分类成功", zap.String("id", cmd.ID))
return nil
}
// GetCategoryTree 获取分类树
func (s *CategoryApplicationServiceImpl) GetCategoryTree(ctx context.Context, query *queries.GetCategoryTreeQuery) (*responses.CategoryTreeResponse, error) {
categories, err := s.categoryRepo.GetCategoryTree(ctx)
if err != nil {
s.logger.Error("获取分类树失败", zap.Error(err))
return nil, fmt.Errorf("获取分类树失败: %w", err)
}
// 构建树形结构
tree := s.buildCategoryTree(categories, query.IncludeDisabled, query.IncludeHidden)
return &responses.CategoryTreeResponse{
Categories: tree,
}, nil
}
// GetCategoriesByLevel 根据层级获取分类
func (s *CategoryApplicationServiceImpl) GetCategoriesByLevel(ctx context.Context, query *queries.GetCategoriesByLevelQuery) ([]*responses.CategoryInfoResponse, error) {
categories, err := s.categoryRepo.FindCategoriesByLevel(ctx, query.Level)
if err != nil {
s.logger.Error("根据层级获取分类失败", zap.Error(err))
return nil, fmt.Errorf("根据层级获取分类失败: %w", err)
}
// 转换为响应对象
items := make([]*responses.CategoryInfoResponse, len(categories))
for i, category := range categories {
items[i] = s.convertToCategoryInfoResponse(category)
}
return items, nil
}
// GetCategoryPath 获取分类路径
func (s *CategoryApplicationServiceImpl) GetCategoryPath(ctx context.Context, query *queries.GetCategoryPathQuery) (*responses.CategoryPathResponse, error) {
path, err := s.buildCategoryPath(ctx, query.CategoryID)
if err != nil {
s.logger.Error("获取分类路径失败", zap.Error(err))
return nil, fmt.Errorf("获取分类路径失败: %w", err)
}
// 转换为正确的类型
pathItems := make([]responses.CategorySimpleResponse, len(path))
for i, item := range path {
pathItems[i] = *item
}
return &responses.CategoryPathResponse{
Path: pathItems,
}, nil
}
// GetCategoryStats 获取分类统计
func (s *CategoryApplicationServiceImpl) GetCategoryStats(ctx context.Context) (*responses.CategoryStatsResponse, error) {
// 使用正确的CountOptions
total, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{})
if err != nil {
s.logger.Error("获取分类总数失败", zap.Error(err))
return nil, fmt.Errorf("获取分类总数失败: %w", err)
}
enabled, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{
Filters: map[string]interface{}{"is_enabled": true},
})
if err != nil {
s.logger.Error("获取启用分类数失败", zap.Error(err))
return nil, fmt.Errorf("获取启用分类数失败: %w", err)
}
visible, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{
Filters: map[string]interface{}{"is_visible": true},
})
if err != nil {
s.logger.Error("获取可见分类数失败", zap.Error(err))
return nil, fmt.Errorf("获取可见分类数失败: %w", err)
}
return &responses.CategoryStatsResponse{
TotalCategories: total,
EnabledCategories: enabled,
VisibleCategories: visible,
RootCategories: total - enabled, // 简化计算,实际应该查询根分类数量
}, nil
}
// 私有方法
func (s *CategoryApplicationServiceImpl) validateCreateCategory(cmd *commands.CreateCategoryCommand) error {
if cmd.Name == "" {
return fmt.Errorf("分类名称不能为空")
}
if cmd.Level <= 0 {
return fmt.Errorf("分类层级必须大于0")
}
return nil
}
// convertToCategoryInfoResponse 转换为分类信息响应
func (s *CategoryApplicationServiceImpl) 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,
@@ -420,76 +187,50 @@ func (s *CategoryApplicationServiceImpl) convertToCategoryInfoResponse(category
}
}
// convertToCategorySimpleResponse 转换为分类简单信息响应
func (s *CategoryApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
return &responses.CategorySimpleResponse{
ID: category.ID,
Name: category.Name,
Code: category.Code,
ParentID: category.ParentID,
Level: category.Level,
ID: category.ID,
Name: category.Name,
Code: category.Code,
}
}
func (s *CategoryApplicationServiceImpl) buildCategoryTree(categories []*entities.ProductCategory, includeDisabled, includeHidden bool) []responses.CategoryInfoResponse {
// 构建ID到分类的映射
categoryMap := make(map[string]*entities.ProductCategory)
for _, category := range categories {
// 根据过滤条件决定是否包含
if !includeDisabled && !category.IsEnabled {
continue
}
if !includeHidden && !category.IsVisible {
continue
}
categoryMap[category.ID] = category
// validateCreateCategory 验证创建分类参数
func (s *CategoryApplicationServiceImpl) validateCreateCategory(cmd *commands.CreateCategoryCommand) error {
if cmd.Name == "" {
return errors.New("分类名称不能为空")
}
// 构建树形结构
var roots []responses.CategoryInfoResponse
for _, category := range categoryMap {
if category.ParentID == nil || *category.ParentID == "" {
// 根节点
root := *s.convertToCategoryInfoResponse(category)
root.Children = s.findChildren(category.ID, categoryMap)
roots = append(roots, root)
}
if cmd.Code == "" {
return errors.New("分类编号不能为空")
}
return roots
return nil
}
func (s *CategoryApplicationServiceImpl) findChildren(parentID string, categoryMap map[string]*entities.ProductCategory) []responses.CategoryInfoResponse {
var children []responses.CategoryInfoResponse
for _, category := range categoryMap {
if category.ParentID != nil && *category.ParentID == parentID {
child := *s.convertToCategoryInfoResponse(category)
child.Children = s.findChildren(category.ID, categoryMap)
children = append(children, child)
}
// validateUpdateCategory 验证更新分类参数
func (s *CategoryApplicationServiceImpl) validateUpdateCategory(cmd *commands.UpdateCategoryCommand) error {
if cmd.ID == "" {
return errors.New("分类ID不能为空")
}
return children
if cmd.Name == "" {
return errors.New("分类名称不能为空")
}
if cmd.Code == "" {
return errors.New("分类编号不能为空")
}
return nil
}
func (s *CategoryApplicationServiceImpl) buildCategoryPath(ctx context.Context, categoryID string) ([]*responses.CategorySimpleResponse, error) {
var path []*responses.CategorySimpleResponse
currentID := categoryID
for currentID != "" {
category, err := s.categoryRepo.GetByID(ctx, currentID)
if err != nil {
return nil, fmt.Errorf("获取分类失败: %w", err)
}
path = append([]*responses.CategorySimpleResponse{
s.convertToCategorySimpleResponse(&category),
}, path...)
if category.ParentID != nil {
currentID = *category.ParentID
} else {
currentID = ""
}
// validateCategoryCode 验证分类编号唯一性
func (s *CategoryApplicationServiceImpl) validateCategoryCode(code, excludeID string) error {
if code == "" {
return errors.New("分类编号不能为空")
}
return path, nil
existingCategory, err := s.categoryRepo.FindByCode(context.Background(), code)
if err == nil && existingCategory != nil && existingCategory.ID != excludeID {
return errors.New("分类编号已存在")
}
return nil
}

View File

@@ -2,57 +2,26 @@ package commands
// CreateCategoryCommand 创建分类命令
type CreateCategoryCommand struct {
Name string `json:"name" binding:"required" comment:"分类名称"`
Code string `json:"code" binding:"required" comment:"分类编号"`
Description string `json:"description" comment:"分类描述"`
ParentID *string `json:"parent_id" comment:"父分类ID"`
Level int `json:"level" binding:"min=1" comment:"分类层级"`
Sort int `json:"sort" comment:"排序"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
Name string `json:"name" binding:"required,min=2,max=50" comment:"分类名称"`
Code string `json:"code" binding:"required,product_code" comment:"分类编号"`
Description string `json:"description" binding:"omitempty,max=200" comment:"分类描述"`
Sort int `json:"sort" binding:"min=0,max=9999" comment:"排序"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
}
// UpdateCategoryCommand 更新分类命令
type UpdateCategoryCommand struct {
ID string `json:"-"`
Name string `json:"name" comment:"分类名称"`
Code string `json:"code" comment:"分类编号"`
Description string `json:"description" comment:"分类描述"`
ParentID *string `json:"parent_id" comment:"父分类ID"`
Level int `json:"level" binding:"min=1" comment:"分类层级"`
Sort int `json:"sort" comment:"排序"`
IsEnabled *bool `json:"is_enabled" comment:"是否启用"`
IsVisible *bool `json:"is_visible" comment:"是否展示"`
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"分类ID"`
Name string `json:"name" binding:"required,min=2,max=50" comment:"分类名称"`
Code string `json:"code" binding:"required,product_code" comment:"分类编号"`
Description string `json:"description" binding:"omitempty,max=200" comment:"分类描述"`
Sort int `json:"sort" binding:"min=0,max=9999" comment:"排序"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
}
// DeleteCategoryCommand 删除分类命令
type DeleteCategoryCommand struct {
ID string `json:"-"`
}
// EnableCategoryCommand 启用分类命令
type EnableCategoryCommand struct {
ID string `json:"-"`
}
// DisableCategoryCommand 禁用分类命令
type DisableCategoryCommand struct {
ID string `json:"-"`
}
// ShowCategoryCommand 显示分类命令
type ShowCategoryCommand struct {
ID string `json:"-"`
}
// HideCategoryCommand 隐藏分类命令
type HideCategoryCommand struct {
ID string `json:"-"`
}
// MoveCategoryCommand 移动分类命令
type MoveCategoryCommand struct {
ID string `json:"-"`
ParentID *string `json:"parent_id" comment:"新的父分类ID"`
Sort int `json:"sort" comment:"新的排序"`
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"分类ID"`
}

View File

@@ -2,70 +2,42 @@ package commands
// CreateProductCommand 创建产品命令
type CreateProductCommand struct {
Name string `json:"name" binding:"required" comment:"产品名称"`
Code string `json:"code" binding:"required" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Content string `json:"content" comment:"产品内容"`
CategoryID string `json:"category_id" binding:"required" comment:"产品分类ID"`
Price float64 `json:"price" binding:"min=0" comment:"产品价格"`
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// SEO信息
SEOTitle string `json:"seo_title" comment:"SEO标题"`
SEODescription string `json:"seo_description" comment:"SEO描述"`
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
SEOKeywords string `json:"seo_keywords" binding:"omitempty,max=200" comment:"SEO关键词"`
}
// UpdateProductCommand 更新产品命令
type UpdateProductCommand struct {
ID string `json:"-"`
Name string `json:"name" comment:"产品名称"`
Code string `json:"code" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Content string `json:"content" comment:"产品内容"`
CategoryID string `json:"category_id" comment:"产品分类ID"`
Price float64 `json:"price" binding:"min=0" comment:"产品价格"`
IsEnabled *bool `json:"is_enabled" comment:"是否启用"`
IsVisible *bool `json:"is_visible" comment:"是否展示"`
IsPackage *bool `json:"is_package" comment:"是否组合包"`
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// SEO信息
SEOTitle string `json:"seo_title" comment:"SEO标题"`
SEODescription string `json:"seo_description" comment:"SEO描述"`
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
SEOKeywords string `json:"seo_keywords" binding:"omitempty,max=200" comment:"SEO关键词"`
}
// DeleteProductCommand 删除产品命令
type DeleteProductCommand struct {
ID string `json:"-"`
}
// EnableProductCommand 启用产品命令
type EnableProductCommand struct {
ID string `json:"-"`
}
// DisableProductCommand 禁用产品命令
type DisableProductCommand struct {
ID string `json:"-"`
}
// ShowProductCommand 显示产品命令
type ShowProductCommand struct {
ID string `json:"-"`
}
// HideProductCommand 隐藏产品命令
type HideProductCommand struct {
ID string `json:"-"`
}
// UpdateProductSEOCommand 更新产品SEO信息命令
type UpdateProductSEOCommand struct {
ID string `json:"-"`
SEOTitle string `json:"seo_title" comment:"SEO标题"`
SEODescription string `json:"seo_description" comment:"SEO描述"`
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
}

View File

@@ -2,56 +2,12 @@ package commands
// CreateSubscriptionCommand 创建订阅命令
type CreateSubscriptionCommand struct {
UserID string `json:"user_id" binding:"required" comment:"用户ID"`
ProductID string `json:"product_id" binding:"required" comment:"产品ID"`
Price float64 `json:"price" binding:"min=0" comment:"订阅价格"`
APILimit int64 `json:"api_limit" comment:"API调用限制"`
AutoRenew bool `json:"auto_renew" comment:"是否自动续费"`
Duration string `json:"duration" comment:"订阅时长"`
UserID string `json:"-" comment:"用户ID"`
ProductID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
}
// UpdateSubscriptionCommand 更新订阅命令
type UpdateSubscriptionCommand struct {
ID string `json:"-"`
Price float64 `json:"price" binding:"min=0" comment:"订阅价格"`
APILimit int64 `json:"api_limit" comment:"API调用限制"`
AutoRenew *bool `json:"auto_renew" comment:"是否自动续费"`
}
// CancelSubscriptionCommand 取消订阅命令
type CancelSubscriptionCommand struct {
ID string `json:"-"`
}
// RenewSubscriptionCommand 续费订阅命令
type RenewSubscriptionCommand struct {
ID string `json:"-"`
Duration string `json:"duration" binding:"required" comment:"续费时长"`
}
// ActivateSubscriptionCommand 激活订阅命令
type ActivateSubscriptionCommand struct {
ID string `json:"-"`
}
// DeactivateSubscriptionCommand 停用订阅命令
type DeactivateSubscriptionCommand struct {
ID string `json:"-"`
}
// UpdateAPIUsageCommand 更新API使用量命令
type UpdateAPIUsageCommand struct {
ID string `json:"-"`
APIUsed int64 `json:"api_used" binding:"min=0" comment:"API使用量"`
}
// ResetAPIUsageCommand 重置API使用量命令
type ResetAPIUsageCommand struct {
ID string `json:"-"`
}
// SetAPILimitCommand 设置API限制命令
type SetAPILimitCommand struct {
ID string `json:"-"`
APILimit int64 `json:"api_limit" binding:"min=0" comment:"API调用限制"`
// UpdateSubscriptionPriceCommand 更新订阅价格命令
type UpdateSubscriptionPriceCommand struct {
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"订阅ID"`
Price float64 `json:"price" binding:"price,min=0" comment:"订阅价格"`
}

View File

@@ -2,34 +2,16 @@ package queries
// ListCategoriesQuery 分类列表查询
type ListCategoriesQuery struct {
Page int `form:"page" binding:"min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
ParentID *string `form:"parent_id" comment:"父分类ID"`
Level *int `form:"level" comment:"分类层级"`
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
IsVisible *bool `form:"is_visible" comment:"是否展示"`
SortBy string `form:"sort_by" comment:"排序字段"`
SortOrder string `form:"sort_order" comment:"排序方向"`
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
IsVisible *bool `form:"is_visible" comment:"是否展示"`
SortBy string `form:"sort_by" binding:"omitempty,oneof=name code sort created_at updated_at" comment:"排序字段"`
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
}
// GetCategoryQuery 获取分类详情查询
type GetCategoryQuery struct {
ID string `uri:"id" comment:"分类ID"`
Code string `form:"code" comment:"分类编号"`
}
// GetCategoryTreeQuery 获取分类树查询
type GetCategoryTreeQuery struct {
IncludeDisabled bool `form:"include_disabled" comment:"是否包含禁用分类"`
IncludeHidden bool `form:"include_hidden" comment:"是否包含隐藏分类"`
}
// GetCategoriesByLevelQuery 根据层级获取分类查询
type GetCategoriesByLevelQuery struct {
Level int `form:"level" binding:"min=1" comment:"分类层级"`
}
// GetCategoryPathQuery 获取分类路径查询
type GetCategoryPathQuery struct {
CategoryID string `uri:"category_id" binding:"required" comment:"分类ID"`
ID string `uri:"id" binding:"omitempty,uuid" comment:"分类ID"`
Code string `form:"code" binding:"omitempty,product_code" comment:"分类编号"`
}

View File

@@ -2,43 +2,43 @@ package queries
// ListProductsQuery 产品列表查询
type ListProductsQuery struct {
Page int `form:"page" binding:"min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
Keyword string `form:"keyword" comment:"搜索关键词"`
CategoryID string `form:"category_id" comment:"分类ID"`
MinPrice *float64 `form:"min_price" comment:"最低价格"`
MaxPrice *float64 `form:"max_price" comment:"最高价格"`
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
IsVisible *bool `form:"is_visible" comment:"是否展示"`
IsPackage *bool `form:"is_package" comment:"是否组合包"`
SortBy string `form:"sort_by" comment:"排序字段"`
SortOrder string `form:"sort_order" comment:"排序方向"`
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
CategoryID string `form:"category_id" binding:"omitempty,uuid" comment:"分类ID"`
MinPrice *float64 `form:"min_price" binding:"omitempty,min=0" comment:"最低价格"`
MaxPrice *float64 `form:"max_price" binding:"omitempty,min=0" comment:"最高价格"`
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
IsVisible *bool `form:"is_visible" comment:"是否展示"`
IsPackage *bool `form:"is_package" comment:"是否组合包"`
SortBy string `form:"sort_by" binding:"omitempty,oneof=name code price created_at updated_at" comment:"排序字段"`
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
}
// SearchProductsQuery 产品搜索查询
type SearchProductsQuery struct {
Page int `form:"page" binding:"min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
Keyword string `form:"keyword" comment:"搜索关键词"`
CategoryID string `form:"category_id" comment:"分类ID"`
MinPrice *float64 `form:"min_price" comment:"最低价格"`
MaxPrice *float64 `form:"max_price" comment:"最高价格"`
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
IsVisible *bool `form:"is_visible" comment:"是否展示"`
IsPackage *bool `form:"is_package" comment:"是否组合包"`
SortBy string `form:"sort_by" comment:"排序字段"`
SortOrder string `form:"sort_order" comment:"排序方向"`
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
CategoryID string `form:"category_id" binding:"omitempty,uuid" comment:"分类ID"`
MinPrice *float64 `form:"min_price" binding:"omitempty,min=0" comment:"最低价格"`
MaxPrice *float64 `form:"max_price" binding:"omitempty,min=0" comment:"最高价格"`
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
IsVisible *bool `form:"is_visible" comment:"是否展示"`
IsPackage *bool `form:"is_package" comment:"是否组合包"`
SortBy string `form:"sort_by" binding:"omitempty,oneof=name code price created_at updated_at" comment:"排序字段"`
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
}
// GetProductQuery 获取产品详情查询
type GetProductQuery struct {
ID string `uri:"id" comment:"产品ID"`
Code string `form:"code" comment:"产品编号"`
ID string `uri:"id" binding:"omitempty,uuid" comment:"产品ID"`
Code string `form:"code" binding:"omitempty,product_code" comment:"产品编号"`
}
// GetProductsByIDsQuery 根据ID列表获取产品查询
type GetProductsByIDsQuery struct {
IDs []string `form:"ids" binding:"required" comment:"产品ID列表"`
IDs []string `form:"ids" binding:"required,dive,uuid" comment:"产品ID列表"`
}
// GetSubscribableProductsQuery 获取可订阅产品查询

View File

@@ -1,36 +1,31 @@
package queries
import "tyapi-server/internal/domains/product/entities"
// ListSubscriptionsQuery 订阅列表查询
type ListSubscriptionsQuery struct {
Page int `form:"page" binding:"min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
UserID string `form:"user_id" comment:"用户ID"`
ProductID string `form:"product_id" comment:"产品ID"`
Status entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
SortBy string `form:"sort_by" comment:"排序字段"`
SortOrder string `form:"sort_order" comment:"排序方向"`
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
UserID string `form:"-" comment:"用户ID"`
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
SortBy string `form:"sort_by" binding:"omitempty,oneof=created_at updated_at price" comment:"排序字段"`
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
}
// GetSubscriptionQuery 获取订阅详情查询
type GetSubscriptionQuery struct {
ID string `uri:"id" binding:"required" comment:"订阅ID"`
ID string `uri:"id" binding:"required,uuid" comment:"订阅ID"`
}
// GetUserSubscriptionsQuery 获取用户订阅查询
type GetUserSubscriptionsQuery struct {
UserID string `form:"user_id" binding:"required" comment:"用户ID"`
Status *entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
UserID string `form:"user_id" binding:"required,uuid" comment:"用户ID"`
}
// GetProductSubscriptionsQuery 获取产品订阅查询
type GetProductSubscriptionsQuery struct {
ProductID string `form:"product_id" binding:"required" comment:"产品ID"`
Status *entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
ProductID string `form:"product_id" binding:"required,uuid" comment:"产品ID"`
}
// GetActiveSubscriptionsQuery 获取活跃订阅查询
type GetActiveSubscriptionsQuery struct {
UserID string `form:"user_id" comment:"用户ID"`
UserID string `form:"user_id" binding:"omitempty,uuid" comment:"用户ID"`
}

View File

@@ -8,16 +8,10 @@ type CategoryInfoResponse struct {
Name string `json:"name" comment:"分类名称"`
Code string `json:"code" comment:"分类编号"`
Description string `json:"description" comment:"分类描述"`
ParentID *string `json:"parent_id" comment:"父分类ID"`
Level int `json:"level" comment:"分类层级"`
Sort int `json:"sort" comment:"排序"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
// 关联信息
Parent *CategoryInfoResponse `json:"parent,omitempty" comment:"父分类"`
Children []CategoryInfoResponse `json:"children,omitempty" comment:"子分类"`
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
}
@@ -30,29 +24,9 @@ type CategoryListResponse struct {
Items []CategoryInfoResponse `json:"items" comment:"分类列表"`
}
// CategoryTreeResponse 分类树响应
type CategoryTreeResponse struct {
Categories []CategoryInfoResponse `json:"categories" comment:"分类树"`
}
// CategorySimpleResponse 分类简单信息响应
type CategorySimpleResponse struct {
ID string `json:"id" comment:"分类ID"`
Name string `json:"name" comment:"分类名称"`
Code string `json:"code" comment:"分类编号"`
ParentID *string `json:"parent_id" comment:"父分类ID"`
Level int `json:"level" comment:"分类层级"`
}
// CategoryPathResponse 分类路径响应
type CategoryPathResponse struct {
Path []CategorySimpleResponse `json:"path" comment:"分类路径"`
}
// CategoryStatsResponse 分类统计响应
type CategoryStatsResponse struct {
TotalCategories int64 `json:"total_categories" comment:"分类总数"`
RootCategories int64 `json:"root_categories" comment:"根分类数"`
EnabledCategories int64 `json:"enabled_categories" comment:"启用分类数"`
VisibleCategories int64 `json:"visible_categories" comment:"可见分类数"`
ID string `json:"id" comment:"分类ID"`
Name string `json:"name" comment:"分类名称"`
Code string `json:"code" comment:"分类编号"`
}

View File

@@ -4,59 +4,60 @@ import "time"
// ProductInfoResponse 产品详情响应
type ProductInfoResponse struct {
ID string `json:"id" comment:"产品ID"`
Name string `json:"name" comment:"产品名称"`
Code string `json:"code" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Content string `json:"content" comment:"产品内容"`
CategoryID string `json:"category_id" comment:"产品分类ID"`
Price float64 `json:"price" comment:"产品价格"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
ID string `json:"id" comment:"产品ID"`
Name string `json:"name" comment:"产品名称"`
Code string `json:"code" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Content string `json:"content" comment:"产品内容"`
CategoryID string `json:"category_id" comment:"产品分类ID"`
Price float64 `json:"price" comment:"产品价格"`
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
IsVisible bool `json:"is_visible" comment:"是否展示"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
// SEO信息
SEOTitle string `json:"seo_title" comment:"SEO标题"`
SEODescription string `json:"seo_description" comment:"SEO描述"`
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
// 关联信息
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
}
// ProductListResponse 产品列表响应
type ProductListResponse struct {
Total int64 `json:"total" comment:"总数"`
Page int `json:"page" comment:"页码"`
Size int `json:"size" comment:"每页数量"`
Total int64 `json:"total" comment:"总数"`
Page int `json:"page" comment:"页码"`
Size int `json:"size" comment:"每页数量"`
Items []ProductInfoResponse `json:"items" comment:"产品列表"`
}
// ProductSearchResponse 产品搜索响应
type ProductSearchResponse struct {
Total int64 `json:"total" comment:"总数"`
Page int `json:"page" comment:"页码"`
Size int `json:"size" comment:"每页数量"`
Total int64 `json:"total" comment:"总数"`
Page int `json:"page" comment:"页码"`
Size int `json:"size" comment:"每页数量"`
Items []ProductInfoResponse `json:"items" comment:"产品列表"`
}
// ProductSimpleResponse 产品简单信息响应
type ProductSimpleResponse struct {
ID string `json:"id" comment:"产品ID"`
Name string `json:"name" comment:"产品名称"`
Code string `json:"code" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Price float64 `json:"price" comment:"产品价格"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
ID string `json:"id" comment:"产品ID"`
Name string `json:"name" comment:"产品名称"`
Code string `json:"code" comment:"产品编号"`
Description string `json:"description" comment:"产品简介"`
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
Price float64 `json:"price" comment:"产品价格"`
IsPackage bool `json:"is_package" comment:"是否组合包"`
}
// ProductStatsResponse 产品统计响应
type ProductStatsResponse struct {
TotalProducts int64 `json:"total_products" comment:"产品总数"`
EnabledProducts int64 `json:"enabled_products" comment:"启用产品数"`
VisibleProducts int64 `json:"visible_products" comment:"可见产品数"`
PackageProducts int64 `json:"package_products" comment:"组合包产品数"`
}
TotalProducts int64 `json:"total_products" comment:"产品总数"`
EnabledProducts int64 `json:"enabled_products" comment:"启用产品数"`
VisibleProducts int64 `json:"visible_products" comment:"可见产品数"`
PackageProducts int64 `json:"package_products" comment:"组合包产品数"`
}

View File

@@ -13,19 +13,11 @@ type ProductApplicationService interface {
CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error
UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error
DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error
GetProductByID(ctx context.Context, query *queries.GetProductQuery) (*responses.ProductInfoResponse, error)
ListProducts(ctx context.Context, query *queries.ListProductsQuery) (*responses.ProductListResponse, error)
GetProductsByIDs(ctx context.Context, query *queries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error)
// 产品状态管理
EnableProduct(ctx context.Context, cmd *commands.EnableProductCommand) error
DisableProduct(ctx context.Context, cmd *commands.DisableProductCommand) error
ShowProduct(ctx context.Context, cmd *commands.ShowProductCommand) error
HideProduct(ctx context.Context, cmd *commands.HideProductCommand) error
// SEO管理
UpdateProductSEO(ctx context.Context, cmd *commands.UpdateProductSEOCommand) error
// 业务查询
GetSubscribableProducts(ctx context.Context, query *queries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error)
GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error)
@@ -37,42 +29,26 @@ type CategoryApplicationService interface {
CreateCategory(ctx context.Context, cmd *commands.CreateCategoryCommand) error
UpdateCategory(ctx context.Context, cmd *commands.UpdateCategoryCommand) error
DeleteCategory(ctx context.Context, cmd *commands.DeleteCategoryCommand) error
GetCategoryByID(ctx context.Context, query *queries.GetCategoryQuery) (*responses.CategoryInfoResponse, error)
ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) (*responses.CategoryListResponse, error)
// 分类状态管理
EnableCategory(ctx context.Context, cmd *commands.EnableCategoryCommand) error
DisableCategory(ctx context.Context, cmd *commands.DisableCategoryCommand) error
ShowCategory(ctx context.Context, cmd *commands.ShowCategoryCommand) error
HideCategory(ctx context.Context, cmd *commands.HideCategoryCommand) error
// 分类结构管理
MoveCategory(ctx context.Context, cmd *commands.MoveCategoryCommand) error
GetCategoryTree(ctx context.Context, query *queries.GetCategoryTreeQuery) (*responses.CategoryTreeResponse, error)
GetCategoriesByLevel(ctx context.Context, query *queries.GetCategoriesByLevelQuery) ([]*responses.CategoryInfoResponse, error)
GetCategoryPath(ctx context.Context, query *queries.GetCategoryPathQuery) (*responses.CategoryPathResponse, error)
// 统计
GetCategoryStats(ctx context.Context) (*responses.CategoryStatsResponse, error)
}
// SubscriptionApplicationService 订阅应用服务接口
type SubscriptionApplicationService interface {
// 订阅管理
UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error
// 订阅管理
CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error
UpdateSubscription(ctx context.Context, cmd *commands.UpdateSubscriptionCommand) error
GetSubscriptionByID(ctx context.Context, query *queries.GetSubscriptionQuery) (*responses.SubscriptionInfoResponse, error)
ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error)
// API使用管理
UpdateAPIUsage(ctx context.Context, cmd *commands.UpdateAPIUsageCommand) error
ResetAPIUsage(ctx context.Context, cmd *commands.ResetAPIUsageCommand) error
// 业务查询
GetUserSubscriptions(ctx context.Context, query *queries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error)
GetProductSubscriptions(ctx context.Context, query *queries.GetProductSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error)
GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error)
// 统计
GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error)
}
}

View File

@@ -2,201 +2,114 @@ package product
import (
"context"
"errors"
"fmt"
"go.uber.org/zap"
"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"
product_service "tyapi-server/internal/domains/product/services"
)
// ProductApplicationServiceImpl 产品应用服务实现
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
type ProductApplicationServiceImpl struct {
productRepo repositories.ProductRepository
categoryRepo repositories.ProductCategoryRepository
subscriptionRepo repositories.SubscriptionRepository
productService *services.ProductService
logger *zap.Logger
productManagementService *product_service.ProductManagementService
productSubscriptionService *product_service.ProductSubscriptionService
logger *zap.Logger
}
// NewProductApplicationService 创建产品应用服务
func NewProductApplicationService(
productRepo repositories.ProductRepository,
categoryRepo repositories.ProductCategoryRepository,
subscriptionRepo repositories.SubscriptionRepository,
productService *services.ProductService,
productManagementService *product_service.ProductManagementService,
productSubscriptionService *product_service.ProductSubscriptionService,
logger *zap.Logger,
) ProductApplicationService {
return &ProductApplicationServiceImpl{
productRepo: productRepo,
categoryRepo: categoryRepo,
subscriptionRepo: subscriptionRepo,
productService: productService,
logger: logger,
productManagementService: productManagementService,
productSubscriptionService: productSubscriptionService,
logger: logger,
}
}
// CreateProduct 创建产品
// 业务流程1. 构建产品实体 2. 创建产品
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. 创建产品实体
// 1. 构建产品实体
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,
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,
SEOTitle: cmd.SEOTitle,
SEODescription: cmd.SEODescription,
SEOKeywords: cmd.SEOKeywords,
}
// 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
// 2. 创建产品
_, err := s.productManagementService.CreateProduct(ctx, product)
return err
}
// UpdateProduct 更新产品
// 业务流程1. 获取现有产品 2. 更新产品信息 3. 保存产品
func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error {
// 1. 获取现有产品
existingProduct, err := s.productRepo.GetByID(ctx, cmd.ID)
existingProduct, err := s.productManagementService.GetProductByID(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
}
// 2. 更新产品信息
existingProduct.Name = cmd.Name
existingProduct.Code = cmd.Code
existingProduct.Description = cmd.Description
existingProduct.Content = cmd.Content
existingProduct.CategoryID = cmd.CategoryID
existingProduct.Price = cmd.Price
existingProduct.IsEnabled = cmd.IsEnabled
existingProduct.IsVisible = cmd.IsVisible
existingProduct.IsPackage = cmd.IsPackage
existingProduct.SEOTitle = cmd.SEOTitle
existingProduct.SEODescription = cmd.SEODescription
existingProduct.SEOKeywords = cmd.SEOKeywords
// 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
// 3. 保存产品
return s.productManagementService.UpdateProduct(ctx, existingProduct)
}
// DeleteProduct 删除产品
// 业务流程1. 删除产品
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
return s.productManagementService.DeleteProduct(ctx, cmd.ID)
}
// ListProducts 获取产品列表
// 业务流程1. 获取产品列表 2. 构建响应数据
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,
// 根据查询条件获取产品列表
var products []*entities.Product
var err error
if query.CategoryID != "" {
products, err = s.productManagementService.GetProductsByCategory(ctx, query.CategoryID)
} else if query.IsVisible != nil && *query.IsVisible {
products, err = s.productManagementService.GetVisibleProducts(ctx)
} else if query.IsEnabled != nil && *query.IsEnabled {
products, err = s.productManagementService.GetEnabledProducts(ctx)
} else {
// 默认获取可见产品
products, err = s.productManagementService.GetVisibleProducts(ctx)
}
// 调用仓储
products, total, err := s.productRepo.ListProducts(ctx, repoQuery)
if err != nil {
s.logger.Error("获取产品列表失败", zap.Error(err))
return nil, fmt.Errorf("获取产品列表失败: %w", err)
return nil, err
}
// 转换为响应对象
@@ -206,7 +119,7 @@ func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query
}
return &responses.ProductListResponse{
Total: total,
Total: int64(len(items)),
Page: query.Page,
Size: query.PageSize,
Items: items,
@@ -214,169 +127,55 @@ func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query
}
// GetProductsByIDs 根据ID列表获取产品
// 业务流程1. 获取产品列表 2. 构建响应数据
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
// 这里需要扩展领域服务来支持批量获取
// 暂时返回空列表
return []*responses.ProductInfoResponse{}, nil
}
// GetSubscribableProducts 获取可订阅产品
// GetSubscribableProducts 获取可订阅产品列表
// 业务流程1. 获取启用产品 2. 过滤可订阅产品 3. 构建响应数据
func (s *ProductApplicationServiceImpl) GetSubscribableProducts(ctx context.Context, query *appQueries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error) {
products, err := s.productRepo.FindSubscribableProducts(ctx, query.UserID)
products, err := s.productManagementService.GetEnabledProducts(ctx)
if err != nil {
s.logger.Error("获取可订阅产品失败", zap.Error(err))
return nil, fmt.Errorf("获取可订阅产品失败: %w", err)
return nil, err
}
// 过滤可订阅的产品
var subscribableProducts []*entities.Product
for _, product := range products {
if product.CanBeSubscribed() {
subscribableProducts = append(subscribableProducts, product)
}
}
// 转换为响应对象
items := make([]*responses.ProductInfoResponse, len(products))
for i := range products {
items[i] = s.convertToProductInfoResponse(products[i])
items := make([]*responses.ProductInfoResponse, len(subscribableProducts))
for i := range subscribableProducts {
items[i] = s.convertToProductInfoResponse(subscribableProducts[i])
}
return items, nil
}
// GetProductByID 根据ID获取产品
// 业务流程1. 获取产品信息 2. 构建响应数据
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)
product, err := s.productManagementService.GetProductWithCategory(ctx, query.ID)
if err != nil {
return fmt.Errorf("产品不存在: %w", err)
return nil, 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
return s.convertToProductInfoResponse(product), 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 获取产品统计
// GetProductStats 获取产品统计信息
// 业务流程1. 获取产品统计 2. 构建响应数据
func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error) {
stats, err := s.productService.GetProductStats()
stats, err := s.productSubscriptionService.GetProductStats(ctx)
if err != nil {
s.logger.Error("获取产品统计失败", zap.Error(err))
return nil, fmt.Errorf("获取产品统计失败: %w", err)
return nil, err
}
return &responses.ProductStatsResponse{
@@ -387,66 +186,42 @@ func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*r
}, 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
}
// convertToProductInfoResponse 转换为产品信息响应
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,
response := &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,
SEOKeywords: product.SEOKeywords,
CreatedAt: product.CreatedAt,
UpdatedAt: product.UpdatedAt,
}
// 添加分类信息
if product.Category != nil {
response.Category = s.convertToCategoryInfoResponse(product.Category)
}
return response
}
// convertToCategoryInfoResponse 转换为分类信息响应
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,
}
}
}

View File

@@ -2,346 +2,149 @@ package product
import (
"context"
"errors"
"fmt"
"time"
"go.uber.org/zap"
"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"
product_service "tyapi-server/internal/domains/product/services"
)
// SubscriptionApplicationServiceImpl 订阅应用服务实现
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
type SubscriptionApplicationServiceImpl struct {
subscriptionRepo repositories.SubscriptionRepository
productRepo repositories.ProductRepository
productService *services.ProductService
logger *zap.Logger
productSubscriptionService *product_service.ProductSubscriptionService
logger *zap.Logger
}
// NewSubscriptionApplicationService 创建订阅应用服务
func NewSubscriptionApplicationService(
subscriptionRepo repositories.SubscriptionRepository,
productRepo repositories.ProductRepository,
productService *services.ProductService,
productSubscriptionService *product_service.ProductSubscriptionService,
logger *zap.Logger,
) SubscriptionApplicationService {
return &SubscriptionApplicationServiceImpl{
subscriptionRepo: subscriptionRepo,
productRepo: productRepo,
productService: productService,
logger: logger,
productSubscriptionService: productSubscriptionService,
logger: logger,
}
}
// UpdateSubscriptionPrice 更新订阅价格
// 业务流程1. 获取订阅 2. 更新价格 3. 保存订阅
func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error {
// 1. 获取现有订阅
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, cmd.ID)
if err != nil {
return err
}
// 2. 更新订阅价格
subscription.Price = cmd.Price
// 3. 保存订阅
// 这里需要扩展领域服务来支持更新操作
// 暂时返回错误
return fmt.Errorf("更新订阅价格功能暂未实现")
}
// CreateSubscription 创建订阅
// 业务流程1. 创建订阅
func (s *SubscriptionApplicationServiceImpl) CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error {
// 1. 参数验证
if err := s.validateCreateSubscription(cmd); err != nil {
return err
}
// 2. 检查产品是否存在且可订阅
canSubscribe, err := s.productService.CanUserSubscribeProduct(cmd.UserID, cmd.ProductID)
if err != nil {
return err
}
if !canSubscribe {
return errors.New("产品不可订阅或用户已有活跃订阅")
}
// 3. 检查产品是否存在
_, err = s.productRepo.GetByID(ctx, cmd.ProductID)
if err != nil {
return fmt.Errorf("产品不存在: %w", err)
}
// 5. 创建订阅实体
subscription := entities.Subscription{
UserID: cmd.UserID,
ProductID: cmd.ProductID,
Status: entities.SubscriptionStatusActive,
Price: cmd.Price,
APIUsed: 0,
}
// 6. 保存到仓储
createdSubscription, err := s.subscriptionRepo.Create(ctx, subscription)
if err != nil {
s.logger.Error("创建订阅失败", zap.Error(err))
return fmt.Errorf("创建订阅失败: %w", err)
}
s.logger.Info("创建订阅成功", zap.String("id", createdSubscription.ID), zap.String("user_id", cmd.UserID), zap.String("product_id", cmd.ProductID))
return nil
}
// UpdateSubscription 更新订阅
func (s *SubscriptionApplicationServiceImpl) UpdateSubscription(ctx context.Context, cmd *commands.UpdateSubscriptionCommand) error {
// 1. 获取现有订阅
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("订阅不存在: %w", err)
}
// 2. 更新字段
if cmd.Price >= 0 {
subscription.Price = cmd.Price
}
// 3. 保存到仓储
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("id", cmd.ID))
return nil
_, err := s.productSubscriptionService.CreateSubscription(ctx, cmd.UserID, cmd.ProductID)
return err
}
// GetSubscriptionByID 根据ID获取订阅
// 业务流程1. 获取订阅信息 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionByID(ctx context.Context, query *appQueries.GetSubscriptionQuery) (*responses.SubscriptionInfoResponse, error) {
subscription, err := s.subscriptionRepo.GetByID(ctx, query.ID)
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, query.ID)
if err != nil {
return nil, fmt.Errorf("订阅不存在: %w", err)
return nil, err
}
// 转换为响应对象
response := s.convertToSubscriptionInfoResponse(&subscription)
// 加载产品信息
product, err := s.productRepo.GetByID(ctx, subscription.ProductID)
if err == nil {
response.Product = s.convertToProductSimpleResponse(&product)
}
return response, nil
return s.convertToSubscriptionInfoResponse(subscription), nil
}
// ListSubscriptions 获取订阅列表
// 业务流程1. 获取订阅列表 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Context, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
// 构建仓储查询
repoQuery := &repoQueries.ListSubscriptionsQuery{
Page: query.Page,
PageSize: query.PageSize,
UserID: query.UserID,
ProductID: query.ProductID,
Status: query.Status,
SortBy: query.SortBy,
SortOrder: query.SortOrder,
}
// 调用仓储
subscriptions, total, err := s.subscriptionRepo.ListSubscriptions(ctx, repoQuery)
if err != nil {
s.logger.Error("获取订阅列表失败", zap.Error(err))
return nil, fmt.Errorf("获取订阅列表失败: %w", err)
}
// 转换为响应对象
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
for i, subscription := range subscriptions {
items[i] = *s.convertToSubscriptionInfoResponse(subscription)
}
// 这里需要扩展领域服务来支持列表查询
// 暂时返回空列表
return &responses.SubscriptionListResponse{
Total: total,
Total: 0,
Page: query.Page,
Size: query.PageSize,
Items: items,
Items: []responses.SubscriptionInfoResponse{},
}, nil
}
// UpdateAPIUsage 更新API使用量
func (s *SubscriptionApplicationServiceImpl) UpdateAPIUsage(ctx context.Context, cmd *commands.UpdateAPIUsageCommand) error {
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("订阅不存在: %w", err)
}
subscription.IncrementAPIUsage(cmd.APIUsed)
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
s.logger.Error("更新API使用量失败", zap.Error(err))
return fmt.Errorf("更新API使用量失败: %w", err)
}
s.logger.Info("更新API使用量成功", zap.String("id", cmd.ID), zap.Int64("api_used", cmd.APIUsed))
return nil
}
// ResetAPIUsage 重置API使用量
func (s *SubscriptionApplicationServiceImpl) ResetAPIUsage(ctx context.Context, cmd *commands.ResetAPIUsageCommand) error {
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
if err != nil {
return fmt.Errorf("订阅不存在: %w", err)
}
subscription.ResetAPIUsage()
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
s.logger.Error("重置API使用量失败", zap.Error(err))
return fmt.Errorf("重置API使用量失败: %w", err)
}
s.logger.Info("重置API使用量成功", zap.String("id", cmd.ID))
return nil
}
// GetUserSubscriptions 获取用户订阅
// 业务流程1. 获取用户订阅 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) GetUserSubscriptions(ctx context.Context, query *appQueries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
subscriptions, err := s.subscriptionRepo.FindByUserID(ctx, query.UserID)
subscriptions, err := s.productSubscriptionService.GetUserSubscriptions(ctx, query.UserID)
if err != nil {
s.logger.Error("获取用户订阅失败", zap.Error(err))
return nil, fmt.Errorf("获取用户订阅失败: %w", err)
}
// 过滤状态
if query.Status != nil {
filtered := make([]*entities.Subscription, 0)
for _, sub := range subscriptions {
if sub.Status == *query.Status {
filtered = append(filtered, sub)
}
}
subscriptions = filtered
return nil, err
}
// 转换为响应对象
items := make([]*responses.SubscriptionInfoResponse, len(subscriptions))
for i, subscription := range subscriptions {
items[i] = s.convertToSubscriptionInfoResponse(subscription)
for i := range subscriptions {
items[i] = s.convertToSubscriptionInfoResponse(subscriptions[i])
}
return items, nil
}
// GetProductSubscriptions 获取产品订阅
// 业务流程1. 获取产品订阅 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) GetProductSubscriptions(ctx context.Context, query *appQueries.GetProductSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
subscriptions, err := s.subscriptionRepo.FindByProductID(ctx, query.ProductID)
if err != nil {
s.logger.Error("获取产品订阅失败", zap.Error(err))
return nil, fmt.Errorf("获取产品订阅失败: %w", err)
}
// 过滤状态
if query.Status != nil {
filtered := make([]*entities.Subscription, 0)
for _, sub := range subscriptions {
if sub.Status == *query.Status {
filtered = append(filtered, sub)
}
}
subscriptions = filtered
}
// 转换为响应对象
items := make([]*responses.SubscriptionInfoResponse, len(subscriptions))
for i, subscription := range subscriptions {
items[i] = s.convertToSubscriptionInfoResponse(subscription)
}
return items, nil
// 这里需要扩展领域服务来支持按产品查询订阅
// 暂时返回空列表
return []*responses.SubscriptionInfoResponse{}, nil
}
// GetSubscriptionUsage 获取订阅使用情况
// 业务流程1. 获取订阅使用情况 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
if err != nil {
return nil, fmt.Errorf("订阅不存在: %w", err)
}
return &responses.SubscriptionUsageResponse{
ID: subscription.ID,
ProductID: subscription.ProductID,
APIUsed: subscription.APIUsed,
}, nil
}
// GetSubscriptionStats 获取订阅统计
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error) {
// 获取各种状态的订阅数量
total, err := s.subscriptionRepo.CountActive(ctx)
if err != nil {
s.logger.Error("获取订阅统计失败", zap.Error(err))
return nil, fmt.Errorf("获取订阅统计失败: %w", err)
}
// TODO: 计算总收入,需要从订单系统获取
totalRevenue := 0.0
return &responses.SubscriptionStatsResponse{
TotalSubscriptions: total,
TotalRevenue: totalRevenue,
}, nil
}
// 私有方法
func (s *SubscriptionApplicationServiceImpl) validateCreateSubscription(cmd *commands.CreateSubscriptionCommand) error {
if cmd.UserID == "" {
return errors.New("用户ID不能为空")
}
if cmd.ProductID == "" {
return errors.New("产品ID不能为空")
}
if cmd.Price < 0 {
return errors.New("订阅价格不能为负数")
}
if cmd.APILimit < 0 {
return errors.New("API调用限制不能为负数")
}
return nil
}
func (s *SubscriptionApplicationServiceImpl) calculateEndDate(duration string) (*time.Time, error) {
if duration == "" {
return nil, nil // 永久订阅
}
d, err := s.parseDuration(duration)
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
if err != nil {
return nil, err
}
endDate := time.Now().Add(d)
return &endDate, nil
return &responses.SubscriptionUsageResponse{
ID: subscription.ID,
ProductID: subscription.ProductID,
APIUsed: subscription.APIUsed,
}, nil
}
func (s *SubscriptionApplicationServiceImpl) parseDuration(duration string) (time.Duration, error) {
switch duration {
case "7d":
return 7 * 24 * time.Hour, nil
case "30d":
return 30 * 24 * time.Hour, nil
case "90d":
return 90 * 24 * time.Hour, nil
case "365d":
return 365 * 24 * time.Hour, nil
default:
return time.ParseDuration(duration)
}
// GetSubscriptionStats 获取订阅统计信息
// 业务流程1. 获取订阅统计 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error) {
// 这里需要扩展领域服务来支持统计功能
// 暂时返回默认值
return &responses.SubscriptionStatsResponse{
TotalSubscriptions: 0,
TotalRevenue: 0,
}, nil
}
// convertToSubscriptionInfoResponse 转换为订阅信息响应
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
return &responses.SubscriptionInfoResponse{
ID: subscription.ID,
UserID: subscription.UserID,
ProductID: subscription.ProductID,
Price: subscription.Price,
APIUsed: subscription.APIUsed,
CreatedAt: subscription.CreatedAt,
UpdatedAt: subscription.UpdatedAt,
ID: subscription.ID,
UserID: subscription.UserID,
ProductID: subscription.ProductID,
Price: subscription.Price,
APIUsed: subscription.APIUsed,
CreatedAt: subscription.CreatedAt,
UpdatedAt: subscription.UpdatedAt,
}
}
// convertToProductSimpleResponse 转换为产品简单信息响应
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(product *entities.Product) *responses.ProductSimpleResponse {
return &responses.ProductSimpleResponse{
ID: product.ID,
@@ -351,4 +154,12 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(prod
Price: product.Price,
IsPackage: product.IsPackage,
}
}
}
// convertToCategorySimpleResponse 转换为分类简单信息响应
func (s *SubscriptionApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
return &responses.CategorySimpleResponse{
ID: category.ID,
Name: category.Name,
}
}