liusuyi
2025-03-11 c197c31a1a7bc8a0d7011e39193e3d94060b397c
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package com.ard.tianyu.websocket.handler;
 
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ard.tianyu.domain.cache.UavStateCache;
import com.ard.tianyu.domain.dto.state.UavState;
import com.ard.tianyu.websocket.domain.BaseMessage;
import com.ard.tianyu.websocket.utils.WebSocketUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
 
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
 
/**
 * Description: 无人机状态管理
 * Author: liusuyi
 * Date: 2025年03月10日11:54
 **/
@Slf4j
@Component
public class UavStateManager {
 
    // 存储状态订阅的用户
    private static final Map<String, Set<Long>> stateSubscriptions = new ConcurrentHashMap<>();
    // 定时任务调度器
    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    // 存储状态推送任务
    private static final Map<String, ScheduledFuture<?>> statePushTasks = new ConcurrentHashMap<>();
 
 
    /**
     * 订阅指定设备的状态
     * 当用户需要接收特定设备的状态更新时调用此方法
     * 如果这是第一次订阅该设备,此方法还将启动一个定时任务来推送设备状态
     *
     * @param boxSn  设备的序列号,用于唯一标识设备
     * @param userId 用户ID,表示谁订阅了设备状态
     */
    public void subscribeState(String boxSn, Long userId) {
        // 向订阅列表中添加该session
        stateSubscriptions.computeIfAbsent(boxSn, k -> new HashSet<>()).add(userId);
 
        // 如果是第一次订阅该无人机的状态,启动定时任务
        if (stateSubscriptions.get(boxSn).size() == 1) {
            startStatePushTask(boxSn);
        }
    }
 
    /**
     * 取消用户对指定设备状态的订阅
     * <p>
     * 当用户不再需要接收特定设备的状态更新时,通过此方法取消订阅这会从设备的订阅者集合中移除当前用户如果该设备没有其他订阅者,
     * 还将停止与该设备状态推送相关的定时任务
     *
     * @param boxSn  设备序列号,用于唯一标识一个设备
     * @param userId 用户ID,表示要取消订阅的用户
     */
    public void unsubscribeState(String boxSn, Long userId) {
        // 获取指定设备的所有订阅者集合
        Set<Long> userIds = stateSubscriptions.get(boxSn);
        if (userIds != null) {
            // 从订阅者集合中移除当前会话
            userIds.remove(userId);
 
            // 如果没有用户再订阅该状态,停止定时任务
            if (userIds.isEmpty()) {
                stopStatePushTask(boxSn);
            }
        }
    }
 
    /**
     * 启动状态推送任务
     * 该方法用于为特定的无人机(通过boxSn标识)启动一个定时任务,定时任务将定期推送无人机的状态
     *
     * @param boxSn 无人机的序列号,用于唯一标识无人机
     */
    private void startStatePushTask(String boxSn) {
        // 启动定时任务,每隔一定时间推送一次状态
        ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> {
            // 推送无人机状态到订阅者
            sendDroneStateToSubscribers(boxSn);
        }, 0, 2, TimeUnit.SECONDS); // 每2秒推送一次状态
        // 将推送任务与无人机序列号关联,以便后续可能的管理或取消
        statePushTasks.put(boxSn, future);
    }
 
    /**
     * 停止并移除与指定盒子SN关联的状态推送定时任务
     *
     * @param boxSn 盒子的唯一序列号,用于标识特定的定时任务
     */
    private void stopStatePushTask(String boxSn) {
        // 停止定时任务
        ScheduledFuture<?> future = statePushTasks.get(boxSn);
        if (future != null) {
            // 如果任务存在,则取消任务,参数false表示不中断正在执行的任务
            future.cancel(false);
        }
        // 从任务集合中移除与该盒子SN关联的任务
        statePushTasks.remove(boxSn);
    }
 
    /**
     * 移除用户订阅的状态
     *
     * @param userId 用户ID
     */
    public void removeSubscribeState(Long userId) {
        // 清理掉该用户订阅的状态
        for (Set<Long> userIds : stateSubscriptions.values()) {
            userIds.remove(userId);
        }
    }
 
    /**
     * 向订阅者发送无人机状态信息
     *
     * @param boxSn 无人机的序列号,用于标识特定的无人机
     *              <p>
     *              此方法首先调用getDroneState方法获取指定无人机的当前状态,然后通过WebSocket向所有订阅该无人机状态的客户端发送状态信息
     *              如果在发送过程中遇到异常,则记录错误日志,包括会话ID和异常信息
     */
    private void sendDroneStateToSubscribers(String boxSn) {
        // 获取当前无人机状态
        UavState uavState = getDroneState(boxSn);
        BaseMessage<UavState>baseMessage=new BaseMessage<>("state",uavState);
        // 向所有订阅者推送状态
        Set<Long> userIds = stateSubscriptions.get(boxSn);
        if (userIds != null) {
            for (Long userId : userIds) {
                try {
                    WebSocketUtil.sendMessage(userId, JSON.toJSONString(baseMessage));
                } catch (Exception e) {
                    log.error("Error sending state message to session {}", userId, e);
                }
            }
        }
    }
 
    /**
     * 获取无人机的状态信息
     * 此方法通过传入的无人机序列号(boxSn)来获取无人机的当前状态
     * 它主要起到了解无人机实时状态的作用,以便于进一步的控制或显示状态信息
     *
     * @param boxSn 无人机的唯一序列号,用于标识特定的无人机
     * @return UavState 无人机的状态对象,包含各种状态信息如电池电量、飞行高度等
     */
    private UavState getDroneState(String boxSn) {
        // 假设这里是获取无人机状态的逻辑,返回状态信息
        return UavStateCache.getUavState(boxSn);
    }
}