package accounts import "github.com/wangjia/pay/config" // Router 是 gateway 面向的 Picker 实现:取候选(EnabledFor)→ 按 hint 排除 → 派发策略。 // 策略实例进程内单例(round_robin 计数器需长存);未配置/未知策略默认 round_robin。 type Router struct { reg *Registry strategies map[string]string // channel -> 策略名 byName map[string]Strategy def Strategy } // NewRouter 装配路由。routing:channel→策略名(缺省/未知→round_robin);usage=nil→NopUsage。 // limit_aware 复合 round_robin 作为合格集内的均摊基础策略。 func NewRouter(reg *Registry, routing map[string]string, usage UsageSource) *Router { rr := NewRoundRobin() byName := map[string]Strategy{ StrategyRoundRobin: rr, StrategyWeighted: NewWeighted(), StrategyLimitAware: NewLimitAware(rr, usage), } cp := map[string]string{} for k, v := range routing { cp[k] = v } return &Router{reg: reg, strategies: cp, byName: byName, def: rr} } func (r *Router) strategyFor(channel string) Strategy { if name, ok := r.strategies[channel]; ok { if s, ok := r.byName[name]; ok { return s } } return r.def // 缺省 / 未知策略名 → round_robin(见计划 D3) } // Pick 实现 accounts.Picker。 // 注:cands 顺序 = EnabledFor 保留的 config.Accounts 声明顺序,原样传给策略—— // weighted 的桶按候选顺序累加边界(见 weighted.go),故 config.yaml 里账户顺序变了 // 等价于重新分桶(reorder = repin),同一 OutTradeNo 命中的账户可能跟着变,不是 bug。 func (r *Router) Pick(channel, region string, hint PickHint) (config.AccountConfig, error) { cands := r.reg.EnabledFor(channel, region) if len(cands) == 0 { return config.AccountConfig{}, ErrNoAccount } if len(hint.ExcludeAccounts) > 0 { if filtered := excludeAccounts(cands, hint.ExcludeAccounts); len(filtered) > 0 { cands = filtered // 过滤到空则保留全集:单账户渠道 retry 仍可用(见计划 D4) } } return r.strategyFor(channel).Pick(channel+"|"+region, cands, hint) } func excludeAccounts(cands []config.AccountConfig, exclude []string) []config.AccountConfig { skip := make(map[string]struct{}, len(exclude)) for _, id := range exclude { skip[id] = struct{}{} } out := make([]config.AccountConfig, 0, len(cands)) for _, c := range cands { if _, ok := skip[c.AccountID]; ok { continue } out = append(out, c) } return out }