Files
junhong_cmp_fiber/openspec/changes/archive/2026-03-30-refactor-traffic-system/tasks.md
huang cebcada950
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m16s
refactor: 流量系统重构 — 增量累加算法 + 日粒度缓冲 + 旧详单清理
核心改造:
- 增量算法:流量计算从覆盖式改为增量累加(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字段、数据初始化、日流量表)
2026-03-30 09:59:30 +08:00

8.0 KiB
Raw Blame History

任务清单refactor-traffic-system

⚠️ 架构改造,涉及 DB 迁移 + 轮询核心路径修改,建议低峰期发布。 严格串行E-2增量算法→ E-1Redis 缓冲)→ E-3查询适配→ 清理。

任务组 1E-2 月流量改增量累加P2-27

1.1 DB 迁移 + Model 扩展

  • 1.1.1 新建迁移:ALTER TABLE tb_carrier ADD COLUMN data_reset_day INT NOT NULL DEFAULT 1;
  • 1.1.2 在同一迁移中 UPDATE 已有运营商:联通 data_reset_day=27,其余 =1(需确认现有运营商数据)
  • 1.1.3 新建迁移:ALTER TABLE tb_iot_card ADD COLUMN last_gateway_reading_mb FLOAT NOT NULL DEFAULT 0;
  • 1.1.4 internal/model/carrier.go 新增 DataResetDay int 字段
  • 1.1.5 internal/model/iot_card.go 新增 LastGatewayReadingMB float64 字段
  • 1.1.6 运营商相关 DTO 新增 DataResetDay 字段
  • 1.1.7 运营商 Create/Update Handler/Service 支持 data_reset_day 参数1-28 范围校验)
  • 1.1.8 执行迁移,通过 DBHub 确认字段已添加
  • 1.1.9 验证:go build ./... 编译通过

1.2 增量算法改写

  • 1.2.1 新增辅助方法 getCarrierResetDay(carrierID uint) int,从 DB/缓存读取运营商重置日
  • 1.2.2 新增辅助函数 isResetWindow(now time.Time, resetDay int) bool:窗口 = 重置日当天 + 前一天(容错网关延迟)。resetDay=27 时窗口含 26、27resetDay=1 时窗口含上月最后一天和 1 号
  • 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(不再 = gatewayFlowMBlast_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
  • 1.2.4 删除旧的 calculateFlowIncrement() 函数
  • 1.2.5 更新调用方:HandleCarddataCheck() 中改为使用合并后的返回值
    updates, flowIncrementMB := h.calculateFlowUpdates(card, gatewayFlowMB, now)
    
  • 1.2.6 验证:go build ./... 编译通过

1.3 数据初始化脚本

  • 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;
  • 1.3.2 通过 DBHub 验证执行结果:SELECT COUNT(*) FROM tb_iot_card WHERE last_gateway_reading_mb = 0 AND current_month_usage_mb > 0; 应为 0

任务组 2E-1 流量详单改日粒度缓冲P2-26

2.1 日流量表 + Model + Store + Redis Key

  • 2.1.1 新建迁移:
    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);
    
  • 2.1.2 新建 internal/model/card_daily_usage.go GORM ModelUsageMB 使用 float64 类型,对应 DB NUMERIC(12,2)
  • 2.1.3 新建 internal/store/postgres/card_daily_usage_store.go,实现 Upsert()覆盖语义ON CONFLICT DO UPDATE SET usage_mb = EXCLUDED.usage_mbListByCardIDAndDateRange() 方法。此 Store 同时供落盘任务2.3和查询层3.x使用
  • 2.1.4 在 pkg/constants/redis.go 新增 RedisCardDailyTrafficKey(cardID uint, date string) string
  • 2.1.5 在 pkg/constants/constants.go 新增 TaskTypeDailyTrafficFlush 常量
  • 2.1.6 执行迁移,通过 DBHub 确认表已创建
  • 2.1.7 验证:go build ./... 编译通过

