Commit 23c642ed7250d8f1c39166579643ce40d901314a

Authored by YunaiV
1 parent 4ae3f6b2

fix(iot): 清理已删除 Modbus 设备的网关轮询

- 过滤 RPC Modbus 配置列表中的已删除设备
- Modbus TCP Server 刷新配置时清理失效设备资源
- 停止轮询任务并清理 Pending 请求、配置缓存和连接
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
@@ -94,6 +94,9 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { @@ -94,6 +94,9 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
94 // 2. 组装返回结果 94 // 2. 组装返回结果
95 Set<Long> deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId); 95 Set<Long> deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId);
96 Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(deviceIds); 96 Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(deviceIds);
  97 + if (CollUtil.isEmpty(deviceMap)) {
  98 + return success(new ArrayList<>());
  99 + }
97 Map<Long, List<IotDeviceModbusPointDO>> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds); 100 Map<Long, List<IotDeviceModbusPointDO>> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds);
98 Map<Long, IotProductDO> productMap = productService.getProductMap(convertSet(deviceMap.values(), IotDeviceDO::getProductId)); 101 Map<Long, IotProductDO> productMap = productService.getProductMap(convertSet(deviceMap.values(), IotDeviceDO::getProductId));
99 List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size()); 102 List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size());
@@ -139,4 +142,4 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { @@ -139,4 +142,4 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
139 return success(deviceService.registerSubDevices(reqDTO)); 142 return success(deviceService.registerSubDevices(reqDTO));
140 } 143 }
141 144
142 -}  
143 \ No newline at end of file 145 \ No newline at end of file
  146 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/IotModbusTcpServerProtocol.java
@@ -326,9 +326,27 @@ public class IotModbusTcpServerProtocol implements IotProtocol { @@ -326,9 +326,27 @@ public class IotModbusTcpServerProtocol implements IotProtocol {
326 log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e); 326 log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e);
327 } 327 }
328 } 328 }
  329 +
  330 + // 3. 清理本轮不再返回配置的已连接设备,避免继续轮询已删除设备的旧点位
  331 + Set<Long> missingDeviceIds = configCacheService.cleanupMissingConfigs(connectedDeviceIds, configs);
  332 + for (Long deviceId : missingDeviceIds) {
  333 + cleanupMissingDevice(deviceId);
  334 + }
329 } catch (Exception e) { 335 } catch (Exception e) {
330 log.error("[refreshConfig][刷新配置失败]", e); 336 log.error("[refreshConfig][刷新配置失败]", e);
331 } 337 }
332 } 338 }
333 339
  340 + private void cleanupMissingDevice(Long deviceId) {
  341 + try {
  342 + pollScheduler.stopPolling(deviceId);
  343 + pendingRequestManager.removeDevice(deviceId);
  344 + configCacheService.removeConfig(deviceId);
  345 + connectionManager.closeConnection(deviceId);
  346 + log.info("[cleanupMissingDevice][设备 {} 配置已失效,已停止轮询并清理连接]", deviceId);
  347 + } catch (Exception e) {
  348 + log.error("[cleanupMissingDevice][清理设备失败, deviceId={}]", deviceId, e);
  349 + }
  350 + }
  351 +
334 } 352 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConfigCacheService.java
@@ -12,11 +12,14 @@ import lombok.RequiredArgsConstructor; @@ -12,11 +12,14 @@ import lombok.RequiredArgsConstructor;
12 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
13 13
14 import java.util.Collections; 14 import java.util.Collections;
  15 +import java.util.HashSet;
15 import java.util.List; 16 import java.util.List;
16 import java.util.Map; 17 import java.util.Map;
17 import java.util.Set; 18 import java.util.Set;
18 import java.util.concurrent.ConcurrentHashMap; 19 import java.util.concurrent.ConcurrentHashMap;
19 20
  21 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  22 +
