From 5aecb18d7c0dea0f60ec163c635e019198832537 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Fri, 10 Jul 2026 08:25:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(v2):=20=E8=B4=A6=E6=88=B7=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=B3=A8=E5=86=8C=E8=A1=A8(config.accounts=20+=20env?= =?UTF-8?q?=20=E5=8F=96=E5=87=AD=E8=AF=81=20+=20=E6=B8=A0=E9=81=93/?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E7=AD=9B=E9=80=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 16 ++++++++++++ internal/accounts/registry.go | 40 ++++++++++++++++++++++++++++++ internal/accounts/registry_test.go | 28 +++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 internal/accounts/registry.go create mode 100644 internal/accounts/registry_test.go diff --git a/config/config.go b/config/config.go index baef165..a1b02ed 100644 --- a/config/config.go +++ b/config/config.go @@ -16,6 +16,22 @@ type Config struct { QuerySync QuerySyncConfig `mapstructure:"query_sync"` // Biz 通用:任意业务系统(jiu / dudu / …)在 config 的 biz. 下声明即可接入,无需改代码。 Biz map[string]BizSystemConfig `mapstructure:"biz"` + // Accounts 多账户配置注册表(v2):凭证不写死配置,只存 env 前缀,真值运行时从环境变量取。 + Accounts []AccountConfig `mapstructure:"accounts"` +} + +// AccountConfig 单个收款账户的配置(v2 多账户路由用)。凭证严禁写进 config.yaml, +// 只存 CredentialEnvPrefix,真值由 internal/accounts.Registry 运行时从环境变量 +// `_` 读取。 +type AccountConfig struct { + AccountID string `mapstructure:"account_id"` + Channel string `mapstructure:"channel"` + Weight int `mapstructure:"weight"` + Enabled bool `mapstructure:"enabled"` + DailyLimit int64 `mapstructure:"daily_limit"` + Region string `mapstructure:"region"` + Subject string `mapstructure:"subject"` + CredentialEnvPrefix string `mapstructure:"credential_env_prefix"` } type ServerConfig struct { diff --git a/internal/accounts/registry.go b/internal/accounts/registry.go new file mode 100644 index 0000000..2ccbcb3 --- /dev/null +++ b/internal/accounts/registry.go @@ -0,0 +1,40 @@ +// Package accounts loads the payment-account registry from config. Credentials +// never live in the config file — only an env-var prefix is stored; the real +// secret is read from the environment at call time. +package accounts + +import ( + "os" + "strings" + + "github.com/wangjia/pay/config" +) + +type Registry struct{ accts []config.AccountConfig } + +func New(accts []config.AccountConfig) *Registry { return &Registry{accts: accts} } + +// EnabledFor returns enabled accounts for a channel (+region if non-empty). +func (r *Registry) EnabledFor(channel, region string) []config.AccountConfig { + var out []config.AccountConfig + for _, a := range r.accts { + if a.Channel != channel || !a.Enabled { + continue + } + if region != "" && a.Region != "" && a.Region != region { + continue + } + out = append(out, a) + } + return out +} + +// Credential reads _ from the environment. +func (r *Registry) Credential(accountID, key string) string { + for _, a := range r.accts { + if a.AccountID == accountID { + return os.Getenv(strings.ToUpper(a.CredentialEnvPrefix + "_" + key)) + } + } + return "" +} diff --git a/internal/accounts/registry_test.go b/internal/accounts/registry_test.go new file mode 100644 index 0000000..03b17bf --- /dev/null +++ b/internal/accounts/registry_test.go @@ -0,0 +1,28 @@ +package accounts_test + +import ( + "testing" + + "github.com/wangjia/pay/config" + "github.com/wangjia/pay/internal/accounts" +) + +func TestEnabledForAndCredential(t *testing.T) { + t.Setenv("USDT_A1_XPUB", "xpub-abc") + reg := accounts.New([]config.AccountConfig{ + {AccountID: "usdt-a1", Channel: "crypto", Region: "global", Enabled: true, Weight: 1, CredentialEnvPrefix: "USDT_A1"}, + {AccountID: "usdt-a2", Channel: "crypto", Region: "global", Enabled: false, Weight: 1, CredentialEnvPrefix: "USDT_A2"}, + {AccountID: "ali-cn1", Channel: "alipay", Region: "cn", Enabled: true, Weight: 1, CredentialEnvPrefix: "ALI_CN1"}, + }) + + got := reg.EnabledFor("crypto", "global") + if len(got) != 1 || got[0].AccountID != "usdt-a1" { + t.Fatalf("EnabledFor crypto/global = %+v", got) + } + if reg.Credential("usdt-a1", "XPUB") != "xpub-abc" { + t.Fatalf("Credential xpub 取值失败") + } + if len(reg.EnabledFor("alipay", "global")) != 0 { + t.Fatalf("alipay 在 global 应无账户") + } +}