Commit 23c642ed7250d8f1c39166579643ce40d901314a
1 parent
4ae3f6b2
fix(iot): 清理已删除 Modbus 设备的网关轮询
- 过滤 RPC Modbus 配置列表中的已删除设备 - Modbus TCP Server 刷新配置时清理失效设备资源 - 停止轮询任务并清理 Pending 请求、配置缓存和连接
Showing
4 changed files
with
66 additions
and
2 deletions
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 |