20 /** 23 /**
21 * IoT Modbus TCP Server 配置缓存:认证时按需加载,断连时清理,定时刷新已连接设备 24 * IoT Modbus TCP Server 配置缓存:认证时按需加载,断连时清理,定时刷新已连接设备
22 * 25 *
@@ -97,6 +100,27 @@ public class IotModbusTcpServerConfigCacheService { @@ -97,6 +100,27 @@ public class IotModbusTcpServerConfigCacheService {
97 } 100 }
98 101
99 /** 102 /**
  103 + * 清理本轮刷新后不再有效的设备配置
  104 + *
  105 + * @param refreshedDeviceIds 本轮参与刷新的设备编号
  106 + * @param currentConfigs 本轮远端返回的有效配置
  107 + * @return 本轮已不再有效的设备编号
  108 + */
  109 + public Set<Long> cleanupMissingConfigs(Set<Long> refreshedDeviceIds,
  110 + List<IotModbusDeviceConfigRespDTO> currentConfigs) {
  111 + if (CollUtil.isEmpty(refreshedDeviceIds)) {
  112 + return Collections.emptySet();
  113 + }
  114 + Set<Long> currentDeviceIds = convertSet(currentConfigs, IotModbusDeviceConfigRespDTO::getDeviceId);
  115 + Set<Long> missingDeviceIds = new HashSet<>(refreshedDeviceIds);
  116 + missingDeviceIds.removeAll(currentDeviceIds);
  117 + for (Long deviceId : missingDeviceIds) {
  118 + configCache.remove(deviceId);
  119 + }
  120 + return missingDeviceIds;
  121 + }
  122 +
  123 + /**
100 * 获取设备配置 124 * 获取设备配置
101 */ 125 */
102 public IotModbusDeviceConfigRespDTO getConfig(Long deviceId) { 126 public IotModbusDeviceConfigRespDTO getConfig(Long deviceId) {
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpserver/manager/IotModbusTcpServerConnectionManager.java
@@ -9,6 +9,7 @@ import lombok.experimental.Accessors; @@ -9,6 +9,7 @@ import lombok.experimental.Accessors;
9 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
10 10
11 import java.util.ArrayList; 11 import java.util.ArrayList;
  12 +import java.util.HashSet;
12 import java.util.List; 13 import java.util.List;
13 import java.util.Map; 14 import java.util.Map;
14 import java.util.Set; 15 import java.util.Set;
@@ -109,7 +110,7 @@ public class IotModbusTcpServerConnectionManager { @@ -109,7 +110,7 @@ public class IotModbusTcpServerConnectionManager {
109 * 获取所有已连接设备的 ID 集合 110 * 获取所有已连接设备的 ID 集合
110 */ 111 */
111 public Set<Long> getConnectedDeviceIds() { 112 public Set<Long> getConnectedDeviceIds() {
112 - return deviceSocketMap.keySet(); 113 + return new HashSet<>(deviceSocketMap.keySet());
113 } 114 }
114 115
115 /** 116 /**
@@ -131,6 +132,24 @@ public class IotModbusTcpServerConnectionManager { @@ -131,6 +132,24 @@ public class IotModbusTcpServerConnectionManager {
131 } 132 }
132 133
133 /** 134 /**
  135 + * 关闭指定设备连接,并先移除映射,避免 closeHandler 再按正常断连发送下线消息
  136 + */
  137 + public void closeConnection(Long deviceId) {
  138 + NetSocket socket = deviceSocketMap.remove(deviceId);
  139 + if (socket == null) {
  140 + return;
  141 + }
  142 + connectionMap.remove(socket);
  143 + try {
  144 + socket.close();
  145 + log.info("[closeConnection][设备 {} 连接已关闭]", deviceId);
  146 + } catch (Exception e) {
  147 + log.warn("[closeConnection][关闭设备连接失败, deviceId={}, remoteAddress={}]",
  148 + deviceId, socket.remoteAddress(), e);
  149 + }
  150 + }
  151 +
  152 + /**
134 * 发送数据到设备 153 * 发送数据到设备
135 * 154 *
136 * @return 发送结果 Future 155 * @return 发送结果 Future