package carrierqueue import ( "context" "strconv" "sync" "time" "github.com/break/junhong_cmp_fiber/pkg/constants" ) // heartbeatFreshness 是分发器心跳有效期:超过该时长没有心跳视为指令无人消费。 const heartbeatFreshness = 30 * time.Second // heartbeatCacheTTL 是进程内缓存心跳判断的时长,避免每次请求评估都读 Redis。 const heartbeatCacheTTL = 3 * time.Second // CommandQueue 是停复机指令队列写入端:开关读取受控系统配置,写入走共享缓存 Lua。 type CommandQueue struct { store *Store settings *SettingsProvider mu sync.Mutex aliveAt time.Time alive bool checkedAt time.Time } // NewCommandQueue 创建停复机指令队列写入端。 func NewCommandQueue(store *Store, settings *SettingsProvider) *CommandQueue { return &CommandQueue{store: store, settings: settings} } // Enabled 返回是否经指令队列执行:停复机出口开关开启,且分发器在心跳有效期内运行(有消费方)。 // 开关开启但没有分发器时回退为各入口当场执行,避免指令写入后无人消费。 func (q *CommandQueue) Enabled(ctx context.Context) bool { if q == nil || q.store == nil || !q.settings.Current(ctx).StopResumeQueueEnabled { return false } return q.dispatcherAlive(ctx) } func (q *CommandQueue) dispatcherAlive(ctx context.Context) bool { q.mu.Lock() defer q.mu.Unlock() if time.Since(q.checkedAt) < heartbeatCacheTTL { return q.alive } q.checkedAt = time.Now() beats, err := q.store.redis.HGetAll(ctx, constants.RedisCarrierSchedulerHeartbeatKey).Result() if err != nil { return q.alive } q.alive = false for _, raw := range beats { if ms, convErr := strconv.ParseInt(raw, 10, 64); convErr == nil && time.Since(time.UnixMilli(ms)) < heartbeatFreshness { q.alive = true break } } return q.alive } // WriteCommand 写入或覆盖卡的停复机指令。 func (q *CommandQueue) WriteCommand(ctx context.Context, cmd StopResumeCommand) (int64, error) { return q.store.WriteCommand(ctx, cmd) } // RequestDeviceEval 请求一次设备维度判断。 func (q *CommandQueue) RequestDeviceEval(ctx context.Context, deviceID uint, scenes []string) error { return q.store.RequestDeviceEval(ctx, deviceID, scenes) }