133 lines
3.5 KiB
Go
133 lines
3.5 KiB
Go
package sms
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/utils"
|
|
dysms "github.com/alibabacloud-go/dysmsapi-20170525/v5/client"
|
|
"github.com/alibabacloud-go/tea/dara"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type AliyunConfig struct {
|
|
AccessKeyID string
|
|
AccessKeySecret string
|
|
Endpoint string
|
|
SignName string
|
|
LoginTemplateCode string
|
|
}
|
|
|
|
type AliyunProvider struct {
|
|
client *dysms.Client
|
|
signName string
|
|
loginTemplateCode string
|
|
log *zap.Logger
|
|
}
|
|
|
|
func NewAliyunProvider(cfg AliyunConfig, log *zap.Logger) (*AliyunProvider, error) {
|
|
cfg.Endpoint = strings.TrimSpace(cfg.Endpoint)
|
|
if cfg.Endpoint == "" {
|
|
cfg.Endpoint = "dysmsapi.aliyuncs.com"
|
|
}
|
|
if strings.TrimSpace(cfg.AccessKeyID) == "" ||
|
|
strings.TrimSpace(cfg.AccessKeySecret) == "" ||
|
|
strings.TrimSpace(cfg.SignName) == "" ||
|
|
strings.TrimSpace(cfg.LoginTemplateCode) == "" {
|
|
return nil, ErrProviderConfigInvalid
|
|
}
|
|
|
|
client, err := dysms.NewClient(&openapi.Config{
|
|
AccessKeyId: dara.String(strings.TrimSpace(cfg.AccessKeyID)),
|
|
AccessKeySecret: dara.String(strings.TrimSpace(cfg.AccessKeySecret)),
|
|
Endpoint: dara.String(cfg.Endpoint),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &AliyunProvider{
|
|
client: client,
|
|
signName: strings.TrimSpace(cfg.SignName),
|
|
loginTemplateCode: strings.TrimSpace(cfg.LoginTemplateCode),
|
|
log: log,
|
|
}, nil
|
|
}
|
|
|
|
func (p *AliyunProvider) SendLoginCode(ctx context.Context, phone string, code string) error {
|
|
return p.SendTemplate(ctx, TemplateRequest{
|
|
Phone: phone,
|
|
TemplateCode: p.loginTemplateCode,
|
|
Params: map[string]string{"code": code},
|
|
})
|
|
}
|
|
|
|
// SendTemplate 发送已报备的模板短信。req.SignName 为空时回退到 Provider 默认签名。
|
|
// 模板无变量时按阿里云要求传空 JSON,避免拼接文案导致模板不匹配。
|
|
func (p *AliyunProvider) SendTemplate(ctx context.Context, req TemplateRequest) error {
|
|
phone := strings.TrimSpace(req.Phone)
|
|
if phone == "" {
|
|
return ErrProviderConfigInvalid
|
|
}
|
|
signName := strings.TrimSpace(req.SignName)
|
|
if signName == "" {
|
|
signName = p.signName
|
|
}
|
|
templateCode := strings.TrimSpace(req.TemplateCode)
|
|
if templateCode == "" {
|
|
return ErrProviderConfigInvalid
|
|
}
|
|
|
|
params := "{}"
|
|
if len(req.Params) > 0 {
|
|
raw, err := json.Marshal(req.Params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
params = string(raw)
|
|
}
|
|
|
|
apiReq := &dysms.SendSmsRequest{}
|
|
apiReq.SetPhoneNumbers(phone)
|
|
apiReq.SetSignName(signName)
|
|
apiReq.SetTemplateCode(templateCode)
|
|
apiReq.SetTemplateParam(params)
|
|
|
|
resp, err := p.client.SendSmsWithContext(ctx, apiReq, &dara.RuntimeOptions{})
|
|
if err != nil {
|
|
return fmt.Errorf("aliyun sms send failed: %w", err)
|
|
}
|
|
if resp == nil || resp.Body == nil {
|
|
return fmt.Errorf("aliyun sms empty response")
|
|
}
|
|
|
|
resultCode := dara.StringValue(resp.Body.Code)
|
|
if resultCode != "OK" {
|
|
resultMessage := dara.StringValue(resp.Body.Message)
|
|
providerErr := &ProviderError{Code: resultCode, Message: resultMessage}
|
|
if resultCode == "isv.BUSINESS_LIMIT_CONTROL" {
|
|
providerErr.Err = ErrProviderRateLimited
|
|
}
|
|
return providerErr
|
|
}
|
|
|
|
if p.log != nil {
|
|
p.log.Debug(
|
|
"短信发送成功",
|
|
zap.String("phone", maskPhone(phone)),
|
|
zap.String("template_code", templateCode),
|
|
zap.String("provider_request_id", dara.StringValue(resp.Body.RequestId)),
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func maskPhone(phone string) string {
|
|
if len(phone) < 7 {
|
|
return phone
|
|
}
|
|
return phone[:3] + "****" + phone[len(phone)-4:]
|
|
}
|