192 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package sms
 | ||
| 
 | ||
| import (
 | ||
| 	"context"
 | ||
| 	"crypto/rand"
 | ||
| 	"fmt"
 | ||
| 	"math/big"
 | ||
| 	"time"
 | ||
| 
 | ||
| 	"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
 | ||
| 	"go.uber.org/zap"
 | ||
| 
 | ||
| 	"tyapi-server/internal/config"
 | ||
| )
 | ||
| 
 | ||
| // AliSMSService 阿里云短信服务
 | ||
| type AliSMSService struct {
 | ||
| 	client *dysmsapi.Client
 | ||
| 	config config.SMSConfig
 | ||
| 	logger *zap.Logger
 | ||
| }
 | ||
| 
 | ||
| // NewAliSMSService 创建阿里云短信服务
 | ||
| func NewAliSMSService(cfg config.SMSConfig, logger *zap.Logger) (*AliSMSService, error) {
 | ||
| 	client, err := dysmsapi.NewClientWithAccessKey("cn-hangzhou", cfg.AccessKeyID, cfg.AccessKeySecret)
 | ||
| 	if err != nil {
 | ||
| 		return nil, fmt.Errorf("创建短信客户端失败: %w", err)
 | ||
| 	}
 | ||
| 	return &AliSMSService{
 | ||
| 		client: client,
 | ||
| 		config: cfg,
 | ||
| 		logger: logger,
 | ||
| 	}, nil
 | ||
| }
 | ||
| 
 | ||
| // SendVerificationCode 发送验证码
 | ||
