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) | |||
|  | } |