Commit 52b8e66466f009b8fef0fdd7ef8eea15c2037dac
1 parent
b2fef46b
feat:【iot】modbus-tcp 协议接入 100%:完善注释、完善单测
Showing
6 changed files
with
83 additions
and
119 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager; |
| 2 | 2 | ||
| 3 | +import io.vertx.core.buffer.Buffer; | ||
| 3 | import io.vertx.core.net.NetSocket; | 4 | import io.vertx.core.net.NetSocket; |
| 4 | import lombok.Data; | 5 | import lombok.Data; |
| 5 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| @@ -119,7 +120,7 @@ public class IotTcpConnectionManager { | @@ -119,7 +120,7 @@ public class IotTcpConnectionManager { | ||
| 119 | } | 120 | } |
| 120 | 121 | ||
| 121 | try { | 122 | try { |
| 122 | - socket.write(io.vertx.core.buffer.Buffer.buffer(data)); | 123 | + socket.write(Buffer.buffer(data)); |
| 123 | log.debug("[sendToDevice][发送消息成功,设备 ID: {},数据长度: {} 字节]", deviceId, data.length); | 124 | log.debug("[sendToDevice][发送消息成功,设备 ID: {},数据长度: {} 字节]", deviceId, data.length); |
| 124 | return true; | 125 | return true; |
| 125 | } catch (Exception e) { | 126 | } catch (Exception e) { |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpUpstreamProtocol.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | ||
| 3 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 4 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | 6 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 5 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; | 7 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| @@ -15,6 +17,8 @@ import jakarta.annotation.PreDestroy; | @@ -15,6 +17,8 @@ import jakarta.annotation.PreDestroy; | ||
| 15 | import lombok.Getter; | 17 | import lombok.Getter; |
| 16 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
| 17 | 19 | ||
| 20 | +import java.util.List; | ||
| 21 | + | ||
| 18 | /** | 22 | /** |
| 19 | * IoT 网关 UDP 协议:接收设备上行消息 | 23 | * IoT 网关 UDP 协议:接收设备上行消息 |
| 20 | * <p> | 24 | * <p> |
| @@ -80,19 +84,18 @@ public class IotUdpUpstreamProtocol { | @@ -80,19 +84,18 @@ public class IotUdpUpstreamProtocol { | ||
| 80 | 84 | ||
| 81 | // 4. 监听端口 | 85 | // 4. 监听端口 |
| 82 | udpSocket.listen(udpProperties.getPort(), "0.0.0.0", result -> { | 86 | udpSocket.listen(udpProperties.getPort(), "0.0.0.0", result -> { |
| 83 | - // TODO @AI:if return;简化下;成功才继续往下走; | ||
| 84 | - if (result.succeeded()) { | ||
| 85 | - // 设置数据包处理器 | ||
| 86 | - udpSocket.handler(packet -> upstreamHandler.handle(packet, udpSocket, this)); | ||
| 87 | - log.info("[start][IoT 网关 UDP 协议启动成功,端口:{},接收缓冲区:{} 字节,发送缓冲区:{} 字节]", | ||
| 88 | - udpProperties.getPort(), udpProperties.getReceiveBufferSize(), | ||
| 89 | - udpProperties.getSendBufferSize()); | ||
| 90 | - | ||
| 91 | - // 5. 启动会话清理定时器 | ||
| 92 | - startSessionCleanTimer(); | ||
| 93 | - } else { | 87 | + if (result.failed()) { |
| 94 | log.error("[start][IoT 网关 UDP 协议启动失败]", result.cause()); | 88 | log.error("[start][IoT 网关 UDP 协议启动失败]", result.cause()); |
| 89 | + return; | ||
| 95 | } | 90 | } |
| 91 | + // 设置数据包处理器 | ||
| 92 | + udpSocket.handler(packet -> upstreamHandler.handle(packet, udpSocket)); | ||
| 93 | + log.info("[start][IoT 网关 UDP 协议启动成功,端口:{},接收缓冲区:{} 字节,发送缓冲区:{} 字节]", | ||
| 94 | + udpProperties.getPort(), udpProperties.getReceiveBufferSize(), | ||
| 95 | + udpProperties.getSendBufferSize()); | ||
| 96 | + | ||
| 97 | + // 5. 启动会话清理定时器 | ||
| 98 | + startSessionCleanTimer(); | ||
| 96 | }); | 99 | }); |
| 97 | } | 100 | } |
| 98 | 101 | ||
| @@ -123,16 +126,14 @@ public class IotUdpUpstreamProtocol { | @@ -123,16 +126,14 @@ public class IotUdpUpstreamProtocol { | ||
| 123 | cleanTimerId = vertx.setPeriodic(udpProperties.getSessionCleanIntervalMs(), id -> { | 126 | cleanTimerId = vertx.setPeriodic(udpProperties.getSessionCleanIntervalMs(), id -> { |
| 124 | try { | 127 | try { |
| 125 | // 1. 清理超时的设备地址映射,并获取离线设备列表 | 128 | // 1. 清理超时的设备地址映射,并获取离线设备列表 |
| 126 | - // TODO @AI:兼容 jdk8,不要用 var; | ||
| 127 | - var offlineDevices = sessionManager.cleanExpiredMappings(udpProperties.getSessionTimeoutMs()); | 129 | + List<Long> offlineDeviceIds = sessionManager.cleanExpiredMappings(udpProperties.getSessionTimeoutMs()); |
| 128 | 130 | ||
| 129 | // 2. 为每个离线设备发送离线消息 | 131 | // 2. 为每个离线设备发送离线消息 |
| 130 | - for (var offlineInfo : offlineDevices) { | ||
| 131 | - sendOfflineMessage(offlineInfo.getDeviceId()); | 132 | + for (Long deviceId : offlineDeviceIds) { |
| 133 | + sendOfflineMessage(deviceId); | ||
| 132 | } | 134 | } |
| 133 | - // TODO @AI:CollUtil.isNotEmpty ;简化下 if 判断; | ||
| 134 | - if (!offlineDevices.isEmpty()) { | ||
| 135 | - log.info("[cleanExpiredMappings][本次清理 {} 个超时设备]", offlineDevices.size()); | 135 | + if (CollUtil.isNotEmpty(offlineDeviceIds)) { |
| 136 | + log.info("[cleanExpiredMappings][本次清理 {} 个超时设备]", offlineDeviceIds.size()); | ||
| 136 | } | 137 | } |
| 137 | } catch (Exception e) { | 138 | } catch (Exception e) { |
| 138 | log.error("[cleanExpiredMappings][清理超时会话失败]", e); | 139 | log.error("[cleanExpiredMappings][清理超时会话失败]", e); |
| @@ -150,7 +151,7 @@ public class IotUdpUpstreamProtocol { | @@ -150,7 +151,7 @@ public class IotUdpUpstreamProtocol { | ||
| 150 | private void sendOfflineMessage(Long deviceId) { | 151 | private void sendOfflineMessage(Long deviceId) { |
| 151 | try { | 152 | try { |
| 152 | // 获取设备信息 | 153 | // 获取设备信息 |
| 153 | - var device = deviceService.getDeviceFromCache(deviceId); | 154 | + IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceId); |
| 154 | if (device == null) { | 155 | if (device == null) { |
| 155 | log.warn("[sendOfflineMessage][设备不存在,设备 ID: {}]", deviceId); | 156 | log.warn("[sendOfflineMessage][设备不存在,设备 ID: {}]", deviceId); |
| 156 | return; | 157 | return; |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/manager/IotUdpSessionManager.java
| @@ -2,12 +2,14 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager; | @@ -2,12 +2,14 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager; | ||
| 2 | 2 | ||
| 3 | import io.vertx.core.buffer.Buffer; | 3 | import io.vertx.core.buffer.Buffer; |
| 4 | import io.vertx.core.datagram.DatagramSocket; | 4 | import io.vertx.core.datagram.DatagramSocket; |
| 5 | -import lombok.Data; | ||
| 6 | import lombok.extern.slf4j.Slf4j; | 5 | import lombok.extern.slf4j.Slf4j; |
| 7 | import org.springframework.stereotype.Component; | 6 | import org.springframework.stereotype.Component; |
| 8 | 7 | ||
| 9 | import java.net.InetSocketAddress; | 8 | import java.net.InetSocketAddress; |
| 9 | +import java.time.LocalDateTime; | ||
| 10 | +import java.util.ArrayList; | ||
| 10 | import java.util.Iterator; | 11 | import java.util.Iterator; |
| 12 | +import java.util.List; | ||
| 11 | import java.util.Map; | 13 | import java.util.Map; |
| 12 | import java.util.concurrent.ConcurrentHashMap; | 14 | import java.util.concurrent.ConcurrentHashMap; |
| 13 | 15 | ||
| @@ -34,8 +36,7 @@ public class IotUdpSessionManager { | @@ -34,8 +36,7 @@ public class IotUdpSessionManager { | ||
| 34 | /** | 36 | /** |
| 35 | * 设备地址 Key -> 最后活跃时间(用于清理) | 37 | * 设备地址 Key -> 最后活跃时间(用于清理) |
| 36 | */ | 38 | */ |
| 37 | - // TODO @AI:是不是尽量使用 LocalDateTime ?统一时间类型 | ||
| 38 | - private final Map<String, Long> lastActiveTimeMap = new ConcurrentHashMap<>(); | 39 | + private final Map<String, LocalDateTime> lastActiveTimeMap = new ConcurrentHashMap<>(); |
| 39 | 40 | ||
| 40 | /** | 41 | /** |
| 41 | * 设备地址 Key -> 设备 ID(反向映射,用于清理时同步) | 42 | * 设备地址 Key -> 设备 ID(反向映射,用于清理时同步) |
| @@ -52,22 +53,11 @@ public class IotUdpSessionManager { | @@ -52,22 +53,11 @@ public class IotUdpSessionManager { | ||
| 52 | String addressKey = buildAddressKey(address); | 53 | String addressKey = buildAddressKey(address); |
| 53 | // 更新设备地址映射 | 54 | // 更新设备地址映射 |
| 54 | deviceAddressMap.put(deviceId, address); | 55 | deviceAddressMap.put(deviceId, address); |
| 55 | - lastActiveTimeMap.put(addressKey, System.currentTimeMillis()); | 56 | + lastActiveTimeMap.put(addressKey, LocalDateTime.now()); |
| 56 | addressDeviceMap.put(addressKey, deviceId); | 57 | addressDeviceMap.put(addressKey, deviceId); |
| 57 | log.debug("[updateDeviceAddress][更新设备地址,设备 ID: {},地址: {}]", deviceId, addressKey); | 58 | log.debug("[updateDeviceAddress][更新设备地址,设备 ID: {},地址: {}]", deviceId, addressKey); |
| 58 | } | 59 | } |
| 59 | 60 | ||
| 60 | - // TODO @AI:是不是用不到?用不掉就删除掉!简化 | ||
| 61 | - /** | ||
| 62 | - * 获取设备地址(下行消息发送时使用) | ||
| 63 | - * | ||
| 64 | - * @param deviceId 设备 ID | ||
| 65 | - * @return 设备地址,如果不存在返回 null | ||
| 66 | - */ | ||
| 67 | - public InetSocketAddress getDeviceAddress(Long deviceId) { | ||
| 68 | - return deviceAddressMap.get(deviceId); | ||
| 69 | - } | ||
| 70 | - | ||
| 71 | /** | 61 | /** |
| 72 | * 检查设备是否在线(即是否有地址映射) | 62 | * 检查设备是否在线(即是否有地址映射) |
| 73 | * | 63 | * |
| @@ -126,46 +116,35 @@ public class IotUdpSessionManager { | @@ -126,46 +116,35 @@ public class IotUdpSessionManager { | ||
| 126 | * @param timeoutMs 超时时间(毫秒) | 116 | * @param timeoutMs 超时时间(毫秒) |
| 127 | * @return 清理的设备 ID 列表(用于发送离线消息) | 117 | * @return 清理的设备 ID 列表(用于发送离线消息) |
| 128 | */ | 118 | */ |
| 129 | - // TODO @AI:目前暂时用不到 address 字段,是不是只返回 list of deviceId 就行?简化 | ||
| 130 | - public java.util.List<DeviceOfflineInfo> cleanExpiredMappings(long timeoutMs) { | ||
| 131 | - java.util.List<DeviceOfflineInfo> offlineDevices = new java.util.ArrayList<>(); | ||
| 132 | - long now = System.currentTimeMillis(); | ||
| 133 | - Iterator<Map.Entry<String, Long>> iterator = lastActiveTimeMap.entrySet().iterator(); | 119 | + public List<Long> cleanExpiredMappings(long timeoutMs) { |
| 120 | + List<Long> offlineDeviceIds = new ArrayList<>(); | ||
| 121 | + LocalDateTime now = LocalDateTime.now(); | ||
| 122 | + LocalDateTime expireTime = now.minusNanos(timeoutMs * 1_000_000); | ||
| 123 | + Iterator<Map.Entry<String, LocalDateTime>> iterator = lastActiveTimeMap.entrySet().iterator(); | ||
| 134 | while (iterator.hasNext()) { | 124 | while (iterator.hasNext()) { |
| 135 | - Map.Entry<String, Long> entry = iterator.next(); | ||
| 136 | - if (now - entry.getValue() > timeoutMs) { | ||
| 137 | - String addressKey = entry.getKey(); | ||
| 138 | - Long deviceId = addressDeviceMap.remove(addressKey); | ||
| 139 | - // TODO @AI:if continue,减少括号层级; | ||
| 140 | - if (deviceId != null) { | ||
| 141 | - InetSocketAddress address = deviceAddressMap.remove(deviceId); | ||
| 142 | - if (address != null) { | ||
| 143 | - // 获取设备信息用于发送离线消息 | ||
| 144 | - offlineDevices.add(new DeviceOfflineInfo(deviceId, addressKey)); | ||
| 145 | - log.info("[cleanExpiredMappings][清理超时设备,设备 ID: {},地址: {},最后活跃时间: {}ms 前]", | ||
| 146 | - deviceId, addressKey, now - entry.getValue()); | ||
| 147 | - } | ||
| 148 | - } | 125 | + // 未过期,跳过 |
| 126 | + Map.Entry<String, LocalDateTime> entry = iterator.next(); | ||
| 127 | + if (entry.getValue().isAfter(expireTime)) { | ||
| 128 | + continue; | ||
| 129 | + } | ||
| 130 | + // 过期处理:记录离线设备 ID | ||
| 131 | + String addressKey = entry.getKey(); | ||
| 132 | + Long deviceId = addressDeviceMap.remove(addressKey); | ||
| 133 | + if (deviceId == null) { | ||
| 149 | iterator.remove(); | 134 | iterator.remove(); |
| 135 | + continue; | ||
| 150 | } | 136 | } |
| 137 | + InetSocketAddress address = deviceAddressMap.remove(deviceId); | ||
| 138 | + if (address == null) { | ||
| 139 | + iterator.remove(); | ||
| 140 | + continue; | ||
| 141 | + } | ||
| 142 | + offlineDeviceIds.add(deviceId); | ||
| 143 | + log.debug("[cleanExpiredMappings][清理超时设备,设备 ID: {},地址: {},最后活跃时间: {}]", | ||
| 144 | + deviceId, addressKey, entry.getValue()); | ||
| 145 | + iterator.remove(); | ||
| 151 | } | 146 | } |
| 152 | - return offlineDevices; | ||
| 153 | - } | ||
| 154 | - | ||
| 155 | - // TODO @AI:是不是用不到?用不掉就删除掉!简化 | ||
| 156 | - /** | ||
| 157 | - * 移除设备地址映射 | ||
| 158 | - * | ||
| 159 | - * @param deviceId 设备 ID | ||
| 160 | - */ | ||
| 161 | - public void removeDeviceAddress(Long deviceId) { | ||
| 162 | - InetSocketAddress address = deviceAddressMap.remove(deviceId); | ||
| 163 | - if (address != null) { | ||
| 164 | - String addressKey = buildAddressKey(address); | ||
| 165 | - lastActiveTimeMap.remove(addressKey); | ||
| 166 | - addressDeviceMap.remove(addressKey); | ||
| 167 | - log.debug("[removeDeviceAddress][移除设备地址,设备 ID: {},地址: {}]", deviceId, addressKey); | ||
| 168 | - } | 147 | + return offlineDeviceIds; |
| 169 | } | 148 | } |
| 170 | 149 | ||
| 171 | /** | 150 | /** |
| @@ -178,27 +157,4 @@ public class IotUdpSessionManager { | @@ -178,27 +157,4 @@ public class IotUdpSessionManager { | ||
| 178 | return address.getHostString() + ":" + address.getPort(); | 157 | return address.getHostString() + ":" + address.getPort(); |
| 179 | } | 158 | } |
| 180 | 159 | ||
| 181 | - /** | ||
| 182 | - * 设备离线信息 | ||
| 183 | - */ | ||
| 184 | - @Data | ||
| 185 | - public static class DeviceOfflineInfo { | ||
| 186 | - | ||
| 187 | - /** | ||
| 188 | - * 设备 ID | ||
| 189 | - */ | ||
| 190 | - private final Long deviceId; | ||
| 191 | - | ||
| 192 | - /** | ||
| 193 | - * 设备地址 | ||
| 194 | - */ | ||
| 195 | - private final String address; | ||
| 196 | - | ||
| 197 | - public DeviceOfflineInfo(Long deviceId, String address) { | ||
| 198 | - this.deviceId = deviceId; | ||
| 199 | - this.address = address; | ||
| 200 | - } | ||
| 201 | - | ||
| 202 | - } | ||
| 203 | - | ||
| 204 | } | 160 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/package-info.java
| 1 | -// TODO @AI:完善下注释,参考 mqtt 的 package.json | 1 | +/** |
| 2 | + * UDP 协议实现包 | ||
| 3 | + * <p> | ||
| 4 | + * 提供基于 Vert.x DatagramSocket 的 IoT 设备连接和消息处理功能 | ||
| 5 | + */ | ||
| 2 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; | 6 | package cn.iocoder.yudao.module.iot.gateway.protocol.udp; |
| 3 | \ No newline at end of file | 7 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpUpstreamHandler.java
| @@ -26,11 +26,10 @@ import lombok.extern.slf4j.Slf4j; | @@ -26,11 +26,10 @@ import lombok.extern.slf4j.Slf4j; | ||
| 26 | import java.net.InetSocketAddress; | 26 | import java.net.InetSocketAddress; |
| 27 | import java.util.Map; | 27 | import java.util.Map; |
| 28 | 28 | ||
| 29 | -// TODO @AI:注释里,不要出现 CoAP,避免理解成本过高; | ||
| 30 | /** | 29 | /** |
| 31 | * UDP 上行消息处理器 | 30 | * UDP 上行消息处理器 |
| 32 | * <p> | 31 | * <p> |
| 33 | - * 采用 CoAP 风格的 Token 机制(无状态,每次请求携带 token): | 32 | + * 采用无状态 Token 机制(每次请求携带 token): |
| 34 | * 1. 认证请求:设备发送 auth 消息,携带 clientId、username、password | 33 | * 1. 认证请求:设备发送 auth 消息,携带 clientId、username、password |
| 35 | * 2. 返回 Token:服务端验证后返回 JWT token | 34 | * 2. 返回 Token:服务端验证后返回 JWT token |
| 36 | * 3. 后续请求:每次请求在 params 中携带 token | 35 | * 3. 后续请求:每次请求在 params 中携带 token |
| @@ -45,6 +44,10 @@ public class IotUdpUpstreamHandler { | @@ -45,6 +44,10 @@ public class IotUdpUpstreamHandler { | ||
| 45 | private static final String CODEC_TYPE_BINARY = IotTcpBinaryDeviceMessageCodec.TYPE; | 44 | private static final String CODEC_TYPE_BINARY = IotTcpBinaryDeviceMessageCodec.TYPE; |
| 46 | 45 | ||
| 47 | private static final String AUTH_METHOD = "auth"; | 46 | private static final String AUTH_METHOD = "auth"; |
| 47 | + /** | ||
| 48 | + * Token 参数 Key | ||
| 49 | + */ | ||
| 50 | + private static final String PARAM_KEY_TOKEN = "token"; | ||
| 48 | 51 | ||
| 49 | private final IotDeviceMessageService deviceMessageService; | 52 | private final IotDeviceMessageService deviceMessageService; |
| 50 | 53 | ||
| @@ -70,15 +73,13 @@ public class IotUdpUpstreamHandler { | @@ -70,15 +73,13 @@ public class IotUdpUpstreamHandler { | ||
| 70 | this.serverId = protocol.getServerId(); | 73 | this.serverId = protocol.getServerId(); |
| 71 | } | 74 | } |
| 72 | 75 | ||
| 73 | - // TODO @AI:protocol 这个参数如果用不到,就删除下; | ||
| 74 | /** | 76 | /** |
| 75 | * 处理 UDP 数据包 | 77 | * 处理 UDP 数据包 |
| 76 | * | 78 | * |
| 77 | - * @param packet 数据包 | ||
| 78 | - * @param socket UDP Socket | ||
| 79 | - * @param protocol UDP 协议 | 79 | + * @param packet 数据包 |
| 80 | + * @param socket UDP Socket | ||
| 80 | */ | 81 | */ |
| 81 | - public void handle(DatagramPacket packet, DatagramSocket socket, IotUdpUpstreamProtocol protocol) { | 82 | + public void handle(DatagramPacket packet, DatagramSocket socket) { |
| 82 | InetSocketAddress senderAddress = new InetSocketAddress(packet.sender().host(), packet.sender().port()); | 83 | InetSocketAddress senderAddress = new InetSocketAddress(packet.sender().host(), packet.sender().port()); |
| 83 | Buffer data = packet.data(); | 84 | Buffer data = packet.data(); |
| 84 | log.debug("[handle][收到 UDP 数据包,来源: {},数据长度: {} 字节]", | 85 | log.debug("[handle][收到 UDP 数据包,来源: {},数据长度: {} 字节]", |
| @@ -180,7 +181,7 @@ public class IotUdpUpstreamHandler { | @@ -180,7 +181,7 @@ public class IotUdpUpstreamHandler { | ||
| 180 | return; | 181 | return; |
| 181 | } | 182 | } |
| 182 | 183 | ||
| 183 | - // 3.1 生成 JWT Token(CoAP 风格) | 184 | + // 3.1 生成 JWT Token(无状态) |
| 184 | String token = deviceTokenService.createToken(device.getProductKey(), device.getDeviceName()); | 185 | String token = deviceTokenService.createToken(device.getProductKey(), device.getDeviceName()); |
| 185 | 186 | ||
| 186 | // 3.2 更新设备地址映射(用于下行消息) | 187 | // 3.2 更新设备地址映射(用于下行消息) |
| @@ -212,20 +213,18 @@ public class IotUdpUpstreamHandler { | @@ -212,20 +213,18 @@ public class IotUdpUpstreamHandler { | ||
| 212 | InetSocketAddress senderAddress, DatagramSocket socket) { | 213 | InetSocketAddress senderAddress, DatagramSocket socket) { |
| 213 | String addressKey = sessionManager.buildAddressKey(senderAddress); | 214 | String addressKey = sessionManager.buildAddressKey(senderAddress); |
| 214 | try { | 215 | try { |
| 215 | - // TODO @AI:token 需要枚举个 KEY;考虑到是通过 params 传递的话,需要获取到后,从 map 里移除掉,避免影响后续业务逻辑处理; | ||
| 216 | - // 1. 从消息中提取 token(CoAP 风格:消息体携带 token) | 216 | + // 1.1 从消息中提取 token(无状态:消息体携带 token) |
| 217 | String token = null; | 217 | String token = null; |
| 218 | if (message.getParams() instanceof Map) { | 218 | if (message.getParams() instanceof Map) { |
| 219 | - token = MapUtil.getStr((Map<String, Object>) message.getParams(), "token"); | 219 | + Map<String, Object> paramsMap = (Map<String, Object>) message.getParams(); |
| 220 | + token = (String) paramsMap.remove(PARAM_KEY_TOKEN); | ||
| 220 | } | 221 | } |
| 221 | - | ||
| 222 | if (StrUtil.isBlank(token)) { | 222 | if (StrUtil.isBlank(token)) { |
| 223 | log.warn("[handleBusinessRequest][缺少 token,来源: {}]", addressKey); | 223 | log.warn("[handleBusinessRequest][缺少 token,来源: {}]", addressKey); |
| 224 | sendErrorResponse(socket, senderAddress, message.getRequestId(), "请先进行认证", codecType); | 224 | sendErrorResponse(socket, senderAddress, message.getRequestId(), "请先进行认证", codecType); |
| 225 | return; | 225 | return; |
| 226 | } | 226 | } |
| 227 | - | ||
| 228 | - // 2. 验证 token,获取设备信息 | 227 | + // 1.2 验证 token,获取设备信息 |
| 229 | IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.verifyToken(token); | 228 | IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.verifyToken(token); |
| 230 | if (deviceInfo == null) { | 229 | if (deviceInfo == null) { |
| 231 | log.warn("[handleBusinessRequest][token 无效或已过期,来源: {}]", addressKey); | 230 | log.warn("[handleBusinessRequest][token 无效或已过期,来源: {}]", addressKey); |
| @@ -233,7 +232,7 @@ public class IotUdpUpstreamHandler { | @@ -233,7 +232,7 @@ public class IotUdpUpstreamHandler { | ||
| 233 | return; | 232 | return; |
| 234 | } | 233 | } |
| 235 | 234 | ||
| 236 | - // 3. 获取设备详细信息 | 235 | + // 2. 获取设备详细信息 |
| 237 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), | 236 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), |
| 238 | deviceInfo.getDeviceName()); | 237 | deviceInfo.getDeviceName()); |
| 239 | if (device == null) { | 238 | if (device == null) { |
| @@ -243,14 +242,14 @@ public class IotUdpUpstreamHandler { | @@ -243,14 +242,14 @@ public class IotUdpUpstreamHandler { | ||
| 243 | return; | 242 | return; |
| 244 | } | 243 | } |
| 245 | 244 | ||
| 246 | - // 4. 更新设备地址映射(保持最新) | 245 | + // 3. 更新设备地址映射(保持最新) |
| 247 | sessionManager.updateDeviceAddress(device.getId(), senderAddress); | 246 | sessionManager.updateDeviceAddress(device.getId(), senderAddress); |
| 248 | 247 | ||
| 249 | - // 5. 发送消息到消息总线 | 248 | + // 4. 发送消息到消息总线 |
| 250 | deviceMessageService.sendDeviceMessage(message, device.getProductKey(), | 249 | deviceMessageService.sendDeviceMessage(message, device.getProductKey(), |
| 251 | device.getDeviceName(), serverId); | 250 | device.getDeviceName(), serverId); |
| 252 | 251 | ||
| 253 | - // 6. 发送成功响应 | 252 | + // 5. 发送成功响应 |
| 254 | sendSuccessResponse(socket, senderAddress, message.getRequestId(), "处理成功", codecType); | 253 | sendSuccessResponse(socket, senderAddress, message.getRequestId(), "处理成功", codecType); |
| 255 | log.debug("[handleBusinessRequest][业务消息处理成功,设备 ID: {},方法: {},来源: {}]", | 254 | log.debug("[handleBusinessRequest][业务消息处理成功,设备 ID: {},方法: {},来源: {}]", |
| 256 | device.getId(), message.getMethod(), addressKey); | 255 | device.getId(), message.getMethod(), addressKey); |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpProtocolIntegrationTest.java
| @@ -36,13 +36,14 @@ public class IotUdpProtocolIntegrationTest { | @@ -36,13 +36,14 @@ public class IotUdpProtocolIntegrationTest { | ||
| 36 | private static final String DEVICE_NAME = "small"; | 36 | private static final String DEVICE_NAME = "small"; |
| 37 | private static final String PASSWORD = "509e2b08f7598eb139d276388c600435913ba4c94cd0d50aebc5c0d1855bcb75"; | 37 | private static final String PASSWORD = "509e2b08f7598eb139d276388c600435913ba4c94cd0d50aebc5c0d1855bcb75"; |
| 38 | 38 | ||
| 39 | + // TODO @芋艿:1、IotDeviceAuthUtils 调整下拼接;2、password 的生成;3、后续给 http 也整个单测;4、后续给 tcp 也整个单测;5、后续给 mqtt 也整个单测;6、后续给 emqp 也整个单测 | ||
| 39 | private static final String CLIENT_ID = PRODUCT_KEY + "." + DEVICE_NAME; | 40 | private static final String CLIENT_ID = PRODUCT_KEY + "." + DEVICE_NAME; |
| 40 | private static final String USERNAME = DEVICE_NAME + "&" + PRODUCT_KEY; | 41 | private static final String USERNAME = DEVICE_NAME + "&" + PRODUCT_KEY; |
| 41 | 42 | ||
| 42 | /** | 43 | /** |
| 43 | * 设备 Token:从 {@link #testAuth()} 方法获取后,粘贴到这里 | 44 | * 设备 Token:从 {@link #testAuth()} 方法获取后,粘贴到这里 |
| 44 | */ | 45 | */ |
| 45 | - private static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoiNGF5bVpnT1RPT0NyREtSVCIsImV4cCI6MTc2OTMwNTA1NSwiZGV2aWNlTmFtZSI6InNtYWxsIn0.mf3MEATCn5bp6cXgULunZjs8d00RGUxj96JEz0hMS7k"; | 46 | + private static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoiNGF5bVpnT1RPT0NyREtSVCIsImV4cCI6MTc2OTMxMTY0NiwiZGV2aWNlTmFtZSI6InNtYWxsIn0.re6LCaRfKiE9VQTP3w0Brh2ScVIgrvN3H96z_snndoM"; |
| 46 | 47 | ||
| 47 | /** | 48 | /** |
| 48 | * 认证测试:获取设备 Token | 49 | * 认证测试:获取设备 Token |
| @@ -107,12 +108,14 @@ public class IotUdpProtocolIntegrationTest { | @@ -107,12 +108,14 @@ public class IotUdpProtocolIntegrationTest { | ||
| 107 | .put("id", IdUtil.fastSimpleUUID()) | 108 | .put("id", IdUtil.fastSimpleUUID()) |
| 108 | .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod()) | 109 | .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod()) |
| 109 | .put("version", "1.0") | 110 | .put("version", "1.0") |
| 110 | - .put("identifier", "eat") | ||
| 111 | .put("params", MapUtil.builder() | 111 | .put("params", MapUtil.builder() |
| 112 | - .put("token", TOKEN) | ||
| 113 | - .put("width", 1) | ||
| 114 | - .put("height", "2") | ||
| 115 | - .put("oneThree", "3") | 112 | + .put("identifier", "eat") |
| 113 | + .put("value", MapUtil.builder() | ||
| 114 | + .put("width", 1) | ||
| 115 | + .put("height", "2") | ||
| 116 | + .put("oneThree", "3") | ||
| 117 | + .build()) | ||
| 118 | + .put("time", System.currentTimeMillis()) | ||
| 116 | .build()) | 119 | .build()) |
| 117 | .build()); | 120 | .build()); |
| 118 | 121 |