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 clients = new ConcurrentHashMap<>(); private final Map> clientCitySubscriptions = new ConcurrentHashMap<>(); private final Map clientVehicleDetailSubscription = new ConcurrentHashMap<>(); private final Map> 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 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 getSubscribedVehicleIds() { return new HashSet<>(vehicleDetailSubscribers.keySet()); } /** * 订阅指定车辆详情的 sessionId 集合 * * @param vehicleId 车辆 ID * @return sessionId 集合 */ public Set getSubscriberSessionIds(Long vehicleId) { Set 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; }); } }