refactor: 流量系统重构 — 增量累加算法 + 日粒度缓冲 + 旧详单清理
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:
2026-03-30 09:59:30 +08:00
parent f5dd2ce4ab
commit cebcada950
44 changed files with 1149 additions and 1613 deletions

View File

@@ -35,11 +35,15 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateCarrierRequest) (*d
}
carrier := &model.Carrier{
CarrierCode: req.CarrierCode,
CarrierName: req.CarrierName,
CarrierType: req.CarrierType,
Description: req.Description,
Status: constants.StatusEnabled,
CarrierCode: req.CarrierCode,
CarrierName: req.CarrierName,
CarrierType: req.CarrierType,
Description: req.Description,
Status: constants.StatusEnabled,
DataResetDay: 1,
}
if req.DataResetDay != nil {
carrier.DataResetDay = *req.DataResetDay
}
if req.RealnameLinkType != nil {
carrier.RealnameLinkType = *req.RealnameLinkType
@@ -87,6 +91,9 @@ func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateCarrierReq
if req.Description != nil {
carrier.Description = *req.Description
}
if req.DataResetDay != nil {
carrier.DataResetDay = *req.DataResetDay
}
if req.RealnameLinkType != nil {
carrier.RealnameLinkType = *req.RealnameLinkType
}
@@ -189,6 +196,7 @@ func (s *Service) toResponse(c *model.Carrier) *dto.CarrierResponse {
CarrierName: c.CarrierName,
CarrierType: c.CarrierType,
Description: c.Description,
DataResetDay: c.DataResetDay,
RealnameLinkType: c.RealnameLinkType,
RealnameLinkTemplate: c.RealnameLinkTemplate,
Status: c.Status,

View File

@@ -0,0 +1,91 @@
// Package traffic 提供流量数据查询服务
// 合并 Redis 缓冲(今日)和 DB历史两段数据源对外提供统一的日粒度流量查询
package traffic
import (
"context"
"sort"
"strconv"
"time"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/redis/go-redis/v9"
)
// DailyUsageItem 日粒度流量记录
type DailyUsageItem struct {
Date string `json:"date"` // 日期,格式 YYYY-MM-DD
UsageMB float64 `json:"usage_mb"` // 当天累计使用流量(MB)
}
// QueryService 流量查询服务
// 合并 Redis当天实时和 DB历史落盘两段数据源
type QueryService struct {
redis *redis.Client
cardDailyUsageStore *postgres.CardDailyUsageStore
}
// NewQueryService 创建流量查询服务实例
func NewQueryService(redisClient *redis.Client, cardDailyUsageStore *postgres.CardDailyUsageStore) *QueryService {
return &QueryService{
redis: redisClient,
cardDailyUsageStore: cardDailyUsageStore,
}
}
// GetDailyUsage 查询卡在指定日期范围内的日粒度流量
// 今天的数据从 Redis 缓冲读取(可能尚未落盘),历史数据从 DB 读取,合并后按日期 ASC 排序返回
func (s *QueryService) GetDailyUsage(ctx context.Context, cardID uint, startDate, endDate time.Time) ([]*DailyUsageItem, error) {
now := time.Now()
todayStr := now.Format("2006-01-02")
// 判断结束日期是否包含今天
endDateStr := endDate.Format("2006-01-02")
includeToday := endDateStr >= todayStr
// 历史部分:从 DB 查询(排除今天,因为今天的数据可能还在 Redis 中)
dbEndDate := endDate
if includeToday {
// DB 只查到昨天,今天从 Redis 取
dbEndDate = now.AddDate(0, 0, -1)
}
var items []*DailyUsageItem
// 只有 startDate <= dbEndDate 时才查 DB
if !startDate.After(dbEndDate) {
records, err := s.cardDailyUsageStore.ListByCardIDAndDateRange(ctx, cardID, startDate, dbEndDate)
if err != nil {
return nil, err
}
for _, r := range records {
items = append(items, &DailyUsageItem{
Date: r.Date.Format("2006-01-02"),
UsageMB: r.UsageMB,
})
}
}
// 今天部分:从 Redis 读取
if includeToday && !startDate.After(now) {
key := constants.RedisCardDailyTrafficKey(cardID, todayStr)
val, err := s.redis.Get(ctx, key).Result()
if err == nil && val != "" {
if usageMB, parseErr := strconv.ParseFloat(val, 64); parseErr == nil && usageMB > 0 {
items = append(items, &DailyUsageItem{
Date: todayStr,
UsageMB: usageMB,
})
}
}
// Redis key 不存在redis.Nil时忽略表示今天暂无流量
}
// 按日期 ASC 排序
sort.Slice(items, func(i, j int) bool {
return items[i].Date < items[j].Date
})
return items, nil
}