187 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package services
 | ||
| 
 | ||
| import (
 | ||
| 	"context"
 | ||
| 	"fmt"
 | ||
| 
 | ||
| 	"github.com/shopspring/decimal"
 | ||
| 	"go.uber.org/zap"
 | ||
| 
 | ||
| 	"tyapi-server/internal/config"
 | ||
| 	"tyapi-server/internal/domains/api/entities"
 | ||
| 	api_repositories "tyapi-server/internal/domains/api/repositories"
 | ||
| 	user_repositories "tyapi-server/internal/domains/user/repositories"
 | ||
| 	"tyapi-server/internal/infrastructure/external/sms"
 | ||
| )
 | ||
| 
 | ||
| // BalanceAlertService 余额预警服务接口
 | ||
| type BalanceAlertService interface {
 | ||
| 	CheckAndSendAlert(ctx context.Context, userID string, balance decimal.Decimal) error
 | ||
| }
 | ||
| 
 | ||
| // BalanceAlertServiceImpl 余额预警服务实现
 | ||
| type BalanceAlertServiceImpl struct {
 | ||
| 	apiUserRepo         api_repositories.ApiUserRepository
 | ||
| 	userRepo            user_repositories.UserRepository
 | ||
| 	enterpriseInfoRepo  user_repositories.EnterpriseInfoRepository
 | ||
| 	smsService          *sms.AliSMSService
 | ||
| 	config              *config.Config
 | ||
| 	logger              *zap.Logger
 | ||
| }
 | ||
| 
 | ||
| // NewBalanceAlertService 创建余额预警服务
 | ||
