feat: 钱包系统分离 - 代理钱包与卡钱包完全隔离
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m17s
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:
@@ -98,8 +98,8 @@ func initServices(s *stores, deps *Dependencies) *services {
|
||||
PersonalCustomer: personalCustomerSvc.NewService(s.PersonalCustomer, s.PersonalCustomerPhone, deps.VerificationService, deps.JWTManager, deps.WechatOfficialAccount, deps.Logger),
|
||||
Shop: shopSvc.New(s.Shop, s.Account, s.ShopRole, s.Role),
|
||||
Auth: authSvc.New(s.Account, s.AccountRole, s.RolePermission, s.Permission, deps.TokenManager, deps.Logger),
|
||||
ShopCommission: shopCommissionSvc.New(s.Shop, s.Account, s.Wallet, s.CommissionWithdrawalRequest, s.CommissionRecord),
|
||||
CommissionWithdrawal: commissionWithdrawalSvc.New(deps.DB, s.Shop, s.Account, s.Wallet, s.WalletTransaction, s.CommissionWithdrawalRequest),
|
||||
ShopCommission: shopCommissionSvc.New(s.Shop, s.Account, s.AgentWallet, s.CommissionWithdrawalRequest, s.CommissionRecord),
|
||||
CommissionWithdrawal: commissionWithdrawalSvc.New(deps.DB, s.Shop, s.Account, s.AgentWallet, s.AgentWalletTransaction, s.CommissionWithdrawalRequest),
|
||||
CommissionWithdrawalSetting: commissionWithdrawalSettingSvc.New(deps.DB, s.Account, s.CommissionWithdrawalSetting),
|
||||
CommissionCalculation: commissionCalculationSvc.New(
|
||||
deps.DB,
|
||||
@@ -110,8 +110,8 @@ func initServices(s *stores, deps *Dependencies) *services {
|
||||
s.PackageSeries,
|
||||
s.IotCard,
|
||||
s.Device,
|
||||
s.Wallet,
|
||||
s.WalletTransaction,
|
||||
s.AgentWallet,
|
||||
s.AgentWalletTransaction,
|
||||
s.Order,
|
||||
s.OrderItem,
|
||||
s.Package,
|
||||
@@ -123,7 +123,7 @@ func initServices(s *stores, deps *Dependencies) *services {
|
||||
EnterpriseCard: enterpriseCardSvc.New(deps.DB, s.Enterprise, s.EnterpriseCardAuthorization),
|
||||
EnterpriseDevice: enterpriseDeviceSvc.New(deps.DB, s.Enterprise, s.Device, s.DeviceSimBinding, s.EnterpriseDeviceAuthorization, s.EnterpriseCardAuthorization, deps.Logger),
|
||||
Authorization: enterpriseCardSvc.NewAuthorizationService(s.Enterprise, s.IotCard, s.EnterpriseCardAuthorization, deps.Logger),
|
||||
MyCommission: myCommissionSvc.New(deps.DB, s.Shop, s.Wallet, s.CommissionWithdrawalRequest, s.CommissionWithdrawalSetting, s.CommissionRecord, s.WalletTransaction),
|
||||
MyCommission: myCommissionSvc.New(deps.DB, s.Shop, s.AgentWallet, s.CommissionWithdrawalRequest, s.CommissionWithdrawalSetting, s.CommissionRecord, s.AgentWalletTransaction),
|
||||
IotCard: iotCard,
|
||||
IotCardImport: iotCardImportSvc.New(deps.DB, s.IotCardImportTask, deps.QueueClient),
|
||||
Device: deviceSvc.New(deps.DB, s.Device, s.DeviceSimBinding, s.IotCard, s.Shop, s.AssetAllocationRecord, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.PackageSeries),
|
||||
@@ -140,8 +140,8 @@ func initServices(s *stores, deps *Dependencies) *services {
|
||||
ShopPackageBatchPricing: shopPackageBatchPricingSvc.New(deps.DB, s.ShopPackageAllocation, s.ShopPackageAllocationPriceHistory, s.Shop),
|
||||
CommissionStats: commissionStatsSvc.New(s.ShopSeriesCommissionStats),
|
||||
PurchaseValidation: purchaseValidation,
|
||||
Order: orderSvc.New(deps.DB, deps.Redis, s.Order, s.OrderItem, s.Wallet, purchaseValidation, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.IotCard, s.Device, s.PackageSeries, s.PackageUsage, s.Package, deps.WechatPayment, deps.QueueClient, deps.Logger),
|
||||
Recharge: rechargeSvc.New(deps.DB, s.Recharge, s.Wallet, s.WalletTransaction, s.IotCard, s.Device, s.ShopSeriesAllocation, s.PackageSeries, s.CommissionRecord, deps.Logger),
|
||||
Order: orderSvc.New(deps.DB, deps.Redis, s.Order, s.OrderItem, s.AgentWallet, s.CardWallet, purchaseValidation, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.IotCard, s.Device, s.PackageSeries, s.PackageUsage, s.Package, deps.WechatPayment, deps.QueueClient, deps.Logger),
|
||||
Recharge: rechargeSvc.New(deps.DB, s.CardRecharge, s.CardWallet, s.CardWalletTransaction, s.IotCard, s.Device, s.ShopSeriesAllocation, s.PackageSeries, s.CommissionRecord, deps.Logger),
|
||||
PollingConfig: pollingSvc.NewConfigService(s.PollingConfig),
|
||||
PollingConcurrency: pollingSvc.NewConcurrencyService(s.PollingConcurrencyConfig, deps.Redis),
|
||||
PollingMonitoring: pollingSvc.NewMonitoringService(deps.Redis),
|
||||
|
||||
@@ -15,10 +15,8 @@ type stores struct {
|
||||
RolePermission *postgres.RolePermissionStore
|
||||
PersonalCustomer *postgres.PersonalCustomerStore
|
||||
PersonalCustomerPhone *postgres.PersonalCustomerPhoneStore
|
||||
Wallet *postgres.WalletStore
|
||||
CommissionWithdrawalRequest *postgres.CommissionWithdrawalRequestStore
|
||||
CommissionRecord *postgres.CommissionRecordStore
|
||||
WalletTransaction *postgres.WalletTransactionStore
|
||||
CommissionWithdrawalSetting *postgres.CommissionWithdrawalSettingStore
|
||||
Enterprise *postgres.EnterpriseStore
|
||||
EnterpriseCardAuthorization *postgres.EnterpriseCardAuthorizationStore
|
||||
@@ -40,7 +38,6 @@ type stores struct {
|
||||
ShopSeriesCommissionStats *postgres.ShopSeriesCommissionStatsStore
|
||||
Order *postgres.OrderStore
|
||||
OrderItem *postgres.OrderItemStore
|
||||
Recharge *postgres.RechargeStore
|
||||
PollingConfig *postgres.PollingConfigStore
|
||||
PollingConcurrencyConfig *postgres.PollingConcurrencyConfigStore
|
||||
PollingAlertRule *postgres.PollingAlertRuleStore
|
||||
@@ -48,6 +45,14 @@ type stores struct {
|
||||
DataCleanupConfig *postgres.DataCleanupConfigStore
|
||||
DataCleanupLog *postgres.DataCleanupLogStore
|
||||
PollingManualTriggerLog *postgres.PollingManualTriggerLogStore
|
||||
// 代理钱包系统
|
||||
AgentWallet *postgres.AgentWalletStore
|
||||
AgentWalletTransaction *postgres.AgentWalletTransactionStore
|
||||
AgentRecharge *postgres.AgentRechargeStore
|
||||
// 卡钱包系统
|
||||
CardWallet *postgres.CardWalletStore
|
||||
CardWalletTransaction *postgres.CardWalletTransactionStore
|
||||
CardRecharge *postgres.CardRechargeStore
|
||||
}
|
||||
|
||||
func initStores(deps *Dependencies) *stores {
|
||||
@@ -62,10 +67,8 @@ func initStores(deps *Dependencies) *stores {
|
||||
RolePermission: postgres.NewRolePermissionStore(deps.DB, deps.Redis),
|
||||
PersonalCustomer: postgres.NewPersonalCustomerStore(deps.DB, deps.Redis),
|
||||
PersonalCustomerPhone: postgres.NewPersonalCustomerPhoneStore(deps.DB),
|
||||
Wallet: postgres.NewWalletStore(deps.DB, deps.Redis),
|
||||
CommissionWithdrawalRequest: postgres.NewCommissionWithdrawalRequestStore(deps.DB, deps.Redis),
|
||||
CommissionRecord: postgres.NewCommissionRecordStore(deps.DB, deps.Redis),
|
||||
WalletTransaction: postgres.NewWalletTransactionStore(deps.DB, deps.Redis),
|
||||
CommissionWithdrawalSetting: postgres.NewCommissionWithdrawalSettingStore(deps.DB, deps.Redis),
|
||||
Enterprise: postgres.NewEnterpriseStore(deps.DB, deps.Redis),
|
||||
EnterpriseCardAuthorization: postgres.NewEnterpriseCardAuthorizationStore(deps.DB, deps.Redis),
|
||||
@@ -87,7 +90,6 @@ func initStores(deps *Dependencies) *stores {
|
||||
ShopSeriesCommissionStats: postgres.NewShopSeriesCommissionStatsStore(deps.DB),
|
||||
Order: postgres.NewOrderStore(deps.DB, deps.Redis),
|
||||
OrderItem: postgres.NewOrderItemStore(deps.DB, deps.Redis),
|
||||
Recharge: postgres.NewRechargeStore(deps.DB, deps.Redis),
|
||||
PollingConfig: postgres.NewPollingConfigStore(deps.DB),
|
||||
PollingConcurrencyConfig: postgres.NewPollingConcurrencyConfigStore(deps.DB),
|
||||
PollingAlertRule: postgres.NewPollingAlertRuleStore(deps.DB),
|
||||
@@ -95,5 +97,13 @@ func initStores(deps *Dependencies) *stores {
|
||||
DataCleanupConfig: postgres.NewDataCleanupConfigStore(deps.DB),
|
||||
DataCleanupLog: postgres.NewDataCleanupLogStore(deps.DB),
|
||||
PollingManualTriggerLog: postgres.NewPollingManualTriggerLogStore(deps.DB),
|
||||
// 代理钱包系统
|
||||
AgentWallet: postgres.NewAgentWalletStore(deps.DB, deps.Redis),
|
||||
AgentWalletTransaction: postgres.NewAgentWalletTransactionStore(deps.DB, deps.Redis),
|
||||
AgentRecharge: postgres.NewAgentRechargeStore(deps.DB, deps.Redis),
|
||||
// 卡钱包系统
|
||||
CardWallet: postgres.NewCardWalletStore(deps.DB, deps.Redis),
|
||||
CardWalletTransaction: postgres.NewCardWalletTransactionStore(deps.DB, deps.Redis),
|
||||
CardRecharge: postgres.NewCardRechargeStore(deps.DB, deps.Redis),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ func initWorkerServices(stores *queue.WorkerStores, deps *WorkerDependencies) *q
|
||||
stores.PackageSeries,
|
||||
stores.IotCard,
|
||||
stores.Device,
|
||||
stores.Wallet,
|
||||
stores.WalletTransaction,
|
||||
stores.AgentWallet,
|
||||
stores.AgentWalletTransaction,
|
||||
stores.Order,
|
||||
stores.OrderItem,
|
||||
stores.Package,
|
||||
|
||||
@@ -17,8 +17,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
|
||||
@@ -28,6 +26,8 @@ type workerStores struct {
|
||||
PollingAlertHistory *postgres.PollingAlertHistoryStore
|
||||
DataCleanupConfig *postgres.DataCleanupConfigStore
|
||||
DataCleanupLog *postgres.DataCleanupLogStore
|
||||
AgentWallet *postgres.AgentWalletStore
|
||||
AgentWalletTransaction *postgres.AgentWalletTransactionStore
|
||||
}
|
||||
|
||||
func initWorkerStores(deps *WorkerDependencies) *queue.WorkerStores {
|
||||
@@ -43,8 +43,6 @@ func initWorkerStores(deps *WorkerDependencies) *queue.WorkerStores {
|
||||
Shop: postgres.NewShopStore(deps.DB, deps.Redis),
|
||||
ShopSeriesAllocation: postgres.NewShopSeriesAllocationStore(deps.DB),
|
||||
PackageSeries: postgres.NewPackageSeriesStore(deps.DB),
|
||||
Wallet: postgres.NewWalletStore(deps.DB, deps.Redis),
|
||||
WalletTransaction: postgres.NewWalletTransactionStore(deps.DB, deps.Redis),
|
||||
Order: postgres.NewOrderStore(deps.DB, deps.Redis),
|
||||
OrderItem: postgres.NewOrderItemStore(deps.DB, deps.Redis),
|
||||
Package: postgres.NewPackageStore(deps.DB),
|
||||
@@ -54,6 +52,8 @@ func initWorkerStores(deps *WorkerDependencies) *queue.WorkerStores {
|
||||
PollingAlertHistory: postgres.NewPollingAlertHistoryStore(deps.DB),
|
||||
DataCleanupConfig: postgres.NewDataCleanupConfigStore(deps.DB),
|
||||
DataCleanupLog: postgres.NewDataCleanupLogStore(deps.DB),
|
||||
AgentWallet: postgres.NewAgentWalletStore(deps.DB, deps.Redis),
|
||||
AgentWalletTransaction: postgres.NewAgentWalletTransactionStore(deps.DB, deps.Redis),
|
||||
}
|
||||
|
||||
return &queue.WorkerStores{
|
||||
@@ -68,8 +68,6 @@ func initWorkerStores(deps *WorkerDependencies) *queue.WorkerStores {
|
||||
Shop: stores.Shop,
|
||||
ShopSeriesAllocation: stores.ShopSeriesAllocation,
|
||||
PackageSeries: stores.PackageSeries,
|
||||
Wallet: stores.Wallet,
|
||||
WalletTransaction: stores.WalletTransaction,
|
||||
Order: stores.Order,
|
||||
OrderItem: stores.OrderItem,
|
||||
Package: stores.Package,
|
||||
@@ -79,5 +77,7 @@ func initWorkerStores(deps *WorkerDependencies) *queue.WorkerStores {
|
||||
PollingAlertHistory: stores.PollingAlertHistory,
|
||||
DataCleanupConfig: stores.DataCleanupConfig,
|
||||
DataCleanupLog: stores.DataCleanupLog,
|
||||
AgentWallet: stores.AgentWallet,
|
||||
AgentWalletTransaction: stores.AgentWalletTransaction,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package admin
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/internal/service/polling"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
)
|
||||
|
||||
|
||||
91
internal/model/agent_wallet.go
Normal file
91
internal/model/agent_wallet.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AgentWallet 代理钱包模型
|
||||
// 管理店铺级别的主钱包和分佣钱包
|
||||
type AgentWallet struct {
|
||||
ID uint `gorm:"column:id;primaryKey" json:"id"`
|
||||
ShopID uint `gorm:"column:shop_id;not null;index;comment:店铺ID" json:"shop_id"`
|
||||
WalletType string `gorm:"column:wallet_type;type:varchar(20);not null;comment:钱包类型(main-主钱包 | commission-分佣钱包)" json:"wallet_type"`
|
||||
Balance int64 `gorm:"column:balance;type:bigint;not null;default:0;comment:余额(单位:分)" json:"balance"`
|
||||
FrozenBalance int64 `gorm:"column:frozen_balance;type:bigint;not null;default:0;comment:冻结余额(单位:分)" json:"frozen_balance"`
|
||||
Currency string `gorm:"column:currency;type:varchar(10);not null;default:'CNY';comment:币种" json:"currency"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;comment:钱包状态(1-正常 2-冻结 3-关闭)" json:"status"`
|
||||
Version int `gorm:"column:version;type:int;not null;default:0;comment:版本号(乐观锁)" json:"version"`
|
||||
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`
|
||||
EnterpriseIDTag *uint `gorm:"column:enterprise_id_tag;index;comment:企业ID标签(多租户过滤)" json:"enterprise_id_tag,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AgentWallet) TableName() string {
|
||||
return "tb_agent_wallet"
|
||||
}
|
||||
|
||||
// GetAvailableBalance 获取可用余额 = balance - frozen_balance
|
||||
func (w *AgentWallet) GetAvailableBalance() int64 {
|
||||
return w.Balance - w.FrozenBalance
|
||||
}
|
||||
|
||||
// AgentWalletTransaction 代理钱包交易记录模型
|
||||
// 记录所有代理钱包余额变动
|
||||
type AgentWalletTransaction struct {
|
||||
ID uint `gorm:"column:id;primaryKey" json:"id"`
|
||||
AgentWalletID uint `gorm:"column:agent_wallet_id;not null;index;comment:代理钱包ID" json:"agent_wallet_id"`
|
||||
ShopID uint `gorm:"column:shop_id;not null;index;comment:店铺ID(冗余字段,便于查询)" json:"shop_id"`
|
||||
UserID uint `gorm:"column:user_id;not null;comment:操作人用户ID" json:"user_id"`
|
||||
TransactionType string `gorm:"column:transaction_type;type:varchar(20);not null;comment:交易类型(recharge-充值 | deduct-扣款 | refund-退款 | commission-分佣 | withdrawal-提现)" json:"transaction_type"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:变动金额(单位:分,正数为增加,负数为减少)" json:"amount"`
|
||||
BalanceBefore int64 `gorm:"column:balance_before;type:bigint;not null;comment:变动前余额(单位:分)" json:"balance_before"`
|
||||
BalanceAfter int64 `gorm:"column:balance_after;type:bigint;not null;comment:变动后余额(单位:分)" json:"balance_after"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;comment:交易状态(1-成功 2-失败 3-处理中)" json:"status"`
|
||||
ReferenceType *string `gorm:"column:reference_type;type:varchar(50);comment:关联业务类型(order | commission | withdrawal | topup)" json:"reference_type,omitempty"`
|
||||
ReferenceID *uint `gorm:"column:reference_id;comment:关联业务ID" json:"reference_id,omitempty"`
|
||||
Remark *string `gorm:"column:remark;type:text;comment:备注" json:"remark,omitempty"`
|
||||
Metadata *string `gorm:"column:metadata;type:jsonb;comment:扩展信息(如手续费、支付方式等)" json:"metadata,omitempty"`
|
||||
Creator uint `gorm:"column:creator;not null;comment:创建人ID" json:"creator"`
|
||||
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`
|
||||
EnterpriseIDTag *uint `gorm:"column:enterprise_id_tag;index;comment:企业ID标签(多租户过滤)" json:"enterprise_id_tag,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AgentWalletTransaction) TableName() string {
|
||||
return "tb_agent_wallet_transaction"
|
||||
}
|
||||
|
||||
// AgentRechargeRecord 代理充值记录模型
|
||||
// 记录所有代理充值操作
|
||||
type AgentRechargeRecord struct {
|
||||
ID uint `gorm:"column:id;primaryKey" json:"id"`
|
||||
UserID uint `gorm:"column:user_id;not null;index;comment:操作人用户ID" json:"user_id"`
|
||||
AgentWalletID uint `gorm:"column:agent_wallet_id;not null;comment:代理钱包ID" json:"agent_wallet_id"`
|
||||
ShopID uint `gorm:"column:shop_id;not null;index;comment:店铺ID(冗余字段,便于查询)" json:"shop_id"`
|
||||
RechargeNo string `gorm:"column:recharge_no;type:varchar(50);not null;uniqueIndex;comment:充值订单号(格式:ARCH+时间戳+随机数)" json:"recharge_no"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:充值金额(单位:分,最小10000分=100元)" json:"amount"`
|
||||
PaymentMethod string `gorm:"column:payment_method;type:varchar(20);not null;comment:支付方式(alipay-支付宝 | wechat-微信 | bank-银行转账 | offline-线下)" json:"payment_method"`
|
||||
PaymentChannel *string `gorm:"column:payment_channel;type:varchar(50);comment:支付渠道" json:"payment_channel,omitempty"`
|
||||
PaymentTransactionID *string `gorm:"column:payment_transaction_id;type:varchar(100);comment:第三方支付交易号" json:"payment_transaction_id,omitempty"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;comment:充值状态(1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款)" json:"status"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;comment:支付时间" json:"paid_at,omitempty"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at,omitempty"`
|
||||
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`
|
||||
EnterpriseIDTag *uint `gorm:"column:enterprise_id_tag;index;comment:企业ID标签(多租户过滤)" json:"enterprise_id_tag,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AgentRechargeRecord) TableName() string {
|
||||
return "tb_agent_recharge_record"
|
||||
}
|
||||
93
internal/model/card_wallet.go
Normal file
93
internal/model/card_wallet.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CardWallet 卡钱包模型
|
||||
// 管理物联网卡和设备级别的钱包
|
||||
type CardWallet struct {
|
||||
ID uint `gorm:"column:id;primaryKey" json:"id"`
|
||||
ResourceType string `gorm:"column:resource_type;type:varchar(20);not null;index;comment:资源类型(iot_card-物联网卡 | device-设备)" json:"resource_type"`
|
||||
ResourceID uint `gorm:"column:resource_id;not null;index;comment:资源ID(关联tb_iot_card.id或tb_device.id)" json:"resource_id"`
|
||||
Balance int64 `gorm:"column:balance;type:bigint;not null;default:0;comment:余额(单位:分)" json:"balance"`
|
||||
FrozenBalance int64 `gorm:"column:frozen_balance;type:bigint;not null;default:0;comment:冻结余额(单位:分)" json:"frozen_balance"`
|
||||
Currency string `gorm:"column:currency;type:varchar(10);not null;default:'CNY';comment:币种" json:"currency"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;comment:钱包状态(1-正常 2-冻结 3-关闭)" json:"status"`
|
||||
Version int `gorm:"column:version;type:int;not null;default:0;comment:版本号(乐观锁)" json:"version"`
|
||||
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`
|
||||
EnterpriseIDTag *uint `gorm:"column:enterprise_id_tag;index;comment:企业ID标签(多租户过滤)" json:"enterprise_id_tag,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CardWallet) TableName() string {
|
||||
return "tb_card_wallet"
|
||||
}
|
||||
|
||||
// GetAvailableBalance 获取可用余额 = balance - frozen_balance
|
||||
func (w *CardWallet) GetAvailableBalance() int64 {
|
||||
return w.Balance - w.FrozenBalance
|
||||
}
|
||||
|
||||
// CardWalletTransaction 卡钱包交易记录模型
|
||||
// 记录所有卡钱包余额变动
|
||||
type CardWalletTransaction struct {
|
||||
ID uint `gorm:"column:id;primaryKey" json:"id"`
|
||||
CardWalletID uint `gorm:"column:card_wallet_id;not null;index;comment:卡钱包ID" json:"card_wallet_id"`
|
||||
ResourceType string `gorm:"column:resource_type;type:varchar(20);not null;index;comment:资源类型(冗余字段,便于查询)" json:"resource_type"`
|
||||
ResourceID uint `gorm:"column:resource_id;not null;index;comment:资源ID(冗余字段,便于查询)" json:"resource_id"`
|
||||
UserID uint `gorm:"column:user_id;not null;comment:操作人用户ID" json:"user_id"`
|
||||
TransactionType string `gorm:"column:transaction_type;type:varchar(20);not null;comment:交易类型(recharge-充值 | deduct-扣款 | refund-退款)" json:"transaction_type"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:变动金额(单位:分,正数为增加,负数为减少)" json:"amount"`
|
||||
BalanceBefore int64 `gorm:"column:balance_before;type:bigint;not null;comment:变动前余额(单位:分)" json:"balance_before"`
|
||||
BalanceAfter int64 `gorm:"column:balance_after;type:bigint;not null;comment:变动后余额(单位:分)" json:"balance_after"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;comment:交易状态(1-成功 2-失败 3-处理中)" json:"status"`
|
||||
ReferenceType *string `gorm:"column:reference_type;type:varchar(50);comment:关联业务类型(order | topup)" json:"reference_type,omitempty"`
|
||||
ReferenceID *uint `gorm:"column:reference_id;comment:关联业务ID" json:"reference_id,omitempty"`
|
||||
Remark *string `gorm:"column:remark;type:text;comment:备注" json:"remark,omitempty"`
|
||||
Metadata *string `gorm:"column:metadata;type:jsonb;comment:扩展信息(如套餐信息、支付方式等)" json:"metadata,omitempty"`
|
||||
Creator uint `gorm:"column:creator;not null;comment:创建人ID" json:"creator"`
|
||||
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`
|
||||
EnterpriseIDTag *uint `gorm:"column:enterprise_id_tag;index;comment:企业ID标签(多租户过滤)" json:"enterprise_id_tag,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CardWalletTransaction) TableName() string {
|
||||
return "tb_card_wallet_transaction"
|
||||
}
|
||||
|
||||
// CardRechargeRecord 卡充值记录模型
|
||||
// 记录所有卡充值操作
|
||||
type CardRechargeRecord struct {
|
||||
ID uint `gorm:"column:id;primaryKey" json:"id"`
|
||||
UserID uint `gorm:"column:user_id;not null;index;comment:操作人用户ID" json:"user_id"`
|
||||
CardWalletID uint `gorm:"column:card_wallet_id;not null;comment:卡钱包ID" json:"card_wallet_id"`
|
||||
ResourceType string `gorm:"column:resource_type;type:varchar(20);not null;index;comment:资源类型(冗余字段)" json:"resource_type"`
|
||||
ResourceID uint `gorm:"column:resource_id;not null;index;comment:资源ID(冗余字段)" json:"resource_id"`
|
||||
RechargeNo string `gorm:"column:recharge_no;type:varchar(50);not null;uniqueIndex;comment:充值订单号(格式:CRCH+时间戳+随机数)" json:"recharge_no"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:充值金额(单位:分,最小100分=1元)" json:"amount"`
|
||||
PaymentMethod string `gorm:"column:payment_method;type:varchar(20);not null;comment:支付方式(alipay-支付宝 | wechat-微信)" json:"payment_method"`
|
||||
PaymentChannel *string `gorm:"column:payment_channel;type:varchar(50);comment:支付渠道" json:"payment_channel,omitempty"`
|
||||
PaymentTransactionID *string `gorm:"column:payment_transaction_id;type:varchar(100);comment:第三方支付交易号" json:"payment_transaction_id,omitempty"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;comment:充值状态(1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款)" json:"status"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;comment:支付时间" json:"paid_at,omitempty"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at,omitempty"`
|
||||
ShopIDTag uint `gorm:"column:shop_id_tag;not null;index;comment:店铺ID标签(多租户过滤)" json:"shop_id_tag"`
|
||||
EnterpriseIDTag *uint `gorm:"column:enterprise_id_tag;index;comment:企业ID标签(多租户过滤)" json:"enterprise_id_tag,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (CardRechargeRecord) TableName() string {
|
||||
return "tb_card_recharge_record"
|
||||
}
|
||||
@@ -20,24 +20,24 @@ type ListStandaloneIotCardRequest struct {
|
||||
}
|
||||
|
||||
type StandaloneIotCardResponse struct {
|
||||
ID uint `json:"id" description:"卡ID"`
|
||||
ICCID string `json:"iccid" description:"ICCID"`
|
||||
CardCategory string `json:"card_category" description:"卡业务类型 (normal:普通卡, industry:行业卡)"`
|
||||
CarrierID uint `json:"carrier_id" description:"运营商ID"`
|
||||
CarrierType string `json:"carrier_type,omitempty" description:"运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)"`
|
||||
CarrierName string `json:"carrier_name,omitempty" description:"运营商名称"`
|
||||
IMSI string `json:"imsi,omitempty" description:"IMSI"`
|
||||
MSISDN string `json:"msisdn,omitempty" description:"卡接入号"`
|
||||
BatchNo string `json:"batch_no,omitempty" description:"批次号"`
|
||||
Supplier string `json:"supplier,omitempty" description:"供应商"`
|
||||
CostPrice int64 `json:"cost_price" description:"成本价(分)"`
|
||||
DistributePrice int64 `json:"distribute_price" description:"分销价(分)"`
|
||||
Status int `json:"status" description:"状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)"`
|
||||
ShopID *uint `json:"shop_id,omitempty" description:"店铺ID"`
|
||||
ShopName string `json:"shop_name,omitempty" description:"店铺名称"`
|
||||
ActivatedAt *time.Time `json:"activated_at,omitempty" description:"激活时间"`
|
||||
ActivationStatus int `json:"activation_status" description:"激活状态 (0:未激活, 1:已激活)"`
|
||||
RealNameStatus int `json:"real_name_status" description:"实名状态 (0:未实名, 1:已实名)"`
|
||||
ID uint `json:"id" description:"卡ID"`
|
||||
ICCID string `json:"iccid" description:"ICCID"`
|
||||
CardCategory string `json:"card_category" description:"卡业务类型 (normal:普通卡, industry:行业卡)"`
|
||||
CarrierID uint `json:"carrier_id" description:"运营商ID"`
|
||||
CarrierType string `json:"carrier_type,omitempty" description:"运营商类型 (CMCC:中国移动, CUCC:中国联通, CTCC:中国电信, CBN:中国广电)"`
|
||||
CarrierName string `json:"carrier_name,omitempty" description:"运营商名称"`
|
||||
IMSI string `json:"imsi,omitempty" description:"IMSI"`
|
||||
MSISDN string `json:"msisdn,omitempty" description:"卡接入号"`
|
||||
BatchNo string `json:"batch_no,omitempty" description:"批次号"`
|
||||
Supplier string `json:"supplier,omitempty" description:"供应商"`
|
||||
CostPrice int64 `json:"cost_price" description:"成本价(分)"`
|
||||
DistributePrice int64 `json:"distribute_price" description:"分销价(分)"`
|
||||
Status int `json:"status" description:"状态 (1:在库, 2:已分销, 3:已激活, 4:已停用)"`
|
||||
ShopID *uint `json:"shop_id,omitempty" description:"店铺ID"`
|
||||
ShopName string `json:"shop_name,omitempty" description:"店铺名称"`
|
||||
ActivatedAt *time.Time `json:"activated_at,omitempty" description:"激活时间"`
|
||||
ActivationStatus int `json:"activation_status" description:"激活状态 (0:未激活, 1:已激活)"`
|
||||
RealNameStatus int `json:"real_name_status" description:"实名状态 (0:未实名, 1:已实名)"`
|
||||
NetworkStatus int `json:"network_status" description:"网络状态 (0:停机, 1:开机)"`
|
||||
DataUsageMB int64 `json:"data_usage_mb" description:"累计流量使用(MB)"`
|
||||
CurrentMonthUsageMB float64 `json:"current_month_usage_mb" description:"本月已用流量(MB)"`
|
||||
@@ -47,11 +47,11 @@ type StandaloneIotCardResponse struct {
|
||||
LastRealNameCheckAt *time.Time `json:"last_real_name_check_at,omitempty" description:"最后实名检查时间"`
|
||||
EnablePolling bool `json:"enable_polling" description:"是否参与轮询"`
|
||||
SeriesID *uint `json:"series_id,omitempty" description:"套餐系列ID"`
|
||||
SeriesName string `json:"series_name,omitempty" description:"套餐系列名称"`
|
||||
FirstCommissionPaid bool `json:"first_commission_paid" description:"一次性佣金是否已发放"`
|
||||
AccumulatedRecharge int64 `json:"accumulated_recharge" description:"累计充值金额(分)"`
|
||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
|
||||
SeriesName string `json:"series_name,omitempty" description:"套餐系列名称"`
|
||||
FirstCommissionPaid bool `json:"first_commission_paid" description:"一次性佣金是否已发放"`
|
||||
AccumulatedRecharge int64 `json:"accumulated_recharge" description:"累计充值金额(分)"`
|
||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
|
||||
}
|
||||
|
||||
type ListStandaloneIotCardResponse struct {
|
||||
|
||||
@@ -26,15 +26,15 @@ type UpdateDataCleanupConfigParams struct {
|
||||
|
||||
// DataCleanupConfigResp 数据清理配置响应
|
||||
type DataCleanupConfigResp struct {
|
||||
ID uint `json:"id" description:"配置ID"`
|
||||
TargetTable string `json:"table_name" description:"表名"`
|
||||
RetentionDays int `json:"retention_days" description:"保留天数"`
|
||||
BatchSize int `json:"batch_size" description:"每批删除条数"`
|
||||
Enabled int `json:"enabled" description:"是否启用:0-禁用,1-启用"`
|
||||
Description string `json:"description" description:"配置说明"`
|
||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
|
||||
UpdatedBy *uint `json:"updated_by,omitempty" description:"更新人ID"`
|
||||
ID uint `json:"id" description:"配置ID"`
|
||||
TargetTable string `json:"table_name" description:"表名"`
|
||||
RetentionDays int `json:"retention_days" description:"保留天数"`
|
||||
BatchSize int `json:"batch_size" description:"每批删除条数"`
|
||||
Enabled int `json:"enabled" description:"是否启用:0-禁用,1-启用"`
|
||||
Description string `json:"description" description:"配置说明"`
|
||||
CreatedAt time.Time `json:"created_at" description:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" description:"更新时间"`
|
||||
UpdatedBy *uint `json:"updated_by,omitempty" description:"更新人ID"`
|
||||
}
|
||||
|
||||
// DataCleanupConfigListResp 数据清理配置列表响应
|
||||
|
||||
@@ -57,27 +57,27 @@ func (Package) TableName() string {
|
||||
// 跟踪单卡套餐和设备级套餐的流量使用
|
||||
type PackageUsage struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
OrderID uint `gorm:"column:order_id;index;not null;comment:订单ID" json:"order_id"`
|
||||
PackageID uint `gorm:"column:package_id;index;not null;comment:套餐ID" json:"package_id"`
|
||||
UsageType string `gorm:"column:usage_type;type:varchar(20);not null;comment:使用类型 single_card-单卡套餐 device-设备级套餐" json:"usage_type"`
|
||||
IotCardID uint `gorm:"column:iot_card_id;index;comment:IoT卡ID(单卡套餐时有值)" json:"iot_card_id"`
|
||||
DeviceID uint `gorm:"column:device_id;index;comment:设备ID(设备级套餐时有值)" json:"device_id"`
|
||||
DataLimitMB int64 `gorm:"column:data_limit_mb;type:bigint;not null;comment:流量限额(MB)" json:"data_limit_mb"`
|
||||
DataUsageMB int64 `gorm:"column:data_usage_mb;type:bigint;default:0;comment:已使用流量(MB)" json:"data_usage_mb"`
|
||||
RealDataUsageMB int64 `gorm:"column:real_data_usage_mb;type:bigint;default:0;comment:真流量使用(MB)" json:"real_data_usage_mb"`
|
||||
VirtualDataUsageMB int64 `gorm:"column:virtual_data_usage_mb;type:bigint;default:0;comment:虚流量使用(MB)" json:"virtual_data_usage_mb"`
|
||||
ActivatedAt time.Time `gorm:"column:activated_at;not null;comment:套餐生效时间" json:"activated_at"`
|
||||
ExpiresAt time.Time `gorm:"column:expires_at;not null;comment:套餐过期时间" json:"expires_at"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 0-待生效 1-生效中 2-已用完 3-已过期 4-已失效" json:"status"`
|
||||
LastPackageCheckAt *time.Time `gorm:"column:last_package_check_at;comment:最后一次套餐流量检查时间" json:"last_package_check_at"`
|
||||
Priority int `gorm:"column:priority;type:int;default:1;index:idx_package_usage_priority;comment:优先级(主套餐和加油包按此字段排队,数字越小优先级越高)" json:"priority"`
|
||||
MasterUsageID *uint `gorm:"column:master_usage_id;type:bigint;index:idx_package_usage_master_usage_id;comment:主套餐使用记录ID(加油包关联主套餐,主套餐此字段为NULL)" json:"master_usage_id"`
|
||||
HasIndependentExpiry bool `gorm:"column:has_independent_expiry;type:boolean;default:false;comment:加油包是否有独立有效期(true-有独立到期时间 false-跟随主套餐)" json:"has_independent_expiry"`
|
||||
PendingRealnameActivation bool `gorm:"column:pending_realname_activation;type:boolean;default:false;comment:是否等待实名激活(true-待实名后激活 false-已激活或不需实名)" json:"pending_realname_activation"`
|
||||
DataResetCycle string `gorm:"column:data_reset_cycle;type:varchar(20);comment:流量重置周期(从Package复制,用于历史记录)" json:"data_reset_cycle"`
|
||||
LastResetAt *time.Time `gorm:"column:last_reset_at;comment:最后一次流量重置时间" json:"last_reset_at"`
|
||||
NextResetAt *time.Time `gorm:"column:next_reset_at;index:idx_package_usage_next_reset_at;comment:下次流量重置时间(用于定时任务查询)" json:"next_reset_at"`
|
||||
BaseModel `gorm:"embedded"`
|
||||
OrderID uint `gorm:"column:order_id;index;not null;comment:订单ID" json:"order_id"`
|
||||
PackageID uint `gorm:"column:package_id;index;not null;comment:套餐ID" json:"package_id"`
|
||||
UsageType string `gorm:"column:usage_type;type:varchar(20);not null;comment:使用类型 single_card-单卡套餐 device-设备级套餐" json:"usage_type"`
|
||||
IotCardID uint `gorm:"column:iot_card_id;index;comment:IoT卡ID(单卡套餐时有值)" json:"iot_card_id"`
|
||||
DeviceID uint `gorm:"column:device_id;index;comment:设备ID(设备级套餐时有值)" json:"device_id"`
|
||||
DataLimitMB int64 `gorm:"column:data_limit_mb;type:bigint;not null;comment:流量限额(MB)" json:"data_limit_mb"`
|
||||
DataUsageMB int64 `gorm:"column:data_usage_mb;type:bigint;default:0;comment:已使用流量(MB)" json:"data_usage_mb"`
|
||||
RealDataUsageMB int64 `gorm:"column:real_data_usage_mb;type:bigint;default:0;comment:真流量使用(MB)" json:"real_data_usage_mb"`
|
||||
VirtualDataUsageMB int64 `gorm:"column:virtual_data_usage_mb;type:bigint;default:0;comment:虚流量使用(MB)" json:"virtual_data_usage_mb"`
|
||||
ActivatedAt time.Time `gorm:"column:activated_at;not null;comment:套餐生效时间" json:"activated_at"`
|
||||
ExpiresAt time.Time `gorm:"column:expires_at;not null;comment:套餐过期时间" json:"expires_at"`
|
||||
Status int `gorm:"column:status;type:int;default:1;not null;comment:状态 0-待生效 1-生效中 2-已用完 3-已过期 4-已失效" json:"status"`
|
||||
LastPackageCheckAt *time.Time `gorm:"column:last_package_check_at;comment:最后一次套餐流量检查时间" json:"last_package_check_at"`
|
||||
Priority int `gorm:"column:priority;type:int;default:1;index:idx_package_usage_priority;comment:优先级(主套餐和加油包按此字段排队,数字越小优先级越高)" json:"priority"`
|
||||
MasterUsageID *uint `gorm:"column:master_usage_id;type:bigint;index:idx_package_usage_master_usage_id;comment:主套餐使用记录ID(加油包关联主套餐,主套餐此字段为NULL)" json:"master_usage_id"`
|
||||
HasIndependentExpiry bool `gorm:"column:has_independent_expiry;type:boolean;default:false;comment:加油包是否有独立有效期(true-有独立到期时间 false-跟随主套餐)" json:"has_independent_expiry"`
|
||||
PendingRealnameActivation bool `gorm:"column:pending_realname_activation;type:boolean;default:false;comment:是否等待实名激活(true-待实名后激活 false-已激活或不需实名)" json:"pending_realname_activation"`
|
||||
DataResetCycle string `gorm:"column:data_reset_cycle;type:varchar(20);comment:流量重置周期(从Package复制,用于历史记录)" json:"data_reset_cycle"`
|
||||
LastResetAt *time.Time `gorm:"column:last_reset_at;comment:最后一次流量重置时间" json:"last_reset_at"`
|
||||
NextResetAt *time.Time `gorm:"column:next_reset_at;index:idx_package_usage_next_reset_at;comment:下次流量重置时间(用于定时任务查询)" json:"next_reset_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
@@ -88,13 +88,13 @@ func (PackageUsage) TableName() string {
|
||||
// PackageUsageDailyRecord 套餐流量日记录模型
|
||||
// 记录每个套餐每天的流量使用情况,用于流量详单查询
|
||||
type PackageUsageDailyRecord struct {
|
||||
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||
PackageUsageID uint `gorm:"column:package_usage_id;not null;uniqueIndex:idx_package_usage_daily_record_unique;index:idx_package_usage_daily_record_date;comment:套餐使用记录ID" json:"package_usage_id"`
|
||||
Date time.Time `gorm:"column:date;type:date;not null;uniqueIndex:idx_package_usage_daily_record_unique;comment:日期" json:"date"`
|
||||
DailyUsageMB int `gorm:"column:daily_usage_mb;type:int;default:0;comment:当日流量使用量(MB)" json:"daily_usage_mb"`
|
||||
CumulativeUsageMB int64 `gorm:"column:cumulative_usage_mb;type:bigint;default:0;comment:截止当日的累计流量(MB)" json:"cumulative_usage_mb"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||
PackageUsageID uint `gorm:"column:package_usage_id;not null;uniqueIndex:idx_package_usage_daily_record_unique;index:idx_package_usage_daily_record_date;comment:套餐使用记录ID" json:"package_usage_id"`
|
||||
Date time.Time `gorm:"column:date;type:date;not null;uniqueIndex:idx_package_usage_daily_record_unique;comment:日期" json:"date"`
|
||||
DailyUsageMB int `gorm:"column:daily_usage_mb;type:int;default:0;comment:当日流量使用量(MB)" json:"daily_usage_mb"`
|
||||
CumulativeUsageMB int64 `gorm:"column:cumulative_usage_mb;type:bigint;default:0;comment:截止当日的累计流量(MB)" json:"cumulative_usage_mb"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
@@ -6,21 +6,21 @@ import (
|
||||
|
||||
// PollingConfig 轮询配置表
|
||||
type PollingConfig struct {
|
||||
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||
ConfigName string `gorm:"column:config_name;type:varchar(100);not null;comment:配置名称" json:"config_name"`
|
||||
CardCondition string `gorm:"column:card_condition;type:varchar(50);comment:卡状态条件:not_real_name/real_name/activated/suspended" json:"card_condition"`
|
||||
CardCategory string `gorm:"column:card_category;type:varchar(50);comment:卡业务类型:normal/industry" json:"card_category"`
|
||||
CarrierID *uint `gorm:"column:carrier_id;comment:运营商ID(可选,精确匹配)" json:"carrier_id"`
|
||||
Priority int `gorm:"column:priority;not null;default:100;comment:优先级(数字越小优先级越高)" json:"priority"`
|
||||
RealnameCheckInterval *int `gorm:"column:realname_check_interval;comment:实名检查间隔(秒),NULL表示不检查" json:"realname_check_interval"`
|
||||
CarddataCheckInterval *int `gorm:"column:carddata_check_interval;comment:流量检查间隔(秒),NULL表示不检查" json:"carddata_check_interval"`
|
||||
PackageCheckInterval *int `gorm:"column:package_check_interval;comment:套餐检查间隔(秒),NULL表示不检查" json:"package_check_interval"`
|
||||
Status int16 `gorm:"column:status;type:smallint;not null;default:1;comment:状态:0-禁用,1-启用" json:"status"`
|
||||
Description string `gorm:"column:description;type:text;comment:配置说明" json:"description"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"`
|
||||
CreatedBy *uint `gorm:"column:created_by;comment:创建人ID" json:"created_by"`
|
||||
UpdatedBy *uint `gorm:"column:updated_by;comment:更新人ID" json:"updated_by"`
|
||||
ID uint `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
|
||||
ConfigName string `gorm:"column:config_name;type:varchar(100);not null;comment:配置名称" json:"config_name"`
|
||||
CardCondition string `gorm:"column:card_condition;type:varchar(50);comment:卡状态条件:not_real_name/real_name/activated/suspended" json:"card_condition"`
|
||||
CardCategory string `gorm:"column:card_category;type:varchar(50);comment:卡业务类型:normal/industry" json:"card_category"`
|
||||
CarrierID *uint `gorm:"column:carrier_id;comment:运营商ID(可选,精确匹配)" json:"carrier_id"`
|
||||
Priority int `gorm:"column:priority;not null;default:100;comment:优先级(数字越小优先级越高)" json:"priority"`
|
||||
RealnameCheckInterval *int `gorm:"column:realname_check_interval;comment:实名检查间隔(秒),NULL表示不检查" json:"realname_check_interval"`
|
||||
CarddataCheckInterval *int `gorm:"column:carddata_check_interval;comment:流量检查间隔(秒),NULL表示不检查" json:"carddata_check_interval"`
|
||||
PackageCheckInterval *int `gorm:"column:package_check_interval;comment:套餐检查间隔(秒),NULL表示不检查" json:"package_check_interval"`
|
||||
Status int16 `gorm:"column:status;type:smallint;not null;default:1;comment:状态:0-禁用,1-启用" json:"status"`
|
||||
Description string `gorm:"column:description;type:text;comment:配置说明" json:"description"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"`
|
||||
CreatedBy *uint `gorm:"column:created_by;comment:创建人ID" json:"created_by"`
|
||||
UpdatedBy *uint `gorm:"column:updated_by;comment:更新人ID" json:"updated_by"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Wallet 钱包模型
|
||||
// 个人客户和代理商的资金账户,支持充值、消费、提现等操作
|
||||
// 使用乐观锁(version字段)防止并发余额冲突
|
||||
// 钱包归属资源(卡/设备/店铺),支持资源转手场景
|
||||
type Wallet struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
ResourceType string `gorm:"column:resource_type;type:varchar(20);not null;uniqueIndex:idx_wallet_resource_type_currency,priority:1;index:idx_wallet_resource,priority:1;comment:资源类型 iot_card-物联网卡 device-设备 shop-店铺" json:"resource_type"`
|
||||
ResourceID uint `gorm:"column:resource_id;not null;uniqueIndex:idx_wallet_resource_type_currency,priority:2;index:idx_wallet_resource,priority:2;comment:资源ID" json:"resource_id"`
|
||||
WalletType string `gorm:"column:wallet_type;type:varchar(20);not null;uniqueIndex:idx_wallet_resource_type_currency,priority:3;comment:钱包类型 main-主钱包 commission-分佣钱包" json:"wallet_type"`
|
||||
Balance int64 `gorm:"column:balance;type:bigint;not null;default:0;comment:余额(分)" json:"balance"`
|
||||
FrozenBalance int64 `gorm:"column:frozen_balance;type:bigint;not null;default:0;comment:冻结余额(分)" json:"frozen_balance"`
|
||||
Currency string `gorm:"column:currency;type:varchar(10);not null;default:'CNY';uniqueIndex:idx_wallet_resource_type_currency,priority:4;comment:币种" json:"currency"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;index:idx_wallet_status;comment:钱包状态 1-正常 2-冻结 3-关闭" json:"status"`
|
||||
Version int `gorm:"column:version;type:int;not null;default:0;comment:版本号(乐观锁)" json:"version"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Wallet) TableName() string {
|
||||
return "tb_wallet"
|
||||
}
|
||||
|
||||
// WalletMetadata 钱包交易扩展信息
|
||||
// 用于存储交易相关的额外数据(JSONB格式)
|
||||
type WalletMetadata map[string]interface{}
|
||||
|
||||
// Value 实现 driver.Valuer 接口
|
||||
func (m WalletMetadata) Value() (driver.Value, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
// Scan 实现 sql.Scanner 接口
|
||||
func (m *WalletMetadata) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*m = make(WalletMetadata)
|
||||
return nil
|
||||
}
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(bytes, m)
|
||||
}
|
||||
|
||||
// WalletTransaction 钱包交易记录模型
|
||||
// 记录所有钱包余额变动,包含变动前后余额用于对账
|
||||
// 支持关联业务对象(订单、分佣、提现等)
|
||||
type WalletTransaction struct {
|
||||
gorm.Model
|
||||
WalletID uint `gorm:"column:wallet_id;not null;index:idx_wallet_tx_wallet;comment:钱包ID" json:"wallet_id"`
|
||||
UserID uint `gorm:"column:user_id;not null;index:idx_wallet_tx_user;comment:用户ID" json:"user_id"`
|
||||
TransactionType string `gorm:"column:transaction_type;type:varchar(20);not null;comment:交易类型 recharge-充值 deduct-扣款 refund-退款 commission-分佣 withdrawal-提现" json:"transaction_type"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:变动金额(分)正数为增加 负数为减少" json:"amount"`
|
||||
BalanceBefore int64 `gorm:"column:balance_before;type:bigint;not null;comment:变动前余额(分)" json:"balance_before"`
|
||||
BalanceAfter int64 `gorm:"column:balance_after;type:bigint;not null;comment:变动后余额(分)" json:"balance_after"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;comment:交易状态 1-成功 2-失败 3-处理中" json:"status"`
|
||||
ReferenceType *string `gorm:"column:reference_type;type:varchar(50);index:idx_wallet_tx_ref,priority:1;comment:关联业务类型 order/commission/withdrawal/topup" json:"reference_type,omitempty"`
|
||||
ReferenceID *uint `gorm:"column:reference_id;type:bigint;index:idx_wallet_tx_ref,priority:2;comment:关联业务ID" json:"reference_id,omitempty"`
|
||||
Remark *string `gorm:"column:remark;type:text;comment:备注" json:"remark,omitempty"`
|
||||
Metadata WalletMetadata `gorm:"column:metadata;type:jsonb;comment:扩展信息" json:"metadata,omitempty"`
|
||||
Creator uint `gorm:"column:creator;comment:创建人ID" json:"creator"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (WalletTransaction) TableName() string {
|
||||
return "tb_wallet_transaction"
|
||||
}
|
||||
|
||||
// RechargeRecord 充值记录模型
|
||||
// 用户和代理的钱包充值订单,记录支付流程和状态
|
||||
type RechargeRecord struct {
|
||||
gorm.Model
|
||||
BaseModel `gorm:"embedded"`
|
||||
UserID uint `gorm:"column:user_id;not null;index:idx_recharge_user;comment:用户ID" json:"user_id"`
|
||||
WalletID uint `gorm:"column:wallet_id;not null;comment:钱包ID" json:"wallet_id"`
|
||||
RechargeNo string `gorm:"column:recharge_no;type:varchar(50);not null;uniqueIndex:idx_recharge_no;comment:充值订单号" json:"recharge_no"`
|
||||
Amount int64 `gorm:"column:amount;type:bigint;not null;comment:充值金额(分)" json:"amount"`
|
||||
PaymentMethod string `gorm:"column:payment_method;type:varchar(20);not null;comment:支付方式 alipay-支付宝 wechat-微信 bank-银行转账 offline-线下" json:"payment_method"`
|
||||
PaymentChannel *string `gorm:"column:payment_channel;type:varchar(50);comment:支付渠道" json:"payment_channel,omitempty"`
|
||||
PaymentTransactionID *string `gorm:"column:payment_transaction_id;type:varchar(100);comment:第三方支付交易号" json:"payment_transaction_id,omitempty"`
|
||||
Status int `gorm:"column:status;type:int;not null;default:1;index:idx_recharge_status;comment:充值状态 1-待支付 2-已支付 3-已完成 4-已关闭 5-已退款" json:"status"`
|
||||
PaidAt *time.Time `gorm:"column:paid_at;comment:支付时间" json:"paid_at,omitempty"`
|
||||
CompletedAt *time.Time `gorm:"column:completed_at;comment:完成时间" json:"completed_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (RechargeRecord) TableName() string {
|
||||
return "tb_recharge_record"
|
||||
}
|
||||
@@ -19,18 +19,18 @@ import (
|
||||
// PackageActivationHandler 套餐激活检查处理器
|
||||
// 任务 19: 处理主套餐过期、加油包级联失效、待生效主套餐激活
|
||||
type PackageActivationHandler struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
queueClient *asynq.Client
|
||||
packageUsageStore *postgres.PackageUsageStore
|
||||
activationService *packagepkg.ActivationService
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
queueClient *asynq.Client
|
||||
packageUsageStore *postgres.PackageUsageStore
|
||||
activationService *packagepkg.ActivationService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// PackageActivationPayload 套餐激活任务载荷
|
||||
type PackageActivationPayload struct {
|
||||
PackageUsageID uint `json:"package_usage_id"`
|
||||
CarrierType string `json:"carrier_type"` // "iot_card" 或 "device"
|
||||
CarrierType string `json:"carrier_type"` // "iot_card" 或 "device"
|
||||
CarrierID uint `json:"carrier_id"`
|
||||
ActivationType string `json:"activation_type"` // "queue" 或 "realname"
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
@@ -102,7 +102,7 @@ func (h *PackageActivationHandler) findExpiredMainPackages(ctx context.Context)
|
||||
Where("status = ?", constants.PackageUsageStatusActive).
|
||||
Where("expires_at <= ?", now).
|
||||
Where("master_usage_id IS NULL"). // 主套餐没有 master_usage_id
|
||||
Limit(1000). // 每次最多处理 1000 个,避免长事务
|
||||
Limit(1000). // 每次最多处理 1000 个,避免长事务
|
||||
Find(&packages).Error
|
||||
|
||||
return packages, err
|
||||
@@ -353,9 +353,9 @@ func (h *PackageActivationHandler) HandlePackageFirstActivation(ctx context.Cont
|
||||
// ActivationService 未注入,直接更新状态(备用逻辑)
|
||||
now := time.Now()
|
||||
if err := h.db.Model(&pkg).Updates(map[string]any{
|
||||
"status": constants.PackageUsageStatusActive,
|
||||
"activated_at": now,
|
||||
"pending_realname_activation": false,
|
||||
"status": constants.PackageUsageStatusActive,
|
||||
"activated_at": now,
|
||||
"pending_realname_activation": false,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ import (
|
||||
// Scheduler 轮询调度器
|
||||
// 负责管理 IoT 卡的定期检查任务(实名、流量、套餐)
|
||||
type Scheduler struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
queueClient *asynq.Client
|
||||
logger *zap.Logger
|
||||
configStore *postgres.PollingConfigStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
concurrencyStore *postgres.PollingConcurrencyConfigStore
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
queueClient *asynq.Client
|
||||
logger *zap.Logger
|
||||
configStore *postgres.PollingConfigStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
concurrencyStore *postgres.PollingConcurrencyConfigStore
|
||||
|
||||
// 任务 19: 套餐激活检查处理器
|
||||
packageActivationHandler *PackageActivationHandler
|
||||
@@ -50,12 +50,12 @@ type Scheduler struct {
|
||||
// InitProgress 初始化进度
|
||||
type InitProgress struct {
|
||||
mu sync.RWMutex
|
||||
TotalCards int64 `json:"total_cards"` // 总卡数
|
||||
LoadedCards int64 `json:"loaded_cards"` // 已加载卡数
|
||||
StartTime time.Time `json:"start_time"` // 开始时间
|
||||
TotalCards int64 `json:"total_cards"` // 总卡数
|
||||
LoadedCards int64 `json:"loaded_cards"` // 已加载卡数
|
||||
StartTime time.Time `json:"start_time"` // 开始时间
|
||||
LastBatchTime time.Time `json:"last_batch_time"` // 最后一批处理时间
|
||||
Status string `json:"status"` // 状态: pending, running, completed, failed
|
||||
ErrorMessage string `json:"error_message"` // 错误信息
|
||||
Status string `json:"status"` // 状态: pending, running, completed, failed
|
||||
ErrorMessage string `json:"error_message"` // 错误信息
|
||||
}
|
||||
|
||||
// SchedulerConfig 调度器配置
|
||||
@@ -74,13 +74,13 @@ type SchedulerConfig struct {
|
||||
// 单 Worker 设计吞吐:50000 张/秒,支持多 Worker 水平扩展
|
||||
func DefaultSchedulerConfig() *SchedulerConfig {
|
||||
return &SchedulerConfig{
|
||||
ScheduleInterval: 1 * time.Second, // 1秒调度一次,提高响应速度
|
||||
InitBatchSize: 100000, // 10万张/批初始化
|
||||
ScheduleInterval: 1 * time.Second, // 1秒调度一次,提高响应速度
|
||||
InitBatchSize: 100000, // 10万张/批初始化
|
||||
InitBatchSleepDuration: 500 * time.Millisecond, // 500ms 间隔,加快初始化
|
||||
ConfigCacheTTL: 5 * time.Minute,
|
||||
CardCacheTTL: 7 * 24 * time.Hour,
|
||||
ScheduleBatchSize: 50000, // 每次取 5 万张,每秒可调度 5 万张
|
||||
MaxManualBatchSize: 1000, // 手动触发每次处理 1000 张
|
||||
ScheduleBatchSize: 50000, // 每次取 5 万张,每秒可调度 5 万张
|
||||
MaxManualBatchSize: 1000, // 手动触发每次处理 1000 张
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,8 +383,8 @@ func (s *Scheduler) enqueueTask(ctx context.Context, taskType, cardID string, is
|
||||
}
|
||||
|
||||
task := asynq.NewTask(taskType, mustMarshal(payload),
|
||||
asynq.MaxRetry(0), // 不重试,失败后重新入队
|
||||
asynq.Timeout(30*time.Second), // 30秒超时
|
||||
asynq.MaxRetry(0), // 不重试,失败后重新入队
|
||||
asynq.Timeout(30*time.Second), // 30秒超时
|
||||
asynq.Queue(constants.QueueDefault),
|
||||
)
|
||||
|
||||
@@ -681,13 +681,13 @@ func (s *Scheduler) cacheCardInfo(ctx context.Context, card *model.IotCard, cach
|
||||
config := DefaultSchedulerConfig()
|
||||
|
||||
data := map[string]interface{}{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"card_category": card.CardCategory,
|
||||
"real_name_status": card.RealNameStatus,
|
||||
"network_status": card.NetworkStatus,
|
||||
"carrier_id": card.CarrierID,
|
||||
"cached_at": cachedAt.Unix(),
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"card_category": card.CardCategory,
|
||||
"real_name_status": card.RealNameStatus,
|
||||
"network_status": card.NetworkStatus,
|
||||
"carrier_id": card.CarrierID,
|
||||
"cached_at": cachedAt.Unix(),
|
||||
}
|
||||
|
||||
pipe := s.redis.Pipeline()
|
||||
|
||||
@@ -14,22 +14,22 @@ import (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
commissionRecordStore *postgres.CommissionRecordStore
|
||||
shopStore *postgres.ShopStore
|
||||
shopPackageAllocationStore *postgres.ShopPackageAllocationStore
|
||||
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
|
||||
packageSeriesStore *postgres.PackageSeriesStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
walletStore *postgres.WalletStore
|
||||
walletTransactionStore *postgres.WalletTransactionStore
|
||||
orderStore *postgres.OrderStore
|
||||
orderItemStore *postgres.OrderItemStore
|
||||
packageStore *postgres.PackageStore
|
||||
commissionStatsStore *postgres.ShopSeriesCommissionStatsStore
|
||||
commissionStatsService *commission_stats.Service
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
commissionRecordStore *postgres.CommissionRecordStore
|
||||
shopStore *postgres.ShopStore
|
||||
shopPackageAllocationStore *postgres.ShopPackageAllocationStore
|
||||
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
|
||||
packageSeriesStore *postgres.PackageSeriesStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
agentWalletStore *postgres.AgentWalletStore
|
||||
agentWalletTransactionStore *postgres.AgentWalletTransactionStore
|
||||
orderStore *postgres.OrderStore
|
||||
orderItemStore *postgres.OrderItemStore
|
||||
packageStore *postgres.PackageStore
|
||||
commissionStatsStore *postgres.ShopSeriesCommissionStatsStore
|
||||
commissionStatsService *commission_stats.Service
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func New(
|
||||
@@ -41,8 +41,8 @@ func New(
|
||||
packageSeriesStore *postgres.PackageSeriesStore,
|
||||
iotCardStore *postgres.IotCardStore,
|
||||
deviceStore *postgres.DeviceStore,
|
||||
walletStore *postgres.WalletStore,
|
||||
walletTransactionStore *postgres.WalletTransactionStore,
|
||||
agentWalletStore *postgres.AgentWalletStore,
|
||||
agentWalletTransactionStore *postgres.AgentWalletTransactionStore,
|
||||
orderStore *postgres.OrderStore,
|
||||
orderItemStore *postgres.OrderItemStore,
|
||||
packageStore *postgres.PackageStore,
|
||||
@@ -51,22 +51,22 @@ func New(
|
||||
logger *zap.Logger,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
commissionRecordStore: commissionRecordStore,
|
||||
shopStore: shopStore,
|
||||
shopPackageAllocationStore: shopPackageAllocationStore,
|
||||
shopSeriesAllocationStore: shopSeriesAllocationStore,
|
||||
packageSeriesStore: packageSeriesStore,
|
||||
iotCardStore: iotCardStore,
|
||||
deviceStore: deviceStore,
|
||||
walletStore: walletStore,
|
||||
walletTransactionStore: walletTransactionStore,
|
||||
orderStore: orderStore,
|
||||
orderItemStore: orderItemStore,
|
||||
packageStore: packageStore,
|
||||
commissionStatsStore: commissionStatsStore,
|
||||
commissionStatsService: commissionStatsService,
|
||||
logger: logger,
|
||||
db: db,
|
||||
commissionRecordStore: commissionRecordStore,
|
||||
shopStore: shopStore,
|
||||
shopPackageAllocationStore: shopPackageAllocationStore,
|
||||
shopSeriesAllocationStore: shopSeriesAllocationStore,
|
||||
packageSeriesStore: packageSeriesStore,
|
||||
iotCardStore: iotCardStore,
|
||||
deviceStore: deviceStore,
|
||||
agentWalletStore: agentWalletStore,
|
||||
agentWalletTransactionStore: agentWalletTransactionStore,
|
||||
orderStore: orderStore,
|
||||
orderItemStore: orderItemStore,
|
||||
packageStore: packageStore,
|
||||
commissionStatsStore: commissionStatsStore,
|
||||
commissionStatsService: commissionStatsService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,25 +562,20 @@ func (s *Service) matchOneTimeCommissionTier(ctx context.Context, shopID uint, s
|
||||
}
|
||||
|
||||
func (s *Service) creditCommissionInTx(ctx context.Context, tx *gorm.DB, record *model.CommissionRecord) error {
|
||||
var wallet model.Wallet
|
||||
if err := tx.Where("resource_type = ? AND resource_id = ? AND wallet_type = ?", "shop", record.ShopID, "commission").First(&wallet).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "获取店铺钱包失败")
|
||||
// 获取店铺的分佣钱包
|
||||
var wallet model.AgentWallet
|
||||
if err := tx.Where("shop_id = ? AND wallet_type = ?", record.ShopID, "commission").First(&wallet).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "获取店铺分佣钱包失败")
|
||||
}
|
||||
|
||||
balanceBefore := wallet.Balance
|
||||
result := tx.Model(&model.Wallet{}).
|
||||
Where("id = ? AND version = ?", wallet.ID, wallet.Version).
|
||||
Updates(map[string]any{
|
||||
"balance": gorm.Expr("balance + ?", record.Amount),
|
||||
"version": gorm.Expr("version + 1"),
|
||||
})
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新钱包余额失败")
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New(errors.CodeInternalError, "钱包版本冲突,请重试")
|
||||
|
||||
// 使用 AgentWalletStore 的方法增加余额(带事务)
|
||||
if err := s.agentWalletStore.AddBalanceWithTx(ctx, tx, wallet.ID, record.Amount); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "更新钱包余额失败")
|
||||
}
|
||||
|
||||
// 更新佣金记录状态
|
||||
now := time.Now()
|
||||
if err := tx.Model(record).Updates(map[string]any{
|
||||
"balance_after": balanceBefore + record.Amount,
|
||||
@@ -590,9 +585,11 @@ func (s *Service) creditCommissionInTx(ctx context.Context, tx *gorm.DB, record
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新佣金记录失败")
|
||||
}
|
||||
|
||||
// 创建代理钱包交易记录
|
||||
remark := "佣金入账"
|
||||
transaction := &model.WalletTransaction{
|
||||
WalletID: wallet.ID,
|
||||
transaction := &model.AgentWalletTransaction{
|
||||
AgentWalletID: wallet.ID,
|
||||
ShopID: record.ShopID,
|
||||
UserID: record.Creator,
|
||||
TransactionType: "commission",
|
||||
Amount: record.Amount,
|
||||
@@ -603,8 +600,9 @@ func (s *Service) creditCommissionInTx(ctx context.Context, tx *gorm.DB, record
|
||||
ReferenceID: &record.ID,
|
||||
Remark: &remark,
|
||||
Creator: record.Creator,
|
||||
ShopIDTag: record.ShopID,
|
||||
}
|
||||
if err := tx.Create(transaction).Error; err != nil {
|
||||
if err := s.agentWalletTransactionStore.CreateWithTx(ctx, tx, transaction); err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "创建钱包交易记录失败")
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ type Service struct {
|
||||
db *gorm.DB
|
||||
shopStore *postgres.ShopStore
|
||||
accountStore *postgres.AccountStore
|
||||
walletStore *postgres.WalletStore
|
||||
walletTransactionStore *postgres.WalletTransactionStore
|
||||
agentWalletStore *postgres.AgentWalletStore
|
||||
agentWalletTransactionStore *postgres.AgentWalletTransactionStore
|
||||
commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore
|
||||
}
|
||||
|
||||
@@ -28,16 +28,16 @@ func New(
|
||||
db *gorm.DB,
|
||||
shopStore *postgres.ShopStore,
|
||||
accountStore *postgres.AccountStore,
|
||||
walletStore *postgres.WalletStore,
|
||||
walletTransactionStore *postgres.WalletTransactionStore,
|
||||
agentWalletStore *postgres.AgentWalletStore,
|
||||
agentWalletTransactionStore *postgres.AgentWalletTransactionStore,
|
||||
commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
shopStore: shopStore,
|
||||
accountStore: accountStore,
|
||||
walletStore: walletStore,
|
||||
walletTransactionStore: walletTransactionStore,
|
||||
agentWalletStore: agentWalletStore,
|
||||
agentWalletTransactionStore: agentWalletTransactionStore,
|
||||
commissionWithdrawalReqStore: commissionWithdrawalReqStore,
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,8 @@ func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveWithdraw
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "申请状态不允许此操作")
|
||||
}
|
||||
|
||||
wallet, err := s.walletStore.GetShopCommissionWallet(ctx, withdrawal.ShopID)
|
||||
// 获取店铺分佣钱包
|
||||
wallet, err := s.agentWalletStore.GetCommissionWallet(ctx, withdrawal.ShopID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.CodeNotFound, "店铺佣金钱包不存在")
|
||||
}
|
||||
@@ -173,14 +174,17 @@ func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveWithdraw
|
||||
|
||||
now := time.Now()
|
||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.walletStore.DeductFrozenBalanceWithTx(ctx, tx, wallet.ID, amount); err != nil {
|
||||
// 从冻结余额扣款
|
||||
if err := s.agentWalletStore.DeductFrozenBalanceWithTx(ctx, tx, wallet.ID, amount); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "扣除冻结余额失败")
|
||||
}
|
||||
|
||||
// 创建代理钱包交易记录
|
||||
refType := "withdrawal"
|
||||
refID := withdrawal.ID
|
||||
transaction := &model.WalletTransaction{
|
||||
WalletID: wallet.ID,
|
||||
transaction := &model.AgentWalletTransaction{
|
||||
AgentWalletID: wallet.ID,
|
||||
ShopID: withdrawal.ShopID,
|
||||
UserID: currentUserID,
|
||||
TransactionType: "withdrawal",
|
||||
Amount: -amount,
|
||||
@@ -190,8 +194,9 @@ func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveWithdraw
|
||||
ReferenceType: &refType,
|
||||
ReferenceID: &refID,
|
||||
Creator: currentUserID,
|
||||
ShopIDTag: withdrawal.ShopID,
|
||||
}
|
||||
if err := s.walletTransactionStore.CreateWithTx(ctx, tx, transaction); err != nil {
|
||||
if err := s.agentWalletTransactionStore.CreateWithTx(ctx, tx, transaction); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "创建交易流水失败")
|
||||
}
|
||||
|
||||
@@ -265,21 +270,22 @@ func (s *Service) Reject(ctx context.Context, id uint, req *dto.RejectWithdrawal
|
||||
return nil, errors.New(errors.CodeInvalidStatus, "申请状态不允许此操作")
|
||||
}
|
||||
|
||||
wallet, err := s.walletStore.GetShopCommissionWallet(ctx, withdrawal.ShopID)
|
||||
wallet, err := s.agentWalletStore.GetCommissionWallet(ctx, withdrawal.ShopID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.CodeNotFound, "店铺佣金钱包不存在")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.walletStore.UnfreezeBalanceWithTx(ctx, tx, wallet.ID, withdrawal.Amount); err != nil {
|
||||
if err := s.agentWalletStore.UnfreezeBalanceWithTx(ctx, tx, wallet.ID, withdrawal.Amount); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "解冻余额失败")
|
||||
}
|
||||
|
||||
refType := "withdrawal"
|
||||
refID := withdrawal.ID
|
||||
transaction := &model.WalletTransaction{
|
||||
WalletID: wallet.ID,
|
||||
transaction := &model.AgentWalletTransaction{
|
||||
AgentWalletID: wallet.ID,
|
||||
ShopID: withdrawal.ShopID,
|
||||
UserID: currentUserID,
|
||||
TransactionType: "refund",
|
||||
Amount: withdrawal.Amount,
|
||||
@@ -289,8 +295,9 @@ func (s *Service) Reject(ctx context.Context, id uint, req *dto.RejectWithdrawal
|
||||
ReferenceType: &refType,
|
||||
ReferenceID: &refID,
|
||||
Creator: currentUserID,
|
||||
ShopIDTag: withdrawal.ShopID,
|
||||
}
|
||||
if err := s.walletTransactionStore.CreateWithTx(ctx, tx, transaction); err != nil {
|
||||
if err := s.agentWalletTransactionStore.CreateWithTx(ctx, tx, transaction); err != nil {
|
||||
return errors.Wrap(errors.CodeInternalError, err, "创建交易流水失败")
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func NewStopResumeService(
|
||||
iotCardStore: iotCardStore,
|
||||
gatewayClient: gatewayClient,
|
||||
logger: logger,
|
||||
maxRetries: 3, // 默认最多重试 3 次
|
||||
maxRetries: 3, // 默认最多重试 3 次
|
||||
retryInterval: 2 * time.Second, // 默认重试间隔 2 秒
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,30 +19,30 @@ import (
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
shopStore *postgres.ShopStore
|
||||
walletStore *postgres.WalletStore
|
||||
agentWalletStore *postgres.AgentWalletStore
|
||||
commissionWithdrawalRequestStore *postgres.CommissionWithdrawalRequestStore
|
||||
commissionWithdrawalSettingStore *postgres.CommissionWithdrawalSettingStore
|
||||
commissionRecordStore *postgres.CommissionRecordStore
|
||||
walletTransactionStore *postgres.WalletTransactionStore
|
||||
agentWalletTransactionStore *postgres.AgentWalletTransactionStore
|
||||
}
|
||||
|
||||
func New(
|
||||
db *gorm.DB,
|
||||
shopStore *postgres.ShopStore,
|
||||
walletStore *postgres.WalletStore,
|
||||
agentWalletStore *postgres.AgentWalletStore,
|
||||
commissionWithdrawalRequestStore *postgres.CommissionWithdrawalRequestStore,
|
||||
commissionWithdrawalSettingStore *postgres.CommissionWithdrawalSettingStore,
|
||||
commissionRecordStore *postgres.CommissionRecordStore,
|
||||
walletTransactionStore *postgres.WalletTransactionStore,
|
||||
agentWalletTransactionStore *postgres.AgentWalletTransactionStore,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
shopStore: shopStore,
|
||||
walletStore: walletStore,
|
||||
agentWalletStore: agentWalletStore,
|
||||
commissionWithdrawalRequestStore: commissionWithdrawalRequestStore,
|
||||
commissionWithdrawalSettingStore: commissionWithdrawalSettingStore,
|
||||
commissionRecordStore: commissionRecordStore,
|
||||
walletTransactionStore: walletTransactionStore,
|
||||
agentWalletTransactionStore: agentWalletTransactionStore,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ func (s *Service) GetCommissionSummary(ctx context.Context) (*dto.MyCommissionSu
|
||||
return nil, errors.New(errors.CodeShopNotFound, "店铺不存在")
|
||||
}
|
||||
|
||||
// 使用 GetShopCommissionWallet 获取店铺佣金钱包
|
||||
wallet, err := s.walletStore.GetShopCommissionWallet(ctx, shopID)
|
||||
// 使用 GetCommissionWallet 获取店铺佣金钱包
|
||||
wallet, err := s.agentWalletStore.GetCommissionWallet(ctx, shopID)
|
||||
if err != nil {
|
||||
// 钱包不存在时返回空数据
|
||||
return &dto.MyCommissionSummaryResp{
|
||||
@@ -120,7 +120,7 @@ func (s *Service) CreateWithdrawalRequest(ctx context.Context, req *dto.CreateMy
|
||||
}
|
||||
|
||||
// 获取钱包
|
||||
wallet, err := s.walletStore.GetShopCommissionWallet(ctx, shopID)
|
||||
wallet, err := s.agentWalletStore.GetCommissionWallet(ctx, shopID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.CodeInsufficientBalance, "钱包不存在")
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func (s *Service) CreateWithdrawalRequest(ctx context.Context, req *dto.CreateMy
|
||||
|
||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 冻结余额
|
||||
if err := tx.WithContext(ctx).Model(&model.Wallet{}).
|
||||
if err := tx.WithContext(ctx).Model(&model.AgentWallet{}).
|
||||
Where("id = ? AND balance >= ?", wallet.ID, req.Amount).
|
||||
Updates(map[string]interface{}{
|
||||
"balance": gorm.Expr("balance - ?", req.Amount),
|
||||
@@ -192,8 +192,9 @@ func (s *Service) CreateWithdrawalRequest(ctx context.Context, req *dto.CreateMy
|
||||
// 创建钱包流水记录
|
||||
remark := fmt.Sprintf("提现冻结,单号:%s", withdrawalNo)
|
||||
refType := constants.ReferenceTypeWithdrawal
|
||||
transaction := &model.WalletTransaction{
|
||||
WalletID: wallet.ID,
|
||||
transaction := &model.AgentWalletTransaction{
|
||||
AgentWalletID: wallet.ID,
|
||||
ShopID: shopID,
|
||||
UserID: currentUserID,
|
||||
TransactionType: constants.TransactionTypeWithdrawal,
|
||||
Amount: -req.Amount, // 冻结为负数
|
||||
@@ -204,6 +205,7 @@ func (s *Service) CreateWithdrawalRequest(ctx context.Context, req *dto.CreateMy
|
||||
ReferenceID: &withdrawalRequest.ID,
|
||||
Remark: &remark,
|
||||
Creator: currentUserID,
|
||||
ShopIDTag: shopID,
|
||||
}
|
||||
|
||||
if err := tx.WithContext(ctx).Create(transaction).Error; err != nil {
|
||||
|
||||
@@ -30,7 +30,8 @@ type Service struct {
|
||||
redis *redis.Client
|
||||
orderStore *postgres.OrderStore
|
||||
orderItemStore *postgres.OrderItemStore
|
||||
walletStore *postgres.WalletStore
|
||||
agentWalletStore *postgres.AgentWalletStore
|
||||
cardWalletStore *postgres.CardWalletStore
|
||||
purchaseValidationService *purchase_validation.Service
|
||||
shopPackageAllocationStore *postgres.ShopPackageAllocationStore
|
||||
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
|
||||
@@ -49,7 +50,8 @@ func New(
|
||||
redisClient *redis.Client,
|
||||
orderStore *postgres.OrderStore,
|
||||
orderItemStore *postgres.OrderItemStore,
|
||||
walletStore *postgres.WalletStore,
|
||||
agentWalletStore *postgres.AgentWalletStore,
|
||||
cardWalletStore *postgres.CardWalletStore,
|
||||
purchaseValidationService *purchase_validation.Service,
|
||||
shopPackageAllocationStore *postgres.ShopPackageAllocationStore,
|
||||
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore,
|
||||
@@ -67,7 +69,8 @@ func New(
|
||||
redis: redisClient,
|
||||
orderStore: orderStore,
|
||||
orderItemStore: orderItemStore,
|
||||
walletStore: walletStore,
|
||||
agentWalletStore: agentWalletStore,
|
||||
cardWalletStore: cardWalletStore,
|
||||
purchaseValidationService: purchaseValidationService,
|
||||
shopPackageAllocationStore: shopPackageAllocationStore,
|
||||
shopSeriesAllocationStore: shopSeriesAllocationStore,
|
||||
@@ -430,64 +433,128 @@ func (s *Service) WalletPay(ctx context.Context, orderID uint, buyerType string,
|
||||
return errors.New(errors.CodeInvalidParam, "不支持的买家类型")
|
||||
}
|
||||
|
||||
wallet, err := s.walletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID, "main")
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeWalletNotFound, "钱包不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if wallet.Balance < order.TotalAmount {
|
||||
return errors.New(errors.CodeInsufficientBalance, "余额不足")
|
||||
}
|
||||
|
||||
// 根据资源类型选择对应的钱包系统
|
||||
now := time.Now()
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Model(&model.Order{}).
|
||||
Where("id = ? AND payment_status = ?", orderID, model.PaymentStatusPending).
|
||||
Updates(map[string]any{
|
||||
"payment_status": model.PaymentStatusPaid,
|
||||
"payment_method": model.PaymentMethodWallet,
|
||||
"paid_at": now,
|
||||
})
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新订单支付状态失败")
|
||||
|
||||
if resourceType == "shop" {
|
||||
// 代理钱包系统(店铺)
|
||||
wallet, err := s.agentWalletStore.GetMainWallet(ctx, resourceID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeWalletNotFound, "钱包不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
var currentOrder model.Order
|
||||
if err := tx.First(¤tOrder, orderID).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败")
|
||||
if wallet.Balance < order.TotalAmount {
|
||||
return errors.New(errors.CodeInsufficientBalance, "余额不足")
|
||||
}
|
||||
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Model(&model.Order{}).
|
||||
Where("id = ? AND payment_status = ?", orderID, model.PaymentStatusPending).
|
||||
Updates(map[string]any{
|
||||
"payment_status": model.PaymentStatusPaid,
|
||||
"payment_method": model.PaymentMethodWallet,
|
||||
"paid_at": now,
|
||||
})
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新订单支付状态失败")
|
||||
}
|
||||
|
||||
switch currentOrder.PaymentStatus {
|
||||
case model.PaymentStatusPaid:
|
||||
return nil
|
||||
case model.PaymentStatusCancelled:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单已取消,无法支付")
|
||||
case model.PaymentStatusRefunded:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单已退款,无法支付")
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单状态异常")
|
||||
if result.RowsAffected == 0 {
|
||||
var currentOrder model.Order
|
||||
if err := tx.First(¤tOrder, orderID).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败")
|
||||
}
|
||||
|
||||
switch currentOrder.PaymentStatus {
|
||||
case model.PaymentStatusPaid:
|
||||
return nil
|
||||
case model.PaymentStatusCancelled:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单已取消,无法支付")
|
||||
case model.PaymentStatusRefunded:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单已退款,无法支付")
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单状态异常")
|
||||
}
|
||||
}
|
||||
|
||||
walletResult := tx.Model(&model.AgentWallet{}).
|
||||
Where("id = ? AND balance >= ? AND version = ?", wallet.ID, order.TotalAmount, wallet.Version).
|
||||
Updates(map[string]any{
|
||||
"balance": gorm.Expr("balance - ?", order.TotalAmount),
|
||||
"version": gorm.Expr("version + 1"),
|
||||
})
|
||||
if walletResult.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, walletResult.Error, "扣减钱包余额失败")
|
||||
}
|
||||
if walletResult.RowsAffected == 0 {
|
||||
return errors.New(errors.CodeInsufficientBalance, "余额不足或并发冲突")
|
||||
}
|
||||
|
||||
return s.activatePackage(ctx, tx, order)
|
||||
})
|
||||
} else {
|
||||
// 卡钱包系统(iot_card 或 device)
|
||||
wallet, err := s.cardWalletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeWalletNotFound, "钱包不存在")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
walletResult := tx.Model(&model.Wallet{}).
|
||||
Where("id = ? AND balance >= ? AND version = ?", wallet.ID, order.TotalAmount, wallet.Version).
|
||||
Updates(map[string]any{
|
||||
"balance": gorm.Expr("balance - ?", order.TotalAmount),
|
||||
"version": gorm.Expr("version + 1"),
|
||||
})
|
||||
if walletResult.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, walletResult.Error, "扣减钱包余额失败")
|
||||
}
|
||||
if walletResult.RowsAffected == 0 {
|
||||
return errors.New(errors.CodeInsufficientBalance, "余额不足或并发冲突")
|
||||
if wallet.Balance < order.TotalAmount {
|
||||
return errors.New(errors.CodeInsufficientBalance, "余额不足")
|
||||
}
|
||||
|
||||
return s.activatePackage(ctx, tx, order)
|
||||
})
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Model(&model.Order{}).
|
||||
Where("id = ? AND payment_status = ?", orderID, model.PaymentStatusPending).
|
||||
Updates(map[string]any{
|
||||
"payment_status": model.PaymentStatusPaid,
|
||||
"payment_method": model.PaymentMethodWallet,
|
||||
"paid_at": now,
|
||||
})
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新订单支付状态失败")
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
var currentOrder model.Order
|
||||
if err := tx.First(¤tOrder, orderID).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败")
|
||||
}
|
||||
|
||||
switch currentOrder.PaymentStatus {
|
||||
case model.PaymentStatusPaid:
|
||||
return nil
|
||||
case model.PaymentStatusCancelled:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单已取消,无法支付")
|
||||
case model.PaymentStatusRefunded:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单已退款,无法支付")
|
||||
default:
|
||||
return errors.New(errors.CodeInvalidStatus, "订单状态异常")
|
||||
}
|
||||
}
|
||||
|
||||
walletResult := tx.Model(&model.CardWallet{}).
|
||||
Where("id = ? AND balance >= ? AND version = ?", wallet.ID, order.TotalAmount, wallet.Version).
|
||||
Updates(map[string]any{
|
||||
"balance": gorm.Expr("balance - ?", order.TotalAmount),
|
||||
"version": gorm.Expr("version + 1"),
|
||||
})
|
||||
if walletResult.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, walletResult.Error, "扣减钱包余额失败")
|
||||
}
|
||||
if walletResult.RowsAffected == 0 {
|
||||
return errors.New(errors.CodeInsufficientBalance, "余额不足或并发冲突")
|
||||
}
|
||||
|
||||
return s.activatePackage(ctx, tx, order)
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -71,10 +71,10 @@ func (s *ResetService) resetDailyUsageWithDB(ctx context.Context, db *gorm.DB) e
|
||||
|
||||
// 批量更新
|
||||
updates := map[string]interface{}{
|
||||
"data_usage_mb": 0,
|
||||
"last_reset_at": now,
|
||||
"next_reset_at": nextReset,
|
||||
"status": constants.PackageUsageStatusActive, // 重置后恢复为生效中
|
||||
"data_usage_mb": 0,
|
||||
"last_reset_at": now,
|
||||
"next_reset_at": nextReset,
|
||||
"status": constants.PackageUsageStatusActive, // 重置后恢复为生效中
|
||||
}
|
||||
|
||||
if err := tx.Model(&model.PackageUsage{}).
|
||||
@@ -221,4 +221,3 @@ func (s *ResetService) resetYearlyUsageWithDB(ctx context.Context, db *gorm.DB)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -152,13 +152,13 @@ func (s *CleanupService) Preview(ctx context.Context) ([]*CleanupPreview, error)
|
||||
|
||||
// CleanupProgress 清理进度
|
||||
type CleanupProgress struct {
|
||||
IsRunning bool `json:"is_running"`
|
||||
CurrentTable string `json:"current_table,omitempty"`
|
||||
TotalTables int `json:"total_tables"`
|
||||
ProcessedTables int `json:"processed_tables"`
|
||||
TotalDeleted int64 `json:"total_deleted"`
|
||||
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||
LastLog *model.DataCleanupLog `json:"last_log,omitempty"`
|
||||
IsRunning bool `json:"is_running"`
|
||||
CurrentTable string `json:"current_table,omitempty"`
|
||||
TotalTables int `json:"total_tables"`
|
||||
ProcessedTables int `json:"processed_tables"`
|
||||
TotalDeleted int64 `json:"total_deleted"`
|
||||
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||
LastLog *model.DataCleanupLog `json:"last_log,omitempty"`
|
||||
}
|
||||
|
||||
// GetProgress 获取清理进度
|
||||
|
||||
@@ -28,11 +28,11 @@ func NewConcurrencyService(store *postgres.PollingConcurrencyConfigStore, redis
|
||||
|
||||
// ConcurrencyStatus 并发状态
|
||||
type ConcurrencyStatus struct {
|
||||
TaskType string `json:"task_type"`
|
||||
TaskTypeName string `json:"task_type_name"`
|
||||
MaxConcurrency int `json:"max_concurrency"`
|
||||
Current int64 `json:"current"`
|
||||
Available int64 `json:"available"`
|
||||
TaskType string `json:"task_type"`
|
||||
TaskTypeName string `json:"task_type_name"`
|
||||
MaxConcurrency int `json:"max_concurrency"`
|
||||
Current int64 `json:"current"`
|
||||
Available int64 `json:"available"`
|
||||
Utilization float64 `json:"utilization"`
|
||||
}
|
||||
|
||||
|
||||
@@ -207,13 +207,13 @@ func (s *ManualTriggerService) processBatchTrigger(ctx context.Context, logID ui
|
||||
|
||||
// ConditionFilter 条件筛选参数
|
||||
type ConditionFilter struct {
|
||||
CardStatus string `json:"card_status,omitempty"` // 卡状态
|
||||
CarrierCode string `json:"carrier_code,omitempty"` // 运营商代码
|
||||
CardType string `json:"card_type,omitempty"` // 卡类型
|
||||
ShopID *uint `json:"shop_id,omitempty"` // 店铺ID
|
||||
PackageIDs []uint `json:"package_ids,omitempty"` // 套餐ID列表
|
||||
EnablePolling *bool `json:"enable_polling,omitempty"` // 是否启用轮询
|
||||
Limit int `json:"limit,omitempty"` // 限制数量
|
||||
CardStatus string `json:"card_status,omitempty"` // 卡状态
|
||||
CarrierCode string `json:"carrier_code,omitempty"` // 运营商代码
|
||||
CardType string `json:"card_type,omitempty"` // 卡类型
|
||||
ShopID *uint `json:"shop_id,omitempty"` // 店铺ID
|
||||
PackageIDs []uint `json:"package_ids,omitempty"` // 套餐ID列表
|
||||
EnablePolling *bool `json:"enable_polling,omitempty"` // 是否启用轮询
|
||||
Limit int `json:"limit,omitempty"` // 限制数量
|
||||
}
|
||||
|
||||
// TriggerByCondition 条件筛选触发
|
||||
|
||||
@@ -29,26 +29,26 @@ type ForceRechargeRequirement struct {
|
||||
}
|
||||
|
||||
// Service 充值服务
|
||||
// 负责充值订单的创建、预检、支付回调处理等业务逻辑
|
||||
// 负责卡钱包(IoT卡/设备)的充值订单创建、预检、支付回调处理等业务逻辑
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
rechargeStore *postgres.RechargeStore
|
||||
walletStore *postgres.WalletStore
|
||||
walletTransactionStore *postgres.WalletTransactionStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
|
||||
packageSeriesStore *postgres.PackageSeriesStore
|
||||
commissionRecordStore *postgres.CommissionRecordStore
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
cardRechargeStore *postgres.CardRechargeStore
|
||||
cardWalletStore *postgres.CardWalletStore
|
||||
cardWalletTransactionStore *postgres.CardWalletTransactionStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
deviceStore *postgres.DeviceStore
|
||||
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
|
||||
packageSeriesStore *postgres.PackageSeriesStore
|
||||
commissionRecordStore *postgres.CommissionRecordStore
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// New 创建充值服务实例
|
||||
func New(
|
||||
db *gorm.DB,
|
||||
rechargeStore *postgres.RechargeStore,
|
||||
walletStore *postgres.WalletStore,
|
||||
walletTransactionStore *postgres.WalletTransactionStore,
|
||||
cardRechargeStore *postgres.CardRechargeStore,
|
||||
cardWalletStore *postgres.CardWalletStore,
|
||||
cardWalletTransactionStore *postgres.CardWalletTransactionStore,
|
||||
iotCardStore *postgres.IotCardStore,
|
||||
deviceStore *postgres.DeviceStore,
|
||||
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore,
|
||||
@@ -57,16 +57,16 @@ func New(
|
||||
logger *zap.Logger,
|
||||
) *Service {
|
||||
return &Service{
|
||||
db: db,
|
||||
rechargeStore: rechargeStore,
|
||||
walletStore: walletStore,
|
||||
walletTransactionStore: walletTransactionStore,
|
||||
iotCardStore: iotCardStore,
|
||||
deviceStore: deviceStore,
|
||||
shopSeriesAllocationStore: shopSeriesAllocationStore,
|
||||
packageSeriesStore: packageSeriesStore,
|
||||
commissionRecordStore: commissionRecordStore,
|
||||
logger: logger,
|
||||
db: db,
|
||||
cardRechargeStore: cardRechargeStore,
|
||||
cardWalletStore: cardWalletStore,
|
||||
cardWalletTransactionStore: cardWalletTransactionStore,
|
||||
iotCardStore: iotCardStore,
|
||||
deviceStore: deviceStore,
|
||||
shopSeriesAllocationStore: shopSeriesAllocationStore,
|
||||
packageSeriesStore: packageSeriesStore,
|
||||
commissionRecordStore: commissionRecordStore,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,14 +81,14 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateRechargeRequest, us
|
||||
return nil, errors.New(errors.CodeRechargeAmountInvalid, "充值金额不能超过100000元")
|
||||
}
|
||||
|
||||
// 2. 获取资源(卡或设备)
|
||||
var wallet *model.Wallet
|
||||
// 2. 获取资源(卡或设备)钱包
|
||||
var wallet *model.CardWallet
|
||||
var err error
|
||||
|
||||
if req.ResourceType == "iot_card" {
|
||||
wallet, err = s.walletStore.GetByResourceTypeAndID(ctx, "iot_card", req.ResourceID, "main")
|
||||
wallet, err = s.cardWalletStore.GetByResourceTypeAndID(ctx, "iot_card", req.ResourceID)
|
||||
} else if req.ResourceType == "device" {
|
||||
wallet, err = s.walletStore.GetByResourceTypeAndID(ctx, "device", req.ResourceID, "main")
|
||||
wallet, err = s.cardWalletStore.GetByResourceTypeAndID(ctx, "device", req.ResourceID)
|
||||
} else {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "无效的资源类型")
|
||||
}
|
||||
@@ -115,20 +115,20 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateRechargeRequest, us
|
||||
rechargeNo := s.generateRechargeNo()
|
||||
|
||||
// 5. 创建充值订单
|
||||
recharge := &model.RechargeRecord{
|
||||
BaseModel: model.BaseModel{
|
||||
Creator: userID,
|
||||
Updater: userID,
|
||||
},
|
||||
UserID: userID,
|
||||
WalletID: wallet.ID,
|
||||
RechargeNo: rechargeNo,
|
||||
Amount: req.Amount,
|
||||
PaymentMethod: req.PaymentMethod,
|
||||
Status: constants.RechargeStatusPending,
|
||||
recharge := &model.CardRechargeRecord{
|
||||
UserID: userID,
|
||||
CardWalletID: wallet.ID,
|
||||
ResourceType: req.ResourceType,
|
||||
ResourceID: req.ResourceID,
|
||||
RechargeNo: rechargeNo,
|
||||
Amount: req.Amount,
|
||||
PaymentMethod: req.PaymentMethod,
|
||||
Status: constants.RechargeStatusPending,
|
||||
ShopIDTag: wallet.ShopIDTag,
|
||||
EnterpriseIDTag: wallet.EnterpriseIDTag,
|
||||
}
|
||||
|
||||
if err := s.rechargeStore.Create(ctx, recharge); err != nil {
|
||||
if err := s.cardRechargeStore.Create(ctx, recharge); err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func (s *Service) GetRechargeCheck(ctx context.Context, resourceType string, res
|
||||
// GetByID 根据ID查询充值订单详情
|
||||
// 支持数据权限过滤
|
||||
func (s *Service) GetByID(ctx context.Context, id uint, userID uint) (*dto.RechargeResponse, error) {
|
||||
recharge, err := s.rechargeStore.GetByID(ctx, id)
|
||||
recharge, err := s.cardRechargeStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeRechargeNotFound, "充值订单不存在")
|
||||
@@ -201,7 +201,7 @@ func (s *Service) List(ctx context.Context, req *dto.RechargeListRequest, userID
|
||||
pageSize = constants.DefaultPageSize
|
||||
}
|
||||
|
||||
params := &postgres.ListRechargeParams{
|
||||
params := &postgres.ListCardRechargeParams{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
UserID: &userID, // 数据权限:只能查看自己的
|
||||
@@ -211,7 +211,8 @@ func (s *Service) List(ctx context.Context, req *dto.RechargeListRequest, userID
|
||||
params.Status = req.Status
|
||||
}
|
||||
if req.WalletID != nil {
|
||||
params.WalletID = req.WalletID
|
||||
walletID := *req.WalletID
|
||||
params.CardWalletID = &walletID
|
||||
}
|
||||
if req.StartTime != nil {
|
||||
params.StartTime = req.StartTime
|
||||
@@ -220,7 +221,7 @@ func (s *Service) List(ctx context.Context, req *dto.RechargeListRequest, userID
|
||||
params.EndTime = req.EndTime
|
||||
}
|
||||
|
||||
recharges, total, err := s.rechargeStore.List(ctx, params)
|
||||
recharges, total, err := s.cardRechargeStore.List(ctx, params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单列表失败")
|
||||
}
|
||||
@@ -248,13 +249,13 @@ func (s *Service) List(ctx context.Context, req *dto.RechargeListRequest, userID
|
||||
// 支持幂等性检查、事务处理、更新余额、触发佣金
|
||||
func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string, paymentMethod string, paymentTransactionID string) error {
|
||||
// 1. 查询充值订单
|
||||
recharge, err := s.rechargeStore.GetByRechargeNo(ctx, rechargeNo)
|
||||
recharge, err := s.cardRechargeStore.GetByRechargeNo(ctx, rechargeNo)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeRechargeNotFound, "充值订单不存在")
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单失败")
|
||||
}
|
||||
if recharge == nil {
|
||||
return errors.New(errors.CodeRechargeNotFound, "充值订单不存在")
|
||||
}
|
||||
|
||||
// 2. 幂等性检查:已支付则直接返回成功
|
||||
if recharge.Status == constants.RechargeStatusPaid || recharge.Status == constants.RechargeStatusCompleted {
|
||||
@@ -271,21 +272,21 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string,
|
||||
}
|
||||
|
||||
// 4. 获取钱包信息
|
||||
wallet, err := s.walletStore.GetByID(ctx, recharge.WalletID)
|
||||
wallet, err := s.cardWalletStore.GetByID(ctx, recharge.CardWalletID)
|
||||
if err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败")
|
||||
}
|
||||
|
||||
// 5. 获取钱包对应的资源类型和ID
|
||||
resourceType := wallet.ResourceType
|
||||
resourceID := wallet.ResourceID
|
||||
// 5. 获取钱包对应的资源类型和ID(从充值记录中直接获取)
|
||||
resourceType := recharge.ResourceType
|
||||
resourceID := recharge.ResourceID
|
||||
|
||||
// 6. 事务处理:更新订单状态、增加余额、更新累计充值、触发佣金
|
||||
now := time.Now()
|
||||
err = s.db.Transaction(func(tx *gorm.DB) error {
|
||||
// 6.1 更新充值订单状态(带状态检查,实现乐观锁)
|
||||
oldStatus := constants.RechargeStatusPending
|
||||
if err := s.rechargeStore.UpdateStatus(ctx, recharge.ID, &oldStatus, constants.RechargeStatusPaid, &now, nil); err != nil {
|
||||
if err := s.cardRechargeStore.UpdateStatusWithOptimisticLock(ctx, recharge.ID, &oldStatus, constants.RechargeStatusPaid, &now, nil); err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// 状态已变更,幂等处理
|
||||
return nil
|
||||
@@ -294,13 +295,13 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string,
|
||||
}
|
||||
|
||||
// 6.2 更新支付信息
|
||||
if err := s.rechargeStore.UpdatePaymentInfo(ctx, recharge.ID, &paymentMethod, &paymentTransactionID); err != nil {
|
||||
if err := s.cardRechargeStore.UpdatePaymentInfo(ctx, recharge.ID, &paymentMethod, &paymentTransactionID); err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新支付信息失败")
|
||||
}
|
||||
|
||||
// 6.3 增加钱包余额(使用乐观锁)
|
||||
balanceBefore := wallet.Balance
|
||||
result := tx.Model(&model.Wallet{}).
|
||||
result := tx.Model(&model.CardWallet{}).
|
||||
Where("id = ? AND version = ?", wallet.ID, wallet.Version).
|
||||
Updates(map[string]any{
|
||||
"balance": gorm.Expr("balance + ?", recharge.Amount),
|
||||
@@ -316,8 +317,10 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string,
|
||||
// 6.4 创建钱包交易记录
|
||||
remark := "钱包充值"
|
||||
refType := "recharge"
|
||||
transaction := &model.WalletTransaction{
|
||||
WalletID: wallet.ID,
|
||||
transaction := &model.CardWalletTransaction{
|
||||
CardWalletID: wallet.ID,
|
||||
ResourceType: resourceType,
|
||||
ResourceID: resourceID,
|
||||
UserID: recharge.UserID,
|
||||
TransactionType: "recharge",
|
||||
Amount: recharge.Amount,
|
||||
@@ -327,7 +330,8 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string,
|
||||
ReferenceType: &refType,
|
||||
ReferenceID: &recharge.ID,
|
||||
Remark: &remark,
|
||||
Creator: recharge.UserID,
|
||||
ShopIDTag: wallet.ShopIDTag,
|
||||
EnterpriseIDTag: wallet.EnterpriseIDTag,
|
||||
}
|
||||
if err := tx.Create(transaction).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "创建钱包交易记录失败")
|
||||
@@ -344,7 +348,7 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string,
|
||||
}
|
||||
|
||||
// 6.7 更新充值订单状态为已完成
|
||||
if err := tx.Model(&model.RechargeRecord{}).
|
||||
if err := tx.Model(&model.CardRechargeRecord{}).
|
||||
Where("id = ?", recharge.ID).
|
||||
Update("status", constants.RechargeStatusCompleted).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "更新充值订单完成状态失败")
|
||||
@@ -590,8 +594,8 @@ func (s *Service) triggerOneTimeCommissionIfNeededInTx(ctx context.Context, tx *
|
||||
return nil
|
||||
}
|
||||
|
||||
var commissionWallet model.Wallet
|
||||
if err := tx.Where("resource_type = ? AND resource_id = ? AND wallet_type = ?", "shop", *shopID, "commission").
|
||||
var commissionWallet model.AgentWallet
|
||||
if err := tx.Where("shop_id = ? AND wallet_type = ?", *shopID, constants.AgentWalletTypeCommission).
|
||||
First(&commissionWallet).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
s.logger.Warn("店铺佣金钱包不存在,跳过佣金发放",
|
||||
@@ -629,7 +633,7 @@ func (s *Service) triggerOneTimeCommissionIfNeededInTx(ctx context.Context, tx *
|
||||
|
||||
// 11. 佣金入账到店铺佣金钱包
|
||||
balanceBefore := commissionWallet.Balance
|
||||
result := tx.Model(&model.Wallet{}).
|
||||
result := tx.Model(&model.AgentWallet{}).
|
||||
Where("id = ? AND version = ?", commissionWallet.ID, commissionWallet.Version).
|
||||
Updates(map[string]any{
|
||||
"balance": gorm.Expr("balance + ?", commissionAmount),
|
||||
@@ -654,8 +658,9 @@ func (s *Service) triggerOneTimeCommissionIfNeededInTx(ctx context.Context, tx *
|
||||
// 13. 创建佣金钱包交易记录
|
||||
remark := "一次性佣金入账(充值触发)"
|
||||
refType := "commission"
|
||||
commissionTransaction := &model.WalletTransaction{
|
||||
WalletID: commissionWallet.ID,
|
||||
commissionTransaction := &model.AgentWalletTransaction{
|
||||
AgentWalletID: commissionWallet.ID,
|
||||
ShopID: *shopID,
|
||||
UserID: userID,
|
||||
TransactionType: "commission",
|
||||
Amount: commissionAmount,
|
||||
@@ -665,7 +670,7 @@ func (s *Service) triggerOneTimeCommissionIfNeededInTx(ctx context.Context, tx *
|
||||
ReferenceType: &refType,
|
||||
ReferenceID: &commissionRecord.ID,
|
||||
Remark: &remark,
|
||||
Creator: userID,
|
||||
ShopIDTag: *shopID,
|
||||
}
|
||||
if err := tx.Create(commissionTransaction).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "创建佣金钱包交易记录失败")
|
||||
@@ -724,7 +729,7 @@ func (s *Service) generateRechargeNo() string {
|
||||
}
|
||||
|
||||
// buildRechargeResponse 构建充值订单响应
|
||||
func (s *Service) buildRechargeResponse(recharge *model.RechargeRecord) *dto.RechargeResponse {
|
||||
func (s *Service) buildRechargeResponse(recharge *model.CardRechargeRecord) *dto.RechargeResponse {
|
||||
statusText := ""
|
||||
switch recharge.Status {
|
||||
case constants.RechargeStatusPending:
|
||||
@@ -743,7 +748,7 @@ func (s *Service) buildRechargeResponse(recharge *model.RechargeRecord) *dto.Rec
|
||||
ID: recharge.ID,
|
||||
RechargeNo: recharge.RechargeNo,
|
||||
UserID: recharge.UserID,
|
||||
WalletID: recharge.WalletID,
|
||||
WalletID: recharge.CardWalletID,
|
||||
Amount: recharge.Amount,
|
||||
PaymentMethod: recharge.PaymentMethod,
|
||||
PaymentChannel: recharge.PaymentChannel,
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
type Service struct {
|
||||
shopStore *postgres.ShopStore
|
||||
accountStore *postgres.AccountStore
|
||||
walletStore *postgres.WalletStore
|
||||
agentWalletStore *postgres.AgentWalletStore
|
||||
commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore
|
||||
commissionRecordStore *postgres.CommissionRecordStore
|
||||
}
|
||||
@@ -24,14 +24,14 @@ type Service struct {
|
||||
func New(
|
||||
shopStore *postgres.ShopStore,
|
||||
accountStore *postgres.AccountStore,
|
||||
walletStore *postgres.WalletStore,
|
||||
agentWalletStore *postgres.AgentWalletStore,
|
||||
commissionWithdrawalReqStore *postgres.CommissionWithdrawalRequestStore,
|
||||
commissionRecordStore *postgres.CommissionRecordStore,
|
||||
) *Service {
|
||||
return &Service{
|
||||
shopStore: shopStore,
|
||||
accountStore: accountStore,
|
||||
walletStore: walletStore,
|
||||
agentWalletStore: agentWalletStore,
|
||||
commissionWithdrawalReqStore: commissionWithdrawalReqStore,
|
||||
commissionRecordStore: commissionRecordStore,
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func (s *Service) ListShopCommissionSummary(ctx context.Context, req *dto.ShopCo
|
||||
shopIDs = append(shopIDs, shop.ID)
|
||||
}
|
||||
|
||||
walletSummaries, err := s.walletStore.GetShopCommissionSummaryBatch(ctx, shopIDs)
|
||||
walletSummaries, err := s.agentWalletStore.GetShopCommissionSummaryBatch(ctx, shopIDs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询店铺钱包汇总失败")
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (s *Service) ListShopCommissionSummary(ctx context.Context, req *dto.ShopCo
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) buildCommissionSummaryItem(shop *model.Shop, walletSummary *postgres.ShopCommissionSummary, withdrawnAmount, withdrawingAmount int64, account *model.Account) dto.ShopCommissionSummaryItem {
|
||||
func (s *Service) buildCommissionSummaryItem(shop *model.Shop, walletSummary *model.AgentWallet, withdrawnAmount, withdrawingAmount int64, account *model.Account) dto.ShopCommissionSummaryItem {
|
||||
var balance, frozenBalance int64
|
||||
if walletSummary != nil {
|
||||
balance = walletSummary.Balance
|
||||
|
||||
125
internal/store/postgres/agent_recharge_store.go
Normal file
125
internal/store/postgres/agent_recharge_store.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AgentRechargeStore 代理充值记录数据访问层
|
||||
type AgentRechargeStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewAgentRechargeStore 创建代理充值记录 Store
|
||||
func NewAgentRechargeStore(db *gorm.DB, redis *redis.Client) *AgentRechargeStore {
|
||||
return &AgentRechargeStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建充值记录
|
||||
func (s *AgentRechargeStore) Create(ctx context.Context, record *model.AgentRechargeRecord) error {
|
||||
return s.db.WithContext(ctx).Create(record).Error
|
||||
}
|
||||
|
||||
// CreateWithTx 创建充值记录(带事务)
|
||||
func (s *AgentRechargeStore) CreateWithTx(ctx context.Context, tx *gorm.DB, record *model.AgentRechargeRecord) error {
|
||||
return tx.WithContext(ctx).Create(record).Error
|
||||
}
|
||||
|
||||
// GetByRechargeNo 根据充值订单号查询
|
||||
func (s *AgentRechargeStore) GetByRechargeNo(ctx context.Context, rechargeNo string) (*model.AgentRechargeRecord, error) {
|
||||
var record model.AgentRechargeRecord
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("recharge_no = ?", rechargeNo).
|
||||
First(&record).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 查询
|
||||
func (s *AgentRechargeStore) GetByID(ctx context.Context, id uint) (*model.AgentRechargeRecord, error) {
|
||||
var record model.AgentRechargeRecord
|
||||
if err := s.db.WithContext(ctx).First(&record, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新充值状态
|
||||
func (s *AgentRechargeStore) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.AgentRechargeRecord{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// UpdateStatusWithTx 更新充值状态(带事务)
|
||||
func (s *AgentRechargeStore) UpdateStatusWithTx(ctx context.Context, tx *gorm.DB, id uint, status int) error {
|
||||
return tx.WithContext(ctx).
|
||||
Model(&model.AgentRechargeRecord{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// Update 更新充值记录
|
||||
func (s *AgentRechargeStore) Update(ctx context.Context, record *model.AgentRechargeRecord) error {
|
||||
return s.db.WithContext(ctx).Save(record).Error
|
||||
}
|
||||
|
||||
// UpdateWithTx 更新充值记录(带事务)
|
||||
func (s *AgentRechargeStore) UpdateWithTx(ctx context.Context, tx *gorm.DB, record *model.AgentRechargeRecord) error {
|
||||
return tx.WithContext(ctx).Save(record).Error
|
||||
}
|
||||
|
||||
// ListByShopID 按店铺查询充值记录(支持分页)
|
||||
func (s *AgentRechargeStore) ListByShopID(ctx context.Context, shopID uint, offset, limit int) ([]*model.AgentRechargeRecord, error) {
|
||||
var records []*model.AgentRechargeRecord
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ?", shopID).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// ListByUserID 按用户查询充值记录(支持分页)
|
||||
func (s *AgentRechargeStore) ListByUserID(ctx context.Context, userID uint, offset, limit int) ([]*model.AgentRechargeRecord, error) {
|
||||
var records []*model.AgentRechargeRecord
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// ListByStatus 按状态查询充值记录(支持分页)
|
||||
func (s *AgentRechargeStore) ListByStatus(ctx context.Context, status int, offset, limit int) ([]*model.AgentRechargeRecord, error) {
|
||||
var records []*model.AgentRechargeRecord
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("status = ?", status).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
238
internal/store/postgres/agent_wallet_store.go
Normal file
238
internal/store/postgres/agent_wallet_store.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AgentWalletStore 代理钱包数据访问层
|
||||
type AgentWalletStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewAgentWalletStore 创建代理钱包 Store
|
||||
func NewAgentWalletStore(db *gorm.DB, redis *redis.Client) *AgentWalletStore {
|
||||
return &AgentWalletStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommissionWallet 获取店铺的分佣钱包
|
||||
func (s *AgentWalletStore) GetCommissionWallet(ctx context.Context, shopID uint) (*model.AgentWallet, error) {
|
||||
return s.GetByShopIDAndType(ctx, shopID, constants.AgentWalletTypeCommission)
|
||||
}
|
||||
|
||||
// GetMainWallet 获取店铺的主钱包
|
||||
func (s *AgentWalletStore) GetMainWallet(ctx context.Context, shopID uint) (*model.AgentWallet, error) {
|
||||
return s.GetByShopIDAndType(ctx, shopID, constants.AgentWalletTypeMain)
|
||||
}
|
||||
|
||||
// GetByShopIDAndType 根据店铺 ID 和钱包类型查询钱包
|
||||
func (s *AgentWalletStore) GetByShopIDAndType(ctx context.Context, shopID uint, walletType string) (*model.AgentWallet, error) {
|
||||
// 尝试从缓存获取
|
||||
cacheKey := constants.RedisAgentWalletBalanceKey(shopID, walletType)
|
||||
// 注意:这里简化处理,实际项目中可以缓存完整的钱包信息
|
||||
|
||||
var wallet model.AgentWallet
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ? AND wallet_type = ?", shopID, walletType).
|
||||
First(&wallet).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 更新缓存(可选)
|
||||
// 这里简化处理,不缓存完整对象
|
||||
_ = cacheKey
|
||||
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
// GetByID 根据钱包 ID 查询
|
||||
func (s *AgentWalletStore) GetByID(ctx context.Context, id uint) (*model.AgentWallet, error) {
|
||||
var wallet model.AgentWallet
|
||||
if err := s.db.WithContext(ctx).First(&wallet, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
// Create 创建代理钱包
|
||||
func (s *AgentWalletStore) Create(ctx context.Context, wallet *model.AgentWallet) error {
|
||||
return s.db.WithContext(ctx).Create(wallet).Error
|
||||
}
|
||||
|
||||
// CreateWithTx 创建代理钱包(带事务)
|
||||
func (s *AgentWalletStore) CreateWithTx(ctx context.Context, tx *gorm.DB, wallet *model.AgentWallet) error {
|
||||
return tx.WithContext(ctx).Create(wallet).Error
|
||||
}
|
||||
|
||||
// DeductFrozenBalanceWithTx 从冻结余额扣款(带事务)
|
||||
// 用于提现完成后,从冻结余额中扣除金额
|
||||
func (s *AgentWalletStore) DeductFrozenBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64) error {
|
||||
// 扣除冻结余额和总余额
|
||||
result := tx.WithContext(ctx).Model(&model.AgentWallet{}).
|
||||
Where("id = ? AND frozen_balance >= ?", walletID, amount).
|
||||
Updates(map[string]interface{}{
|
||||
"balance": gorm.Expr("balance - ?", amount),
|
||||
"frozen_balance": gorm.Expr("frozen_balance - ?", amount),
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound // 冻结余额不足
|
||||
}
|
||||
|
||||
// 删除缓存
|
||||
s.clearWalletCache(ctx, walletID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnfreezeBalanceWithTx 解冻余额到可用余额(带事务)
|
||||
// 用于提现取消,将冻结余额转回可用余额
|
||||
func (s *AgentWalletStore) UnfreezeBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64) error {
|
||||
// 减少冻结余额(总余额不变)
|
||||
result := tx.WithContext(ctx).Model(&model.AgentWallet{}).
|
||||
Where("id = ? AND frozen_balance >= ?", walletID, amount).
|
||||
Updates(map[string]interface{}{
|
||||
"frozen_balance": gorm.Expr("frozen_balance - ?", amount),
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound // 冻结余额不足
|
||||
}
|
||||
|
||||
// 删除缓存
|
||||
s.clearWalletCache(ctx, walletID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FreezeBalanceWithTx 冻结余额(带事务,使用乐观锁)
|
||||
// 用于提现申请,将可用余额转为冻结状态
|
||||
func (s *AgentWalletStore) FreezeBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64, version int) error {
|
||||
// 增加冻结余额(总余额不变),使用乐观锁
|
||||
result := tx.WithContext(ctx).Model(&model.AgentWallet{}).
|
||||
Where("id = ? AND balance - frozen_balance >= ? AND version = ?", walletID, amount, version).
|
||||
Updates(map[string]interface{}{
|
||||
"frozen_balance": gorm.Expr("frozen_balance + ?", amount),
|
||||
"version": gorm.Expr("version + 1"),
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound // 可用余额不足或版本冲突
|
||||
}
|
||||
|
||||
// 删除缓存
|
||||
s.clearWalletCache(ctx, walletID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddBalanceWithTx 增加余额(带事务)
|
||||
// 用于充值、退款等增加余额的操作
|
||||
func (s *AgentWalletStore) AddBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64) error {
|
||||
result := tx.WithContext(ctx).Model(&model.AgentWallet{}).
|
||||
Where("id = ?", walletID).
|
||||
Updates(map[string]interface{}{
|
||||
"balance": gorm.Expr("balance + ?", amount),
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
// 删除缓存
|
||||
s.clearWalletCache(ctx, walletID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeductBalanceWithTx 扣除余额(带事务,使用乐观锁)
|
||||
// 用于扣款操作,检查可用余额是否充足
|
||||
func (s *AgentWalletStore) DeductBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64, version int) error {
|
||||
// 使用乐观锁,检查可用余额是否充足
|
||||
result := tx.WithContext(ctx).Model(&model.AgentWallet{}).
|
||||
Where("id = ? AND balance - frozen_balance >= ? AND version = ?", walletID, amount, version).
|
||||
Updates(map[string]interface{}{
|
||||
"balance": gorm.Expr("balance - ?", amount),
|
||||
"version": gorm.Expr("version + 1"),
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound // 余额不足或版本冲突
|
||||
}
|
||||
|
||||
// 删除缓存
|
||||
s.clearWalletCache(ctx, walletID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetShopCommissionSummaryBatch 批量获取店铺佣金钱包汇总
|
||||
// 返回 map[shopID]*AgentWallet
|
||||
func (s *AgentWalletStore) GetShopCommissionSummaryBatch(ctx context.Context, shopIDs []uint) (map[uint]*model.AgentWallet, error) {
|
||||
if len(shopIDs) == 0 {
|
||||
return make(map[uint]*model.AgentWallet), nil
|
||||
}
|
||||
|
||||
var wallets []model.AgentWallet
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("shop_id IN ? AND wallet_type = ?", shopIDs, constants.AgentWalletTypeCommission).
|
||||
Find(&wallets).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为 map
|
||||
result := make(map[uint]*model.AgentWallet, len(wallets))
|
||||
for i := range wallets {
|
||||
result[wallets[i].ShopID] = &wallets[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// clearWalletCache 清除钱包缓存
|
||||
func (s *AgentWalletStore) clearWalletCache(ctx context.Context, walletID uint) {
|
||||
// 查询钱包信息以获取 shop_id 和 wallet_type
|
||||
var wallet model.AgentWallet
|
||||
if err := s.db.WithContext(ctx).Select("shop_id, wallet_type").First(&wallet, walletID).Error; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cacheKey := constants.RedisAgentWalletBalanceKey(wallet.ShopID, wallet.WalletType)
|
||||
_ = s.redis.Del(ctx, cacheKey).Err()
|
||||
}
|
||||
80
internal/store/postgres/agent_wallet_transaction_store.go
Normal file
80
internal/store/postgres/agent_wallet_transaction_store.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AgentWalletTransactionStore 代理钱包交易记录数据访问层
|
||||
type AgentWalletTransactionStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewAgentWalletTransactionStore 创建代理钱包交易记录 Store
|
||||
func NewAgentWalletTransactionStore(db *gorm.DB, redis *redis.Client) *AgentWalletTransactionStore {
|
||||
return &AgentWalletTransactionStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWithTx 创建代理钱包交易记录(带事务)
|
||||
func (s *AgentWalletTransactionStore) CreateWithTx(ctx context.Context, tx *gorm.DB, transaction *model.AgentWalletTransaction) error {
|
||||
return tx.WithContext(ctx).Create(transaction).Error
|
||||
}
|
||||
|
||||
// ListByShopID 按店铺查询交易记录(支持分页)
|
||||
func (s *AgentWalletTransactionStore) ListByShopID(ctx context.Context, shopID uint, offset, limit int) ([]*model.AgentWalletTransaction, error) {
|
||||
var transactions []*model.AgentWalletTransaction
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("shop_id = ?", shopID).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&transactions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
// CountByShopID 统计店铺的交易记录数量
|
||||
func (s *AgentWalletTransactionStore) CountByShopID(ctx context.Context, shopID uint) (int64, error) {
|
||||
var count int64
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&model.AgentWalletTransaction{}).
|
||||
Where("shop_id = ?", shopID).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// ListByWalletID 按钱包查询交易记录(支持分页)
|
||||
func (s *AgentWalletTransactionStore) ListByWalletID(ctx context.Context, walletID uint, offset, limit int) ([]*model.AgentWalletTransaction, error) {
|
||||
var transactions []*model.AgentWalletTransaction
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("agent_wallet_id = ?", walletID).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&transactions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
// GetByReference 根据关联业务查询交易记录
|
||||
func (s *AgentWalletTransactionStore) GetByReference(ctx context.Context, referenceType string, referenceID uint) (*model.AgentWalletTransaction, error) {
|
||||
var transaction model.AgentWalletTransaction
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("reference_type = ? AND reference_id = ?", referenceType, referenceID).
|
||||
First(&transaction).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &transaction, nil
|
||||
}
|
||||
240
internal/store/postgres/card_recharge_store.go
Normal file
240
internal/store/postgres/card_recharge_store.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CardRechargeStore 卡充值记录数据访问层
|
||||
type CardRechargeStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewCardRechargeStore 创建卡充值记录 Store
|
||||
func NewCardRechargeStore(db *gorm.DB, redis *redis.Client) *CardRechargeStore {
|
||||
return &CardRechargeStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建充值记录
|
||||
func (s *CardRechargeStore) Create(ctx context.Context, record *model.CardRechargeRecord) error {
|
||||
return s.db.WithContext(ctx).Create(record).Error
|
||||
}
|
||||
|
||||
// CreateWithTx 创建充值记录(带事务)
|
||||
func (s *CardRechargeStore) CreateWithTx(ctx context.Context, tx *gorm.DB, record *model.CardRechargeRecord) error {
|
||||
return tx.WithContext(ctx).Create(record).Error
|
||||
}
|
||||
|
||||
// GetByRechargeNo 根据充值订单号查询
|
||||
func (s *CardRechargeStore) GetByRechargeNo(ctx context.Context, rechargeNo string) (*model.CardRechargeRecord, error) {
|
||||
var record model.CardRechargeRecord
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("recharge_no = ?", rechargeNo).
|
||||
First(&record).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 查询
|
||||
func (s *CardRechargeStore) GetByID(ctx context.Context, id uint) (*model.CardRechargeRecord, error) {
|
||||
var record model.CardRechargeRecord
|
||||
if err := s.db.WithContext(ctx).First(&record, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新充值状态
|
||||
func (s *CardRechargeStore) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Model(&model.CardRechargeRecord{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// UpdateStatusWithTx 更新充值状态(带事务)
|
||||
func (s *CardRechargeStore) UpdateStatusWithTx(ctx context.Context, tx *gorm.DB, id uint, status int) error {
|
||||
return tx.WithContext(ctx).
|
||||
Model(&model.CardRechargeRecord{}).
|
||||
Where("id = ?", id).
|
||||
Update("status", status).Error
|
||||
}
|
||||
|
||||
// Update 更新充值记录
|
||||
func (s *CardRechargeStore) Update(ctx context.Context, record *model.CardRechargeRecord) error {
|
||||
return s.db.WithContext(ctx).Save(record).Error
|
||||
}
|
||||
|
||||
// UpdateWithTx 更新充值记录(带事务)
|
||||
func (s *CardRechargeStore) UpdateWithTx(ctx context.Context, tx *gorm.DB, record *model.CardRechargeRecord) error {
|
||||
return tx.WithContext(ctx).Save(record).Error
|
||||
}
|
||||
|
||||
// ListByResourceID 按资源查询充值记录(支持分页)
|
||||
func (s *CardRechargeStore) ListByResourceID(ctx context.Context, resourceType string, resourceID uint, offset, limit int) ([]*model.CardRechargeRecord, error) {
|
||||
var records []*model.CardRechargeRecord
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("resource_type = ? AND resource_id = ?", resourceType, resourceID).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// ListByUserID 按用户查询充值记录(支持分页)
|
||||
func (s *CardRechargeStore) ListByUserID(ctx context.Context, userID uint, offset, limit int) ([]*model.CardRechargeRecord, error) {
|
||||
var records []*model.CardRechargeRecord
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// ListByStatus 按状态查询充值记录(支持分页)
|
||||
func (s *CardRechargeStore) ListByStatus(ctx context.Context, status int, offset, limit int) ([]*model.CardRechargeRecord, error) {
|
||||
var records []*model.CardRechargeRecord
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("status = ?", status).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// ListRechargeParams 充值记录列表查询参数
|
||||
type ListCardRechargeParams struct {
|
||||
Page int
|
||||
PageSize int
|
||||
UserID *uint
|
||||
CardWalletID *uint
|
||||
ResourceType *string
|
||||
ResourceID *uint
|
||||
Status *int
|
||||
StartTime interface{}
|
||||
EndTime interface{}
|
||||
}
|
||||
|
||||
// List 查询充值记录列表(支持分页和筛选)
|
||||
func (s *CardRechargeStore) List(ctx context.Context, params *ListCardRechargeParams) ([]*model.CardRechargeRecord, int64, error) {
|
||||
var records []*model.CardRechargeRecord
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.CardRechargeRecord{})
|
||||
|
||||
if params.UserID != nil {
|
||||
query = query.Where("user_id = ?", *params.UserID)
|
||||
}
|
||||
if params.CardWalletID != nil {
|
||||
query = query.Where("card_wallet_id = ?", *params.CardWalletID)
|
||||
}
|
||||
if params.ResourceType != nil {
|
||||
query = query.Where("resource_type = ?", *params.ResourceType)
|
||||
}
|
||||
if params.ResourceID != nil {
|
||||
query = query.Where("resource_id = ?", *params.ResourceID)
|
||||
}
|
||||
if params.Status != nil {
|
||||
query = query.Where("status = ?", *params.Status)
|
||||
}
|
||||
if params.StartTime != nil {
|
||||
query = query.Where("created_at >= ?", params.StartTime)
|
||||
}
|
||||
if params.EndTime != nil {
|
||||
query = query.Where("created_at <= ?", params.EndTime)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
page := params.Page
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize := params.PageSize
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("id DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return records, total, nil
|
||||
}
|
||||
|
||||
// UpdatePaymentInfo 更新支付信息
|
||||
func (s *CardRechargeStore) UpdatePaymentInfo(ctx context.Context, id uint, paymentMethod *string, paymentTransactionID *string) error {
|
||||
updates := map[string]interface{}{}
|
||||
if paymentMethod != nil {
|
||||
updates["payment_method"] = paymentMethod
|
||||
}
|
||||
if paymentTransactionID != nil {
|
||||
updates["payment_transaction_id"] = paymentTransactionID
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := s.db.WithContext(ctx).Model(&model.CardRechargeRecord{}).Where("id = ?", id).Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStatusWithOptimisticLock 更新充值状态(支持乐观锁)
|
||||
func (s *CardRechargeStore) UpdateStatusWithOptimisticLock(ctx context.Context, id uint, oldStatus *int, newStatus int, paidAt interface{}, completedAt interface{}) error {
|
||||
updates := map[string]interface{}{
|
||||
"status": newStatus,
|
||||
}
|
||||
if paidAt != nil {
|
||||
updates["paid_at"] = paidAt
|
||||
}
|
||||
if completedAt != nil {
|
||||
updates["completed_at"] = completedAt
|
||||
}
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.CardRechargeRecord{}).Where("id = ?", id)
|
||||
|
||||
if oldStatus != nil {
|
||||
query = query.Where("status = ?", *oldStatus)
|
||||
}
|
||||
|
||||
result := query.Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
116
internal/store/postgres/card_wallet_store.go
Normal file
116
internal/store/postgres/card_wallet_store.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CardWalletStore 卡钱包数据访问层
|
||||
type CardWalletStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewCardWalletStore 创建卡钱包 Store
|
||||
func NewCardWalletStore(db *gorm.DB, redis *redis.Client) *CardWalletStore {
|
||||
return &CardWalletStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// GetByResourceTypeAndID 根据资源类型和 ID 查询钱包
|
||||
func (s *CardWalletStore) GetByResourceTypeAndID(ctx context.Context, resourceType string, resourceID uint) (*model.CardWallet, error) {
|
||||
var wallet model.CardWallet
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("resource_type = ? AND resource_id = ?", resourceType, resourceID).
|
||||
First(&wallet).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
// GetByID 根据钱包 ID 查询
|
||||
func (s *CardWalletStore) GetByID(ctx context.Context, id uint) (*model.CardWallet, error) {
|
||||
var wallet model.CardWallet
|
||||
if err := s.db.WithContext(ctx).First(&wallet, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
// Create 创建卡钱包
|
||||
func (s *CardWalletStore) Create(ctx context.Context, wallet *model.CardWallet) error {
|
||||
return s.db.WithContext(ctx).Create(wallet).Error
|
||||
}
|
||||
|
||||
// CreateWithTx 创建卡钱包(带事务)
|
||||
func (s *CardWalletStore) CreateWithTx(ctx context.Context, tx *gorm.DB, wallet *model.CardWallet) error {
|
||||
return tx.WithContext(ctx).Create(wallet).Error
|
||||
}
|
||||
|
||||
// DeductBalanceWithTx 扣款(带事务,使用乐观锁)
|
||||
func (s *CardWalletStore) DeductBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64, version int) error {
|
||||
// 使用乐观锁,检查可用余额是否充足
|
||||
result := tx.WithContext(ctx).Model(&model.CardWallet{}).
|
||||
Where("id = ? AND balance - frozen_balance >= ? AND version = ?", walletID, amount, version).
|
||||
Updates(map[string]interface{}{
|
||||
"balance": gorm.Expr("balance - ?", amount),
|
||||
"version": gorm.Expr("version + 1"),
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound // 余额不足或版本冲突
|
||||
}
|
||||
|
||||
// 删除缓存
|
||||
s.clearWalletCache(ctx, walletID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddBalanceWithTx 增加余额(带事务)
|
||||
func (s *CardWalletStore) AddBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64) error {
|
||||
result := tx.WithContext(ctx).Model(&model.CardWallet{}).
|
||||
Where("id = ?", walletID).
|
||||
Updates(map[string]interface{}{
|
||||
"balance": gorm.Expr("balance + ?", amount),
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
// 删除缓存
|
||||
s.clearWalletCache(ctx, walletID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clearWalletCache 清除钱包缓存
|
||||
func (s *CardWalletStore) clearWalletCache(ctx context.Context, walletID uint) {
|
||||
// 查询钱包信息以获取 resource_type 和 resource_id
|
||||
var wallet model.CardWallet
|
||||
if err := s.db.WithContext(ctx).Select("resource_type, resource_id").First(&wallet, walletID).Error; err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cacheKey := constants.RedisCardWalletBalanceKey(wallet.ResourceType, wallet.ResourceID)
|
||||
_ = s.redis.Del(ctx, cacheKey).Err()
|
||||
}
|
||||
80
internal/store/postgres/card_wallet_transaction_store.go
Normal file
80
internal/store/postgres/card_wallet_transaction_store.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CardWalletTransactionStore 卡钱包交易记录数据访问层
|
||||
type CardWalletTransactionStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewCardWalletTransactionStore 创建卡钱包交易记录 Store
|
||||
func NewCardWalletTransactionStore(db *gorm.DB, redis *redis.Client) *CardWalletTransactionStore {
|
||||
return &CardWalletTransactionStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWithTx 创建卡钱包交易记录(带事务)
|
||||
func (s *CardWalletTransactionStore) CreateWithTx(ctx context.Context, tx *gorm.DB, transaction *model.CardWalletTransaction) error {
|
||||
return tx.WithContext(ctx).Create(transaction).Error
|
||||
}
|
||||
|
||||
// ListByResourceID 按资源查询交易记录(支持分页)
|
||||
func (s *CardWalletTransactionStore) ListByResourceID(ctx context.Context, resourceType string, resourceID uint, offset, limit int) ([]*model.CardWalletTransaction, error) {
|
||||
var transactions []*model.CardWalletTransaction
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("resource_type = ? AND resource_id = ?", resourceType, resourceID).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&transactions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
// CountByResourceID 统计资源的交易记录数量
|
||||
func (s *CardWalletTransactionStore) CountByResourceID(ctx context.Context, resourceType string, resourceID uint) (int64, error) {
|
||||
var count int64
|
||||
err := s.db.WithContext(ctx).
|
||||
Model(&model.CardWalletTransaction{}).
|
||||
Where("resource_type = ? AND resource_id = ?", resourceType, resourceID).
|
||||
Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// ListByWalletID 按钱包查询交易记录(支持分页)
|
||||
func (s *CardWalletTransactionStore) ListByWalletID(ctx context.Context, walletID uint, offset, limit int) ([]*model.CardWalletTransaction, error) {
|
||||
var transactions []*model.CardWalletTransaction
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("card_wallet_id = ?", walletID).
|
||||
Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(limit).
|
||||
Find(&transactions).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
// GetByReference 根据关联业务查询交易记录
|
||||
func (s *CardWalletTransactionStore) GetByReference(ctx context.Context, referenceType string, referenceID uint) (*model.CardWalletTransaction, error) {
|
||||
var transaction model.CardWalletTransaction
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("reference_type = ? AND reference_id = ?", referenceType, referenceID).
|
||||
First(&transaction).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &transaction, nil
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type RechargeStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewRechargeStore 创建充值订单 Store 实例
|
||||
func NewRechargeStore(db *gorm.DB, redis *redis.Client) *RechargeStore {
|
||||
return &RechargeStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建充值订单
|
||||
func (s *RechargeStore) Create(ctx context.Context, recharge *model.RechargeRecord) error {
|
||||
return s.db.WithContext(ctx).Create(recharge).Error
|
||||
}
|
||||
|
||||
// GetByRechargeNo 根据充值订单号查询充值订单
|
||||
// 不存在时返回 nil, nil
|
||||
func (s *RechargeStore) GetByRechargeNo(ctx context.Context, rechargeNo string) (*model.RechargeRecord, error) {
|
||||
var recharge model.RechargeRecord
|
||||
err := s.db.WithContext(ctx).Where("recharge_no = ?", rechargeNo).First(&recharge).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &recharge, nil
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 查询充值订单
|
||||
func (s *RechargeStore) GetByID(ctx context.Context, id uint) (*model.RechargeRecord, error) {
|
||||
var recharge model.RechargeRecord
|
||||
if err := s.db.WithContext(ctx).First(&recharge, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &recharge, nil
|
||||
}
|
||||
|
||||
// ListRechargeParams 充值订单列表查询参数
|
||||
type ListRechargeParams struct {
|
||||
Page int // 页码(从 1 开始)
|
||||
PageSize int // 每页数量
|
||||
UserID *uint // 用户 ID 筛选
|
||||
WalletID *uint // 钱包 ID 筛选
|
||||
Status *int // 状态筛选
|
||||
StartTime *time.Time // 开始时间
|
||||
EndTime *time.Time // 结束时间
|
||||
}
|
||||
|
||||
// List 查询充值订单列表(支持分页和筛选)
|
||||
func (s *RechargeStore) List(ctx context.Context, params *ListRechargeParams) ([]*model.RechargeRecord, int64, error) {
|
||||
var recharges []*model.RechargeRecord
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.RechargeRecord{})
|
||||
|
||||
// 应用筛选条件
|
||||
if params.UserID != nil {
|
||||
query = query.Where("user_id = ?", *params.UserID)
|
||||
}
|
||||
if params.WalletID != nil {
|
||||
query = query.Where("wallet_id = ?", *params.WalletID)
|
||||
}
|
||||
if params.Status != nil {
|
||||
query = query.Where("status = ?", *params.Status)
|
||||
}
|
||||
if params.StartTime != nil {
|
||||
query = query.Where("created_at >= ?", *params.StartTime)
|
||||
}
|
||||
if params.EndTime != nil {
|
||||
query = query.Where("created_at <= ?", *params.EndTime)
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
page := params.Page
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
pageSize := params.PageSize
|
||||
if pageSize < 1 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("id DESC").Offset(offset).Limit(pageSize).Find(&recharges).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return recharges, total, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新充值订单状态(支持乐观锁检查)
|
||||
// oldStatus: 原状态(用于乐观锁检查,传 nil 则跳过检查)
|
||||
// newStatus: 新状态
|
||||
// paidAt: 支付时间(状态变为已支付时传入)
|
||||
// completedAt: 完成时间(状态变为已完成时传入)
|
||||
func (s *RechargeStore) UpdateStatus(ctx context.Context, id uint, oldStatus *int, newStatus int, paidAt *time.Time, completedAt *time.Time) error {
|
||||
updates := map[string]interface{}{
|
||||
"status": newStatus,
|
||||
}
|
||||
if paidAt != nil {
|
||||
updates["paid_at"] = paidAt
|
||||
}
|
||||
if completedAt != nil {
|
||||
updates["completed_at"] = completedAt
|
||||
}
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.RechargeRecord{}).Where("id = ?", id)
|
||||
|
||||
// 乐观锁检查
|
||||
if oldStatus != nil {
|
||||
query = query.Where("status = ?", *oldStatus)
|
||||
}
|
||||
|
||||
result := query.Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePaymentInfo 更新支付信息
|
||||
func (s *RechargeStore) UpdatePaymentInfo(ctx context.Context, id uint, paymentChannel *string, paymentTransactionID *string) error {
|
||||
updates := map[string]interface{}{}
|
||||
if paymentChannel != nil {
|
||||
updates["payment_channel"] = paymentChannel
|
||||
}
|
||||
if paymentTransactionID != nil {
|
||||
updates["payment_transaction_id"] = paymentTransactionID
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := s.db.WithContext(ctx).Model(&model.RechargeRecord{}).Where("id = ?", id).Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WalletStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
func NewWalletStore(db *gorm.DB, redis *redis.Client) *WalletStore {
|
||||
return &WalletStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WalletStore) GetByResourceTypeAndID(ctx context.Context, resourceType string, resourceID uint, walletType string) (*model.Wallet, error) {
|
||||
var wallet model.Wallet
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("resource_type = ? AND resource_id = ? AND wallet_type = ?", resourceType, resourceID, walletType).
|
||||
First(&wallet).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
func (s *WalletStore) GetShopCommissionWallet(ctx context.Context, shopID uint) (*model.Wallet, error) {
|
||||
return s.GetByResourceTypeAndID(ctx, "shop", shopID, "commission")
|
||||
}
|
||||
|
||||
type ShopCommissionSummary struct {
|
||||
ShopID uint
|
||||
Balance int64
|
||||
FrozenBalance int64
|
||||
}
|
||||
|
||||
func (s *WalletStore) GetShopCommissionSummaryBatch(ctx context.Context, shopIDs []uint) (map[uint]*ShopCommissionSummary, error) {
|
||||
if len(shopIDs) == 0 {
|
||||
return make(map[uint]*ShopCommissionSummary), nil
|
||||
}
|
||||
|
||||
var wallets []model.Wallet
|
||||
err := s.db.WithContext(ctx).
|
||||
Where("resource_type = ? AND resource_id IN ? AND wallet_type = ?", "shop", shopIDs, "commission").
|
||||
Find(&wallets).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[uint]*ShopCommissionSummary)
|
||||
for _, w := range wallets {
|
||||
result[w.ResourceID] = &ShopCommissionSummary{
|
||||
ShopID: w.ResourceID,
|
||||
Balance: w.Balance,
|
||||
FrozenBalance: w.FrozenBalance,
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *WalletStore) GetByID(ctx context.Context, id uint) (*model.Wallet, error) {
|
||||
var wallet model.Wallet
|
||||
if err := s.db.WithContext(ctx).First(&wallet, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
func (s *WalletStore) DeductFrozenBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64) error {
|
||||
result := tx.WithContext(ctx).
|
||||
Model(&model.Wallet{}).
|
||||
Where("id = ? AND frozen_balance >= ?", walletID, amount).
|
||||
Updates(map[string]interface{}{
|
||||
"frozen_balance": gorm.Expr("frozen_balance - ?", amount),
|
||||
})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WalletStore) UnfreezeBalanceWithTx(ctx context.Context, tx *gorm.DB, walletID uint, amount int64) error {
|
||||
result := tx.WithContext(ctx).
|
||||
Model(&model.Wallet{}).
|
||||
Where("id = ? AND frozen_balance >= ?", walletID, amount).
|
||||
Updates(map[string]interface{}{
|
||||
"balance": gorm.Expr("balance + ?", amount),
|
||||
"frozen_balance": gorm.Expr("frozen_balance - ?", amount),
|
||||
})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WalletTransactionStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
func NewWalletTransactionStore(db *gorm.DB, redis *redis.Client) *WalletTransactionStore {
|
||||
return &WalletTransactionStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WalletTransactionStore) CreateWithTx(ctx context.Context, tx *gorm.DB, transaction *model.WalletTransaction) error {
|
||||
return tx.WithContext(ctx).Create(transaction).Error
|
||||
}
|
||||
|
||||
func (s *WalletTransactionStore) Create(ctx context.Context, transaction *model.WalletTransaction) error {
|
||||
return s.db.WithContext(ctx).Create(transaction).Error
|
||||
}
|
||||
|
||||
func (s *WalletTransactionStore) GetByID(ctx context.Context, id uint) (*model.WalletTransaction, error) {
|
||||
var tx model.WalletTransaction
|
||||
if err := s.db.WithContext(ctx).First(&tx, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tx, nil
|
||||
}
|
||||
@@ -28,16 +28,16 @@ type PollingTaskPayload struct {
|
||||
|
||||
// PollingHandler 轮询任务处理器
|
||||
type PollingHandler struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
gatewayClient *gateway.Client
|
||||
iotCardStore *postgres.IotCardStore
|
||||
concurrencyStore *postgres.PollingConcurrencyConfigStore
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore
|
||||
dataUsageRecordStore *postgres.DataUsageRecordStore
|
||||
packageUsageStore *postgres.PackageUsageStore
|
||||
usageService *packagepkg.UsageService
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
gatewayClient *gateway.Client
|
||||
iotCardStore *postgres.IotCardStore
|
||||
concurrencyStore *postgres.PollingConcurrencyConfigStore
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore
|
||||
dataUsageRecordStore *postgres.DataUsageRecordStore
|
||||
packageUsageStore *postgres.PackageUsageStore
|
||||
usageService *packagepkg.UsageService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewPollingHandler 创建轮询任务处理器
|
||||
@@ -925,14 +925,14 @@ func (h *PollingHandler) getCardWithCache(ctx context.Context, cardID uint) (*mo
|
||||
go func() {
|
||||
cacheCtx := context.Background()
|
||||
cacheData := map[string]any{
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"card_category": card.CardCategory,
|
||||
"real_name_status": card.RealNameStatus,
|
||||
"network_status": card.NetworkStatus,
|
||||
"carrier_id": card.CarrierID,
|
||||
"id": card.ID,
|
||||
"iccid": card.ICCID,
|
||||
"card_category": card.CardCategory,
|
||||
"real_name_status": card.RealNameStatus,
|
||||
"network_status": card.NetworkStatus,
|
||||
"carrier_id": card.CarrierID,
|
||||
"current_month_usage_mb": card.CurrentMonthUsageMB,
|
||||
"cached_at": time.Now().Unix(),
|
||||
"cached_at": time.Now().Unix(),
|
||||
}
|
||||
if card.SeriesID != nil {
|
||||
cacheData["series_id"] = *card.SeriesID
|
||||
|
||||
Reference in New Issue
Block a user