Files
tyapi-server/internal/application/product/subscription_application_service_impl.go

482 lines
17 KiB
Go
Raw Normal View History

2025-07-15 13:21:34 +08:00
package product
import (
"context"
2025-11-13 21:28:08 +08:00
"fmt"
2025-07-28 01:46:39 +08:00
2025-08-23 16:30:34 +08:00
"github.com/shopspring/decimal"
2025-07-20 20:53:26 +08:00
"go.uber.org/zap"
2025-07-15 13:21:34 +08:00
"tyapi-server/internal/application/product/dto/commands"
appQueries "tyapi-server/internal/application/product/dto/queries"
"tyapi-server/internal/application/product/dto/responses"
2025-12-05 14:59:23 +08:00
domain_api_repo "tyapi-server/internal/domains/api/repositories"
2025-07-15 13:21:34 +08:00
"tyapi-server/internal/domains/product/entities"
2025-07-28 01:46:39 +08:00
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
2025-07-20 20:53:26 +08:00
product_service "tyapi-server/internal/domains/product/services"
2025-08-02 02:54:21 +08:00
user_repositories "tyapi-server/internal/domains/user/repositories"
2025-07-15 13:21:34 +08:00
)
// SubscriptionApplicationServiceImpl 订阅应用服务实现
2025-07-20 20:53:26 +08:00
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
2025-07-15 13:21:34 +08:00
type SubscriptionApplicationServiceImpl struct {
2025-07-20 20:53:26 +08:00
productSubscriptionService *product_service.ProductSubscriptionService
2025-08-02 02:54:21 +08:00
userRepo user_repositories.UserRepository
2025-12-05 14:59:23 +08:00
apiCallRepository domain_api_repo.ApiCallRepository
2025-07-20 20:53:26 +08:00
logger *zap.Logger
2025-07-15 13:21:34 +08:00
}
// NewSubscriptionApplicationService 创建订阅应用服务
func NewSubscriptionApplicationService(
2025-07-20 20:53:26 +08:00
productSubscriptionService *product_service.ProductSubscriptionService,
2025-08-02 02:54:21 +08:00
userRepo user_repositories.UserRepository,
2025-12-05 14:59:23 +08:00
apiCallRepository domain_api_repo.ApiCallRepository,
2025-07-15 13:21:34 +08:00
logger *zap.Logger,
) SubscriptionApplicationService {
return &SubscriptionApplicationServiceImpl{
2025-07-20 20:53:26 +08:00
productSubscriptionService: productSubscriptionService,
2025-08-02 02:54:21 +08:00
userRepo: userRepo,
2025-12-05 14:59:23 +08:00
apiCallRepository: apiCallRepository,
2025-07-20 20:53:26 +08:00
logger: logger,
2025-07-15 13:21:34 +08:00
}
}
2025-07-20 20:53:26 +08:00
// UpdateSubscriptionPrice 更新订阅价格
// 业务流程1. 获取订阅 2. 更新价格 3. 保存订阅
func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error {
2025-08-02 02:54:21 +08:00
return s.productSubscriptionService.UpdateSubscriptionPrice(ctx, cmd.ID, cmd.Price)
2025-07-15 13:21:34 +08:00
}
2025-08-23 16:30:34 +08:00
// BatchUpdateSubscriptionPrices 一键改价
// 业务流程1. 获取用户所有订阅 2. 根据范围筛选 3. 批量更新价格
func (s *SubscriptionApplicationServiceImpl) BatchUpdateSubscriptionPrices(ctx context.Context, cmd *commands.BatchUpdateSubscriptionPricesCommand) error {
2025-11-13 21:28:08 +08:00
// 记录请求参数
s.logger.Info("开始批量更新订阅价格",
zap.String("user_id", cmd.UserID),
zap.String("adjustment_type", cmd.AdjustmentType),
zap.Float64("discount", cmd.Discount),
zap.Float64("cost_multiple", cmd.CostMultiple),
zap.String("scope", cmd.Scope))
// 验证调整方式对应的参数
if cmd.AdjustmentType == "discount" && cmd.Discount <= 0 {
return fmt.Errorf("按售价折扣调整时折扣比例必须大于0")
}
if cmd.AdjustmentType == "cost_multiple" && cmd.CostMultiple <= 0 {
return fmt.Errorf("按成本价倍数调整时倍数必须大于0")
}
2025-08-23 16:30:34 +08:00
subscriptions, _, err := s.productSubscriptionService.ListSubscriptions(ctx, &repoQueries.ListSubscriptionsQuery{
UserID: cmd.UserID,
Page: 1,
PageSize: 1000,
})
if err != nil {
return err
}
2025-11-13 21:28:08 +08:00
s.logger.Info("获取到订阅列表",
zap.Int("total_subscriptions", len(subscriptions)))
2025-08-23 16:30:34 +08:00
// 根据范围筛选订阅
var targetSubscriptions []*entities.Subscription
for _, sub := range subscriptions {
if cmd.Scope == "all" {
// 所有订阅都修改
targetSubscriptions = append(targetSubscriptions, sub)
} else if cmd.Scope == "undiscounted" {
// 只修改未打折的订阅(价格等于产品原价)
if sub.Product != nil && sub.Price.Equal(sub.Product.Price) {
targetSubscriptions = append(targetSubscriptions, sub)
}
}
}
// 批量更新价格
2025-11-13 21:28:08 +08:00
updatedCount := 0
skippedCount := 0
2025-08-23 16:30:34 +08:00
for _, sub := range targetSubscriptions {
2025-11-13 21:28:08 +08:00
if sub.Product == nil {
skippedCount++
continue
}
2025-08-23 16:30:34 +08:00
2025-11-13 21:28:08 +08:00
var newPrice decimal.Decimal
if cmd.AdjustmentType == "discount" {
// 按售价折扣调整
discountRatio := cmd.Discount / 10
newPrice = sub.Product.Price.Mul(decimal.NewFromFloat(discountRatio))
} else if cmd.AdjustmentType == "cost_multiple" {
// 按成本价倍数调整
// 检查成本价是否有效必须大于0
// 使用严格检查成本价必须大于0
if !sub.Product.CostPrice.GreaterThan(decimal.Zero) {
// 跳过没有成本价或成本价为0的产品
skippedCount++
s.logger.Info("跳过未设置成本价或成本价为0的订阅",
2025-08-23 16:30:34 +08:00
zap.String("subscription_id", sub.ID),
2025-11-13 21:28:08 +08:00
zap.String("product_id", sub.ProductID),
zap.String("product_name", sub.Product.Name),
zap.String("cost_price", sub.Product.CostPrice.String()))
continue
2025-08-23 16:30:34 +08:00
}
2025-11-13 21:28:08 +08:00
// 计算成本价倍数后的价格
newPrice = sub.Product.CostPrice.Mul(decimal.NewFromFloat(cmd.CostMultiple))
} else {
s.logger.Warn("未知的调整方式",
zap.String("adjustment_type", cmd.AdjustmentType),
zap.String("subscription_id", sub.ID))
skippedCount++
continue
}
// 四舍五入到2位小数
newPrice = newPrice.Round(2)
err := s.productSubscriptionService.UpdateSubscriptionPrice(ctx, sub.ID, newPrice.InexactFloat64())
if err != nil {
s.logger.Error("批量更新订阅价格失败",
zap.String("subscription_id", sub.ID),
zap.Error(err))
skippedCount++
// 继续处理其他订阅,不中断整个流程
} else {
updatedCount++
2025-08-23 16:30:34 +08:00
}
}
2025-11-13 21:28:08 +08:00
s.logger.Info("批量更新订阅价格完成",
zap.Int("total", len(targetSubscriptions)),
zap.Int("updated", updatedCount),
zap.Int("skipped", skippedCount))
2025-08-23 16:30:34 +08:00
return nil
}
2025-07-20 20:53:26 +08:00
// CreateSubscription 创建订阅
// 业务流程1. 创建订阅
func (s *SubscriptionApplicationServiceImpl) CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error {
_, err := s.productSubscriptionService.CreateSubscription(ctx, cmd.UserID, cmd.ProductID)
return err
2025-07-15 13:21:34 +08:00
}
// GetSubscriptionByID 根据ID获取订阅
2025-07-20 20:53:26 +08:00
// 业务流程1. 获取订阅信息 2. 构建响应数据
2025-07-15 13:21:34 +08:00
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionByID(ctx context.Context, query *appQueries.GetSubscriptionQuery) (*responses.SubscriptionInfoResponse, error) {
2025-07-20 20:53:26 +08:00
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, query.ID)
2025-07-15 13:21:34 +08:00
if err != nil {
2025-07-20 20:53:26 +08:00
return nil, err
2025-07-15 13:21:34 +08:00
}
2025-07-20 20:53:26 +08:00
return s.convertToSubscriptionInfoResponse(subscription), nil
2025-07-15 13:21:34 +08:00
}
2025-07-28 23:28:24 +08:00
// ListSubscriptions 获取订阅列表(管理员用)
2025-07-20 20:53:26 +08:00
// 业务流程1. 获取订阅列表 2. 构建响应数据
2025-07-15 13:21:34 +08:00
func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Context, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
2025-07-28 01:46:39 +08:00
repoQuery := &repoQueries.ListSubscriptionsQuery{
2025-08-02 02:54:21 +08:00
Page: query.Page,
PageSize: query.PageSize,
UserID: query.UserID, // 管理员可以按用户筛选
Keyword: query.Keyword,
SortBy: query.SortBy,
SortOrder: query.SortOrder,
CompanyName: query.CompanyName,
ProductName: query.ProductName,
StartTime: query.StartTime,
EndTime: query.EndTime,
2025-07-28 23:28:24 +08:00
}
subscriptions, total, err := s.productSubscriptionService.ListSubscriptions(ctx, repoQuery)
if err != nil {
return nil, err
}
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
for i := range subscriptions {
2025-11-13 21:28:08 +08:00
resp := s.convertToSubscriptionInfoResponseForAdmin(subscriptions[i])
2025-07-28 23:28:24 +08:00
if resp != nil {
items[i] = *resp // 解引用指针
}
}
return &responses.SubscriptionListResponse{
Total: total,
Page: query.Page,
Size: query.PageSize,
Items: items,
}, nil
}
// ListMySubscriptions 获取我的订阅列表(用户用)
// 业务流程1. 获取用户订阅列表 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) ListMySubscriptions(ctx context.Context, userID string, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
repoQuery := &repoQueries.ListSubscriptionsQuery{
2025-08-02 02:54:21 +08:00
Page: query.Page,
PageSize: query.PageSize,
UserID: userID, // 强制设置为当前用户ID
Keyword: query.Keyword,
SortBy: query.SortBy,
SortOrder: query.SortOrder,
CompanyName: query.CompanyName,
ProductName: query.ProductName,
StartTime: query.StartTime,
EndTime: query.EndTime,
2025-07-28 01:46:39 +08:00
}
subscriptions, total, err := s.productSubscriptionService.ListSubscriptions(ctx, repoQuery)
if err != nil {
return nil, err
}
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
for i := range subscriptions {
resp := s.convertToSubscriptionInfoResponse(subscriptions[i])
if resp != nil {
items[i] = *resp // 解引用指针
}
}
2025-07-15 13:21:34 +08:00
return &responses.SubscriptionListResponse{
2025-07-28 01:46:39 +08:00
Total: total,
2025-07-15 13:21:34 +08:00
Page: query.Page,
Size: query.PageSize,
2025-07-28 01:46:39 +08:00
Items: items,
2025-07-15 13:21:34 +08:00
}, nil
}
// GetUserSubscriptions 获取用户订阅
2025-07-20 20:53:26 +08:00
// 业务流程1. 获取用户订阅 2. 构建响应数据
2025-07-15 13:21:34 +08:00
func (s *SubscriptionApplicationServiceImpl) GetUserSubscriptions(ctx context.Context, query *appQueries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
2025-07-20 20:53:26 +08:00
subscriptions, err := s.productSubscriptionService.GetUserSubscriptions(ctx, query.UserID)
2025-07-15 13:21:34 +08:00
if err != nil {
2025-07-20 20:53:26 +08:00
return nil, err
2025-07-15 13:21:34 +08:00
}
// 转换为响应对象
items := make([]*responses.SubscriptionInfoResponse, len(subscriptions))
2025-07-20 20:53:26 +08:00
for i := range subscriptions {
items[i] = s.convertToSubscriptionInfoResponse(subscriptions[i])
2025-07-15 13:21:34 +08:00
}
return items, nil
}
// GetProductSubscriptions 获取产品订阅
2025-07-20 20:53:26 +08:00
// 业务流程1. 获取产品订阅 2. 构建响应数据
2025-07-15 13:21:34 +08:00
func (s *SubscriptionApplicationServiceImpl) GetProductSubscriptions(ctx context.Context, query *appQueries.GetProductSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
2025-07-20 20:53:26 +08:00
// 这里需要扩展领域服务来支持按产品查询订阅
// 暂时返回空列表
return []*responses.SubscriptionInfoResponse{}, nil
2025-07-15 13:21:34 +08:00
}
// GetSubscriptionUsage 获取订阅使用情况
2025-12-05 14:59:23 +08:00
// 业务流程1. 获取订阅信息 2. 根据产品ID和用户ID统计API调用次数 3. 构建响应数据
2025-07-15 13:21:34 +08:00
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
2025-12-05 14:59:23 +08:00
// 获取订阅信息
2025-07-20 20:53:26 +08:00
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
2025-07-15 13:21:34 +08:00
if err != nil {
2025-07-20 20:53:26 +08:00
return nil, err
2025-07-15 13:21:34 +08:00
}
2025-12-05 14:59:23 +08:00
// 根据用户ID和产品ID统计API调用次数
apiCallCount, err := s.apiCallRepository.CountByUserIdAndProductId(ctx, subscription.UserID, subscription.ProductID)
if err != nil {
s.logger.Warn("统计API调用次数失败使用订阅记录中的值",
zap.String("subscription_id", subscriptionID),
zap.String("user_id", subscription.UserID),
zap.String("product_id", subscription.ProductID),
zap.Error(err))
// 如果统计失败使用订阅实体中的APIUsed字段作为备选
apiCallCount = subscription.APIUsed
}
2025-07-15 13:21:34 +08:00
return &responses.SubscriptionUsageResponse{
2025-08-23 16:30:34 +08:00
ID: subscription.ID,
2025-07-20 20:53:26 +08:00
ProductID: subscription.ProductID,
2025-12-05 14:59:23 +08:00
APIUsed: apiCallCount,
2025-07-15 13:21:34 +08:00
}, nil
}
2025-07-20 20:53:26 +08:00
// GetSubscriptionStats 获取订阅统计信息
// 业务流程1. 获取订阅统计 2. 构建响应数据
2025-07-15 13:21:34 +08:00
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error) {
2025-08-02 02:54:21 +08:00
stats, err := s.productSubscriptionService.GetSubscriptionStats(ctx)
if err != nil {
return nil, err
}
2025-08-23 16:30:34 +08:00
2025-07-15 13:21:34 +08:00
return &responses.SubscriptionStatsResponse{
2025-08-02 02:54:21 +08:00
TotalSubscriptions: stats["total_subscriptions"].(int64),
TotalRevenue: stats["total_revenue"].(float64),
2025-07-15 13:21:34 +08:00
}, nil
}
2025-07-28 23:28:24 +08:00
// GetMySubscriptionStats 获取我的订阅统计信息
// 业务流程1. 获取用户订阅统计 2. 构建响应数据
func (s *SubscriptionApplicationServiceImpl) GetMySubscriptionStats(ctx context.Context, userID string) (*responses.SubscriptionStatsResponse, error) {
2025-08-02 02:54:21 +08:00
stats, err := s.productSubscriptionService.GetUserSubscriptionStats(ctx, userID)
2025-07-28 23:28:24 +08:00
if err != nil {
return nil, err
}
2025-08-23 16:30:34 +08:00
2025-07-28 23:28:24 +08:00
return &responses.SubscriptionStatsResponse{
2025-08-02 02:54:21 +08:00
TotalSubscriptions: stats["total_subscriptions"].(int64),
TotalRevenue: stats["total_revenue"].(float64),
2025-07-28 23:28:24 +08:00
}, nil
}
2025-12-04 12:30:33 +08:00
// CancelMySubscription 取消我的订阅
// 业务流程1. 验证订阅是否属于当前用户 2. 取消订阅
func (s *SubscriptionApplicationServiceImpl) CancelMySubscription(ctx context.Context, userID string, subscriptionID string) error {
// 1. 获取订阅信息
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
if err != nil {
s.logger.Error("获取订阅信息失败", zap.String("subscription_id", subscriptionID), zap.Error(err))
return fmt.Errorf("订阅不存在")
}
// 2. 验证订阅是否属于当前用户
if subscription.UserID != userID {
s.logger.Warn("用户尝试取消不属于自己的订阅",
zap.String("user_id", userID),
zap.String("subscription_id", subscriptionID),
zap.String("subscription_user_id", subscription.UserID))
return fmt.Errorf("无权取消此订阅")
}
// 3. 取消订阅(软删除)
if err := s.productSubscriptionService.CancelSubscription(ctx, subscriptionID); err != nil {
s.logger.Error("取消订阅失败", zap.String("subscription_id", subscriptionID), zap.Error(err))
return fmt.Errorf("取消订阅失败: %w", err)
}
s.logger.Info("用户取消订阅成功",
zap.String("user_id", userID),
zap.String("subscription_id", subscriptionID))
return nil
}
2025-07-20 20:53:26 +08:00
// convertToSubscriptionInfoResponse 转换为订阅信息响应
2025-07-15 13:21:34 +08:00
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
2025-08-02 02:54:21 +08:00
// 查询用户信息
var userInfo *responses.UserSimpleResponse
if subscription.UserID != "" {
user, err := s.userRepo.GetByIDWithEnterpriseInfo(context.Background(), subscription.UserID)
if err == nil {
companyName := "未知公司"
if user.EnterpriseInfo != nil {
companyName = user.EnterpriseInfo.CompanyName
}
userInfo = &responses.UserSimpleResponse{
ID: user.ID,
CompanyName: companyName,
Phone: user.Phone,
}
}
}
2025-08-18 14:13:16 +08:00
var productResponse *responses.ProductSimpleResponse
if subscription.Product != nil {
productResponse = s.convertToProductSimpleResponse(subscription.Product)
}
2025-07-15 13:21:34 +08:00
return &responses.SubscriptionInfoResponse{
2025-07-20 20:53:26 +08:00
ID: subscription.ID,
UserID: subscription.UserID,
ProductID: subscription.ProductID,
2025-07-28 01:46:39 +08:00
Price: subscription.Price.InexactFloat64(),
2025-08-02 02:54:21 +08:00
User: userInfo,
2025-08-18 14:13:16 +08:00
Product: productResponse,
2025-07-20 20:53:26 +08:00
APIUsed: subscription.APIUsed,
CreatedAt: subscription.CreatedAt,
UpdatedAt: subscription.UpdatedAt,
2025-07-15 13:21:34 +08:00
}
}
2025-07-20 20:53:26 +08:00
// convertToProductSimpleResponse 转换为产品简单信息响应
2025-07-15 13:21:34 +08:00
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(product *entities.Product) *responses.ProductSimpleResponse {
2025-08-18 14:13:16 +08:00
var categoryResponse *responses.CategorySimpleResponse
if product.Category != nil {
categoryResponse = s.convertToCategorySimpleResponse(product.Category)
}
2025-07-15 13:21:34 +08:00
return &responses.ProductSimpleResponse{
ID: product.ID,
2025-08-02 20:44:09 +08:00
OldID: product.OldID,
2025-07-15 13:21:34 +08:00
Name: product.Name,
Code: product.Code,
Description: product.Description,
2025-07-28 01:46:39 +08:00
Price: product.Price.InexactFloat64(),
2025-08-18 14:13:16 +08:00
Category: categoryResponse,
2025-07-15 13:21:34 +08:00
IsPackage: product.IsPackage,
}
2025-07-20 20:53:26 +08:00
}
2025-11-13 21:28:08 +08:00
// convertToSubscriptionInfoResponseForAdmin 转换为订阅信息响应(管理员端,包含成本价)
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponseForAdmin(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
// 查询用户信息
var userInfo *responses.UserSimpleResponse
if subscription.UserID != "" {
user, err := s.userRepo.GetByIDWithEnterpriseInfo(context.Background(), subscription.UserID)
if err == nil {
companyName := "未知公司"
if user.EnterpriseInfo != nil {
companyName = user.EnterpriseInfo.CompanyName
}
userInfo = &responses.UserSimpleResponse{
ID: user.ID,
CompanyName: companyName,
Phone: user.Phone,
}
}
}
var productAdminResponse *responses.ProductSimpleAdminResponse
if subscription.Product != nil {
productAdminResponse = s.convertToProductSimpleAdminResponse(subscription.Product)
}
return &responses.SubscriptionInfoResponse{
ID: subscription.ID,
UserID: subscription.UserID,
ProductID: subscription.ProductID,
Price: subscription.Price.InexactFloat64(),
User: userInfo,
ProductAdmin: productAdminResponse,
APIUsed: subscription.APIUsed,
CreatedAt: subscription.CreatedAt,
UpdatedAt: subscription.UpdatedAt,
}
}
// convertToProductSimpleAdminResponse 转换为管理员产品简单信息响应(包含成本价)
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleAdminResponse(product *entities.Product) *responses.ProductSimpleAdminResponse {
var categoryResponse *responses.CategorySimpleResponse
if product.Category != nil {
categoryResponse = s.convertToCategorySimpleResponse(product.Category)
}
return &responses.ProductSimpleAdminResponse{
ProductSimpleResponse: responses.ProductSimpleResponse{
ID: product.ID,
OldID: product.OldID,
Name: product.Name,
Code: product.Code,
Description: product.Description,
Price: product.Price.InexactFloat64(),
Category: categoryResponse,
IsPackage: product.IsPackage,
},
CostPrice: product.CostPrice.InexactFloat64(),
}
}
2025-07-20 20:53:26 +08:00
// convertToCategorySimpleResponse 转换为分类简单信息响应
func (s *SubscriptionApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
2025-08-18 14:13:16 +08:00
if category == nil {
return nil
}
2025-08-23 16:30:34 +08:00
2025-07-20 20:53:26 +08:00
return &responses.CategorySimpleResponse{
ID: category.ID,
Name: category.Name,
}
}