liusuyi
2026-05-18 0b8c8d8986a35c3e36db1503125e2dff79d6d10e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package com.ard.gb28181.task.deviceStatus;
 
import com.ard.gb28181.api.bean.SipTransactionInfo;
import com.ard.gb28181.config.UserSetting;
import com.ard.gb28181.utils.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
 
@Slf4j
@Component
public class DeviceStatusTaskRunner {
 
    private final Map<String, DeviceStatusTask> subscribes = new ConcurrentHashMap<>();
 
    private final DelayQueue<DeviceStatusTask> delayQueue = new DelayQueue<>();
 
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
 
    @Autowired
    private UserSetting userSetting;
 
    private final String prefix = "QS_DEVICE_STATUS";
 
    @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS)
    public void expirationCheck() {
        // 使用 poll() 而不是 take()
        // poll() 会立即返回:如果有到期任务则返回,如果没有(或没到期)则返回 null
        DeviceStatusTask task = delayQueue.poll();
 
        // 只要取到了任务(说明有任务到期了),就处理
        while (task != null) {
            try {
                removeTask(task.getDeviceId());
                task.expired();
            } catch (Exception e) {
                log.error("[设备状态到期] 到期处理时出现异常,设备编号: {} ", task.getDeviceId(), e);
            }
            // 继续尝试取下一个已到期任务
            task = delayQueue.poll();
        }
        // 如果 poll() 返回 null,说明当前没有到期任务,方法直接结束
        // 线程释放,Spring 调度器可以在 500ms 后再次调用此方法
    }
 
    public void addTask(DeviceStatusTask task) {
        Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis()) / 1000);
        if (duration.getSeconds() < 0) {
            return;
        }
        subscribes.put(task.getDeviceId(), task);
        String key = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId());
        redisTemplate.opsForValue().set(key, task.getInfo(), duration);
        delayQueue.offer(task);
    }
 
    public boolean removeTask(String key) {
        DeviceStatusTask task = subscribes.get(key);
        if (task == null) {
            return false;
        }
        String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId());
        redisTemplate.delete(redisKey);
        subscribes.remove(key);
        if (delayQueue.contains(task)) {
            boolean remove = delayQueue.remove(task);
            if (!remove) {
                log.info("[移除状态任务] 从延时队列内移除失败: {}", key);
            }
        }
        return true;
    }
 
    public SipTransactionInfo getTransactionInfo(String key) {
        DeviceStatusTask task = subscribes.get(key);
        if (task == null) {
            return null;
        }
        return task.getTransactionInfo();
    }
 
    public boolean updateDelay(String key, long expirationTime) {
        DeviceStatusTask task = subscribes.get(key);
        if (task == null) {
            return false;
        }
        log.debug("[更新状态任务时间] 编号: {}", key);
        // 如果值更改时间,如果队列中有多个元素时 超时无法出发。目前采用移除再加入的方法
        delayQueue.remove(task);
        task.setDelayTime(expirationTime);
        delayQueue.offer(task);
        String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getDeviceId());
        Duration duration = Duration.ofSeconds((expirationTime - System.currentTimeMillis()) / 1000);
        redisTemplate.expire(redisKey, duration);
        return true;
    }
 
    public boolean containsKey(String key) {
        return subscribes.containsKey(key);
    }
 
    public List<DeviceStatusTaskInfo> getAllTaskInfo() {
        String scanKey = String.format("%s_%s_*", prefix, userSetting.getServerId());
        List<Object> values = RedisUtil.scan(redisTemplate, scanKey);
        if (values.isEmpty()) {
            return new ArrayList<>();
        }
        List<DeviceStatusTaskInfo> result = new ArrayList<>();
        for (Object value : values) {
            String redisKey = (String) value;
            DeviceStatusTaskInfo taskInfo = (DeviceStatusTaskInfo) redisTemplate.opsForValue().get(redisKey);
            if (taskInfo == null) {
                continue;
            }
            Long expire = redisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);
            taskInfo.setExpireTime(expire);
            result.add(taskInfo);
        }
        return result;
 
    }
}