package wechat_auth import ( "context" "errors" "sync" ) type ( ISys interface { Auth(ctx context.Context, idToken string) (info *WeChatUserInfoResponse, err error) } WeChatTokenResponse struct { AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` RefreshToken string `json:"refresh_token"` OpenID string `json:"openid"` Scope string `json:"scope"` WeChatError // 内嵌错误结构 } WeChatUserInfoResponse struct { OpenID string `json:"openid"` Nickname string `json:"nickname"` HeadImgURL string `json:"headimgurl"` UnionID string `json:"unionid"` WeChatError } WeChatError struct { ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } ) // defsys 由 OnInit(启动) 与 Reload(后台改配置后热更) 写、Auth 读,故必须加锁: // 无锁并发读写接口值是 data race。 var ( mu sync.RWMutex defsys ISys ) func get() ISys { mu.RLock() defer mu.RUnlock() return defsys } func set(sys ISys) { mu.Lock() defsys = sys mu.Unlock() } // ErrNotInited 微信登录未初始化。OnInit 失败时调用方可选择降级;此时包级函数返回本错误,而不是 panic。 var ErrNotInited = errors.New("wechat_auth 未初始化:配置缺失") // OnInit 配置缺失时返回 error(调用方降级为「微信登录不可用」),不再像以前那样空着 AppSecret // 也报 success——那会让启动日志说一切正常、实际每次登录都被微信回 41004 appsecret missing。 func OnInit(config map[string]interface{}, option ...Option) error { options, err := newOptionsChecked(config, option...) if err != nil { return err } sys, err := newSys(options) if err != nil { // 出错时不要给 defsys 赋值,避免「非 nil 接口 + nil 指针」让守卫失效。 return err } set(sys) return nil } // Reload 后台「业务功能配置 → 微信登录」保存后热更,无需重启。 // 校验不过直接返回 error、保留旧实例继续服务(与 sys/sms 的 Reload 同口径)。 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) { sys, err = newSys(newOptionsByOption(option...)) return } func Auth(ctx context.Context, idToken string) (info *WeChatUserInfoResponse, err error) { sys := get() if sys == nil { return nil, ErrNotInited } return sys.Auth(ctx, idToken) }