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 94 // 2. 组装返回结果
95 95 Set<Long> deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId);
96 96 Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(deviceIds);
  97 + if (CollUtil.isEmpty(deviceMap)) {
  98 + return success(new ArrayList<>());
  99 + }
97 100 Map<Long, List<IotDeviceModbusPointDO>> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds);
98 101 Map<Long, IotProductDO> productMap = productService.getProductMap(convertSet(deviceMap.values(), IotDeviceDO::getProductId));
99 102 List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size());
... ... @@ -139,4 +142,4 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
139 142 return success(deviceService.registerSubDevices(reqDTO));
140 143 }
141 144  
142   -}
143 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 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 335 } catch (Exception e) {
330 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 12 import lombok.extern.slf4j.Slf4j;
13 13  
14 14 import java.util.Collections;
  15 +import java.util.HashSet;
15 16 import java.util.List;
16 17 import java.util.Map;
17 18 import java.util.Set;
18 19 import java.util.concurrent.ConcurrentHashMap;
19 20  
  21 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  22 +
20 23 /**
21 24 * IoT Modbus TCP Server 配置缓存:认证时按需加载,断连时清理,定时刷新已连接设备
22 25 *
... ... @@ -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 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 9 import lombok.extern.slf4j.Slf4j;
10 10  
11 11 import java.util.ArrayList;
  12 +import java.util.HashSet;
12 13 import java.util.List;
13 14 import java.util.Map;
14 15 import java.util.Set;
... ... @@ -109,7 +110,7 @@ public class IotModbusTcpServerConnectionManager {
109 110 * 获取所有已连接设备的 ID 集合
110 111 */
111 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 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 155 * @return 发送结果 Future
... ...