修正数据
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m55s

This commit is contained in:
2026-04-27 18:10:08 +08:00
parent bb33232b1b
commit fe4c545308
57 changed files with 4477 additions and 226 deletions

View File

@@ -0,0 +1,110 @@
# 流量模型统一字段替换矩阵
## 1. 目的
本文用于固化 `refactor-traffic-model-semantics` 变更前后的字段替换关系,作为后台 / C 端接口、OpenAPI 文档和手工回放的统一对照基线。
## 2. 统一后的核心字段
本次 contract 收口后,流量相关字段统一为:
| 字段 | 含义 |
|------|------|
| `current_package_usage_id` | 当前主套餐对应的套餐使用记录 ID无主套餐时为 `null` |
| `real_total_mb` | 套餐真实总量,内部来源 `tb_package_usage.data_limit_mb` |
| `real_used_mb` | 套餐真实已用,内部来源 `tb_package_usage.data_usage_mb` |
| `virtual_total_mb` | 套餐业务停机阈值,优先来源 `tb_package_usage.virtual_total_mb_snapshot` |
| `virtual_used_mb` | 展示已用量,公式 `min(real_used_mb * display_gain_ratio, real_total_mb)` |
| `reduction_pct` | 展示增幅比例,公式 `(real_total_mb / virtual_total_mb) - 1` |
## 3. 字段替换矩阵
### 3.1 后台 `GET /api/admin/assets/resolve/:identifier`
| 现状字段 | 现状来源 | 新字段 | 新语义 |
|---------|---------|--------|--------|
| `current_package` | 主套餐名称 | `current_package` | 保留,语义不变 |
| `package_total_mb` | `data_limit_mb / virtual_ratio` | `real_total_mb` | 真总量 |
| `package_used_mb` | `data_usage_mb / virtual_ratio` | `real_used_mb` | 真已用 |
| `package_remain_mb` | `(data_limit_mb - data_usage_mb) / virtual_ratio` | `virtual_total_mb` / `virtual_used_mb` | 不再返回 remain改为显式真/虚语义 |
| 无 | 无 | `current_package_usage_id` | 当前主套餐使用记录 ID |
| 无 | 无 | `reduction_pct` | 展示增幅比例 |
### 3.2 后台 `GET /api/admin/assets/:identifier/current-package`
| 现状字段 | 现状来源 | 新字段 | 新语义 |
|---------|---------|--------|--------|
| `data_limit_mb` | `tb_package_usage.data_limit_mb` | `real_total_mb` | 真总量 |
| `data_usage_mb` | `tb_package_usage.data_usage_mb` | `real_used_mb` | 真已用 |
| `virtual_limit_mb` | `data_limit_mb / virtual_ratio` | `virtual_total_mb` | 业务停机阈值 |
| `virtual_used_mb` | `data_usage_mb / virtual_ratio` | `virtual_used_mb` | 改为统一公式计算 |
| `virtual_remain_mb` | `(data_limit_mb - data_usage_mb) / virtual_ratio` | 删除 | 不再返回 |
| `virtual_ratio` | `tb_package.virtual_ratio` | `reduction_pct` | 只保留外部增幅比例 |
| 无 | 无 | `package_usage_id` | 保留既有字段 |
### 3.3 后台 `GET /api/admin/assets/:identifier/packages`
| 现状字段 | 现状来源 | 新字段 | 新语义 |
|---------|---------|--------|--------|
| `data_limit_mb` | `tb_package_usage.data_limit_mb` | `real_total_mb` | 真总量 |
| `data_usage_mb` | `tb_package_usage.data_usage_mb` | `real_used_mb` | 真已用 |
| `virtual_limit_mb` | `data_limit_mb / virtual_ratio` | `virtual_total_mb` | 业务停机阈值 |
| `virtual_used_mb` | `data_usage_mb / virtual_ratio` | `virtual_used_mb` | 统一公式 |
| `virtual_remain_mb` | `(data_limit_mb - data_usage_mb) / virtual_ratio` | 删除 | 不再返回 |
| `virtual_ratio` | `tb_package.virtual_ratio` | `reduction_pct` | 只保留外部比例 |
| `package_usage_id` | `tb_package_usage.id` | `package_usage_id` | 保留,作为记录主键 |
### 3.4 C 端 `GET /api/c/v1/asset/info`
| 现状字段 | 现状来源 | 新字段 | 新语义 |
|---------|---------|--------|--------|
| `current_package` | 主套餐名称 | `current_package` | 保留 |
| `package_total_mb` | 来自后台资产解析结果 | `real_total_mb` | 真总量 |
| `package_used_mb` | 来自后台资产解析结果 | `real_used_mb` | 真已用 |
| `package_remain_mb` | 来自后台资产解析结果 | `virtual_total_mb` / `virtual_used_mb` | 不再返回 remain |
| 无 | 无 | `current_package_usage_id` | 当前主套餐使用记录 ID |
| 无 | 无 | `reduction_pct` | 展示增幅比例 |
### 3.5 C 端 `GET /api/c/v1/asset/package-history`
| 现状字段 | 现状来源 | 新字段 | 新语义 |
|---------|---------|--------|--------|
| `data_limit_mb` | `tb_package_usage.data_limit_mb` | `real_total_mb` | 真总量 |
| `data_usage_mb` | `tb_package_usage.data_usage_mb` | `real_used_mb` | 真已用 |
| `virtual_limit_mb` | `data_limit_mb / virtual_ratio` | `virtual_total_mb` | 业务停机阈值 |
| `virtual_used_mb` | `data_usage_mb / virtual_ratio` | `virtual_used_mb` | 统一公式 |
| `virtual_remain_mb` | `(data_limit_mb - data_usage_mb) / virtual_ratio` | 删除 | 不再返回 |
| `virtual_ratio` | `tb_package.virtual_ratio` | `reduction_pct` | 只保留外部比例 |
| `package_usage_id` | `tb_package_usage.id` | `package_usage_id` | 保留 |
## 4. OpenAPI 对齐基线
当前 `docs/admin-openapi.yaml` 仍存在以下待淘汰字段定义:
- `package_total_mb`
- `package_used_mb`
- `package_remain_mb`
- `virtual_limit_mb`
- `virtual_remain_mb`
- `virtual_ratio`
本次变更完成后,后台 OpenAPI 需仅保留:
- `current_package_usage_id`
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
- `reduction_pct`
## 5. 路由基线
当前实际注册路由已经统一为 `:identifier` 口径:
- `GET /api/admin/assets/resolve/:identifier`
- `GET /api/admin/assets/:identifier/current-package`
- `GET /api/admin/assets/:identifier/packages`
- `GET /api/c/v1/asset/info`
- `GET /api/c/v1/asset/package-history`
因此,本次变更仅做字段契约收口,不调整上述路由路径。

