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 | 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 | ... | ... |