关于上游流量同步覆盖修复,以及新增redis同步迁移
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m25s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m25s
This commit is contained in:
@@ -45,6 +45,16 @@ func NewPollingConfigManager(configStore *postgres.PollingConfigStore, redisClie
|
||||
// Load 加载配置到内存缓存并同步到 Redis
|
||||
// 加载失败时保留原缓存不清空,确保可用性
|
||||
func (m *PollingConfigManager) Load(ctx context.Context) error {
|
||||
return m.load(ctx, true)
|
||||
}
|
||||
|
||||
// LoadForInspection 加载配置到内存缓存但不同步 Redis。
|
||||
// 用于迁移 dry-run 等只读检查场景,避免检查动作产生外部副作用。
|
||||
func (m *PollingConfigManager) LoadForInspection(ctx context.Context) error {
|
||||
return m.load(ctx, false)
|
||||
}
|
||||
|
||||
func (m *PollingConfigManager) load(ctx context.Context, syncRedis bool) error {
|
||||
configs, err := m.configStore.ListEnabled(ctx)
|
||||
if err != nil {
|
||||
m.logger.Error("加载轮询配置失败", zap.Error(err))
|
||||
@@ -55,8 +65,12 @@ func (m *PollingConfigManager) Load(ctx context.Context) error {
|
||||
m.configs = configs
|
||||
m.mu.Unlock()
|
||||
|
||||
if syncErr := m.syncToRedis(ctx, configs); syncErr != nil {
|
||||
m.logger.Warn("同步配置到 Redis 失败", zap.Error(syncErr))
|
||||
if syncRedis && m.redis != nil {
|
||||
if syncErr := m.syncToRedis(ctx, configs); syncErr != nil {
|
||||
m.logger.Warn("同步配置到 Redis 失败", zap.Error(syncErr))
|
||||
}
|
||||
} else if syncRedis {
|
||||
m.logger.Warn("Redis 客户端为空,跳过轮询配置同步")
|
||||
}
|
||||
|
||||
m.logger.Info("轮询配置已加载", zap.Int("count", len(configs)))
|
||||
|
||||
@@ -123,6 +123,12 @@ func (p *PollingInitializer) GetProgress() InitProgress {
|
||||
}
|
||||
}
|
||||
|
||||
// InitCards 将指定卡批量写入轮询分片队列和卡缓存。
|
||||
// 用于迁移收尾等按批次重建场景;调用方负责决定卡集合和是否先清理旧队列成员。
|
||||
func (p *PollingInitializer) InitCards(ctx context.Context, cards []*model.IotCard) error {
|
||||
return p.initBatch(ctx, cards)
|
||||
}
|
||||
|
||||
// run 执行渐进式初始化
|
||||
func (p *PollingInitializer) run(ctx context.Context) {
|
||||
defer p.wg.Done()
|
||||
|
||||
@@ -113,6 +113,26 @@ func (m *PollingQueueManager) RemoveFromAllQueues(ctx context.Context, cardID ui
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveBatchFromCurrentShardQueues 批量移除卡在当前分片队列中的旧成员,并清理卡缓存。
|
||||
// 迁移收尾重建队列前使用,避免重复执行时残留旧任务类型或旧缓存。
|
||||
func (m *PollingQueueManager) RemoveBatchFromCurrentShardQueues(ctx context.Context, cardIDs []uint) error {
|
||||
if len(cardIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
pipe := m.redis.Pipeline()
|
||||
for _, cardID := range cardIDs {
|
||||
member := fmt.Sprintf("%d", cardID)
|
||||
shardID := int(cardID) % m.shardCount
|
||||
for _, taskType := range allTaskTypes {
|
||||
key := constants.RedisPollingShardQueueKey(shardID, taskType)
|
||||
pipe.ZRem(ctx, key, member)
|
||||
}
|
||||
pipe.Del(ctx, constants.RedisPollingCardInfoKey(cardID))
|
||||
}
|
||||
_, err := pipe.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// EnqueueManual 手动触发入队(List RPUSH,调度器优先消费)
|
||||
func (m *PollingQueueManager) EnqueueManual(ctx context.Context, cardID uint, taskType string) error {
|
||||
key := constants.RedisPollingManualQueueKey(taskType)
|
||||
|
||||
@@ -1499,6 +1499,7 @@ func (s *Service) RefreshCardDataFromGateway(ctx context.Context, iccid string)
|
||||
// 返回本次增量值(MB),用于触发套餐扣减
|
||||
func (s *Service) calculateRefreshFlowUpdates(card *model.IotCard, gatewayFlowMB float64, now time.Time, updates map[string]any) float64 {
|
||||
increment := gatewayFlowMB - card.LastGatewayReadingMB
|
||||
shouldUpdateGatewayReading := true
|
||||
|
||||
if increment < 0 {
|
||||
// 当前值比上次小,检查是否为上游运营商正常重置
|
||||
@@ -1518,11 +1519,13 @@ func (s *Service) calculateRefreshFlowUpdates(card *model.IotCard, gatewayFlowMB
|
||||
zap.Float64("gateway_flow", gatewayFlowMB),
|
||||
zap.Float64("last_reading", card.LastGatewayReadingMB))
|
||||
increment = 0
|
||||
shouldUpdateGatewayReading = false
|
||||
}
|
||||
}
|
||||
|
||||
// 始终更新网关读数基准
|
||||
updates["last_gateway_reading_mb"] = gatewayFlowMB
|
||||
if shouldUpdateGatewayReading {
|
||||
updates["last_gateway_reading_mb"] = gatewayFlowMB
|
||||
}
|
||||
|
||||
// 检测跨自然月
|
||||
currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
|
||||
@@ -109,7 +109,10 @@ func (h *PollingCarddataHandler) Handle(ctx context.Context, t *asynq.Task) erro
|
||||
|
||||
go h.base.insertDataUsageRecord(context.Background(), cardID, flowIncrementMB, now)
|
||||
|
||||
cacheUpdates := map[string]any{"last_gateway_reading_mb": gatewayFlowMB}
|
||||
cacheUpdates := map[string]any{}
|
||||
if reading, ok := updates["last_gateway_reading_mb"]; ok {
|
||||
cacheUpdates["last_gateway_reading_mb"] = reading
|
||||
}
|
||||
if isCrossMonth {
|
||||
// 跨月时同步更新缓存中的本月开始日期和本月用量,避免下次从缓存读取时再次误判跨月
|
||||
currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
@@ -150,6 +153,7 @@ func (h *PollingCarddataHandler) calculateFlowUpdates(card *model.IotCard, gatew
|
||||
updates := make(map[string]any)
|
||||
|
||||
increment := gatewayFlowMB - card.LastGatewayReadingMB
|
||||
shouldUpdateGatewayReading := true
|
||||
if increment < 0 {
|
||||
if isResetWindow(now, resetDay) {
|
||||
increment = gatewayFlowMB
|
||||
@@ -164,10 +168,13 @@ func (h *PollingCarddataHandler) calculateFlowUpdates(card *model.IotCard, gatew
|
||||
zap.Float64("gateway_flow", gatewayFlowMB),
|
||||
zap.Float64("last_reading", card.LastGatewayReadingMB))
|
||||
increment = 0
|
||||
shouldUpdateGatewayReading = false
|
||||
}
|
||||
}
|
||||
|
||||
updates["last_gateway_reading_mb"] = gatewayFlowMB
|
||||
if shouldUpdateGatewayReading {
|
||||
updates["last_gateway_reading_mb"] = gatewayFlowMB
|
||||
}
|
||||
|
||||
currentMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||||
isCrossMonth := card.CurrentMonthStartDate == nil ||
|
||||
|
||||
Reference in New Issue
Block a user