feat(v2): limit_aware 策略(UsageSource 留位)+ Router 按配置派发 + config.Routing
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user