sync: 同步短信服务 v1.0.4 并脱敏
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 场景配置启用状态。
|
||||
const sceneStatusEnabled = 1
|
||||
|
||||
// SceneConfig 是「业务场景 -> 短信模板」的配置,来源于数据库表 t_sms_template_config。
|
||||
// 运营侧修改该表后,短信服务在缓存过期后即按新配置发送,无需改代码或重启。
|
||||
type SceneConfig struct {
|
||||
Scene string
|
||||
ServiceType string
|
||||
SignName string
|
||||
TemplateID string
|
||||
MsgContentTemplate string
|
||||
Status int
|
||||
}
|
||||
|
||||
// SceneProvider 提供按场景码查询模板配置的能力。
|
||||
type SceneProvider interface {
|
||||
Lookup(ctx context.Context, scene string) (*SceneConfig, error)
|
||||
}
|
||||
|
||||
const sceneQuery = "SELECT `scene`, `service_type`, `sign_name`, `template_id`, `msg_content_template`, `status` " +
|
||||
"FROM `t_sms_template_config` WHERE `scene` = ? LIMIT 1"
|
||||
|
||||
type sceneCacheEntry struct {
|
||||
config *SceneConfig
|
||||
expired time.Time
|
||||
}
|
||||
|
||||
// SceneStore 从数据库读取场景模板配置,并做短 TTL 内存缓存:
|
||||
// 改库后最多 ttl 时间即生效,同时避免每次发送都查库。
|
||||
type SceneStore struct {
|
||||
db *sql.DB
|
||||
ttl time.Duration
|
||||
mu sync.RWMutex
|
||||
cache map[string]sceneCacheEntry
|
||||
}
|
||||
|
||||
// NewSceneStore 创建一个基于数据库的场景配置存储。ttl<=0 时使用默认 60s。
|
||||
func NewSceneStore(db *sql.DB, ttl time.Duration) *SceneStore {
|
||||
if ttl <= 0 {
|
||||
ttl = 60 * time.Second
|
||||
}
|
||||
return &SceneStore{
|
||||
db: db,
|
||||
ttl: ttl,
|
||||
cache: make(map[string]sceneCacheEntry),
|
||||
}
|
||||
}
|
||||
|
||||
// Close 关闭底层数据库连接。
|
||||
func (s *SceneStore) Close() error {
|
||||
if s == nil || s.db == nil {
|
||||
return nil
|
||||
}
|
||||
return s.db.Close()
|
||||
}
|
||||
|
||||
// Lookup 返回指定场景的模板配置;场景未配置时返回 (nil, nil)。
|
||||
// 命中与未命中都会缓存 ttl,避免对不存在的场景反复查库。
|
||||
func (s *SceneStore) Lookup(ctx context.Context, scene string) (*SceneConfig, error) {
|
||||
if s == nil || s.db == nil {
|
||||
return nil, nil
|
||||
}
|
||||
scene = strings.TrimSpace(scene)
|
||||
if scene == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if cfg, ok := s.cacheGet(scene); ok {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
var item SceneConfig
|
||||
row := s.db.QueryRowContext(ctx, sceneQuery, scene)
|
||||
err := row.Scan(
|
||||
&item.Scene,
|
||||
&item.ServiceType,
|
||||
&item.SignName,
|
||||
&item.TemplateID,
|
||||
&item.MsgContentTemplate,
|
||||
&item.Status,
|
||||
)
|
||||
switch {
|
||||
case err == nil:
|
||||
result := item
|
||||
s.cacheSet(scene, &result)
|
||||
return &result, nil
|
||||
case err == sql.ErrNoRows:
|
||||
s.cacheSet(scene, nil)
|
||||
return nil, nil
|
||||
default:
|
||||
// 查询失败不缓存,下次请求重试。
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SceneStore) cacheGet(scene string) (*SceneConfig, bool) {
|
||||
s.mu.RLock()
|
||||
entry, ok := s.cache[scene]
|
||||
s.mu.RUnlock()
|
||||
if !ok || time.Now().After(entry.expired) {
|
||||
return nil, false
|
||||
}
|
||||
return entry.config, true
|
||||
}
|
||||
|
||||
func (s *SceneStore) cacheSet(scene string, cfg *SceneConfig) {
|
||||
s.mu.Lock()
|
||||
s.cache[scene] = sceneCacheEntry{config: cfg, expired: time.Now().Add(s.ttl)}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
Reference in New Issue
Block a user