package sms import ( "context" "encoding/json" "fmt" "strings" "time" "github.com/aliyun/alibaba-cloud-sdk-go/sdk" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" "github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi" ) // Config 阿里云短信配置 type Config struct { AccessKeyID string `json:"access_key_id" yaml:"access_key_id"` AccessKeySecret string `json:"access_key_secret" yaml:"access_key_secret"` Endpoint string `json:"endpoint" yaml:"endpoint"` SignName string `json:"sign_name" yaml:"sign_name"` RetryTimes int `json:"retry_times" yaml:"retry_times"` Timeout int `json:"timeout" yaml:"timeout"` } // Service 短信服务接口 type Service interface { SendSMS(ctx context.Context, phoneNumbers []string, templateCode string, params map[string]string) error SendVerificationCode(ctx context.Context, phoneNumber, templateCode, code string) error } // AliYunService 阿里云短信服务实现 type AliYunService struct { config Config client *dysmsapi.Client retryTime int timeout time.Duration } // NewService 创建阿里云短信服务实例 func NewService(config Config) (Service, error) { // 创建配置 cfg := sdk.NewConfig() //cfg.HttpTransport.MaxIdleConns = 100 //cfg.HttpTransport.IdleConnTimeout = 90 * time.Second //cfg.HttpTransport.TLSHandshakeTimeout = 10 * time.Second // 默认超时时间 if config.Timeout <= 0 { config.Timeout = 10 } // 默认重试次数 if config.RetryTimes <= 0 { config.RetryTimes = 3 } // 创建凭证 credential := credentials.NewAccessKeyCredential( config.AccessKeyID, config.AccessKeySecret, ) // 创建客户端 client, err := dysmsapi.NewClientWithOptions( config.Endpoint, cfg, credential, ) if err != nil { return nil, fmt.Errorf("创建阿里云客户端失败: %w", err) } return &AliYunService{ config: config, client: client, retryTime: config.RetryTimes, timeout: time.Duration(config.Timeout) * time.Second, }, nil } // SendSMS 发送短信 func (s *AliYunService) SendSMS(ctx context.Context, phoneNumbers []string, templateCode string, params map[string]string) error { if len(phoneNumbers) == 0 { return fmt.Errorf("电话号码不能为空") } if templateCode == "" { return fmt.Errorf("模板代码不能为空") } // 转换参数为JSON paramJSON, err := json.Marshal(params) if err != nil { return fmt.Errorf("参数序列化失败: %w", err) } // 创建请求 request := dysmsapi.CreateSendSmsRequest() request.PhoneNumbers = strings.Join(phoneNumbers, ",") request.SignName = s.config.SignName request.TemplateCode = templateCode request.TemplateParam = string(paramJSON) // 设置超时 ctx, cancel := context.WithTimeout(ctx, s.timeout) defer cancel() // 执行发送(带重试) var lastError error for i := 0; i <= s.retryTime; i++ { if i > 0 { // 指数退避重试 backoff := time.Duration(200*i) * time.Millisecond select { case <-time.After(backoff): case <-ctx.Done(): return ctx.Err() } } response, err := s.client.SendSms(request) if err != nil { lastError = fmt.Errorf("发送短信失败 (尝试 %d/%d): %w", i+1, s.retryTime+1, err) continue } if response.Code != "OK" { lastError = fmt.Errorf("发送短信失败 (尝试 %d/%d): 代码=%s, 消息=%s", i+1, s.retryTime+1, response.Code, response.Message) continue } return nil } return lastError } // SendVerificationCode 发送验证码 func (s *AliYunService) SendVerificationCode(ctx context.Context, phoneNumber, templateCode, code string) error { return s.SendSMS(ctx, []string{phoneNumber}, templateCode, map[string]string{"code": code}) }