新增单行文本编码器与结构化 GORM 日志,统一错误记录与请求日志策略,收紧日志文件权限并修复按天切分与压缩,支付回调参数脱敏,生产强制阿里云短信,RequestID 校验防注入,日志文案中文化。
105 lines
2.7 KiB
Go
105 lines
2.7 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 {
|
|
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 {
|
|
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("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:]
|
|
}
|