| func NewBalanceAlertService(
 | ||
| 	apiUserRepo api_repositories.ApiUserRepository,
 | ||
| 	userRepo user_repositories.UserRepository,
 | ||
| 	enterpriseInfoRepo user_repositories.EnterpriseInfoRepository,
 | ||
| 	smsService *sms.AliSMSService,
 | ||
| 	config *config.Config,
 | ||
| 	logger *zap.Logger,
 | ||
| ) BalanceAlertService {
 | ||
| 	return &BalanceAlertServiceImpl{
 | ||
| 		apiUserRepo:        apiUserRepo,
 | ||
| 		userRepo:           userRepo,
 | ||
| 		enterpriseInfoRepo: enterpriseInfoRepo,
 | ||
| 		smsService:         smsService,
 | ||
| 		config:             config,
 | ||
| 		logger:             logger,
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // CheckAndSendAlert 检查余额并发送预警
 | ||
| func (s *BalanceAlertServiceImpl) CheckAndSendAlert(ctx context.Context, userID string, balance decimal.Decimal) error {
 | ||
| 	// 1. 获取API用户信息
 | ||
| 	apiUser, err := s.apiUserRepo.FindByUserId(ctx, userID)
 | ||
| 	if err != nil {
 | ||
| 		s.logger.Error("获取API用户信息失败",
 | ||
| 			zap.String("user_id", userID),
 | ||
| 			zap.Error(err))
 | ||
| 		return fmt.Errorf("获取API用户信息失败: %w", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if apiUser == nil {
 | ||
| 		s.logger.Debug("API用户不存在,跳过余额预警检查", zap.String("user_id", userID))
 | ||
| 		return nil
 | ||
| 	}
 | ||
| 
 | ||
| 	// 2. 兼容性处理:如果API用户没有配置预警信息,从用户表获取并更新
 | ||
| 	needUpdate := false
 | ||
| 	if apiUser.AlertPhone == "" {
 | ||
| 		user, err := s.userRepo.GetByID(ctx, userID)
 | ||
| 		if err != nil {
 | ||
| 			s.logger.Error("获取用户信息失败",
 | ||
| 				zap.String("user_id", userID),
 | ||
| 				zap.Error(err))
 | ||
| 			return fmt.Errorf("获取用户信息失败: %w", err)
 | ||
| 		}
 | ||
| 		if user.Phone != "" {
 | ||
| 			apiUser.AlertPhone = user.Phone
 | ||
| 			needUpdate = true
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// 3. 兼容性处理:如果API用户没有配置预警阈值,使用默认值
 | ||
| 	if apiUser.BalanceAlertThreshold == 0 {
 | ||
| 		apiUser.BalanceAlertThreshold = s.config.Wallet.BalanceAlert.DefaultThreshold
 | ||
| 		needUpdate = true
 | ||
| 	}
 | ||
| 
 | ||
| 	// 4. 如果需要更新API用户信息,保存到数据库
 | ||
| 	if needUpdate {
 | ||
| 		if err := s.apiUserRepo.Update(ctx, apiUser); err != nil {
 | ||
| 			s.logger.Error("更新API用户预警配置失败",
 | ||
| 				zap.String("user_id", userID),
 | ||
| 				zap.Error(err))
 | ||
| 			// 不返回错误,继续执行预警检查
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	balanceFloat, _ := balance.Float64()
 | ||
| 
 | ||
| 	// 5. 检查是否需要发送欠费预警(不受冷却期限制)
 | ||
| 	if apiUser.ShouldSendArrearsAlert(balanceFloat) {
 | ||
| 		if err := s.sendArrearsAlert(ctx, apiUser, balanceFloat); err != nil {
 | ||
| 			s.logger.Error("发送欠费预警失败",
 | ||
| 				zap.String("user_id", userID),
 | ||
| 				zap.Error(err))
 | ||
| 			return err
 | ||
| 		}
 | ||
| 		// 欠费预警不受冷却期限制,不需要更新LastArrearsAlert时间
 | ||
| 		return nil
 | ||
| 	}
 | ||
| 
 | ||
| 	// 6. 检查是否需要发送低余额预警
 | ||
| 	if apiUser.ShouldSendLowBalanceAlert(balanceFloat) {
 | ||
| 		if err := s.sendLowBalanceAlert(ctx, apiUser, balanceFloat); err != nil {
 | ||
| 			s.logger.Error("发送低余额预警失败",
 | ||
| 				zap.String("user_id", userID),
 | ||
| 				zap.Error(err))
 | ||
| 			return err
 | ||
| 		}
 | ||
| 		// 标记预警已发送
 | ||
| 		apiUser.MarkLowBalanceAlertSent()
 | ||
| 		if err := s.apiUserRepo.Update(ctx, apiUser); err != nil {
 | ||
| 			s.logger.Error("更新API用户预警时间失败",
 | ||
| 				zap.String("user_id", userID),
 | ||
| 				zap.Error(err))
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // sendArrearsAlert 发送欠费预警
 | ||
| func (s *BalanceAlertServiceImpl) sendArrearsAlert(ctx context.Context, apiUser *entities.ApiUser, balance float64) error {
 | ||
| 	// 直接从企业信息表获取企业名称
 | ||
| 	enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, apiUser.UserId)
 | ||
| 	if err != nil {
 | ||
| 		s.logger.Error("获取企业信息失败", 
 | ||
| 			zap.String("user_id", apiUser.UserId), 
 | ||
| 			zap.Error(err))
 | ||
| 		// 如果获取企业信息失败,使用默认名称
 | ||
| 		return s.smsService.SendBalanceAlert(ctx, apiUser.AlertPhone, balance, 0, "arrears", "天远数据用户")
 | ||
| 	}
 | ||
| 
 | ||
| 	// 获取企业名称,如果没有则使用默认名称
 | ||
| 	enterpriseName := "天远数据用户"
 | ||
| 	if enterpriseInfo != nil && enterpriseInfo.CompanyName != "" {
 | ||
| 		enterpriseName = enterpriseInfo.CompanyName
 | ||
| 	}
 | ||
| 
 | ||
| 	s.logger.Info("发送欠费预警短信",
 | ||
| 		zap.String("user_id", apiUser.UserId),
 | ||
| 		zap.String("phone", apiUser.AlertPhone),
 | ||
| 		zap.Float64("balance", balance),
 | ||
| 		zap.String("enterprise_name", enterpriseName))
 | ||
| 
 | ||
| 	return s.smsService.SendBalanceAlert(ctx, apiUser.AlertPhone, balance, 0, "arrears", enterpriseName)
 | ||
| }
 | ||
| 
 | ||
| // sendLowBalanceAlert 发送低余额预警
 | ||
| func (s *BalanceAlertServiceImpl) sendLowBalanceAlert(ctx context.Context, apiUser *entities.ApiUser, balance float64) error {
 | ||
| 	// 直接从企业信息表获取企业名称
 | ||
| 	enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, apiUser.UserId)
 | ||
| 	if err != nil {
 | ||
| 		s.logger.Error("获取企业信息失败", 
 | ||
| 			zap.String("user_id", apiUser.UserId), 
 | ||
| 			zap.Error(err))
 | ||
| 		// 如果获取企业信息失败,使用默认名称
 | ||
| 		return s.smsService.SendBalanceAlert(ctx, apiUser.AlertPhone, balance, apiUser.BalanceAlertThreshold, "low_balance", "天远数据用户")
 | ||
| 	}
 | ||
| 
 | ||
| 	// 获取企业名称,如果没有则使用默认名称
 | ||
| 	enterpriseName := "天远数据用户"
 | ||
| 	if enterpriseInfo != nil && enterpriseInfo.CompanyName != "" {
 | ||
| 		enterpriseName = enterpriseInfo.CompanyName
 | ||
| 	}
 | ||
| 
 | ||
| 	s.logger.Info("发送低余额预警短信",
 | ||
| 		zap.String("user_id", apiUser.UserId),
 | ||
| 		zap.String("phone", apiUser.AlertPhone),
 | ||
| 		zap.Float64("balance", balance),
 | ||
| 		zap.Float64("threshold", apiUser.BalanceAlertThreshold),
 | ||
| 		zap.String("enterprise_name", enterpriseName))
 | ||
| 
 | ||
| 	return s.smsService.SendBalanceAlert(ctx, apiUser.AlertPhone, balance, apiUser.BalanceAlertThreshold, "low_balance", enterpriseName)
 | ||
| }
 |