| func (s *AliSMSService) SendVerificationCode(ctx context.Context, phone string, code string) error {
 | ||
| 	request := dysmsapi.CreateSendSmsRequest()
 | ||
| 	request.Scheme = "https"
 | ||
| 	request.PhoneNumbers = phone
 | ||
| 	request.SignName = s.config.SignName
 | ||
| 	request.TemplateCode = s.config.TemplateCode
 | ||
| 	request.TemplateParam = fmt.Sprintf(`{"code":"%s"}`, code)
 | ||
| 
 | ||
| 	response, err := s.client.SendSms(request)
 | ||
| 	if err != nil {
 | ||
| 		s.logger.Error("Failed to send SMS",
 | ||
| 			zap.String("phone", phone),
 | ||
| 			zap.Error(err))
 | ||
| 		return fmt.Errorf("短信发送失败: %w", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if response.Code != "OK" {
 | ||
| 		s.logger.Error("SMS send failed",
 | ||
| 			zap.String("phone", phone),
 | ||
| 			zap.String("code", response.Code),
 | ||
| 			zap.String("message", response.Message))
 | ||
| 		return fmt.Errorf("短信发送失败: %s - %s", response.Code, response.Message)
 | ||
| 	}
 | ||
| 
 | ||
| 	s.logger.Info("SMS sent successfully",
 | ||
| 		zap.String("phone", phone),
 | ||
| 		zap.String("bizId", response.BizId))
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // SendBalanceAlert 发送余额预警短信
 | ||
| func (s *AliSMSService) SendBalanceAlert(ctx context.Context, phone string, balance float64, threshold float64, alertType string, enterpriseName ...string) error {
 | ||
| 	request := dysmsapi.CreateSendSmsRequest()
 | ||
| 	request.Scheme = "https"
 | ||
| 	request.PhoneNumbers = phone
 | ||
| 	request.SignName = s.config.SignName
 | ||
| 
 | ||
| 	var templateCode string
 | ||
| 	var templateParam string
 | ||
| 
 | ||
| 	if alertType == "low_balance" {
 | ||
| 		// 低余额预警也使用欠费预警模板
 | ||
| 		templateCode = "SMS_494605047" // 阿里云欠费预警模板
 | ||
| 
 | ||
| 		// 使用传入的企业名称,如果没有则使用默认值
 | ||
| 		name := "天远数据用户"
 | ||
| 		if len(enterpriseName) > 0 && enterpriseName[0] != "" {
 | ||
| 			name = enterpriseName[0]
 | ||
| 		}
 | ||
| 
 | ||
| 		templateParam = fmt.Sprintf(`{"name":"%s","time":"%s","money":"%.2f"}`,
 | ||
| 			name, time.Now().Format("2006-01-02 15:04:05"), threshold)
 | ||
| 	} else if alertType == "arrears" {
 | ||
| 		// 欠费预警模板
 | ||
| 		templateCode = "SMS_494605047" // 阿里云欠费预警模板
 | ||
| 
 | ||
| 		// 使用传入的企业名称,如果没有则使用默认值
 | ||
| 		name := "天远数据用户"
 | ||
| 		if len(enterpriseName) > 0 && enterpriseName[0] != "" {
 | ||
| 			name = enterpriseName[0]
 | ||
| 		}
 | ||
| 
 | ||
| 		templateParam = fmt.Sprintf(`{"name":"%s","time":"%s","money":"%.2f"}`,
 | ||
| 			name, time.Now().Format("2006-01-02 15:04:05"), balance)
 | ||
| 	} else {
 | ||
| 		return fmt.Errorf("不支持的预警类型: %s", alertType)
 | ||
| 	}
 | ||
| 
 | ||
| 	request.TemplateCode = templateCode
 | ||
| 	request.TemplateParam = templateParam
 | ||
| 
 | ||
| 	response, err := s.client.SendSms(request)
 | ||
| 	if err != nil {
 | ||
| 		s.logger.Error("发送余额预警短信失败",
 | ||
| 			zap.String("phone", phone),
 | ||
| 			zap.String("alert_type", alertType),
 | ||
| 			zap.Error(err))
 | ||
| 		return fmt.Errorf("短信发送失败: %w", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	if response.Code != "OK" {
 | ||
| 		s.logger.Error("余额预警短信发送失败",
 | ||
| 			zap.String("phone", phone),
 | ||
| 			zap.String("alert_type", alertType),
 | ||
| 			zap.String("code", response.Code),
 | ||
| 			zap.String("message", response.Message))
 | ||
| 		return fmt.Errorf("短信发送失败: %s - %s", response.Code, response.Message)
 | ||
| 	}
 | ||
| 
 | ||
| 	s.logger.Info("余额预警短信发送成功",
 | ||
| 		zap.String("phone", phone),
 | ||
| 		zap.String("alert_type", alertType),
 | ||
| 		zap.String("bizId", response.BizId))
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // GenerateCode 生成验证码
 | ||
| func (s *AliSMSService) GenerateCode(length int) string {
 | ||
| 	if length <= 0 {
 | ||
| 		length = 6
 | ||
| 	}
 | ||
| 
 | ||
| 	// 生成指定长度的数字验证码
 | ||
| 	max := big.NewInt(int64(pow10(length)))
 | ||
| 	n, _ := rand.Int(rand.Reader, max)
 | ||
| 
 | ||
| 	// 格式化为指定长度,不足时前面补0
 | ||
| 	format := fmt.Sprintf("%%0%dd", length)
 | ||
| 	return fmt.Sprintf(format, n.Int64())
 | ||
| }
 | ||
| 
 | ||
| // pow10 计算10的n次方
 | ||
| func pow10(n int) int {
 | ||
| 	result := 1
 | ||
| 	for i := 0; i < n; i++ {
 | ||
| 		result *= 10
 | ||
| 	}
 | ||
| 	return result
 | ||
| }
 | ||
| 
 | ||
| // MockSMSService 模拟短信服务(用于开发和测试)
 | ||
| type MockSMSService struct {
 | ||
| 	logger *zap.Logger
 | ||
| }
 | ||
| 
 | ||
| // NewMockSMSService 创建模拟短信服务
 | ||
| func NewMockSMSService(logger *zap.Logger) *MockSMSService {
 | ||
| 	return &MockSMSService{
 | ||
| 		logger: logger,
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // SendVerificationCode 模拟发送验证码
 | ||
| func (s *MockSMSService) SendVerificationCode(ctx context.Context, phone string, code string) error {
 | ||
| 	s.logger.Info("Mock SMS sent",
 | ||
| 		zap.String("phone", phone),
 | ||
| 		zap.String("code", code))
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // GenerateCode 生成验证码
 | ||
| func (s *MockSMSService) GenerateCode(length int) string {
 | ||
| 	if length <= 0 {
 | ||
| 		length = 6
 | ||
| 	}
 | ||
| 
 | ||
| 	// 开发环境使用固定验证码便于测试
 | ||
| 	result := ""
 | ||
| 	for i := 0; i < length; i++ {
 | ||
| 		result += "1"
 | ||
| 	}
 | ||
| 	return result
 | ||
| }
 |