refactor: 流量系统重构 — 增量累加算法 + 日粒度缓冲 + 旧详单清理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m16s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m16s
核心改造: - 增量算法:流量计算从覆盖式改为增量累加(gateway - lastReading),支持上游运营商重置检测 - 日流量缓冲:insertDataUsageRecord 改为 Redis INCRBYFLOAT,每日凌晨落盘到 tb_card_daily_usage - 运营商:新增 data_reset_day 字段(联通=27,其余=1) - IoT卡:新增 last_gateway_reading_mb 字段存储上次网关读数 - 查询层:新建 TrafficQueryService 合并 Redis(今日)+ DB(历史)数据源 - 清理:删除 DataUsageRecord model/store,移除 polling_handler 旧引用 迁移:000094-000097(carrier字段、iot_card字段、数据初始化、日流量表)
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# 任务清单:refactor-traffic-system
|
||||
|
||||
> ⚠️ 架构改造,涉及 DB 迁移 + 轮询核心路径修改,建议低峰期发布。
|
||||
> 严格串行:E-2(增量算法)→ E-1(Redis 缓冲)→ E-3(查询适配)→ 清理。
|
||||
|
||||
## 任务组 1:E-2 月流量改增量累加(P2-27)
|
||||
|
||||
### 1.1 DB 迁移 + Model 扩展
|
||||
|
||||
- [x] 1.1.1 新建迁移:`ALTER TABLE tb_carrier ADD COLUMN data_reset_day INT NOT NULL DEFAULT 1;`
|
||||
- [x] 1.1.2 在同一迁移中 UPDATE 已有运营商:联通 `data_reset_day=27`,其余 `=1`(需确认现有运营商数据)
|
||||
- [x] 1.1.3 新建迁移:`ALTER TABLE tb_iot_card ADD COLUMN last_gateway_reading_mb FLOAT NOT NULL DEFAULT 0;`
|
||||
- [x] 1.1.4 `internal/model/carrier.go` 新增 `DataResetDay int` 字段
|
||||
- [x] 1.1.5 `internal/model/iot_card.go` 新增 `LastGatewayReadingMB float64` 字段
|
||||
- [x] 1.1.6 运营商相关 DTO 新增 `DataResetDay` 字段
|
||||
- [x] 1.1.7 运营商 Create/Update Handler/Service 支持 `data_reset_day` 参数(1-28 范围校验)
|
||||
- [x] 1.1.8 执行迁移,通过 DBHub 确认字段已添加
|
||||
- [x] 1.1.9 验证:`go build ./...` 编译通过
|
||||
|
||||
### 1.2 增量算法改写
|
||||
|
||||
- [x] 1.2.1 新增辅助方法 `getCarrierResetDay(carrierID uint) int`,从 DB/缓存读取运营商重置日
|
||||
- [x] 1.2.2 新增辅助函数 `isResetWindow(now time.Time, resetDay int) bool`:窗口 = 重置日当天 + 前一天(容错网关延迟)。`resetDay=27` 时窗口含 26、27;`resetDay=1` 时窗口含上月最后一天和 1 号
|
||||
- [x] 1.2.3 **合并** `calculateFlowUpdates()` 和 `calculateFlowIncrement()` 为一个函数,签名改为 `calculateFlowUpdates(card, gatewayFlowMB, now) (map[string]any, float64)`,返回 `(updates, increment)`:
|
||||
- 计算增量 `increment = gatewayFlowMB - card.LastGatewayReadingMB`
|
||||
- 检测上游运营商重置(increment < 0 时用 `isResetWindow` 判断)
|
||||
- 检测跨自然月:跨月时 `current_month_usage_mb = 0`(不再 `= gatewayFlowMB`),`last_month_total_mb = 旧值`
|
||||
- 正常增量:`current_month_usage_mb` 改为 `gorm.Expr("current_month_usage_mb + ?", increment)`
|
||||
- 更新 `data_usage_mb`(卡生命周期总用量):`gorm.Expr("data_usage_mb + ?", int64(increment))`
|
||||
- 始终更新 `last_gateway_reading_mb = gatewayFlowMB`
|
||||
- [x] 1.2.4 删除旧的 `calculateFlowIncrement()` 函数
|
||||
- [x] 1.2.5 更新调用方:`HandleCarddataCheck()` 中改为使用合并后的返回值
|
||||
```go
|
||||
updates, flowIncrementMB := h.calculateFlowUpdates(card, gatewayFlowMB, now)
|
||||
```
|
||||
- [x] 1.2.6 验证:`go build ./...` 编译通过
|
||||
|
||||
### 1.3 数据初始化脚本
|
||||
|
||||
- [x] 1.3.1 新建迁移文件(或独立 SQL 脚本):`UPDATE tb_iot_card SET last_gateway_reading_mb = current_month_usage_mb WHERE last_gateway_reading_mb = 0 AND current_month_usage_mb > 0;`
|
||||
- [x] 1.3.2 通过 DBHub 验证执行结果:`SELECT COUNT(*) FROM tb_iot_card WHERE last_gateway_reading_mb = 0 AND current_month_usage_mb > 0;` 应为 0
|
||||
|
||||
## 任务组 2:E-1 流量详单改日粒度缓冲(P2-26)
|
||||
|
||||
### 2.1 日流量表 + Model + Store + Redis Key
|
||||
|
||||
- [x] 2.1.1 新建迁移:
|
||||
```sql
|
||||
CREATE TABLE tb_card_daily_usage (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
iot_card_id BIGINT NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
usage_mb NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CONSTRAINT uk_card_daily UNIQUE (iot_card_id, date)
|
||||
);
|
||||
CREATE INDEX idx_card_daily_date ON tb_card_daily_usage (date);
|
||||
```
|
||||
- [x] 2.1.2 新建 `internal/model/card_daily_usage.go` GORM Model(`UsageMB` 使用 `float64` 类型,对应 DB `NUMERIC(12,2)`)
|
||||
- [x] 2.1.3 新建 `internal/store/postgres/card_daily_usage_store.go`,实现 `Upsert()`(覆盖语义,ON CONFLICT DO UPDATE SET usage_mb = EXCLUDED.usage_mb)和 `ListByCardIDAndDateRange()` 方法。**此 Store 同时供落盘任务(2.3)和查询层(3.x)使用**
|
||||
- [x] 2.1.4 在 `pkg/constants/redis.go` 新增 `RedisCardDailyTrafficKey(cardID uint, date string) string`
|
||||
- [x] 2.1.5 在 `pkg/constants/constants.go` 新增 `TaskTypeDailyTrafficFlush` 常量
|
||||
- [x] 2.1.6 执行迁移,通过 DBHub 确认表已创建
|
||||
- [x] 2.1.7 验证:`go build ./...` 编译通过
|
||||
|
||||
### 2.2 insertDataUsageRecord 改造
|
||||
|
||||
- [x] 2.2.1 改写 `insertDataUsageRecord()`:增量 <= 0 直接 return;增量 > 0 时 `INCRBYFLOAT` 写 Redis + `Expire` 48h。**不再写 `tb_data_usage_record`**(直接替换,不双写)
|
||||
- [x] 2.2.2 验证:`go build ./...` 编译通过
|
||||
|
||||
### 2.3 每日落盘任务
|
||||
|
||||
- [x] 2.3.1 新建 `internal/task/daily_traffic_flush.go`,实现落盘 Handler:
|
||||
- 分批 SCAN `traffic:daily:*:{昨日}`(每批 COUNT 500)
|
||||
- 解析 card_id 和 date,收集所有 entry
|
||||
- 分批 UPSERT 到 `tb_card_daily_usage`(每批 200 条,使用 2.1.3 的 `CardDailyUsageStore.Upsert()`,覆盖语义保证幂等)
|
||||
- 分批 Pipeline 删除已落盘的 Redis key
|
||||
- 记录日志:落盘条数、耗时
|
||||
- [x] 2.3.2 在 `pkg/queue/handler.go` 中注册落盘 Handler
|
||||
- [x] 2.3.3 在 Asynq Scheduler 中配置每天凌晨 2 点(Asia/Shanghai)触发 `TaskTypeDailyTrafficFlush`,超时设为 5 分钟
|
||||
- [x] 2.3.4 验证:`go build ./...` 编译通过
|
||||
|
||||
## 任务组 3:E-3 流量查询层适配(P2-31)
|
||||
|
||||
### 3.1 TrafficQueryService + bootstrap 注册
|
||||
|
||||
- [x] 3.1.1 新建 `internal/service/traffic/query_service.go`,实现 `GetDailyUsage(ctx, cardID, startDate, endDate)` 方法:今天 → Redis,历史 → DB(使用 2.1.3 的 `CardDailyUsageStore`),合并排序返回
|
||||
- [x] 3.1.2 在 bootstrap 中注册 `TrafficQueryService`
|
||||
|
||||
### 3.2 管理端接口适配
|
||||
|
||||
- [x] 3.2.1 更新 `GET /api/admin/assets/:type/:id/realtime-status`(`service/asset/service.go`):`CurrentMonthUsageMB` 语义从"上游原始值"变为"系统累计值",确认注释和 DTO description 更新
|
||||
- [x] 3.2.2 更新 `GET /api/admin/assets/resolve/:identifier`(`handler/admin/asset.go`):resolve 返回的流量概况使用新语义
|
||||
- [x] 3.2.3 更新 `GET /api/admin/assets/device/:id/realtime-status`(`service/asset/service.go`):设备级聚合绑定卡的 `CurrentMonthUsageMB`,确认聚合逻辑和注释更新
|
||||
|
||||
### 3.3 C端接口适配
|
||||
|
||||
- [x] 3.3.1 更新 `GET /api/c/v1/asset/info`(`handler/app/client_asset.go`):C端首页资产信息中的流量数据使用新语义
|
||||
- [x] 3.3.2 更新 `GET /api/c/v1/asset/packages`(`handler/app/client_asset.go`):套餐列表中的用量展示
|
||||
- [x] 3.3.3 更新 `GET /api/c/v1/asset/refresh`(`handler/app/client_asset.go`):刷新后返回的流量数据
|
||||
- [x] 3.3.4 更新 `GET /api/admin/assets/:type/:id/packages`(`service/asset/service.go`):管理端套餐列表
|
||||
|
||||
### 3.4 DTO 和注释更新
|
||||
|
||||
- [x] 3.4.1 更新 `AssetRealtimeStatusResponse.CurrentMonthUsageMB` 的 description 标签:从"Gateway返回的自然月流量总量"改为"系统累计的自然月流量"
|
||||
- [x] 3.4.2 更新 `IotCard.CurrentMonthUsageMB` 的 GORM comment:同上
|
||||
- [x] 3.4.3 验证:`go build ./...` 编译通过
|
||||
|
||||
## 任务组 4:旧流量详单代码清理
|
||||
|
||||
- [x] 4.1 删除 `internal/model/data_usage.go`(`DataUsageRecord` model)
|
||||
- [x] 4.2 删除 `internal/store/postgres/data_usage_record_store.go`
|
||||
- [x] 4.3 清理 `internal/bootstrap/stores.go`、`services.go`、`worker_stores.go`、`worker_services.go`、`handlers.go` 中 `DataUsageRecord` 相关引用
|
||||
- [x] 4.4 清理 `pkg/queue/types.go` 中 `DataUsageRecord` 相关字段
|
||||
- [x] 4.5 清理 `scripts/cleanup/main.go` 中 `tb_data_usage_record` 引用(改为 `tb_card_daily_usage`)
|
||||
- [x] 4.6 删除 `internal/task/polling_handler.go` 中旧的 `insertDataUsageRecord` 相关的已废弃代码(如有残留)
|
||||
- [x] 4.7 验证:`go build ./...` 编译通过
|
||||
|
||||
## 收尾验证
|
||||
|
||||
- [x] 5.1 执行 `go build ./...`,确认全量编译通过
|
||||
- [x] 5.2 通过 DBHub 确认新增表和字段存在
|
||||
- [x] 5.3 通过 `rg "RedisCardDailyTrafficKey\|TaskTypeDailyTrafficFlush\|TrafficQueryService\|CardDailyUsageStore"` 确认新组件已连通
|
||||
- [x] 5.4 通过 `rg "DataUsageRecord\|data_usage_record"` 确认旧代码已清理(只允许出现在迁移文件或注释中)
|
||||
Reference in New Issue
Block a user