docs: 记录换货迁移导致主套餐双生效的生产数据修正
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
133
docs/data-fix/2026-11-04-换货迁移套餐双生效数据修正.md
Normal file
133
docs/data-fix/2026-11-04-换货迁移套餐双生效数据修正.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# 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`。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
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;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第二步:调整排队顺序
|
||||||
|
|
||||||
|
```sql
|
||||||
|
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` 递增);
|
||||||
|
- 或者在换货前检查,直接拒绝,提示运营先处理。
|
||||||
|
- **同类问题未排查。** 生产中可能还有其他卡或设备同时挂着多个生效中的主套餐,尚未全量排查。
|
||||||
Reference in New Issue
Block a user