feat: 新增数据库迁移,重命名 device_no 为 virtual_no,新增 iot_card.virtual_no 和 package.virtual_ratio 字段
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m3s

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-14 18:27:28 +08:00
parent b5147d1acb
commit b9c3875c08
77 changed files with 5832 additions and 2393 deletions

View File

@@ -167,8 +167,9 @@ func (h *IotCardImportHandler) downloadAndParse(ctx context.Context, task *model
cards := make(model.CardListJSON, 0, len(parseResult.Cards))
for _, card := range parseResult.Cards {
cards = append(cards, model.CardItem{
ICCID: card.ICCID,
MSISDN: card.MSISDN,
ICCID: card.ICCID,
MSISDN: card.MSISDN,
VirtualNo: card.VirtualNo,
})
}
@@ -210,15 +211,16 @@ func (h *IotCardImportHandler) getCardsFromTask(task *model.IotCardImportTask) [
func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.IotCardImportTask, batch []model.CardItem, startLine int, result *importResult) {
type cardMeta struct {
line int
msisdn string
line int
msisdn string
virtualNo string
}
validCards := make([]model.CardItem, 0)
cardMetaMap := make(map[string]cardMeta)
for i, card := range batch {
line := startLine + i
cardMetaMap[card.ICCID] = cardMeta{line: line, msisdn: card.MSISDN}
cardMetaMap[card.ICCID] = cardMeta{line: line, msisdn: card.MSISDN, virtualNo: card.VirtualNo}
validationResult := validator.ValidateICCID(card.ICCID, task.CarrierType)
if !validationResult.Valid {
@@ -282,12 +284,56 @@ func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.Iot
return
}
iotCards := make([]*model.IotCard, 0, len(newCards))
now := time.Now()
// 批量检查 virtual_no 唯一性
virtualNos := make([]string, 0)
for _, card := range newCards {
if card.VirtualNo != "" {
virtualNos = append(virtualNos, card.VirtualNo)
}
}
existingVirtualNos := make(map[string]bool)
if len(virtualNos) > 0 {
existingVirtualNos, err = h.iotCardStore.ExistsByVirtualNoBatch(ctx, virtualNos)
if err != nil {
h.logger.Error("批量检查 virtual_no 是否存在失败",
zap.Error(err),
zap.Int("batch_size", len(virtualNos)),
)
}
}
// 批内去重:记录本批次已分配的 virtual_no
batchUsedVirtualNos := make(map[string]bool)
finalCards := make([]model.CardItem, 0, len(newCards))
for _, card := range newCards {
meta := cardMetaMap[card.ICCID]
if card.VirtualNo != "" {
if existingVirtualNos[card.VirtualNo] || batchUsedVirtualNos[card.VirtualNo] {
result.failedItems = append(result.failedItems, model.ImportResultItem{
Line: meta.line,
ICCID: card.ICCID,
MSISDN: meta.msisdn,
Reason: "virtual_no 已被占用: " + card.VirtualNo,
})
result.failCount++
continue
}
batchUsedVirtualNos[card.VirtualNo] = true
}
finalCards = append(finalCards, card)
}
if len(finalCards) == 0 {
return
}
iotCards := make([]*model.IotCard, 0, len(finalCards))
now := time.Now()
for _, card := range finalCards {
iotCard := &model.IotCard{
ICCID: card.ICCID,
MSISDN: card.MSISDN,
VirtualNo: card.VirtualNo,
CarrierID: task.CarrierID,
BatchNo: task.BatchNo,
Status: constants.IotCardStatusInStock,
@@ -308,7 +354,7 @@ func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.Iot
zap.Error(err),
zap.Int("batch_size", len(iotCards)),
)
for _, card := range newCards {
for _, card := range finalCards {
meta := cardMetaMap[card.ICCID]
result.failedItems = append(result.failedItems, model.ImportResultItem{
Line: meta.line,
@@ -321,9 +367,8 @@ func (h *IotCardImportHandler) processBatch(ctx context.Context, task *model.Iot
return
}
result.successCount += len(newCards)
result.successCount += len(finalCards)
// 通知轮询系统:批量卡已创建
if h.pollingCallback != nil {
h.pollingCallback.OnBatchCardsCreated(ctx, iotCards)
}