package sms import ( "errors" "sync" ) type ( ISys interface { SendCaptcha(mobile string, captcha string) error } ) // defsys 由 OnInit(启动) 与 Reload(运行时热更) 写、SendCaptcha 读,故必须加锁: // 无锁并发读写接口值是 data race。 var ( mu sync.RWMutex defsys ISys ) var errNotInit = errors.New("sms 系统未初始化") func get() ISys { mu.RLock() defer mu.RUnlock() return defsys } func set(sys ISys) { mu.Lock() defsys = sys mu.Unlock() } // OnInit 配置缺失/不合法时返回 error 而非 panic——短信属可选能力,缺配置应由调用方降级 // (未初始化时 SendCaptcha 返回 errNotInit),后续可经 console 配好后 Reload 热生效。 func OnInit(config map[string]interface{}, option ...Option) (err error) { options, err := newOptionsChecked(config, option...) if err != nil { return } sys, err := newSys(options) if err != nil { return } set(sys) return } // Reload 运行时热替换短信客户端(console 后台改配置后由业务侧调用)。 // 配置不合法时返回 error 而非 panic,且**保留原客户端不动**。 func Reload(config map[string]interface{}, option ...Option) error { options, err := newOptionsChecked(config, option...) if err != nil { return err } sys, err := newSys(options) if err != nil { return err } set(sys) return nil } func NewSys(option ...Option) (sys ISys, err error) { options, err := newOptionsByOption(option...) if err != nil { return } sys, err = newSys(options) return } func SendCaptcha(_email string, _captcha string) error { sys := get() if sys == nil { return errNotInit } return sys.SendCaptcha(_email, _captcha) }