sync: 同步短信服务 v1.0.4 并脱敏
This commit is contained in:
+69
-4
@@ -2,6 +2,7 @@ package sms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -14,13 +15,18 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProviderNotFound = errors.New("未选择有效的短信发送供应商")
|
||||
ErrInvalidRequest = errors.New("短信请求参数不正确")
|
||||
ErrProviderNotFound = errors.New("未选择有效的短信发送供应商")
|
||||
ErrInvalidRequest = errors.New("短信请求参数不正确")
|
||||
ErrSceneNotConfigured = errors.New("短信场景未配置或已停用")
|
||||
)
|
||||
|
||||
// Config contains the provider credentials and template configuration.
|
||||
// serviceType: 1=Aliyun, 2=Tencent Cloud.
|
||||
//
|
||||
// Scene 为可选业务场景码(如 bind_phone)。当 Scene 非空时,短信服务会按该场景从数据库
|
||||
// 配置表读取模板ID/签名等配置并回填到本次请求,调用方无需关心具体模板。
|
||||
type Config struct {
|
||||
Scene string `json:"scene" yaml:"scene"`
|
||||
ServiceType string `json:"serviceType" yaml:"serviceType"`
|
||||
RegionID string `json:"regionId" yaml:"regionId"`
|
||||
AccessKeyID string `json:"accessKeyId" yaml:"accessKeyId"`
|
||||
@@ -49,6 +55,7 @@ type Registry struct {
|
||||
mu sync.RWMutex
|
||||
providers map[string]Provider
|
||||
defaultConfig Config
|
||||
scenes SceneProvider
|
||||
}
|
||||
|
||||
// NewRegistry 创建一个无默认配置的注册表。
|
||||
@@ -58,8 +65,9 @@ func NewRegistry() *Registry {
|
||||
|
||||
// NewRegistryWithDefault 创建一个带默认配置的注册表。调用方请求中未显式提供的字段
|
||||
// 将回退到 defaultConfig(例如供应商凭证与签名由短信服务自身环境变量注入)。
|
||||
func NewRegistryWithDefault(dc Config) *Registry {
|
||||
return &Registry{providers: make(map[string]Provider), defaultConfig: dc}
|
||||
// scenes 为可选的场景配置提供方(按 scene 从数据库读取模板配置),可为 nil。
|
||||
func NewRegistryWithDefault(dc Config, scenes SceneProvider) *Registry {
|
||||
return &Registry{providers: make(map[string]Provider), defaultConfig: dc, scenes: scenes}
|
||||
}
|
||||
|
||||
func (r *Registry) Register(serviceType string, provider Provider) {
|
||||
@@ -69,6 +77,19 @@ func (r *Registry) Register(serviceType string, provider Provider) {
|
||||
}
|
||||
|
||||
func (r *Registry) Send(ctx context.Context, config Config, message Message) (Result, error) {
|
||||
// 按业务场景加载模板配置(来自数据库),实现“改配置不改代码、不重启服务”。
|
||||
if scene := strings.TrimSpace(config.Scene); scene != "" {
|
||||
sceneCfg, err := r.loadSceneConfig(ctx, scene)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrSceneNotConfigured) {
|
||||
return Result{Code: 400, Message: err.Error()}, err
|
||||
}
|
||||
return Result{Code: 500, Message: "查询短信场景配置失败: " + err.Error()}, err
|
||||
}
|
||||
config = mergeSceneConfig(sceneCfg, config)
|
||||
message.MsgContent = renderSceneMsgContent(sceneCfg.MsgContentTemplate, message.MsgContent)
|
||||
}
|
||||
|
||||
// 回退默认值:请求中未提供的字段使用短信服务自身的默认配置。
|
||||
config = mergeConfig(r.defaultConfig, config)
|
||||
if err := Validate(config, message); err != nil {
|
||||
@@ -84,6 +105,50 @@ func (r *Registry) Send(ctx context.Context, config Config, message Message) (Re
|
||||
return provider.Send(ctx, config, message)
|
||||
}
|
||||
|
||||
// loadSceneConfig 按场景码加载模板配置,并校验场景已启用。
|
||||
func (r *Registry) loadSceneConfig(ctx context.Context, scene string) (SceneConfig, error) {
|
||||
if r.scenes == nil {
|
||||
return SceneConfig{}, fmt.Errorf("%w: %s", ErrSceneNotConfigured, scene)
|
||||
}
|
||||
cfg, err := r.scenes.Lookup(ctx, scene)
|
||||
if err != nil {
|
||||
return SceneConfig{}, err
|
||||
}
|
||||
if cfg == nil || cfg.Status != sceneStatusEnabled {
|
||||
return SceneConfig{}, fmt.Errorf("%w: %s", ErrSceneNotConfigured, scene)
|
||||
}
|
||||
return *cfg, nil
|
||||
}
|
||||
|
||||
// mergeSceneConfig 用场景配置填充请求中未显式提供的字段,调用方显式值优先。
|
||||
func mergeSceneConfig(scene SceneConfig, config Config) Config {
|
||||
if strings.TrimSpace(config.ServiceType) == "" {
|
||||
config.ServiceType = scene.ServiceType
|
||||
}
|
||||
if strings.TrimSpace(config.SignName) == "" {
|
||||
config.SignName = scene.SignName
|
||||
}
|
||||
if strings.TrimSpace(config.TemplateID) == "" {
|
||||
config.TemplateID = scene.TemplateID
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// renderSceneMsgContent 按场景内容模板渲染短信参数;模板为空时直接使用调用方内容。
|
||||
// 支持 {content}(原样)与 {contentJson}(JSON 转义)两种占位符。
|
||||
func renderSceneMsgContent(template string, content string) string {
|
||||
template = strings.TrimSpace(template)
|
||||
if template == "" {
|
||||
return content
|
||||
}
|
||||
contentJSON, _ := json.Marshal(content)
|
||||
replacer := strings.NewReplacer(
|
||||
"{content}", content,
|
||||
"{contentJson}", string(contentJSON),
|
||||
)
|
||||
return replacer.Replace(template)
|
||||
}
|
||||
|
||||
func Validate(config Config, message Message) error {
|
||||
missing := make([]string, 0, 6)
|
||||
if strings.TrimSpace(config.ServiceType) == "" {
|
||||
|
||||
Reference in New Issue
Block a user