fix: 资产钱包自动创建机制 — 修复C端购买时钱包不存在报错
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m8s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m8s
- client_order: 新增 getOrCreateWallet 兜底,钱包不存在时自动创建 - device_import: 设备导入事务内同步创建设备钱包 - iot_card_import: IoT卡批量导入后批量创建卡钱包 - queue/handler: 传递 AssetWalletStore 给两个导入 handler - migration 000098: 为存量IoT卡和设备补建资产钱包
This commit is contained in:
@@ -333,12 +333,9 @@ func (s *Service) createForceRechargeOrder(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wallet, err := s.walletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID)
|
||||
wallet, err := s.getOrCreateWallet(ctx, resourceType, resourceID, resolveSellerShopID(validationResult))
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeWalletNotFound, "钱包不存在")
|
||||
}
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产钱包失败")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkedPackageIDs, err := sonic.Marshal(extractPackageIDs(validationResult.Packages))
|
||||
@@ -558,6 +555,46 @@ func buildLinkedPackageInfo(result *purchase_validation.PurchaseValidationResult
|
||||
}
|
||||
}
|
||||
|
||||
// getOrCreateWallet 获取或自动创建资产钱包
|
||||
// 兜底机制:当钱包不存在时自动创建,避免因钱包未提前创建导致业务中断
|
||||
func (s *Service) getOrCreateWallet(ctx context.Context, resourceType string, resourceID uint, shopIDTag uint) (*model.AssetWallet, error) {
|
||||
wallet, err := s.walletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID)
|
||||
if err == nil {
|
||||
return wallet, nil
|
||||
}
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产钱包失败")
|
||||
}
|
||||
|
||||
// 钱包不存在,自动创建空钱包
|
||||
s.logger.Info("资产钱包不存在,自动创建",
|
||||
zap.String("resource_type", resourceType),
|
||||
zap.Uint("resource_id", resourceID),
|
||||
zap.Uint("shop_id_tag", shopIDTag),
|
||||
)
|
||||
|
||||
newWallet := &model.AssetWallet{
|
||||
ResourceType: resourceType,
|
||||
ResourceID: resourceID,
|
||||
Balance: 0,
|
||||
FrozenBalance: 0,
|
||||
Currency: "CNY",
|
||||
Status: constants.AssetWalletStatusNormal,
|
||||
Version: 0,
|
||||
ShopIDTag: shopIDTag,
|
||||
}
|
||||
if createErr := s.walletStore.Create(ctx, newWallet); createErr != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, createErr, "自动创建资产钱包失败")
|
||||
}
|
||||
|
||||
// 重新查询获取完整数据(含数据库生成的字段)
|
||||
wallet, err = s.walletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产钱包失败")
|
||||
}
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
func resolveWalletResource(result *purchase_validation.PurchaseValidationResult) (string, uint, error) {
|
||||
if result.Card != nil {
|
||||
return constants.AssetWalletResourceTypeIotCard, result.Card.ID, nil
|
||||
|
||||
@@ -34,6 +34,7 @@ type DeviceImportHandler struct {
|
||||
deviceStore *postgres.DeviceStore
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
assetWalletStore *postgres.AssetWalletStore
|
||||
storageService *storage.Service
|
||||
logger *zap.Logger
|
||||
}
|
||||
@@ -45,6 +46,7 @@ func NewDeviceImportHandler(
|
||||
deviceStore *postgres.DeviceStore,
|
||||
deviceSimBindingStore *postgres.DeviceSimBindingStore,
|
||||
iotCardStore *postgres.IotCardStore,
|
||||
assetWalletStore *postgres.AssetWalletStore,
|
||||
storageSvc *storage.Service,
|
||||
logger *zap.Logger,
|
||||
) *DeviceImportHandler {
|
||||
@@ -55,6 +57,7 @@ func NewDeviceImportHandler(
|
||||
deviceStore: deviceStore,
|
||||
deviceSimBindingStore: deviceSimBindingStore,
|
||||
iotCardStore: iotCardStore,
|
||||
assetWalletStore: assetWalletStore,
|
||||
storageService: storageSvc,
|
||||
logger: logger,
|
||||
}
|
||||
@@ -294,6 +297,21 @@ func (h *DeviceImportHandler) processBatch(ctx context.Context, task *model.Devi
|
||||
}
|
||||
}
|
||||
|
||||
// 创建设备资产钱包(设备存在即应有钱包,避免购买时才发现缺失)
|
||||
txWalletStore := postgres.NewAssetWalletStore(tx, nil)
|
||||
deviceWallet := &model.AssetWallet{
|
||||
ResourceType: constants.AssetWalletResourceTypeDevice,
|
||||
ResourceID: device.ID,
|
||||
Balance: 0,
|
||||
FrozenBalance: 0,
|
||||
Currency: "CNY",
|
||||
Status: constants.AssetWalletStatusNormal,
|
||||
Version: 0,
|
||||
}
|
||||
if err := txWalletStore.Create(ctx, deviceWallet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
@@ -41,13 +41,14 @@ type PollingCallback interface {
|
||||
}
|
||||
|
||||
type IotCardImportHandler struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
importTaskStore *postgres.IotCardImportTaskStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
storageService *storage.Service
|
||||
pollingCallback PollingCallback
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
importTaskStore *postgres.IotCardImportTaskStore
|
||||
iotCardStore *postgres.IotCardStore
|
||||
assetWalletStore *postgres.AssetWalletStore
|
||||
storageService *storage.Service
|
||||
pollingCallback PollingCallback
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewIotCardImportHandler(
|
||||
@@ -55,18 +56,20 @@ func NewIotCardImportHandler(
|
||||
redis *redis.Client,
|
||||
importTaskStore *postgres.IotCardImportTaskStore,
|
||||
iotCardStore *postgres.IotCardStore,
|
||||
assetWalletStore *postgres.AssetWalletStore,
|
||||
storageSvc *storage.Service,
|
||||
pollingCallback PollingCallback,
|
||||
logger *zap.Logger,
|
||||
) *IotCardImportHandler {
|
||||
return &IotCardImportHandler{
|
||||
db: db,
|
||||
redis: redis,
|
||||
importTaskStore: importTaskStore,
|
||||
iotCardStore: iotCardStore,
|
||||
storageService: storageSvc,
|
||||
pollingCallback: pollingCallback,
|
||||
logger: logger,
|
||||
db: db,
|
||||
redis: redis,
|
||||
importTaskStore: importTaskStore,
|
||||
iotCardStore: iotCardStore,
|
||||
assetWalletStore: assetWalletStore,
|
||||
storageService: storageSvc,
|
||||
pollingCallback: pollingCallback,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +372,44 @@ func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.Iot
|
||||
|
||||
result.successCount += len(finalCards)
|
||||
|
||||
// 为导入的卡批量创建资产钱包(钱包创建失败不影响卡导入结果,后续访问时 getOrCreateWallet 兜底)
|
||||
h.batchCreateWallets(ctx, iotCards)
|
||||
|
||||
if h.pollingCallback != nil {
|
||||
h.pollingCallback.OnBatchCardsCreated(ctx, iotCards)
|
||||
}
|
||||
}
|
||||
|
||||
// batchCreateWallets 批量为 IoT 卡创建资产钱包
|
||||
func (h *IotCardImportHandler) batchCreateWallets(ctx context.Context, cards []*model.IotCard) {
|
||||
if h.assetWalletStore == nil {
|
||||
return
|
||||
}
|
||||
|
||||
wallets := make([]*model.AssetWallet, 0, len(cards))
|
||||
for _, card := range cards {
|
||||
if card.ID == 0 {
|
||||
continue
|
||||
}
|
||||
wallets = append(wallets, &model.AssetWallet{
|
||||
ResourceType: constants.AssetWalletResourceTypeIotCard,
|
||||
ResourceID: card.ID,
|
||||
Balance: 0,
|
||||
FrozenBalance: 0,
|
||||
Currency: "CNY",
|
||||
Status: constants.AssetWalletStatusNormal,
|
||||
Version: 0,
|
||||
})
|
||||
}
|
||||
|
||||
if len(wallets) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.WithContext(ctx).Create(&wallets).Error; err != nil {
|
||||
h.logger.Warn("批量创建 IoT 卡资产钱包失败,后续访问时将自动创建",
|
||||
zap.Int("count", len(wallets)),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user