feat: 钱包系统分离 - 代理钱包与卡钱包完全隔离
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m17s

## 变更概述
将统一钱包系统拆分为代理钱包和卡钱包两个独立系统,实现数据表和代码层面的完全隔离。

## 数据库变更
- 新增 6 张表:tb_agent_wallet、tb_agent_wallet_transaction、tb_agent_recharge_record、tb_card_wallet、tb_card_wallet_transaction、tb_card_recharge_record
- 删除 3 张旧表:tb_wallet、tb_wallet_transaction、tb_recharge_record
- 代理钱包:按 (shop_id, wallet_type) 唯一标识,支持主钱包和分佣钱包
- 卡钱包:按 (resource_type, resource_id) 唯一标识,支持物联网卡和设备

## 代码变更
- Model 层:新增 AgentWallet、AgentWalletTransaction、AgentRechargeRecord、CardWallet、CardWalletTransaction、CardRechargeRecord 模型
- Store 层:新增 6 个独立 Store,支持事务、乐观锁、Redis 缓存
- Service 层:重构 commission_calculation、commission_withdrawal、order、recharge 等 8 个服务
- Bootstrap 层:更新 Store 和 Service 依赖注入
- 常量层:按钱包类型重新组织常量和 Redis Key 生成函数

## 技术特性
- 乐观锁:使用 version 字段防止并发冲突
- 多租户:支持 shop_id_tag 和 enterprise_id_tag 过滤
- 事务管理:所有余额变动使用事务保证 ACID
- 缓存策略:Cache-Aside 模式,余额变动后删除缓存

## 业务影响
- 代理钱包和卡钱包业务完全隔离,互不影响
- 为独立监控、优化、扩展打下基础
- 提升代理钱包的稳定性和独立性

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 09:51:00 +08:00
parent f32d32cd36
commit 18daeae65a
66 changed files with 4420 additions and 1090 deletions

View File

