Files
tyapi-server/internal/domains/finance/services/balance_alert_service.go
2025-09-12 01:15:09 +08:00

187 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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