Files
tyapi-server/internal/domains/finance/services/balance_alert_service.go

187 lines
6.3 KiB
Go
Raw Normal View History

2025-09-12 01:15:09 +08:00
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)
}