View File

@@ -0,0 +1,140 @@
-- 流量模型统一改造手工验收脚本
-- 用途:验证 package_usage 快照、统一公式和 B/C 端 contract 改造后的数据库基线。
-- 1. 查看 package_usage 快照字段是否存在
SELECT
column_name,
data_type,
column_default,
is_nullable
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'tb_package_usage'
AND column_name IN (
'data_limit_mb',
'data_usage_mb',
'virtual_total_mb_snapshot',
'display_gain_ratio_snapshot',
'enable_virtual_data_snapshot'
)
ORDER BY column_name;
-- 2. 查看历史记录快照回填结果
SELECT
pu.id AS package_usage_id,
pu.package_id,
pu.data_limit_mb,
pu.data_usage_mb,
pu.virtual_total_mb_snapshot,
pu.display_gain_ratio_snapshot,
pu.enable_virtual_data_snapshot,
pu.status,
pu.master_usage_id,
pu.activated_at,
pu.expires_at
FROM tb_package_usage pu
ORDER BY pu.id DESC
LIMIT 50;
-- 3. 统一公式核验:启用虚流量时的五字段与 reduction_pct
SELECT
pu.id AS package_usage_id,
pu.data_limit_mb AS real_total_mb,
pu.data_usage_mb AS real_used_mb,
CASE
WHEN pu.enable_virtual_data_snapshot AND pu.virtual_total_mb_snapshot > 0 THEN pu.virtual_total_mb_snapshot
ELSE pu.data_limit_mb
END AS virtual_total_mb,
CASE
WHEN pu.enable_virtual_data_snapshot AND pu.virtual_total_mb_snapshot > 0
THEN LEAST(pu.data_usage_mb * pu.display_gain_ratio_snapshot, pu.data_limit_mb)
ELSE pu.data_usage_mb
END AS virtual_used_mb,
CASE
WHEN pu.enable_virtual_data_snapshot AND pu.virtual_total_mb_snapshot > 0
THEN pu.display_gain_ratio_snapshot - 1
ELSE 0
END AS reduction_pct
FROM tb_package_usage pu
ORDER BY pu.id DESC
LIMIT 50;
-- 4. 停机阈值核验:统一 effective_threshold_mb
SELECT
pu.id AS package_usage_id,
pu.data_limit_mb AS real_total_mb,
pu.data_usage_mb AS real_used_mb,
CASE
WHEN pu.enable_virtual_data_snapshot AND pu.virtual_total_mb_snapshot > 0 THEN pu.virtual_total_mb_snapshot
ELSE pu.data_limit_mb
END AS effective_threshold_mb,
CASE
WHEN pu.data_usage_mb >= CASE
WHEN pu.enable_virtual_data_snapshot AND pu.virtual_total_mb_snapshot > 0 THEN pu.virtual_total_mb_snapshot
ELSE pu.data_limit_mb
END THEN TRUE
ELSE FALSE
END AS is_depleted
FROM tb_package_usage pu
ORDER BY pu.id DESC
LIMIT 50;
-- 5. 样例一100 / 70 / 50 -> 71.43
WITH sample AS (
SELECT
100::bigint AS real_total_mb,
50::bigint AS real_used_mb,
70::bigint AS virtual_total_mb,
ROUND(100::numeric / 70::numeric, 6) AS display_gain_ratio
)
SELECT
real_total_mb,
real_used_mb,
virtual_total_mb,
display_gain_ratio,
ROUND(LEAST(real_used_mb * display_gain_ratio, real_total_mb)::numeric, 2) AS expected_virtual_used_mb,
ROUND((display_gain_ratio - 1)::numeric, 6) AS expected_reduction_pct
FROM sample;
-- 6. 样例二100 / 70 / 69 -> 98.57
WITH sample AS (
SELECT
100::bigint AS real_total_mb,
69::bigint AS real_used_mb,
70::bigint AS virtual_total_mb,
ROUND(100::numeric / 70::numeric, 6) AS display_gain_ratio
)
SELECT
real_total_mb,
real_used_mb,
virtual_total_mb,
display_gain_ratio,
ROUND(LEAST(real_used_mb * display_gain_ratio, real_total_mb)::numeric, 2) AS expected_virtual_used_mb,
ROUND((display_gain_ratio - 1)::numeric, 6) AS expected_reduction_pct
FROM sample;
-- 7. 样例三:未启用虚流量时应退化为真流量视图
WITH sample AS (
SELECT
100::bigint AS real_total_mb,
69::bigint AS real_used_mb,
FALSE AS enable_virtual_data_snapshot,
100::bigint AS virtual_total_mb_snapshot,
1.0::numeric AS display_gain_ratio_snapshot
)
SELECT
real_total_mb,
real_used_mb,
CASE
WHEN enable_virtual_data_snapshot AND virtual_total_mb_snapshot > 0 THEN virtual_total_mb_snapshot
ELSE real_total_mb
END AS expected_virtual_total_mb,
CASE
WHEN enable_virtual_data_snapshot AND virtual_total_mb_snapshot > 0 THEN LEAST(real_used_mb * display_gain_ratio_snapshot, real_total_mb)
ELSE real_used_mb
END AS expected_virtual_used_mb,
CASE
WHEN enable_virtual_data_snapshot AND virtual_total_mb_snapshot > 0 THEN display_gain_ratio_snapshot - 1
ELSE 0
END AS expected_reduction_pct
FROM sample;

