This commit is contained in:
@@ -67,6 +67,16 @@ func (s *DeviceImportTaskStore) UpdateStatus(ctx context.Context, id uint, statu
|
|||||||
return s.db.WithContext(ctx).Model(&model.DeviceImportTask{}).Where("id = ?", id).Updates(updates).Error
|
return s.db.WithContext(ctx).Model(&model.DeviceImportTask{}).Where("id = ?", id).Updates(updates).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateProgress 实时更新导入进度计数(不更新详情列表,用于批量处理中间状态)
|
||||||
|
func (s *DeviceImportTaskStore) UpdateProgress(ctx context.Context, id uint, successCount, skipCount, failCount int) error {
|
||||||
|
return s.db.WithContext(ctx).Model(&model.DeviceImportTask{}).Where("id = ?", id).Updates(map[string]any{
|
||||||
|
"success_count": successCount,
|
||||||
|
"skip_count": skipCount,
|
||||||
|
"fail_count": failCount,
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DeviceImportTaskStore) UpdateResult(ctx context.Context, id uint, totalCount, successCount, skipCount, failCount, warningCount int, skippedItems, failedItems, warningItems model.ImportResultItems) error {
|
func (s *DeviceImportTaskStore) UpdateResult(ctx context.Context, id uint, totalCount, successCount, skipCount, failCount, warningCount int, skippedItems, failedItems, warningItems model.ImportResultItems) error {
|
||||||
updates := map[string]any{
|
updates := map[string]any{
|
||||||
"total_count": totalCount,
|
"total_count": totalCount,
|
||||||
|
|||||||
@@ -72,6 +72,16 @@ func (s *IotCardImportTaskStore) UpdateStatus(ctx context.Context, id uint, stat
|
|||||||
return s.db.WithContext(ctx).Model(&model.IotCardImportTask{}).Where("id = ?", id).Updates(updates).Error
|
return s.db.WithContext(ctx).Model(&model.IotCardImportTask{}).Where("id = ?", id).Updates(updates).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateProgress 实时更新导入进度计数(不更新详情列表,用于批量处理中间状态)
|
||||||
|
func (s *IotCardImportTaskStore) UpdateProgress(ctx context.Context, id uint, successCount, skipCount, failCount int) error {
|
||||||
|
return s.db.WithContext(ctx).Model(&model.IotCardImportTask{}).Where("id = ?", id).Updates(map[string]interface{}{
|
||||||
|
"success_count": successCount,
|
||||||
|
"skip_count": skipCount,
|
||||||
|
"fail_count": failCount,
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *IotCardImportTaskStore) UpdateResult(ctx context.Context, id uint, successCount, skipCount, failCount int, skippedItems, failedItems model.ImportResultItems) error {
|
func (s *IotCardImportTaskStore) UpdateResult(ctx context.Context, id uint, successCount, skipCount, failCount int, skippedItems, failedItems model.ImportResultItems) error {
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
"success_count": successCount,
|
"success_count": successCount,
|
||||||
|
|||||||
@@ -185,6 +185,8 @@ func (h *DeviceImportHandler) processImport(ctx context.Context, task *model.Dev
|
|||||||
end := min(i+deviceBatchSize, len(rows))
|
end := min(i+deviceBatchSize, len(rows))
|
||||||
batch := rows[i:end]
|
batch := rows[i:end]
|
||||||
h.processBatch(ctx, task, batch, result)
|
h.processBatch(ctx, task, batch, result)
|
||||||
|
// 每批完成后实时更新进度计数,让前端可以看到处理进度
|
||||||
|
h.importTaskStore.UpdateProgress(ctx, task.ID, result.successCount, result.skipCount, result.failCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -41,15 +41,14 @@ type PollingCallback interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IotCardImportHandler struct {
|
type IotCardImportHandler struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
redis *redis.Client
|
redis *redis.Client
|
||||||
importTaskStore *postgres.IotCardImportTaskStore
|
importTaskStore *postgres.IotCardImportTaskStore
|
||||||
iotCardStore *postgres.IotCardStore
|
iotCardStore *postgres.IotCardStore
|
||||||
assetWalletStore *postgres.AssetWalletStore
|
assetWalletStore *postgres.AssetWalletStore
|
||||||
assetIdentifierStore *postgres.AssetIdentifierStore
|
storageService *storage.Service
|
||||||
storageService *storage.Service
|
pollingCallback PollingCallback
|
||||||
pollingCallback PollingCallback
|
logger *zap.Logger
|
||||||
logger *zap.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIotCardImportHandler(
|
func NewIotCardImportHandler(
|
||||||
@@ -58,21 +57,19 @@ func NewIotCardImportHandler(
|
|||||||
importTaskStore *postgres.IotCardImportTaskStore,
|
importTaskStore *postgres.IotCardImportTaskStore,
|
||||||
iotCardStore *postgres.IotCardStore,
|
iotCardStore *postgres.IotCardStore,
|
||||||
assetWalletStore *postgres.AssetWalletStore,
|
assetWalletStore *postgres.AssetWalletStore,
|
||||||
assetIdentifierStore *postgres.AssetIdentifierStore,
|
|
||||||
storageSvc *storage.Service,
|
storageSvc *storage.Service,
|
||||||
pollingCallback PollingCallback,
|
pollingCallback PollingCallback,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) *IotCardImportHandler {
|
) *IotCardImportHandler {
|
||||||
return &IotCardImportHandler{
|
return &IotCardImportHandler{
|
||||||
db: db,
|
db: db,
|
||||||
redis: redis,
|
redis: redis,
|
||||||
importTaskStore: importTaskStore,
|
importTaskStore: importTaskStore,
|
||||||
iotCardStore: iotCardStore,
|
iotCardStore: iotCardStore,
|
||||||
assetWalletStore: assetWalletStore,
|
assetWalletStore: assetWalletStore,
|
||||||
assetIdentifierStore: assetIdentifierStore,
|
storageService: storageSvc,
|
||||||
storageService: storageSvc,
|
pollingCallback: pollingCallback,
|
||||||
pollingCallback: pollingCallback,
|
logger: logger,
|
||||||
logger: logger,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +215,8 @@ func (h *IotCardImportHandler) processImport(ctx context.Context, task *model.Io
|
|||||||
end := min(i+batchSize, len(cards))
|
end := min(i+batchSize, len(cards))
|
||||||
batch := cards[i:end]
|
batch := cards[i:end]
|
||||||
h.processBatch(ctx, task, batch, i+1, result)
|
h.processBatch(ctx, task, batch, i+1, result)
|
||||||
|
// 每批完成后实时更新进度计数,让前端可以看到处理进度
|
||||||
|
h.importTaskStore.UpdateProgress(ctx, task.ID, result.successCount, result.skipCount, result.failCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -341,7 +340,14 @@ func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.Iot
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
createdCards := make([]*model.IotCard, 0, len(finalCards))
|
|
||||||
|
// 构建待批量写入的卡列表,同时记录原始信息用于失败回溯
|
||||||
|
type pendingEntry struct {
|
||||||
|
item model.CardItem
|
||||||
|
meta cardMeta
|
||||||
|
}
|
||||||
|
pending := make([]pendingEntry, 0, len(finalCards))
|
||||||
|
iotCards := make([]*model.IotCard, 0, len(finalCards))
|
||||||
|
|
||||||
for _, card := range finalCards {
|
for _, card := range finalCards {
|
||||||
meta := cardMetaMap[card.ICCID]
|
meta := cardMetaMap[card.ICCID]
|
||||||
@@ -386,43 +392,63 @@ func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.Iot
|
|||||||
iotCard.CreatedAt = now
|
iotCard.CreatedAt = now
|
||||||
iotCard.UpdatedAt = now
|
iotCard.UpdatedAt = now
|
||||||
|
|
||||||
txErr := h.db.Transaction(func(tx *gorm.DB) error {
|
iotCards = append(iotCards, iotCard)
|
||||||
if err := tx.Create(iotCard).Error; err != nil {
|
pending = append(pending, pendingEntry{item: card, meta: meta})
|
||||||
return err
|
|
||||||
}
|
|
||||||
txIdentifierStore := postgres.NewAssetIdentifierStore(tx)
|
|
||||||
if err := txIdentifierStore.Register(ctx, card.ICCID, model.AssetTypeIotCard, iotCard.ID); err != nil {
|
|
||||||
return fmt.Errorf("ICCID 已被占用: %s", card.ICCID)
|
|
||||||
}
|
|
||||||
if card.VirtualNo != "" {
|
|
||||||
if err := txIdentifierStore.Register(ctx, card.VirtualNo, model.AssetTypeIotCard, iotCard.ID); err != nil {
|
|
||||||
return fmt.Errorf("虚拟号已被占用: %s", card.VirtualNo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if txErr != nil {
|
|
||||||
h.logger.Error("创建 IoT 卡失败",
|
|
||||||
zap.String("iccid", card.ICCID),
|
|
||||||
zap.Error(txErr),
|
|
||||||
)
|
|
||||||
result.failedItems = append(result.failedItems, model.ImportResultItem{
|
|
||||||
Line: meta.line,
|
|
||||||
ICCID: card.ICCID,
|
|
||||||
MSISDN: meta.msisdn,
|
|
||||||
Reason: txErr.Error(),
|
|
||||||
})
|
|
||||||
result.failCount++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
createdCards = append(createdCards, iotCard)
|
|
||||||
result.successCount++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h.batchCreateWallets(ctx, createdCards)
|
if len(iotCards) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 整批一个事务:批量写入 iot_card + asset_identifier
|
||||||
|
// 相比逐卡事务,将 N 个事务压缩为 1 个,大幅减少数据库往返
|
||||||
|
txErr := h.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// 批量插入 iot_card,GORM 会通过 RETURNING 回填所有 ID
|
||||||
|
if err := tx.CreateInBatches(&iotCards, 500).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 用回填的 ID 构建 asset_identifier 列表
|
||||||
|
identifiers := make([]*model.AssetIdentifier, 0, len(iotCards)*2)
|
||||||
|
for _, c := range iotCards {
|
||||||
|
identifiers = append(identifiers, &model.AssetIdentifier{
|
||||||
|
Identifier: c.ICCID,
|
||||||
|
AssetType: model.AssetTypeIotCard,
|
||||||
|
AssetID: c.ID,
|
||||||
|
})
|
||||||
|
if c.VirtualNo != "" {
|
||||||
|
identifiers = append(identifiers, &model.AssetIdentifier{
|
||||||
|
Identifier: c.VirtualNo,
|
||||||
|
AssetType: model.AssetTypeIotCard,
|
||||||
|
AssetID: c.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tx.CreateInBatches(&identifiers, 500).Error
|
||||||
|
})
|
||||||
|
|
||||||
|
if txErr != nil {
|
||||||
|
h.logger.Error("批量创建 IoT 卡失败",
|
||||||
|
zap.Error(txErr),
|
||||||
|
zap.Int("batch_size", len(iotCards)),
|
||||||
|
)
|
||||||
|
for _, p := range pending {
|
||||||
|
result.failedItems = append(result.failedItems, model.ImportResultItem{
|
||||||
|
Line: p.meta.line,
|
||||||
|
ICCID: p.item.ICCID,
|
||||||
|
MSISDN: p.meta.msisdn,
|
||||||
|
Reason: "批量写入失败: " + txErr.Error(),
|
||||||
|
})
|
||||||
|
result.failCount++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result.successCount += len(iotCards)
|
||||||
|
|
||||||
|
h.batchCreateWallets(ctx, iotCards)
|
||||||
|
|
||||||
if h.pollingCallback != nil {
|
if h.pollingCallback != nil {
|
||||||
h.pollingCallback.OnBatchCardsCreated(ctx, createdCards)
|
h.pollingCallback.OnBatchCardsCreated(ctx, iotCards)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ func (h *Handler) registerIotCardImportHandler() {
|
|||||||
h.workerResult.Stores.IotCardImportTask,
|
h.workerResult.Stores.IotCardImportTask,
|
||||||
h.workerResult.Stores.IotCard,
|
h.workerResult.Stores.IotCard,
|
||||||
h.workerResult.Stores.AssetWallet,
|
h.workerResult.Stores.AssetWallet,
|
||||||
h.workerResult.Stores.AssetIdentifier,
|
|
||||||
h.storage,
|
h.storage,
|
||||||
h.pollingCallback,
|
h.pollingCallback,
|
||||||
h.logger,
|
h.logger,
|
||||||
|
|||||||
Reference in New Issue
Block a user