fix: 修正轮询配置多匹配逻辑,支持同卡匹配多个配置并按 priority 合并 interval
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:
2026-04-16 14:27:47 +08:00
parent 5065d925ad
commit 5d9be1d7e4
15 changed files with 770 additions and 231 deletions

View File

@@ -27,6 +27,12 @@ type PollingConfigManager struct {
refreshOnce sync.Once
}
// TaskTypeInterval 某任务类型的轮询间隔信息
type TaskTypeInterval struct {
Interval int // 秒
Priority int // 来源配置的 priority
}
// NewPollingConfigManager 创建配置管理器
func NewPollingConfigManager(configStore *postgres.PollingConfigStore, redisClient *redis.Client, logger *zap.Logger) *PollingConfigManager {
return &PollingConfigManager{
@@ -81,16 +87,79 @@ func (m *PollingConfigManager) Start(ctx context.Context) {
// MatchConfig 按优先级匹配第一个符合条件的轮询配置
// 配置按 priority ASC 排序DB 层保证),数字越小优先级越高
// MatchConfigs()[0] 的前向兼容包装
func (m *PollingConfigManager) MatchConfig(card *model.IotCard) *model.PollingConfig {
configs := m.MatchConfigs(card)
if len(configs) == 0 {
return nil
}
return configs[0]
}
// MatchConfigs 按 priority ASC 遍历所有启用的配置,返回所有满足匹配条件且至少有一个非 NULL interval 的配置
// 无匹配时返回空切片
func (m *PollingConfigManager) MatchConfigs(card *model.IotCard) []*model.PollingConfig {
m.mu.RLock()
defer m.mu.RUnlock()
var result []*model.PollingConfig
for _, cfg := range m.configs {
if matchConfigConditions(cfg, card) {
return cfg
if matchConfigConditions(cfg, card) && hasAnyEnabledInterval(cfg) {
result = append(result, cfg)
}
}
return nil
return result
}
// hasAnyEnabledInterval 检查配置是否有至少一个非 NULL 且大于 0 的轮询间隔
func hasAnyEnabledInterval(cfg *model.PollingConfig) bool {
return (cfg.RealnameCheckInterval != nil && *cfg.RealnameCheckInterval > 0) ||
(cfg.CarddataCheckInterval != nil && *cfg.CarddataCheckInterval > 0) ||
(cfg.PackageCheckInterval != nil && *cfg.PackageCheckInterval > 0) ||
(cfg.ProtectCheckInterval != nil && *cfg.ProtectCheckInterval > 0) ||
(cfg.CardStatusCheckInterval != nil && *cfg.CardStatusCheckInterval > 0)
}
// MergedTaskIntervals 对所有匹配配置按 priority 遍历,对每种 task type 选取最高优先级的非 nil interval
// 返回 map: taskType → TaskTypeInterval{Interval, Priority}
func (m *PollingConfigManager) MergedTaskIntervals(card *model.IotCard) map[string]TaskTypeInterval {
configs := m.MatchConfigs(card)
merged := make(map[string]TaskTypeInterval)
for _, cfg := range configs {
priority := int(cfg.Priority)
if cfg.RealnameCheckInterval != nil && *cfg.RealnameCheckInterval > 0 {
taskType := constants.TaskTypePollingRealname
if _, exists := merged[taskType]; !exists {
merged[taskType] = TaskTypeInterval{Interval: *cfg.RealnameCheckInterval, Priority: priority}
}
}
if cfg.CarddataCheckInterval != nil && *cfg.CarddataCheckInterval > 0 {
taskType := constants.TaskTypePollingCarddata
if _, exists := merged[taskType]; !exists {
merged[taskType] = TaskTypeInterval{Interval: *cfg.CarddataCheckInterval, Priority: priority}
}
}
if cfg.PackageCheckInterval != nil && *cfg.PackageCheckInterval > 0 {
taskType := constants.TaskTypePollingPackage
if _, exists := merged[taskType]; !exists {
merged[taskType] = TaskTypeInterval{Interval: *cfg.PackageCheckInterval, Priority: priority}
}
}
if cfg.ProtectCheckInterval != nil && *cfg.ProtectCheckInterval > 0 {
taskType := constants.TaskTypePollingProtect
if _, exists := merged[taskType]; !exists {
merged[taskType] = TaskTypeInterval{Interval: *cfg.ProtectCheckInterval, Priority: priority}
}
}
if cfg.CardStatusCheckInterval != nil && *cfg.CardStatusCheckInterval > 0 {
taskType := constants.TaskTypePollingCardStatus
if _, exists := merged[taskType]; !exists {
merged[taskType] = TaskTypeInterval{Interval: *cfg.CardStatusCheckInterval, Priority: priority}
}
}
}
return merged
}
// matchConfigConditions 检查卡是否满足配置的匹配条件