2.2 insertDataUsageRecord 改造

  • 2.2.1 改写 insertDataUsageRecord():增量 <= 0 直接 return增量 > 0 时 INCRBYFLOAT 写 Redis + Expire 48h。不再写 tb_data_usage_record(直接替换,不双写)
  • 2.2.2 验证:go build ./... 编译通过

2.3 每日落盘任务

  • 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
    • 记录日志:落盘条数、耗时
  • 2.3.2 在 pkg/queue/handler.go 中注册落盘 Handler
  • 2.3.3 在 Asynq Scheduler 中配置每天凌晨 2 点Asia/Shanghai触发 TaskTypeDailyTrafficFlush,超时设为 5 分钟
  • 2.3.4 验证:go build ./... 编译通过

任务组 3E-3 流量查询层适配P2-31

3.1 TrafficQueryService + bootstrap 注册

  • 3.1.1 新建 internal/service/traffic/query_service.go,实现 GetDailyUsage(ctx, cardID, startDate, endDate) 方法:今天 → Redis历史 → DB使用 2.1.3 的 CardDailyUsageStore),合并排序返回
  • 3.1.2 在 bootstrap 中注册 TrafficQueryService

3.2 管理端接口适配

  • 3.2.1 更新 GET /api/admin/assets/:type/:id/realtime-statusservice/asset/service.goCurrentMonthUsageMB 语义从"上游原始值"变为"系统累计值",确认注释和 DTO description 更新
  • 3.2.2 更新 GET /api/admin/assets/resolve/:identifierhandler/admin/asset.goresolve 返回的流量概况使用新语义
  • 3.2.3 更新 GET /api/admin/assets/device/:id/realtime-statusservice/asset/service.go):设备级聚合绑定卡的 CurrentMonthUsageMB,确认聚合逻辑和注释更新

3.3 C端接口适配

  • 3.3.1 更新 GET /api/c/v1/asset/infohandler/app/client_asset.goC端首页资产信息中的流量数据使用新语义
  • 3.3.2 更新 GET /api/c/v1/asset/packageshandler/app/client_asset.go):套餐列表中的用量展示
  • 3.3.3 更新 GET /api/c/v1/asset/refreshhandler/app/client_asset.go):刷新后返回的流量数据
  • 3.3.4 更新 GET /api/admin/assets/:type/:id/packagesservice/asset/service.go):管理端套餐列表

3.4 DTO 和注释更新

  • 3.4.1 更新 AssetRealtimeStatusResponse.CurrentMonthUsageMB 的 description 标签:从"Gateway返回的自然月流量总量"改为"系统累计的自然月流量"
  • 3.4.2 更新 IotCard.CurrentMonthUsageMB 的 GORM comment同上
  • 3.4.3 验证:go build ./... 编译通过

任务组 4旧流量详单代码清理

  • 4.1 删除 internal/model/data_usage.goDataUsageRecord model
  • 4.2 删除 internal/store/postgres/data_usage_record_store.go
  • 4.3 清理 internal/bootstrap/stores.goservices.goworker_stores.goworker_services.gohandlers.goDataUsageRecord 相关引用
  • 4.4 清理 pkg/queue/types.goDataUsageRecord 相关字段
  • 4.5 清理 scripts/cleanup/main.gotb_data_usage_record 引用(改为 tb_card_daily_usage
  • 4.6 删除 internal/task/polling_handler.go 中旧的 insertDataUsageRecord 相关的已废弃代码(如有残留)
  • 4.7 验证:go build ./... 编译通过

收尾验证

  • 5.1 执行 go build ./...,确认全量编译通过
  • 5.2 通过 DBHub 确认新增表和字段存在
  • 5.3 通过 rg "RedisCardDailyTrafficKey\|TaskTypeDailyTrafficFlush\|TrafficQueryService\|CardDailyUsageStore" 确认新组件已连通
  • 5.4 通过 rg "DataUsageRecord\|data_usage_record" 确认旧代码已清理(只允许出现在迁移文件或注释中)