对接阿里云短信OK

This commit is contained in:
yml2213
2026-05-29 12:11:13 +08:00
parent a334f1837d
commit eb80a3ba84
14 changed files with 495 additions and 29 deletions
+112
View File
@@ -0,0 +1,112 @@
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 {
params, err := json.Marshal(map[string]string{"code": code})
if err != nil {
return err
}
req := &dysms.SendSmsRequest{}
req.SetPhoneNumbers(phone)
req.SetSignName(p.signName)
req.SetTemplateCode(p.loginTemplateCode)
req.SetTemplateParam(string(params))
resp, err := p.client.SendSmsWithContext(ctx, req, &dara.RuntimeOptions{})
if err != nil {
if p.log != nil {
p.log.Warn("aliyun sms send failed", zap.String("phone", maskPhone(phone)), zap.Error(err))
}
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" {
if p.log != nil {
p.log.Warn(
"aliyun sms rejected",
zap.String("phone", maskPhone(phone)),
zap.String("request_id", dara.StringValue(resp.Body.RequestId)),
zap.String("code", resultCode),
zap.String("message", dara.StringValue(resp.Body.Message)),
)
}
return fmt.Errorf("aliyun sms rejected: %s", resultCode)
}
if p.log != nil {
p.log.Info(
"aliyun sms sent",
zap.String("phone", maskPhone(phone)),
zap.String("request_id", dara.StringValue(resp.Body.RequestId)),
zap.String("biz_id", dara.StringValue(resp.Body.BizId)),
)
}
return nil
}
func maskPhone(phone string) string {
if len(phone) < 7 {
return phone
}
return phone[:3] + "****" + phone[len(phone)-4:]
}
+25
View File
@@ -0,0 +1,25 @@
package sms
import (
"context"
"go.uber.org/zap"
)
type MockProvider struct {
log *zap.Logger
}
func NewMockProvider(log *zap.Logger) *MockProvider {
return &MockProvider{log: log}
}
func (p *MockProvider) SendLoginCode(ctx context.Context, phone string, code string) error {
if err := ctx.Err(); err != nil {
return err
}
if p.log != nil {
p.log.Info("mock sms code generated", zap.String("phone", phone), zap.String("code", code))
}
return nil
}
@@ -0,0 +1,12 @@
package sms
import (
"context"
"errors"
)
var ErrProviderConfigInvalid = errors.New("sms provider config invalid")
type Provider interface {
SendLoginCode(ctx context.Context, phone string, code string) error
}
@@ -0,0 +1,21 @@
package sms
import "context"
type UnavailableProvider struct {
err error
}
func NewUnavailableProvider(err error) *UnavailableProvider {
return &UnavailableProvider{err: err}
}
func (p *UnavailableProvider) SendLoginCode(ctx context.Context, phone string, code string) error {
if err := ctx.Err(); err != nil {
return err
}
if p.err != nil {
return p.err
}
return ErrProviderConfigInvalid
}