Files
hyapi-server/internal/infrastructure/external/sms/aliyun_sms.go
2026-04-23 22:16:12 +08:00

159 lines
4.3 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"
"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) {
if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" {
return nil, fmt.Errorf("阿里云短信未配置 access_key_id / access_key_secret")
}
if cfg.SignName == "" {
return nil, fmt.Errorf("阿里云短信未配置 sign_name")
}
if cfg.TemplateCode == "" {
return nil, fmt.Errorf("阿里云短信未配置 template_code")
}
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
}