增加短信渠道
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
IntegrationKindSMS = "sms"
|
||||
IntegrationKindPayment = "payment"
|
||||
IntegrationKindMQ = "mq"
|
||||
IntegrationKindWebSocket = "websocket"
|
||||
@@ -255,6 +256,8 @@ func ValidateIntegrationConfig(kind, provider string, values map[string]any) err
|
||||
kind = normalizeIntegrationPart(kind)
|
||||
provider = normalizeIntegrationPart(provider)
|
||||
switch kind {
|
||||
case IntegrationKindSMS:
|
||||
return validateSMSConfig(provider, values)
|
||||
case IntegrationKindPayment:
|
||||
return validatePaymentIntegrationConfig(provider, values)
|
||||
case IntegrationKindMQ, IntegrationKindWebSocket:
|
||||
|
||||
@@ -120,6 +120,22 @@ func genericPaymentDefinition(provider, name, description string) IntegrationCon
|
||||
}
|
||||
|
||||
var integrationDefinitions = map[string][]IntegrationConfigDefinition{
|
||||
IntegrationKindSMS: {
|
||||
{Kind: IntegrationKindSMS, Provider: "aliyun", Name: "阿里云短信", Description: "中国大陆短信,使用已审核的签名和模板。", Defaults: map[string]any{"access_key_id": "", "access_key_secret": "", "sign_name": "", "template_id": ""}, Fields: []IntegrationConfigField{
|
||||
integrationField("access_key_id", "AccessKey ID", true, false, "text"),
|
||||
integrationField("access_key_secret", "AccessKey Secret", true, true, "password"),
|
||||
integrationField("sign_name", "短信签名", true, false, "text"),
|
||||
integrationField("template_id", "模板 CODE", true, false, "text"),
|
||||
}},
|
||||
{Kind: IntegrationKindSMS, Provider: "tencent", Name: "腾讯云短信", Description: "使用短信应用、已审核的签名和模板,模板参数按顺序填写。", Defaults: map[string]any{"secret_id": "", "secret_key": "", "sdk_app_id": "", "region": "ap-guangzhou", "sign_name": "", "template_id": ""}, Fields: []IntegrationConfigField{
|
||||
integrationField("secret_id", "Secret ID", true, true, "password"),
|
||||
integrationField("secret_key", "Secret Key", true, true, "password"),
|
||||
integrationField("sdk_app_id", "短信应用 SDK App ID", true, false, "text"),
|
||||
integrationField("region", "地域", true, false, "text"),
|
||||
integrationField("sign_name", "短信签名", true, false, "text"),
|
||||
integrationField("template_id", "模板 ID", true, false, "text"),
|
||||
}},
|
||||
},
|
||||
IntegrationKindMQ: {
|
||||
{
|
||||
Kind: IntegrationKindMQ, Provider: "emqx", Name: "EMQX", Description: "EMQX MQTT 消息服务",
|
||||
|
||||
@@ -3,4 +3,4 @@ package integration
|
||||
import "github.com/google/wire"
|
||||
|
||||
// ProviderSet wires communication and integration-configuration usecases.
|
||||
var ProviderSet = wire.NewSet(NewIntegrationConfigUsecase)
|
||||
var ProviderSet = wire.NewSet(NewIntegrationConfigUsecase, NewSMSUsecase)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kratos/kratos/v3/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSMSInvalid = errors.BadRequest("SMS_INVALID", "短信参数无效")
|
||||
ErrSMSDisabled = errors.BadRequest("SMS_DISABLED", "短信渠道未配置或未启用")
|
||||
ErrSMSFailed = errors.BadRequest("SMS_SEND_FAILED", "短信提交失败,请检查服务商配置和账户状态")
|
||||
)
|
||||
|
||||
// SMSMessage carries one recipient and provider-specific template parameters.
|
||||
type SMSMessage struct {
|
||||
Provider string
|
||||
Phone string
|
||||
NamedParams map[string]string
|
||||
Params []string
|
||||
}
|
||||
|
||||
type SMSResult struct {
|
||||
RequestID string
|
||||
MessageID string
|
||||
}
|
||||
|
||||
type SMSSender interface {
|
||||
Send(context.Context, *SMSMessage) (*SMSResult, error)
|
||||
}
|
||||
|
||||
type SMSUsecase struct{ sender SMSSender }
|
||||
|
||||
func NewSMSUsecase(sender SMSSender) *SMSUsecase { return &SMSUsecase{sender: sender} }
|
||||
|
||||
var smsPhonePattern = regexp.MustCompile(`^\+[1-9][0-9]{6,14}$`)
|
||||
|
||||
func ValidateSMSMessage(m *SMSMessage) error {
|
||||
if m == nil || (m.Provider != "aliyun" && m.Provider != "tencent") || !smsPhonePattern.MatchString(m.Phone) {
|
||||
return ErrSMSInvalid
|
||||
}
|
||||
// The Aliyun adapter uses the mainland China SendSms API.
|
||||
if m.Provider == "aliyun" && (!strings.HasPrefix(m.Phone, "+861") || len(m.Phone) != 14 || len(m.Params) != 0) {
|
||||
return errors.BadRequest("SMS_INVALID", "阿里云短信请使用 +86 开头的中国大陆手机号及对象格式模板参数")
|
||||
}
|
||||
if m.Provider == "tencent" && len(m.NamedParams) != 0 {
|
||||
return errors.BadRequest("SMS_INVALID", "腾讯云短信模板参数必须是按模板顺序排列的字符串数组")
|
||||
}
|
||||
if len(m.NamedParams) > 20 || len(m.Params) > 20 {
|
||||
return ErrSMSInvalid
|
||||
}
|
||||
for key, value := range m.NamedParams {
|
||||
if strings.TrimSpace(key) == "" || len(key) > 64 || len(value) > 500 {
|
||||
return ErrSMSInvalid
|
||||
}
|
||||
}
|
||||
for _, value := range m.Params {
|
||||
if len(value) > 500 {
|
||||
return ErrSMSInvalid
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uc *SMSUsecase) Send(ctx context.Context, m *SMSMessage) (*SMSResult, error) {
|
||||
if err := ValidateSMSMessage(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uc.sender.Send(ctx, m)
|
||||
}
|
||||
|
||||
func validateSMSConfig(provider string, values map[string]any) error {
|
||||
definition, ok := IntegrationDefinition(IntegrationKindSMS, provider)
|
||||
if !ok {
|
||||
return errors.BadRequest("SMS_PROVIDER_INVALID", "不支持的短信服务商")
|
||||
}
|
||||
for _, field := range definition.Fields {
|
||||
value, ok := values[field.Key].(string)
|
||||
if !ok || len(value) > 512 || (field.Required && strings.TrimSpace(value) == "") {
|
||||
return errors.BadRequest("SMS_CONFIG_INVALID", "请填写有效的"+field.Label)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user