fix: 修正轮询配置多匹配逻辑,支持同卡匹配多个配置并按 priority 合并 interval
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m27s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m27s
核心变更: - MatchConfig 改为 MatchConfigs,返回所有匹配配置 - MergedTaskIntervals 按 task type 合并各配置,选取最高优先级(非 nil 且最小 priority 值) - hasAnyEnabledInterval 过滤所有 interval 均为 NULL 的配置 - calcInitialDelay 重构为纯函数,接收 interval 参数 - 移除 getEnabledTaskTypes 和 getIntervalByTaskType(被 MergedTaskIntervals 替代) - scheduler.go 新增心跳 key + 顶层 panic recovery + Init 完成守卫 - initializer.go 批量失败日志升级为 Error,逐条检查 Pipeline 命令错误 - 数据迁移:禁用 id=29 的轮询配置(所有 interval 均为 NULL) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -157,7 +157,8 @@ func (p *PollingInitializer) run(ctx context.Context) {
|
||||
}
|
||||
|
||||
if initErr := p.initBatch(ctx, cards); initErr != nil {
|
||||
p.logger.Warn("批量初始化失败", zap.Error(initErr))
|
||||
p.logger.Error("批量初始化失败,该批次卡未入轮询队列",
|
||||
zap.Uint("batch_start_id", lastID), zap.Error(initErr))
|
||||
}
|
||||
|
||||
lastID = cards[len(cards)-1].ID
|
||||
@@ -206,16 +207,24 @@ func (p *PollingInitializer) initBatch(ctx context.Context, cards []*model.IotCa
|
||||
if cmdCount == 0 {
|
||||
return
|
||||
}
|
||||
if _, execErr := pipe.Exec(ctx); execErr != nil {
|
||||
p.logger.Warn("Pipeline flush 失败,继续下一批", zap.Error(execErr))
|
||||
cmds, execErr := pipe.Exec(ctx)
|
||||
if execErr != nil {
|
||||
p.logger.Error("Pipeline flush 失败,部分卡可能未入队", zap.Error(execErr))
|
||||
}
|
||||
// 逐条检查,记录具体失败命令(不中断批次)
|
||||
for _, cmd := range cmds {
|
||||
if cmdErr := cmd.Err(); cmdErr != nil && cmdErr != redis.Nil {
|
||||
p.logger.Warn("Pipeline 单条命令失败",
|
||||
zap.String("cmd", cmd.Name()), zap.Error(cmdErr))
|
||||
}
|
||||
}
|
||||
pipe = p.redis.Pipeline()
|
||||
cmdCount = 0
|
||||
}
|
||||
|
||||
for _, card := range cards {
|
||||
cfg := p.configMgr.MatchConfig(card)
|
||||
if cfg == nil {
|
||||
intervals := p.configMgr.MergedTaskIntervals(card)
|
||||
if len(intervals) == 0 {
|
||||
skippedCards++
|
||||
continue
|
||||
}
|
||||
@@ -224,37 +233,10 @@ func (p *PollingInitializer) initBatch(ctx context.Context, cards []*model.IotCa
|
||||
shardID := int(card.ID) % p.queueMgr.shardCount
|
||||
cardIDStr := fmt.Sprintf("%d", card.ID)
|
||||
|
||||
if cfg.RealnameCheckInterval != nil && *cfg.RealnameCheckInterval > 0 {
|
||||
nextCheck := calculateNextCheckTime(card.LastRealNameCheckAt, *cfg.RealnameCheckInterval, now)
|
||||
pipe.ZAdd(ctx, constants.RedisPollingShardQueueKey(shardID, constants.TaskTypePollingRealname), redis.Z{
|
||||
Score: float64(nextCheck.Unix()), Member: cardIDStr,
|
||||
})
|
||||
cmdCount++
|
||||
}
|
||||
if cfg.CarddataCheckInterval != nil && *cfg.CarddataCheckInterval > 0 {
|
||||
nextCheck := calculateNextCheckTime(card.LastDataCheckAt, *cfg.CarddataCheckInterval, now)
|
||||
pipe.ZAdd(ctx, constants.RedisPollingShardQueueKey(shardID, constants.TaskTypePollingCarddata), redis.Z{
|
||||
Score: float64(nextCheck.Unix()), Member: cardIDStr,
|
||||
})
|
||||
cmdCount++
|
||||
}
|
||||
if cfg.PackageCheckInterval != nil && *cfg.PackageCheckInterval > 0 {
|
||||
nextCheck := calculateNextCheckTime(card.LastDataCheckAt, *cfg.PackageCheckInterval, now)
|
||||
pipe.ZAdd(ctx, constants.RedisPollingShardQueueKey(shardID, constants.TaskTypePollingPackage), redis.Z{
|
||||
Score: float64(nextCheck.Unix()), Member: cardIDStr,
|
||||
})
|
||||
cmdCount++
|
||||
}
|
||||
if cfg.ProtectCheckInterval != nil && *cfg.ProtectCheckInterval > 0 {
|
||||
nextCheck := calculateNextCheckTime(card.LastProtectCheckAt, *cfg.ProtectCheckInterval, now)
|
||||
pipe.ZAdd(ctx, constants.RedisPollingShardQueueKey(shardID, constants.TaskTypePollingProtect), redis.Z{
|
||||
Score: float64(nextCheck.Unix()), Member: cardIDStr,
|
||||
})
|
||||
cmdCount++
|
||||
}
|
||||
if cfg.CardStatusCheckInterval != nil && *cfg.CardStatusCheckInterval > 0 {
|
||||
nextCheck := calculateNextCheckTime(card.LastCardStatusCheckAt, *cfg.CardStatusCheckInterval, now)
|
||||
pipe.ZAdd(ctx, constants.RedisPollingShardQueueKey(shardID, constants.TaskTypePollingCardStatus), redis.Z{
|
||||
for taskType, info := range intervals {
|
||||
lastCheckAt := lastCheckAtByTaskType(card, taskType)
|
||||
nextCheck := calculateNextCheckTime(lastCheckAt, info.Interval, now)
|
||||
pipe.ZAdd(ctx, constants.RedisPollingShardQueueKey(shardID, taskType), redis.Z{
|
||||
Score: float64(nextCheck.Unix()), Member: cardIDStr,
|
||||
})
|
||||
cmdCount++
|
||||
@@ -294,6 +276,22 @@ func (p *PollingInitializer) initBatch(ctx context.Context, cards []*model.IotCa
|
||||
return nil
|
||||
}
|
||||
|
||||
// lastCheckAtByTaskType 根据 task type 返回对应的上次检查时间字段
|
||||
func lastCheckAtByTaskType(card *model.IotCard, taskType string) *time.Time {
|
||||
switch taskType {
|
||||
case constants.TaskTypePollingRealname:
|
||||
return card.LastRealNameCheckAt
|
||||
case constants.TaskTypePollingCarddata, constants.TaskTypePollingPackage:
|
||||
return card.LastDataCheckAt
|
||||
case constants.TaskTypePollingProtect:
|
||||
return card.LastProtectCheckAt
|
||||
case constants.TaskTypePollingCardStatus:
|
||||
return card.LastCardStatusCheckAt
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// calculateNextCheckTime 计算下次检查时间
|
||||
func calculateNextCheckTime(lastCheckAt *time.Time, intervalSeconds int, now time.Time) time.Time {
|
||||
if lastCheckAt == nil {
|
||||
|
||||
Reference in New Issue
Block a user