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.deviceSubscribe.deviceSubscribe;
 
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 SubscribeTaskRunner {
 
    private final Map<String, SubscribeTask> subscribes = new ConcurrentHashMap<>();
 
    private final DelayQueue<SubscribeTask> delayQueue = new DelayQueue<>();
 
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
 
    @Autowired
    private UserSetting userSetting;
 
    private final String prefix = "VMP_DEVICE_SUBSCRIBE";
 
    // 订阅过期检查
    @Scheduled(fixedDelay = 500, timeUnit = TimeUnit.MILLISECONDS)
    public void expirationCheck() {
        // 1. 使用 poll() 替代 take()
        // poll() 会立即返回:如果有已超期的任务则返回,否则返回 null
        SubscribeTask take = delayQueue.poll();
 
        // 2. 循环处理所有已超期的任务
        while (take != null) {
            try {
                removeSubscribe(take.getKey());
                take.expired();
            } catch (Exception e) {
                log.error("[设备订阅到期] {} 到期处理时出现异常, 设备编号: {} ", take.getName(), take.getDeviceId());
            }
            // 3. 继续尝试获取下一个已超期的任务
            take = delayQueue.poll();
        }
        // 4. 如果 poll() 返回 null,说明当前没有超期的任务,方法直接结束
        // 线程释放,等待下一次 500ms 调度
    }
 
    public void addSubscribe(SubscribeTask task) {
        Duration duration = Duration.ofSeconds((task.getDelayTime() - System.currentTimeMillis()) / 1000);
        if (duration.getSeconds() < 0) {
            return;
        }
        subscribes.put(task.getKey(), task);
        String key = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey());
        redisTemplate.opsForValue().set(key, task.getInfo(), duration);
        delayQueue.offer(task);
    }
 
    public boolean removeSubscribe(String key) {
        SubscribeTask task = subscribes.get(key);
        if (task == null) {
            return false;
        }
        String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey());
        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) {
        SubscribeTask task = subscribes.get(key);
        if (task == null) {
            return null;
        }
        return task.getTransactionInfo();
    }
 
    public boolean updateDelay(String key, long expirationTime) {
        SubscribeTask task = subscribes.get(key);
        if (task == null) {
            return false;
        }
        log.info("[更新订阅任务时间] {}, 编号: {}", task.getName(), key);
        delayQueue.remove(task);
        task.setDelayTime(expirationTime);
        delayQueue.offer(task);
        String redisKey = String.format("%s_%s_%s", prefix, userSetting.getServerId(), task.getKey());
        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<SubscribeTaskInfo> getAllTaskInfo() {
        String scanKey = String.format("%s_%s_*", prefix, userSetting.getServerId());
        List<Object> values = RedisUtil.scan(redisTemplate, scanKey);
        if (values.isEmpty()) {
            return new ArrayList<>();
        }
        List<SubscribeTaskInfo> result = new ArrayList<>();
        for (Object value : values) {
            String redisKey = (String) value;
            SubscribeTaskInfo taskInfo = (SubscribeTaskInfo) redisTemplate.opsForValue().get(redisKey);
            if (taskInfo == null) {
                continue;
            }
            Long expire = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
            taskInfo.setExpireTime(expire);
            result.add(taskInfo);
        }
        return result;
 
    }
}