package accounts import ( "sync" "github.com/wangjia/pay/config" ) // UsageSource 提供账户今日已收金额(minor),供 limit_aware 判定是否越 DailyLimit。 // P5 内存实现(MemUsage)+ 生产空源(NopUsage);真实用量数据源由 P6 对账 job 提供, // 届时实现本接口注入 Router 即可,LimitAware/Router 不改(见计划 D5)。 type UsageSource interface { TodayUsedMinor(accountID string) int64 } // NopUsage 恒返回 0:P6 前生产装配用,limit_aware 退化为基础策略(不误拒)。 type NopUsage struct{} func (NopUsage) TodayUsedMinor(string) int64 { return 0 } // MemUsage 进程内用量计数(测试确定性 / 简单场景)。 type MemUsage struct { mu sync.Mutex used map[string]int64 } func NewMemUsage() *MemUsage { return &MemUsage{used: map[string]int64{}} } func (m *MemUsage) Add(accountID string, minor int64) { m.mu.Lock() m.used[accountID] += minor m.mu.Unlock() } func (m *MemUsage) TodayUsedMinor(accountID string) int64 { m.mu.Lock() defer m.mu.Unlock() return m.used[accountID] } // LimitAware 过滤掉「DailyLimit>0 且 已用+本单 > DailyLimit」的账户,再把合格集交给 // base 策略选(默认 round_robin)。全部越限 → ErrNoAccount(分摊避免单账户触风控冻结)。 type LimitAware struct { base Strategy usage UsageSource } func NewLimitAware(base Strategy, usage UsageSource) *LimitAware { if usage == nil { usage = NopUsage{} } return &LimitAware{base: base, usage: usage} } func (l *LimitAware) Pick(key string, candidates []config.AccountConfig, hint PickHint) (config.AccountConfig, error) { eligible := make([]config.AccountConfig, 0, len(candidates)) for _, c := range candidates { if c.DailyLimit > 0 && l.usage.TodayUsedMinor(c.AccountID)+hint.AmountMinor > c.DailyLimit { continue } eligible = append(eligible, c) } if len(eligible) == 0 { return config.AccountConfig{}, ErrNoAccount } return l.base.Pick(key, eligible, hint) }