// 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 }