puzhibing
2026-08-21 3f6d5b788670170da195be8d8cee814069b3b224
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
package com.xiaopeng.admin.socketio;
 
import com.corundumstudio.socketio.SocketIOClient;
import org.springframework.stereotype.Component;
 
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * Socket.IO 会话管理
 *
 * @ClassName SocketIOSessionManager
 * @Description 跟踪连接、城市订阅与单车详情订阅
 */
@Component
public class SocketIOSessionManager {
 
    private final Map<String, SocketIOClient> clients = new ConcurrentHashMap<>();
    private final Map<String, Set<String>> clientCitySubscriptions = new ConcurrentHashMap<>();
    private final Map<String, Long> clientVehicleDetailSubscription = new ConcurrentHashMap<>();
    private final Map<Long, Set<String>> vehicleDetailSubscribers = new ConcurrentHashMap<>();
 
    /**
     * 注册客户端
     *
     * @param client Socket 客户端
     */
    public void register(SocketIOClient client) {
        clients.put(client.getSessionId().toString(), client);
    }
 
    /**
     * 注销客户端
     *
     * @param client Socket 客户端
     */
    public void unregister(SocketIOClient client) {
        unsubscribeVehicleDetail(client);
        clients.remove(client.getSessionId().toString());
        clientCitySubscriptions.remove(client.getSessionId().toString());
    }
 
    /**
     * 记录城市订阅
     *
     * @param client   Socket 客户端
     * @param cityCode 城市编码
     */
    public void trackSubscription(SocketIOClient client, String cityCode) {
        clientCitySubscriptions
                .computeIfAbsent(client.getSessionId().toString(), key -> ConcurrentHashMap.newKeySet())
                .add(cityCode);
    }
 
    /**
     * 移除城市订阅记录
     *
     * @param client   Socket 客户端
     * @param cityCode 城市编码
     */
    public void untrackSubscription(SocketIOClient client, String cityCode) {
        Set<String> cities = clientCitySubscriptions.get(client.getSessionId().toString());
        if (cities != null) {
            cities.remove(cityCode);
        }
    }
 
    /**
     * 订阅单车详情推送(同一客户端仅保留一个 vehicleId)
     *
     * @param client    Socket 客户端
     * @param vehicleId 车辆 ID
     */
    public void subscribeVehicleDetail(SocketIOClient client, Long vehicleId) {
        if (vehicleId == null) {
            return;
        }
        String sessionId = client.getSessionId().toString();
        Long previous = clientVehicleDetailSubscription.get(sessionId);
        if (previous != null) {
            removeVehicleSubscriber(previous, sessionId);
        }
        clientVehicleDetailSubscription.put(sessionId, vehicleId);
        vehicleDetailSubscribers.computeIfAbsent(vehicleId, key -> ConcurrentHashMap.newKeySet()).add(sessionId);
    }
 
    /**
     * 取消单车详情订阅
     *
     * @param client Socket 客户端
     */
    public void unsubscribeVehicleDetail(SocketIOClient client) {
        String sessionId = client.getSessionId().toString();
        Long vehicleId = clientVehicleDetailSubscription.remove(sessionId);
        if (vehicleId != null) {
            removeVehicleSubscriber(vehicleId, sessionId);
        }
    }
 
    /**
     * 当前被订阅的所有车辆 ID(去重)
     *
     * @return 车辆 ID 集合
     */
    public Set<Long> getSubscribedVehicleIds() {
        return new HashSet<>(vehicleDetailSubscribers.keySet());
    }
 
    /**
     * 订阅指定车辆详情的 sessionId 集合
     *
     * @param vehicleId 车辆 ID
     * @return sessionId 集合
     */
    public Set<String> getSubscriberSessionIds(Long vehicleId) {
        Set<String> subscribers = vehicleDetailSubscribers.get(vehicleId);
        return subscribers != null ? new HashSet<>(subscribers) : Collections.emptySet();
    }
 
    /**
     * 按 sessionId 获取客户端
     *
     * @param sessionId 会话 ID
     * @return 客户端,不存在返回 null
     */
    public SocketIOClient getClient(String sessionId) {
        return clients.get(sessionId);
    }
 
    private void removeVehicleSubscriber(Long vehicleId, String sessionId) {
        vehicleDetailSubscribers.computeIfPresent(vehicleId, (key, subscribers) -> {
            subscribers.remove(sessionId);
            return subscribers.isEmpty() ? null : subscribers;
        });
    }
}