feat(sms): 新增节点短信提醒(审核未通过/被租用需辅助/上架未成交)

This commit is contained in:
yml2213
2026-10-14 14:57:52 +08:00
parent efa0aa4ffd
commit 93e102b7c0
38 changed files with 3228 additions and 70 deletions
+37 -9
View File
@@ -57,18 +57,45 @@ func NewAliyunProvider(cfg AliyunConfig, log *zap.Logger) (*AliyunProvider, erro
}
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
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
}
req := &dysms.SendSmsRequest{}
req.SetPhoneNumbers(phone)
req.SetSignName(p.signName)
req.SetTemplateCode(p.loginTemplateCode)
req.SetTemplateParam(string(params))
params := "{}"
if len(req.Params) > 0 {
raw, err := json.Marshal(req.Params)
if err != nil {
return err
}
params = string(raw)
}
resp, err := p.client.SendSmsWithContext(ctx, req, &dara.RuntimeOptions{})
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)
}
@@ -90,6 +117,7 @@ func (p *AliyunProvider) SendLoginCode(ctx context.Context, phone string, code s
p.log.Debug(
"短信发送成功",
zap.String("phone", maskPhone(phone)),
zap.String("template_code", templateCode),
zap.String("provider_request_id", dara.StringValue(resp.Body.RequestId)),
)
}
+14
View File
@@ -23,3 +23,17 @@ func (p *MockProvider) SendLoginCode(ctx context.Context, phone string, code str
}
return nil
}
func (p *MockProvider) SendTemplate(ctx context.Context, req TemplateRequest) error {
if err := ctx.Err(); err != nil {
return err
}
if p.log != nil {
p.log.Info("本地模板短信(未真实发送)",
zap.String("phone", maskPhone(req.Phone)),
zap.String("template_code", req.TemplateCode),
zap.Any("params", req.Params),
)
}
return nil
}
@@ -11,8 +11,19 @@ var (
ErrProviderRateLimited = errors.New("sms provider rate limited")
)
// TemplateRequest 描述一条模板短信。SignName 留空时使用 Provider 自身配置的默认签名。
type TemplateRequest struct {
Phone string
SignName string
TemplateCode string
Params map[string]string
}
type Provider interface {
SendLoginCode(ctx context.Context, phone string, code string) error
// SendTemplate 发送模板短信,用于登录验证码以外的业务节点触达。
// 阿里云要求模板必须提前报备审核,因此调用方只传模板 CODE 与变量,不拼接文案。
SendTemplate(ctx context.Context, req TemplateRequest) error
}
type ProviderError struct {
@@ -19,3 +19,7 @@ func (p *UnavailableProvider) SendLoginCode(ctx context.Context, phone string, c
}
return ErrProviderConfigInvalid
}
func (p *UnavailableProvider) SendTemplate(ctx context.Context, req TemplateRequest) error {
return p.SendLoginCode(ctx, req.Phone, "")
}