package task import ( "context" "strconv" "time" "go.uber.org/zap" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" ) // getCardWithCache 优先从 Redis 缓存获取卡信息,缓存 miss 时查 DB 并异步写缓存 func (b *PollingBase) getCardWithCache(ctx context.Context, cardID uint) (*model.IotCard, error) { key := constants.RedisPollingCardInfoKey(cardID) result, err := b.redis.HGetAll(ctx, key).Result() if err == nil && len(result) > 0 { return parseCardFromCache(cardID, result), nil } card, err := b.iotCardStore.GetByID(ctx, cardID) if err != nil { return nil, err } go writeCardToCache(b, key, card) return card, nil } // updateCardCache 更新 Redis 卡信息缓存中的指定字段 func (b *PollingBase) updateCardCache(ctx context.Context, cardID uint, updates map[string]any) { key := constants.RedisPollingCardInfoKey(cardID) args := make([]any, 0, len(updates)*2) for k, v := range updates { args = append(args, k, v) } if len(args) > 0 { if err := b.redis.HSet(ctx, key, args...).Err(); err != nil { b.logger.Warn("更新卡缓存失败", zap.Uint("card_id", cardID), zap.Error(err)) } } } // parseCardFromCache 从 Redis Hash 反序列化卡信息 func parseCardFromCache(cardID uint, result map[string]string) *model.IotCard { card := &model.IotCard{} card.ID = cardID if v, ok := result["iccid"]; ok { card.ICCID = v } if v, ok := result["card_category"]; ok { card.CardCategory = v } if v, ok := result["real_name_status"]; ok { if status, err := strconv.Atoi(v); err == nil { card.RealNameStatus = status } } if v, ok := result["network_status"]; ok { if status, err := strconv.Atoi(v); err == nil { card.NetworkStatus = status } } if v, ok := result["carrier_id"]; ok { if id, err := strconv.ParseUint(v, 10, 64); err == nil { card.CarrierID = uint(id) } } if v, ok := result["current_month_usage_mb"]; ok { if usage, err := strconv.ParseFloat(v, 64); err == nil { card.CurrentMonthUsageMB = usage } } if v, ok := result["last_gateway_reading_mb"]; ok { if reading, err := strconv.ParseFloat(v, 64); err == nil { card.LastGatewayReadingMB = reading } } if v, ok := result["data_usage_mb"]; ok { if usage, err := strconv.ParseInt(v, 10, 64); err == nil { card.DataUsageMB = usage } } if v, ok := result["stop_reason"]; ok { card.StopReason = v } if v, ok := result["is_standalone"]; ok { card.IsStandalone = v == "1" || v == "true" } else { card.IsStandalone = true } if v, ok := result["series_id"]; ok { if id, err := strconv.ParseUint(v, 10, 64); err == nil { seriesID := uint(id) card.SeriesID = &seriesID } } if v, ok := result["current_month_start_date"]; ok { if ts, err := strconv.ParseInt(v, 10, 64); err == nil { t := time.Unix(ts, 0) card.CurrentMonthStartDate = &t } } return card } // writeCardToCache 将卡信息异步写入 Redis 缓存(7 天 TTL) func writeCardToCache(b *PollingBase, key string, card *model.IotCard) { cacheCtx := context.Background() cacheData := map[string]any{ "id": card.ID, "iccid": card.ICCID, "card_category": card.CardCategory, "real_name_status": card.RealNameStatus, "network_status": card.NetworkStatus, "carrier_id": card.CarrierID, "current_month_usage_mb": card.CurrentMonthUsageMB, "last_gateway_reading_mb": card.LastGatewayReadingMB, "data_usage_mb": card.DataUsageMB, "stop_reason": card.StopReason, "is_standalone": boolToStr(card.IsStandalone), "cached_at": time.Now().Unix(), } if card.SeriesID != nil { cacheData["series_id"] = *card.SeriesID } if card.CurrentMonthStartDate != nil { cacheData["current_month_start_date"] = card.CurrentMonthStartDate.Unix() } pipe := b.redis.Pipeline() pipe.HSet(cacheCtx, key, cacheData) pipe.Expire(cacheCtx, key, 7*24*time.Hour) _, _ = pipe.Exec(cacheCtx) }