@@ -1,54 +1,89 @@
package constants
import "fmt"
// ========================================
// 钱包系统常量定义
// ========================================
// 钱包资源类型
// ========== 代理钱包常量 ==========
// 代理钱包类型
const (
WalletResourceTypeIotCard = "iot_card" // 物联网卡钱包(个人客户)
WalletResourceTypeDevice = "device" // 设备钱包(个人客户,多卡共享)
WalletResourceTypeShop = "shop" // 店铺钱包(代理商)
AgentWalletTypeMain = "main" // 主钱包
AgentWalletTypeCommission = "commission" // 分佣钱包
)
// 钱包类型
// 代理钱包状态
const (
WalletTypeMain = "main" // 主钱包
WalletTypeCommission = "commission" // 分佣钱包
AgentWalletStatusNormal = 1 // 正常
AgentWalletStatusFrozen = 2 // 冻结
AgentWalletStatusClosed = 3 // 关闭
)
// 钱包状态
// 代理钱包交易类型
const (
WalletStatusNormal = 1 // 正常
WalletStatusFrozen = 2 // 冻结
WalletStatusClosed = 3 // 关闭
AgentTransactionTypeRecharge = "recharge" // 充值
AgentTransactionTypeDeduct = "deduct" // 扣款
AgentTransactionTypeRefund = "refund" // 退款
AgentTransactionTypeCommission = "commission" // 分佣
AgentTransactionTypeWithdrawal = "withdrawal" // 提现
)
// 交易类型
// 代理充值订单号前缀
const (
TransactionTypeRecharge = "recharge" // 充值
TransactionTypeDeduct = "deduct" // 扣款
TransactionTypeRefund = "refund" // 退款
TransactionTypeCommission = "commission" // 分佣
TransactionTypeWithdrawal = "withdrawal" // 提现
AgentRechargeOrderPrefix = "ARCH" // 代理充值订单号前缀
)
// 交易状态
// 代理充值金额限制(单位:分)
const (
AgentRechargeMinAmount = 10000 // 最小充值金额100元
AgentRechargeMaxAmount = 100000000 // 最大充值金额1000000元
)
// ========== 卡钱包常量 ==========
// 卡钱包资源类型
const (
CardWalletResourceTypeIotCard = "iot_card" // 物联网卡钱包
CardWalletResourceTypeDevice = "device" // 设备钱包(多卡共享)
)
// 卡钱包状态
const (
CardWalletStatusNormal = 1 // 正常
CardWalletStatusFrozen = 2 // 冻结
CardWalletStatusClosed = 3 // 关闭
)
// 卡钱包交易类型
const (
CardTransactionTypeRecharge = "recharge" // 充值
CardTransactionTypeDeduct = "deduct" // 扣款
CardTransactionTypeRefund = "refund" // 退款
)
// 卡充值订单号前缀
const (
CardRechargeOrderPrefix = "CRCH" // 卡充值订单号前缀
)
// 卡充值金额限制(单位:分)
const (
CardRechargeMinAmount = 100 // 最小充值金额1元
CardRechargeMaxAmount = 10000000 // 最大充值金额100000元
)
// ========== 通用常量 ==========
// 交易状态(代理钱包和卡钱包通用)
const (
TransactionStatusSuccess = 1 // 成功
TransactionStatusFailed = 2 // 失败
TransactionStatusProcessing = 3 // 处理中
)
// 关联业务类型
const (
ReferenceTypeOrder = "order" // 订单
ReferenceTypeCommission = "commission" // 分佣
ReferenceTypeWithdrawal = "withdrawal" // 提现
ReferenceTypeTopup = "topup" // 充值
)
// 充值状态
// 充值状态(代理钱包和卡钱包通用)
const (
RechargeStatusPending = 1 // 待支付
RechargeStatusPaid = 2 // 已支付
@@ -61,17 +96,96 @@ const (
const (
RechargeMethodAlipay = "alipay" // 支付宝
RechargeMethodWechat = "wechat" // 微信
RechargeMethodBank = "bank" // 银行转账
RechargeMethodOffline = "offline" // 线下
RechargeMethodBank = "bank" // 银行转账(仅代理钱包支持)
RechargeMethodOffline = "offline" // 线下(仅代理钱包支持)
)
// 充值订单号前缀
// 关联业务类型
const (
RechargeOrderPrefix = "RCH" // 充值订单号前缀
ReferenceTypeOrder = "order" // 订单
ReferenceTypeCommission = "commission" // 分佣
ReferenceTypeWithdrawal = "withdrawal" // 提现
ReferenceTypeTopup = "topup" // 充值
)
// 充值金额限制(单位:分)
const (
RechargeMinAmount = 100 // 最小充值金额1元
RechargeMaxAmount = 10000000 // 最大充值金额100000元
)
// ========== Redis Key 生成函数 ==========
// RedisAgentWalletBalanceKey 代理钱包余额缓存 Key
// 格式agent_wallet:balance:{shop_id}:{wallet_type}
// TTL300 秒5 分钟)
func RedisAgentWalletBalanceKey(shopID uint, walletType string) string {
return fmt.Sprintf("agent_wallet:balance:%d:%s", shopID, walletType)
}
// RedisAgentWalletLockKey 代理钱包分布式锁 Key
// 格式agent_wallet:lock:{shop_id}:{wallet_type}
// TTL10 秒
func RedisAgentWalletLockKey(shopID uint, walletType string) string {
return fmt.Sprintf("agent_wallet:lock:%d:%s", shopID, walletType)
}
// RedisCardWalletBalanceKey 卡钱包余额缓存 Key
// 格式card_wallet:balance:{resource_type}:{resource_id}
// TTL180 秒3 分钟)
func RedisCardWalletBalanceKey(resourceType string, resourceID uint) string {
return fmt.Sprintf("card_wallet:balance:%s:%d", resourceType, resourceID)
}
// RedisCardWalletLockKey 卡钱包分布式锁 Key
// 格式card_wallet:lock:{resource_type}:{resource_id}
// TTL10 秒
func RedisCardWalletLockKey(resourceType string, resourceID uint) string {
return fmt.Sprintf("card_wallet:lock:%s:%d", resourceType, resourceID)
}
// ========== 兼容性别名(待清理)==========
// 以下常量保留用于向后兼容,待旧代码清理后删除
// WalletTypeMain 主钱包(已废弃,使用 AgentWalletTypeMain
const WalletTypeMain = AgentWalletTypeMain
// WalletTypeCommission 分佣钱包(已废弃,使用 AgentWalletTypeCommission
const WalletTypeCommission = AgentWalletTypeCommission
// WalletResourceTypeIotCard 物联网卡钱包(已废弃,使用 CardWalletResourceTypeIotCard
const WalletResourceTypeIotCard = CardWalletResourceTypeIotCard
// WalletResourceTypeDevice 设备钱包(已废弃,使用 CardWalletResourceTypeDevice
const WalletResourceTypeDevice = CardWalletResourceTypeDevice
// WalletResourceTypeShop 店铺钱包(已废弃,代理钱包不再使用 resource_type
const WalletResourceTypeShop = "shop"
// WalletStatusNormal 钱包状态-正常(已废弃,使用 AgentWalletStatusNormal 或 CardWalletStatusNormal
const WalletStatusNormal = AgentWalletStatusNormal
// WalletStatusFrozen 钱包状态-冻结(已废弃,使用 AgentWalletStatusFrozen 或 CardWalletStatusFrozen
const WalletStatusFrozen = AgentWalletStatusFrozen
// WalletStatusClosed 钱包状态-关闭(已废弃,使用 AgentWalletStatusClosed 或 CardWalletStatusClosed
const WalletStatusClosed = AgentWalletStatusClosed
// TransactionTypeRecharge 交易类型-充值(已废弃,使用 AgentTransactionTypeRecharge 或 CardTransactionTypeRecharge
const TransactionTypeRecharge = AgentTransactionTypeRecharge
// TransactionTypeDeduct 交易类型-扣款(已废弃,使用 AgentTransactionTypeDeduct 或 CardTransactionTypeDeduct
const TransactionTypeDeduct = AgentTransactionTypeDeduct
// TransactionTypeRefund 交易类型-退款(已废弃,使用 AgentTransactionTypeRefund 或 CardTransactionTypeRefund
const TransactionTypeRefund = AgentTransactionTypeRefund
// TransactionTypeCommission 交易类型-分佣(已废弃,使用 AgentTransactionTypeCommission
const TransactionTypeCommission = AgentTransactionTypeCommission
// TransactionTypeWithdrawal 交易类型-提现(已废弃,使用 AgentTransactionTypeWithdrawal
const TransactionTypeWithdrawal = AgentTransactionTypeWithdrawal
// RechargeOrderPrefix 充值订单号前缀(已废弃,使用 AgentRechargeOrderPrefix 或 CardRechargeOrderPrefix
const RechargeOrderPrefix = "RCH"
// RechargeMinAmount 最小充值金额(已废弃,使用 AgentRechargeMinAmount 或 CardRechargeMinAmount
const RechargeMinAmount = CardRechargeMinAmount
// RechargeMaxAmount 最大充值金额(已废弃,使用 AgentRechargeMaxAmount 或 CardRechargeMaxAmount
const RechargeMaxAmount = CardRechargeMaxAmount

View File

@@ -117,20 +117,20 @@ const (
CodeForceRechargeAmountMismatch = 1141 // 强充金额不匹配
// 轮询系统相关错误 (1150-1169)
CodePollingConfigNotFound = 1150 // 轮询配置不存在
CodePollingConfigNameExists = 1151 // 轮询配置名称已存在
CodePollingQueueFull = 1152 // 轮询队列已满
CodePollingConcurrencyLimit = 1153 // 并发数已达上限
CodePollingAlertRuleNotFound = 1154 // 告警规则不存在
CodePollingConfigNotFound = 1150 // 轮询配置不存在
CodePollingConfigNameExists = 1151 // 轮询配置名称已存在
CodePollingQueueFull = 1152 // 轮询队列已满
CodePollingConcurrencyLimit = 1153 // 并发数已达上限
CodePollingAlertRuleNotFound = 1154 // 告警规则不存在
CodePollingCleanupConfigNotFound = 1155 // 数据清理配置不存在
CodePollingManualTriggerLimit = 1156 // 手动触发次数已达上限
CodePollingManualTriggerLimit = 1156 // 手动触发次数已达上限
// 套餐相关错误 (1160-1179)
CodeNoAvailablePackage = 1160 // 没有可用套餐
CodePackageActivationConflict = 1161 // 套餐正在激活中
CodeNoMainPackage = 1162 // 必须有主套餐才能购买加油包
CodeRealnameRequired = 1163 // 设备/卡必须先完成实名认证才能购买套餐
CodeMixedOrderForbidden = 1164 // 同订单不能同时购买正式套餐和加油包
CodeNoAvailablePackage = 1160 // 没有可用套餐
CodePackageActivationConflict = 1161 // 套餐正在激活中
CodeNoMainPackage = 1162 // 必须有主套餐才能购买加油包
CodeRealnameRequired = 1163 // 设备/卡必须先完成实名认证才能购买套餐
CodeMixedOrderForbidden = 1164 // 同订单不能同时购买正式套餐和加油包
// 服务端错误 (2000-2999) -> 5xx HTTP 状态码
CodeInternalError = 2001 // 内部服务器错误

View File

@@ -21,8 +21,6 @@ type WorkerStores struct {
Shop *postgres.ShopStore
ShopSeriesAllocation *postgres.ShopSeriesAllocationStore
PackageSeries *postgres.PackageSeriesStore
Wallet *postgres.WalletStore
WalletTransaction *postgres.WalletTransactionStore
Order *postgres.OrderStore
OrderItem *postgres.OrderItemStore
Package *postgres.PackageStore
@@ -32,6 +30,9 @@ type WorkerStores struct {
PollingAlertHistory *postgres.PollingAlertHistoryStore
DataCleanupConfig *postgres.DataCleanupConfigStore
DataCleanupLog *postgres.DataCleanupLogStore
// 新增代理钱包 Store
AgentWallet *postgres.AgentWalletStore
AgentWalletTransaction *postgres.AgentWalletTransactionStore
}
// WorkerServices Worker 侧所有 Service 的集合