package sms import ( "context" "crypto/rand" "encoding/json" "fmt" "math/big" "time" "github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi" "go.uber.org/zap" "hyapi-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 发送余额预警短信(低余额与欠费共用 balance_alert_template_code;模板需包含 name、time、money) 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 name := "海宇数据用户" if len(enterpriseName) > 0 && enterpriseName[0] != "" { name = enterpriseName[0] } t := time.Now().Format("2006-01-02 15:04:05") var money float64 if alertType == "low_balance" { money = threshold } else { money = balance } templateCode := s.config.BalanceAlertTemplateCode if templateCode == "" { templateCode = "SMS_500565339" } tp, err := json.Marshal(struct { Name string `json:"name"` Time string `json:"time"` Money string `json:"money"` }{Name: name, Time: t, Money: fmt.Sprintf("%.2f", money)}) if err != nil { return fmt.Errorf("构建短信模板参数失败: %w", err) } request.TemplateCode = templateCode request.TemplateParam = string(tp) 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) format := fmt.Sprintf("%%0%dd", length) return fmt.Sprintf(format, n.Int64()) } func pow10(n int) int { result := 1 for i := 0; i < n; i++ { result *= 10 } return result }