View File

@@ -0,0 +1,149 @@
# 流量模型统一接口回放样例
## 1. 目的
本文用于给 `refactor-traffic-model-semantics` 提供固定回放样例,确保后台 / C 端接口、OpenAPI 和数据库公式在同一口径下验收。
## 2. 典型样例
### 样例 A100 / 70 / 50 → 71.43
- `real_total_mb = 100`
- `virtual_total_mb = 70`
- `real_used_mb = 50`
- `display_gain_ratio = 100 / 70 = 1.428571`
- `virtual_used_mb = min(50 * 1.428571, 100) = 71.43`
- `reduction_pct = 1.428571 - 1 = 0.428571`
### 样例 B100 / 70 / 69 → 98.57
- `real_total_mb = 100`
- `virtual_total_mb = 70`
- `real_used_mb = 69`
- `display_gain_ratio = 100 / 70 = 1.428571`
- `virtual_used_mb = min(69 * 1.428571, 100) = 98.57`
- `reduction_pct = 0.428571`
### 样例 C100 / 70 / 70 → 100
- `real_total_mb = 100`
- `virtual_total_mb = 70`
- `real_used_mb = 70`
- `virtual_used_mb = min(70 * 1.428571, 100) = 100`
- 该套餐应判定为耗尽
### 样例 D未启用虚流量退化为真流量视图
- `real_total_mb = 100`
- `virtual_total_mb = 100`
- `real_used_mb = 69`
- `virtual_used_mb = 69`
- `reduction_pct = 0`
## 3. 后台接口回放
### 3.1 资产解析
```bash
curl -X GET "http://localhost:8080/api/admin/assets/resolve/<identifier>" \
-H "Authorization: Bearer <admin-token>"
```
重点核对:
- `current_package_usage_id`
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
- `reduction_pct`
- 不再出现 `package_total_mb` / `package_used_mb` / `package_remain_mb`
### 3.2 当前主套餐
```bash
curl -X GET "http://localhost:8080/api/admin/assets/<identifier>/current-package" \
-H "Authorization: Bearer <admin-token>"
```
重点核对:
- 有主套餐时仅返回主套餐记录
- 无主套餐时返回 `200 + null`
- 记录字段包含 `package_usage_id + 统一五字段 + reduction_pct`
### 3.3 套餐历史
```bash
curl -X GET "http://localhost:8080/api/admin/assets/<identifier>/packages?page=1&page_size=20" \
-H "Authorization: Bearer <admin-token>"
```
重点核对:
- 列表每条记录都包含 `package_usage_id + 统一五字段 + reduction_pct`
- 不再出现 `virtual_limit_mb` / `virtual_remain_mb` / `virtual_ratio`
## 4. C 端接口回放
### 4.1 资产信息
```bash
curl -X GET "http://localhost:8080/api/c/v1/asset/info?identifier=<identifier>" \
-H "Authorization: Bearer <client-token>"
```
重点核对:
- 仅返回当前主套餐摘要
- 无主套餐时五字段为 `0``current_package_usage_id = null`
- 不再出现 `package_total_mb` / `package_used_mb` / `package_remain_mb`
### 4.2 套餐历史
```bash
curl -X GET "http://localhost:8080/api/c/v1/asset/package-history?identifier=<identifier>&page=1&page_size=20" \
-H "Authorization: Bearer <client-token>"
```
重点核对:
- 列表项统一返回 `package_usage_id + 统一五字段 + reduction_pct`
- `generation` 过滤仍然有效
- 不新增 `total.virtual_used_mb` 或等价聚合字段
## 5. OpenAPI 核对点
### 5.1 后台 `docs/admin-openapi.yaml`
必须包含:
- `current_package_usage_id`
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
- `reduction_pct`
必须移除:
- `package_total_mb`
- `package_used_mb`
- `package_remain_mb`
- `virtual_limit_mb`
- `virtual_remain_mb`
- `virtual_ratio`
## 6. 验收结论模板
每次回放后,按下述模板记录:
```markdown
- [ ] 样例 A100/70/50 → 71.43)通过
- [ ] 样例 B100/70/69 → 98.57)通过
- [ ] 样例 C100/70/70 → 100且耗尽通过
- [ ] 样例 D未启用虚流量退化通过
- [ ] 后台 resolve/current-package/packages 不再返回旧字段
- [ ] C 端 asset/info/package-history 不再返回旧字段
- [ ] OpenAPI 与 DTO 完全一致
```

