354 lines
11 KiB
Go
354 lines
11 KiB
Go
package product
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
"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"
|
|
)
|
|
|
|
// SubscriptionApplicationServiceImpl 订阅应用服务实现
|
|
type SubscriptionApplicationServiceImpl struct {
|
|
subscriptionRepo repositories.SubscriptionRepository
|
|
productRepo repositories.ProductRepository
|
|
productService *services.ProductService
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewSubscriptionApplicationService 创建订阅应用服务
|
|
func NewSubscriptionApplicationService(
|
|
subscriptionRepo repositories.SubscriptionRepository,
|
|
productRepo repositories.ProductRepository,
|
|
productService *services.ProductService,
|
|
logger *zap.Logger,
|
|
) SubscriptionApplicationService {
|
|
return &SubscriptionApplicationServiceImpl{
|
|
subscriptionRepo: subscriptionRepo,
|
|
productRepo: productRepo,
|
|
productService: productService,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// CreateSubscription 创建订阅
|
|
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
|
|
}
|
|
|
|
// GetSubscriptionByID 根据ID获取订阅
|
|
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionByID(ctx context.Context, query *appQueries.GetSubscriptionQuery) (*responses.SubscriptionInfoResponse, error) {
|
|
subscription, err := s.subscriptionRepo.GetByID(ctx, query.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("订阅不存在: %w", err)
|
|
}
|
|
|
|
// 转换为响应对象
|
|
response := s.convertToSubscriptionInfoResponse(&subscription)
|
|
|
|
// 加载产品信息
|
|
product, err := s.productRepo.GetByID(ctx, subscription.ProductID)
|
|
if err == nil {
|
|
response.Product = s.convertToProductSimpleResponse(&product)
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// ListSubscriptions 获取订阅列表
|
|
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,
|
|
Page: query.Page,
|
|
Size: query.PageSize,
|
|
Items: items,
|
|
}, 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 获取用户订阅
|
|
func (s *SubscriptionApplicationServiceImpl) GetUserSubscriptions(ctx context.Context, query *appQueries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
|
|
subscriptions, err := s.subscriptionRepo.FindByUserID(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
|
|
}
|
|
|
|
// 转换为响应对象
|
|
items := make([]*responses.SubscriptionInfoResponse, len(subscriptions))
|
|
for i, subscription := range subscriptions {
|
|
items[i] = s.convertToSubscriptionInfoResponse(subscription)
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
// GetProductSubscriptions 获取产品订阅
|
|
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
|
|
}
|
|
|
|
// GetSubscriptionUsage 获取订阅使用情况
|
|
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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
endDate := time.Now().Add(d)
|
|
return &endDate, 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)
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
|
|
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(product *entities.Product) *responses.ProductSimpleResponse {
|
|
return &responses.ProductSimpleResponse{
|
|
ID: product.ID,
|
|
Name: product.Name,
|
|
Code: product.Code,
|
|
Description: product.Description,
|
|
Price: product.Price,
|
|
IsPackage: product.IsPackage,
|
|
}
|
|
} |