Files
tyapi-server/internal/infrastructure/external/sms/sms_service.go
2025-09-12 01:15:09 +08:00

192 lines
5.1 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 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
}