View File

@@ -0,0 +1,634 @@
# 流量模型统一改造方案
## 1. 背景
当前项目中的流量逻辑同时存在以下几套口径:
1. **卡同步口径**
- `tb_iot_card.last_gateway_reading_mb`
- `tb_iot_card.current_month_usage_mb`
- `tb_iot_card.data_usage_mb`
2. **套餐使用口径**
- `tb_package_usage.data_limit_mb`
- `tb_package_usage.data_usage_mb`
3. **展示换算口径**
- `tb_package.virtual_ratio`
- B 端/C 端多个接口中对 `virtual_*` 字段的换算
这三套口径目前没有统一到同一业务模型上,导致以下问题:
1. 流量同步与流量展示不是同一套业务事实源。
2. 停机判断仍按真总量而不是按虚阈值判断。
3. 历史套餐展示依赖 `tb_package` 当前值,缺少使用记录快照。
4. 资产视角接口和套餐视角接口混用,前端不知道该信哪一个字段。
5. 已存在字段 `real_data_usage_mb``virtual_data_usage_mb` 没有进入主链路,语义噪音较大。
本方案的目标不是“小修展示口径”,而是**统一整个项目的流量业务模型**。
---
## 2. 核心结论
### 2.1 业务事实源统一
**结论:业务流量永远跟随套餐,不跟随卡。**
- **卡表(`tb_iot_card`** 只负责:
- 同步上游网关累计流量
- 计算增量
- 记录同步状态与同步时间
- 排查上游异常
- **套餐使用表(`tb_package_usage`** 才是:
- 停机判断事实源
- B 端资产流量展示事实源
- C 端流量展示事实源
- 套餐历史与当前套餐流量事实源
### 2.2 流量摘要主体统一
**结论:流量摘要的主体应为 `package_usage_id`,不是 `asset_id`,也不是 `package_id`。**
原因:
1. 同一个资产可能存在多条套餐使用记录(主套餐、加油包、历史套餐)。
2. 同一个 `package_id` 会被多个资产、多次购买复用。
3. 前端真正要看的,是某一条具体套餐使用记录当前的四个业务值。
因此,流量摘要应以 **`tb_package_usage.id`** 为主键进行查询和展示。
### 2.3 前端最终只看四个业务值
本次统一后的对外流量模型只保留四个业务值:
1. `real_total_mb`:真总量
2. `real_used_mb`:真已用
3. `virtual_total_mb`:虚总量(原始虚阈值)
4. `virtual_used_mb`:按比例换算后、与真总量同尺度的展示值
说明:
- 用户已确认:`virtual_total_mb` 使用**原始虚总量**,不是换算后的总量。
- 用户已确认:`virtual_used_mb` 使用**按比例换算后的展示值**。
---
## 3. 统一后的业务定义
### 3.1 套餐模板层(`tb_package`
套餐模板保留以下字段定义:
- `real_data_mb`:套餐真总量
- `virtual_data_mb`:套餐虚总量 / 虚阈值
- `enable_virtual_data`:是否启用虚流量
- `virtual_ratio`:内部展示倍率,公式为 `real_data_mb / virtual_data_mb`
### 3.2 套餐使用层(`tb_package_usage`
套餐使用记录作为最终业务事实源,统一定义为:
- `data_limit_mb`**真总量快照**
- `data_usage_mb`**真已用**
- `virtual_total_mb_snapshot`**虚总量快照**
- `display_gain_ratio_snapshot`**展示倍率快照**
- `enable_virtual_data_snapshot`**是否启用虚流量快照**
### 3.3 四个业务值公式
对于任意一条 `package_usage`,统一用以下公式计算:
#### 场景 A启用虚流量且 `virtual_total_mb_snapshot > 0`
- `real_total_mb = data_limit_mb`
- `real_used_mb = data_usage_mb`
- `virtual_total_mb = virtual_total_mb_snapshot`
- `virtual_used_mb = min(real_used_mb * display_gain_ratio_snapshot, real_total_mb)`
- `reduction_pct = display_gain_ratio_snapshot - 1`
#### 场景 B未启用虚流量
- `real_total_mb = data_limit_mb`
- `real_used_mb = data_usage_mb`
- `virtual_total_mb = data_limit_mb`
- `virtual_used_mb = real_used_mb`
说明:
1. 未启用虚流量时,虚流量视图退化为真流量视图,保证前端仍然能稳定收到四个值。
2. `virtual_used_mb` 的上限必须封顶到 `real_total_mb`,避免出现展示值超过真总量。
### 3.4 停机判断公式
统一后的停机判断必须基于套餐使用记录:
#### 场景 A启用虚流量且 `virtual_total_mb_snapshot > 0`
-`real_used_mb >= virtual_total_mb` 时,套餐视为已耗尽
#### 场景 B未启用虚流量
-`real_used_mb >= real_total_mb` 时,套餐视为已耗尽
这一定义与本次 OpenSpec 的典型样例一致:
-`100` / 虚 `70`:真已用到 `70` 时停机,展示 `virtual_used_mb = 100`
- 未启用虚流量:真已用到 `100` 时停机,展示 `virtual_used_mb = real_used_mb`
### 3.5 `virtual_data_mb` 校验规则
本次统一后的规则为:
1. `enable_virtual_data = true` 时,`virtual_data_mb` 必须大于 `0`
2. `enable_virtual_data = true` 时,`virtual_data_mb` 必须小于等于 `real_data_mb`
3. 未启用虚流量时,展示视图退化为真流量视图
---
## 4. 同步链路统一方案
### 4.1 卡同步链路保留,但降级为“同步事实链路”
当前同步链路的核心算法可以保留:
1. 从网关读取当前累计流量读数 `Used`
2. 使用 `Used - last_gateway_reading_mb` 计算本次增量
3. 遇到上游重置窗口时,将当前读数视为新周期增量
该链路应继续写入卡表:
- `tb_iot_card.last_gateway_reading_mb`
- `tb_iot_card.current_month_usage_mb`
- `tb_iot_card.current_month_start_date`
- `tb_iot_card.last_month_total_mb`
- `tb_iot_card.last_sync_time`
- `tb_iot_card.last_data_check_at`
### 4.2 卡同步增量如何进入套餐层
同步出的真流量增量必须继续扣减到 `tb_package_usage.data_usage_mb`
统一口径:
- **同步层永远只同步“真流量增量”**
- **套餐层永远只累加“真已用”**
- **虚流量不参与同步,只参与展示换算和停机阈值判断**
### 4.3 `tb_iot_card.data_usage_mb` 的处理
当前 `tb_iot_card.data_usage_mb` 表示卡生命周期累计流量,但它不是业务展示主来源。
本次建议:
- **保留字段**
- **降级为同步辅助/运维诊断字段**
- 不再作为 B 端/C 端业务展示的主流量来源
说明:
1. 当前该字段写入时直接使用 `int64(increment)`,存在小数截断。
2. 套餐层已通过 `UsageService` 的余量缓存机制处理小数累计,更适合作为业务事实源。
---
## 5. 展示链路统一方案
### 5.1 资产视角不再直接承载流量摘要
`asset` 本身不是流量摘要的正确主体,因为一个资产可能有多条套餐使用记录。
因此:
- `资产详情`
- `资产解析`
- `C 端资产信息`
不应再把流量摘要作为“资产级别唯一摘要”长期保留。
### 5.2 套餐视角承载流量摘要
流量摘要应绑定到具体的 `package_usage_id`
- B 端:资产套餐列表 / 当前套餐
- C 端:当前套餐 / 套餐历史 / 套餐流量详情
都应围绕 `package_usage_id` 返回四个业务值。
### 5.3 建议的接口模型
#### 方案 A现有套餐接口直接返回四值
- `GET /api/admin/assets/:identifier/packages`
- `GET /api/admin/assets/:identifier/current-package`
- `GET /api/c/v1/asset/package-history`
每条 `AssetPackageResponse` 直接返回:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
#### 方案 B新增“套餐流量摘要”接口
`package_usage_id` 查询单条套餐流量摘要,例如:
- `GET /api/admin/asset-packages/:package_usage_id/traffic-summary`
- `GET /api/c/v1/asset/package-usages/:package_usage_id/traffic-summary`
推荐做法:
1. 套餐列表接口仍返回四值,方便列表页直接展示
2. 同时提供单条摘要接口,供详情弹窗/详情页按 `package_usage_id` 精确查询
### 5.4 本次建议下线的旧展示字段
以下字段语义模糊,应从对外 DTO 中逐步移除:
- `package_total_mb`
- `package_used_mb`
- `package_remain_mb`
- `virtual_remain_mb`
替代为显式字段:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
---
## 6. 数据库改造方案
### 6.1 本次必须新增的字段
`tb_package_usage` 新增:
1. `virtual_total_mb_snapshot BIGINT NOT NULL DEFAULT 0`
2. `display_gain_ratio_snapshot DECIMAL(18,6) NOT NULL DEFAULT 1.0`
3. `enable_virtual_data_snapshot BOOLEAN NOT NULL DEFAULT FALSE`
### 6.2 为什么必须加快照字段
如果不加快照,会有三个问题:
1. 历史套餐展示依赖 `tb_package` 当前值,模板一改,历史流量就漂
2. 历史停机判断无法复现当时口径
3. 退款、历史订单、历史套餐详情都无法稳定回放
### 6.3 回填策略
对已有 `tb_package_usage` 数据执行回填:
#### 若 `tb_package.enable_virtual_data = true` 且 `tb_package.virtual_data_mb > 0`
- `virtual_total_mb_snapshot = tb_package.virtual_data_mb`
- `display_gain_ratio_snapshot = tb_package.virtual_ratio`
- `enable_virtual_data_snapshot = true`
#### 否则
- `virtual_total_mb_snapshot = data_limit_mb`
- `display_gain_ratio_snapshot = 1.0`
- `enable_virtual_data_snapshot = false`
### 6.4 现有字段暂不重命名
本次不建议直接重命名以下数据库字段:
- `tb_package_usage.data_limit_mb`
- `tb_package_usage.data_usage_mb`
原因:
1. 改名会牵涉大量 SQL、模型、历史脚本、迁移与兼容问题
2. 当前可以通过“统一文义 + 新增快照字段”先完成业务收口
本次统一后的文义为:
- `data_limit_mb` = 真总量
- `data_usage_mb` = 真已用
### 6.5 最终可删除字段
以下字段当前未进入主链路,本轮已在迁移中物理删除:
- `tb_package_usage.real_data_usage_mb`
- `tb_package_usage.virtual_data_usage_mb`
执行顺序:
1. 代码层先完全停止依赖它们
2. 再通过独立迁移删除数据库旧列
---
## 7. 代码改造清单
### 7.1 数据模型与迁移
需要改动:
- `migrations/`:新增 `tb_package_usage` 快照字段迁移与历史回填
- `internal/model/package.go`
改动内容:
1. `PackageUsage` 增加三类快照字段
2.`data_limit_mb``data_usage_mb` 的注释补充“真总量 / 真已用”的语义说明
3. 为后续删除 `real_data_usage_mb``virtual_data_usage_mb` 做准备
### 7.2 套餐创建与激活链路
需要改动:
- `internal/service/order/service.go`
- `internal/task/auto_purchase.go`
改动内容:
1. 创建 `PackageUsage` 时写入:
- `data_limit_mb = pkg.RealDataMB`
- `virtual_total_mb_snapshot`
- `display_gain_ratio_snapshot`
- `enable_virtual_data_snapshot`
2. 所有主套餐、加油包、自动购包路径保持一致
### 7.3 套餐配置校验
需要改动:
- `internal/service/package/service.go`
改动内容:
1. 保留“启用虚流量时必须填写虚流量”的校验
2. 收紧为 `virtual_data_mb > 0 且 <= real_data_mb`
3. `virtual_ratio` 继续使用 `real_data_mb / virtual_data_mb`
### 7.4 流量同步链路
需要改动:
- `internal/task/polling_carddata_handler.go`
- `internal/service/iot_card/service.go`
改动内容:
1. 核心同步算法保持不变
2. 明确注释与日志:该链路同步的是“真流量增量”
3. 保持将真增量扣减到套餐层
4. 不在该链路引入任何虚流量换算逻辑
### 7.5 套餐扣减链路
需要改动:
- `internal/service/package/usage_service.go`
改动内容:
1. `data_usage_mb` 继续作为真已用累加
2. 套餐是否“已用完”必须改为按“虚阈值快照或真总量”判断
3. 加油包 / 主套餐的耗尽逻辑全部统一到快照字段
4. 日记录 `PackageUsageDailyRecord` 继续记录真流量使用量
### 7.6 停机判断链路
需要改动:
- `internal/service/iot_card/stop_resume_service.go`
改动内容:
1. `isTrafficExhausted` 改为基于 `package_usage` 快照字段判断
2. 启用虚流量时,按 `real_used >= virtual_total_snapshot`
3. 未启用时,按 `real_used >= real_total`
### 7.7 套餐重置链路
需要改动:
- `internal/service/package/reset_service.go`
- `internal/store/postgres/package_usage_store.go`
改动内容:
1. 重置时继续清零 `data_usage_mb`
2. 重置后根据快照阈值恢复 `status=active`
3. 不引入额外的虚流量计数器
### 7.8 后台管理接口
需要改动:
- `internal/service/asset/service.go`
- `internal/model/dto/asset_dto.go`
- 如有前端依赖生成文档,还需同步更新 `docs/admin-openapi.yaml`
改动内容:
1. `AssetResolveResponse` 去掉资产级别模糊流量摘要字段
2. `AssetPackageResponse` 改为输出四个显式业务值:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
3. `packages` / `current-package` 统一按 `package_usage_id` 展示和计算
### 7.9 C 端接口
需要改动:
- `internal/handler/app/client_asset.go`
- `internal/model/dto/client_asset_dto.go`
- `internal/service/package/customer_view_service.go`
改动内容:
1. C 端资产信息页不再把资产本身当作唯一流量摘要主体
2. C 端套餐历史/当前套餐改为套餐视角四值
3. 如果需要单条摘要页,则新增按 `package_usage_id` 查询的接口
### 7.10 套餐商品展示联动确认
需要关注:
- `internal/handler/app/client_asset.go` 中可购套餐列表当前 `data_allowance` 在启用虚流量时直接展示 `virtual_data_mb`
该处不是本次核心链路,但建议联动确认:
1. 商品展示要展示真流量、虚流量,还是只展示一种
2. 若前端也要透明展示两种总量DTO 需补充显式字段
---
## 8. DTO 与接口建议
### 8.1 建议新增或替换的字段
建议在套餐相关 DTO 中统一使用:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
### 8.2 建议废弃的字段
建议废弃:
- `package_total_mb`
- `package_used_mb`
- `package_remain_mb`
- `virtual_limit_mb`
- `virtual_remain_mb`
- `data_limit_mb`(对外 DTO 层)
- `data_usage_mb`(对外 DTO 层)
说明:
数据库内部暂时仍可保留 `data_limit_mb` / `data_usage_mb`,但 DTO 层应改为语义明确的新字段。
### 8.3 接口职责建议
#### 卡实时状态接口
保留同步诊断字段,例如:
- `last_gateway_reading_mb`
- `current_month_usage_mb`
- `last_sync_time`
#### 套餐流量接口
只返回四个业务值,不再掺杂卡级同步字段。
---
## 9. 迁移与上线顺序
建议按以下顺序执行:
### 第一步:数据库准备
1. 新增 `tb_package_usage` 快照字段
2. 回填历史数据
3. 为后续删列保留可回滚窗口
### 第二步:写链路切换
1. 套餐激活/自动购包路径写入快照字段
2. 扣减逻辑与停机逻辑改为读取快照字段
### 第三步:读链路切换
1. B 端套餐接口改为返回四个业务值
2. C 端套餐接口改为返回四个业务值
3. 资产级模糊流量摘要降级或移除
### 第四步:前端联调
1. 管理后台改为只消费套餐视角四值
2. C 端改为只消费套餐视角四值
3. 如需套餐流量详情页,按 `package_usage_id` 查询
### 第五步:清理旧字段
1. 停止使用 `real_data_usage_mb` / `virtual_data_usage_mb`
2. 物理删除旧列
3. 对外移除旧 DTO 字段
---
## 10. 手工验证清单
本项目不写自动化测试,本次改造建议用以下方式手工验证:
### 10.1 PostgreSQL 验证
验证项:
1. `tb_package_usage` 快照字段是否已正确回填
2. 启用虚流量的套餐是否满足:
- `virtual_total_mb_snapshot = tb_package.virtual_data_mb`
- `display_gain_ratio_snapshot = tb_package.virtual_ratio`
3. 未启用虚流量的套餐是否满足:
- `virtual_total_mb_snapshot = data_limit_mb`
- `display_gain_ratio_snapshot = 1.0`
### 10.2 上游同步验证
验证项:
1. 轮询后 `tb_iot_card.last_gateway_reading_mb` 是否更新
2. 同步增量是否正确进入 `tb_package_usage.data_usage_mb`
3. 跨月与运营商重置窗口时增量是否正确
### 10.3 停机验证
验证项:
1.`100` / 虚 `70`:真已用到 `70` 时停机
2.`100` / 虚 `70` / 真已用 `69`:展示 `virtual_used_mb = 98.57`
3. 未启用虚流量:真已用到真总量时停机
### 10.4 展示验证
验证项:
1. B 端套餐列表与当前套餐返回四个业务值
2. C 端套餐信息返回四个业务值
3. 资产详情页不再混用卡同步流量与套餐业务流量
---
## 11. 本次改造范围边界
### 本次必须做
1. 套餐使用记录补快照
2. 同步、扣减、停机、展示统一到套餐层
3. DTO 和接口统一四个业务值
4. 资产流量视角切换为套餐视角
### 本次不建议强行做
1. 直接重命名数据库旧字段
2. 在本轮立即物理删除所有历史字段
3. 把卡同步辅助字段全部移除
原因:
1. 本次首要目标是统一业务口径,先消除逻辑混乱
2. 物理清理应放在口径稳定后单独做
---
## 12. 最终结论
本次流量统一改造的核心原则只有两条:
1. **同步看卡,业务看套餐**
2. **展示永远以 `package_usage_id` 为主体,不以资产为主体**
按本方案落地后:
1. 同步链路只负责真流量增量采集
2. 套餐链路负责真已用、停机阈值和四值展示
3. B 端和 C 端最终只消费:
- `real_total_mb`
- `real_used_mb`
- `virtual_total_mb`
- `virtual_used_mb`
这套模型可以同时覆盖:
1. 真总量大于虚阈值
2. 真总量小于虚阈值
3. 未启用虚流量
4. 历史套餐快照展示
5. 当前套餐与套餐历史流量统一展示