Files
junhong_cmp_fiber/docs/data-fix/2026-11-04-换货迁移套餐双生效数据修正.md
2026-11-04 13:19:04 +09:00

6.5 KiB
Raw Blame History

2026-11-04 换货迁移导致卡上两个主套餐同时生效的数据修正

现象

卡 89860624630055040571(tb_iot_card.id = 93937)在后台的预计套餐到期时间显示"数据异常"(expiry_estimate_status = invalid_data)。

原因

1. 换货迁移把老卡的生效中套餐原样挂到了新卡上

2026-11-03 17:42,换货单 EXC20261103174239616885(tb_exchange_order.id = 1094)完成直接换货:

  • 老卡:89860624630055027198(id = 93846),停机、未实名,从未产生流量。
  • 新卡:89860624630055040571(id = 93937)。
  • 选了迁移数据。换货原因是"未实名卡,给老卡续费"。

internal/service/exchange/migration.go 的 migratePackageUsageWithTx 只把老卡套餐使用记录的 iot_card_id 改成新卡,状态原样保留,也不检查新卡是否已有生效中的主套餐。迁移后新卡上同时有两个生效中(status = 1)的主套餐:

套餐使用记录 套餐 订单号 修正前状态 到期时间
47051 Mifi-U省内版(12个月省内流量每月3000G),package_id = 272 MIG-89860624630055040571-1 生效中 2026-12-14
46981 12个月省内流量每月1000G,package_id = 230 MIG-89860624630055027198-1 生效中,activated_at 为空 2027-12-14

46981 的到期时间正好是 47051 到期后再加一年,说明运营的本意是"续费,接在现有套餐后面"。

internal/query/packageexpiry/query.go 的 Calculate 要求一个资产只能有一个生效中或已用完的主套餐。出现两个时返回 invalid_data。

2. 流量扣到了续费的套餐上

换货后,新卡的流量全部扣到了 46981 上,47051 从 11-03 17:40 起没有再被扣过:

日期 47051 日用量 46981 日用量
11-01 941 MB
11-02 255 MB
11-03 5,393 MB 52 MB
11-04 2,707 MB

合计 9,348 MB,和新卡 11 月的 current_month_usage_mb = 9348.33 一致。老卡本月和上月的用量都是 0,所以 46981 上的用量全部来自新卡。

3. 第一次修正后仍显示数据异常:排序问题

46981 改成待生效后,页面仍然显示数据异常。原因是两条记录的 priority 都是 1,created_at 也完全相同(同一次迁移写入,都是 2026-09-02 16:40:32.81965),排序只能按 id,46981 就排在了 47051 前面。Calculate 不允许待生效套餐排在当前套餐之前,所以仍然判成 invalid_data。把 46981 的 priority 改成 2 后,它排到了 47051 后面。

已执行的修正(生产库,2026-11-04)

两步都在单个事务中执行:先锁行,改完后自动校验,校验不通过就整体回滚。

第一步:用量转回 47051,46981 改为待生效

执行前:46981 的 data_usage_mb = 2759,47051 的 data_usage_mb = 112102。

BEGIN;
SELECT id, status, data_usage_mb, iot_card_id FROM tb_package_usage WHERE id IN (46981, 47051) ORDER BY id FOR UPDATE;

-- 46981 的已用量转回 47051
UPDATE tb_package_usage a
SET data_usage_mb = a.data_usage_mb + b.data_usage_mb, updated_at = now()
FROM tb_package_usage b
WHERE a.id = 47051 AND b.id = 46981 AND a.status = 1 AND b.status = 1 AND a.iot_card_id = 93937 AND b.iot_card_id = 93937;

-- 日用量记录并到 47051
INSERT INTO tb_package_usage_daily_record (package_usage_id, date, daily_usage_mb, cumulative_usage_mb, created_at, updated_at)
SELECT 47051, d.date, d.daily_usage_mb, 0, now(), now()
FROM tb_package_usage_daily_record d WHERE d.package_usage_id = 46981
ON CONFLICT (package_usage_id, date) DO UPDATE
SET daily_usage_mb = tb_package_usage_daily_record.daily_usage_mb + EXCLUDED.daily_usage_mb, updated_at = now();

-- 以 11-02 的累计值为起点,重算 47051 从 11-03 起的累计用量
UPDATE tb_package_usage_daily_record r SET cumulative_usage_mb = x.cum, updated_at = now()
FROM (
  SELECT id,
         (SELECT cumulative_usage_mb FROM tb_package_usage_daily_record WHERE package_usage_id = 47051 AND date = '2026-11-02')
         + sum(daily_usage_mb) OVER (ORDER BY date) AS cum
  FROM tb_package_usage_daily_record
  WHERE package_usage_id = 47051 AND date >= '2026-11-03'
) x
WHERE r.id = x.id;

DELETE FROM tb_package_usage_daily_record WHERE package_usage_id = 46981;

-- 46981 改为待生效,和现有待生效套餐的数据形态一致;激活时不会把已用量清零,所以这里必须清零
UPDATE tb_package_usage
SET status = 0, data_usage_mb = 0, activated_at = NULL, expires_at = NULL,
    last_reset_at = NULL, next_reset_at = NULL, updated_at = now()
WHERE id = 46981 AND status = 1 AND iot_card_id = 93937;
-- 校验项:47051 的已用量等于两者之和;46981 是待生效、已用量为 0;
-- 47051 最后一天的累计用量等于它的已用量;46981 已经没有日用量记录
COMMIT;

第二步:调整排队顺序

BEGIN;
UPDATE tb_package_usage SET priority = 2, updated_at = now()
WHERE id = 46981 AND status = 0 AND iot_card_id = 93937 AND priority = 1;
COMMIT;

修正后的状态

套餐使用记录 状态 优先级 已用量 生效时间 到期时间 下次重置
47051 生效中 1 114,861 MB 2025-12-14 2026-12-14 2026-11-09
46981 待生效 2 0 空 空 空

47051 的日用量记录:11-03 为 5,445 MB,累计 112,154 MB;11-04 为 2,707 MB,累计 114,861 MB。

预期效果:

  • 后续流量扣到 47051 上。
  • 预计到期时间推算为约 2027-12-14:47051 到期后,46981 由排队激活接上 365 天。

回滚

一般不需要回滚。如果必须恢复到修正前的状态:

  1. 46981 恢复为 status = 1、priority = 1、data_usage_mb = 2759、expires_at = '2027-12-14 00:00:00'、last_reset_at = '2026-10-18 01:23:14.86183'、next_reset_at = '2026-11-17 00:00:00'。
  2. 47051 的 data_usage_mb 减去 2759。
  3. 日用量记录拆回原样:
    • 46981:11-03 为 52/52,11-04 为 2707/2759;
    • 47051:11-03 为 5393/112102,并删除 47051 的 11-04 记录。

回滚前,要先确认修正之后又扣进 47051 的新用量。

遗留问题

  • 代码漏洞未修。 换货迁移不处理新资产已有生效中主套餐的情况,以后同类换货还会再出现两个主套餐同时生效。可选的修法:
    • 迁移过来的主套餐改为待生效,排在新资产现有套餐之后(priority 递增);
    • 或者在换货前检查,直接拒绝,提示运营先处理。
  • 同类问题未排查。 生产中可能还有其他卡或设备同时挂着多个生效中的主套餐,尚未全量排查。