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 }