Commit 8449ccbb7d7d13b51a1665c8b49d3d46ca551a9b

Authored by haohao
1 parent 00bd4293

feat:【IoT 物联网】优化 TCP 二进制协议的消息编码和解码逻辑

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpBinaryDeviceMessageCodec.java
@@ -4,10 +4,9 @@ import cn.hutool.core.lang.Assert; @@ -4,10 +4,9 @@ import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 5 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; 8 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
8 import io.vertx.core.buffer.Buffer; 9 import io.vertx.core.buffer.Buffer;
9 -import lombok.AllArgsConstructor;  
10 -import lombok.Data;  
11 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
12 import org.springframework.stereotype.Component; 11 import org.springframework.stereotype.Component;
13 12
@@ -19,9 +18,9 @@ import java.nio.charset.StandardCharsets; @@ -19,9 +18,9 @@ import java.nio.charset.StandardCharsets;
19 * 二进制协议格式(所有数值使用大端序): 18 * 二进制协议格式(所有数值使用大端序):
20 * 19 *
21 * <pre> 20 * <pre>
22 - * +--------+--------+--------+--------+--------+--------+--------+--------+  
23 - * | 魔术字 | 版本号 | 消息类型| 消息标志| 消息长度(4 字节) |  
24 - * +--------+--------+--------+--------+--------+--------+--------+--------+ 21 + * +--------+--------+--------+---------------------------+--------+--------+
  22 + * | 魔术字 | 版本号 | 消息类型| 消息长度(4 字节) |
  23 + * +--------+--------+--------+---------------------------+--------+--------+
25 * | 消息 ID 长度(2 字节) | 消息 ID (变长字符串) | 24 * | 消息 ID 长度(2 字节) | 消息 ID (变长字符串) |
26 * +--------+--------+--------+--------+--------+--------+--------+--------+ 25 * +--------+--------+--------+--------+--------+--------+--------+--------+
27 * | 方法名长度(2 字节) | 方法名(变长字符串) | 26 * | 方法名长度(2 字节) | 方法名(变长字符串) |
@@ -44,8 +43,6 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -44,8 +43,6 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
44 43
45 public static final String TYPE = "TCP_BINARY"; 44 public static final String TYPE = "TCP_BINARY";
46 45
47 - // ==================== 协议常量 ====================  
48 -  
49 /** 46 /**
50 * 协议魔术字,用于协议识别 47 * 协议魔术字,用于协议识别
51 */ 48 */
@@ -56,27 +53,20 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -56,27 +53,20 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
56 */ 53 */
57 private static final byte PROTOCOL_VERSION = (byte) 0x01; 54 private static final byte PROTOCOL_VERSION = (byte) 0x01;
58 55
59 - // TODO @haohao:这个要不直接静态枚举,不用 MessageType  
60 /** 56 /**
61 - * 消息类型常量 57 + * 请求消息类型
62 */ 58 */
63 - public static class MessageType { 59 + private static final byte REQUEST = (byte) 0x01;
64 60
65 - /**  
66 - * 请求消息  
67 - */  
68 - public static final byte REQUEST = 0x01;  
69 - /**  
70 - * 响应消息  
71 - */  
72 - public static final byte RESPONSE = 0x02;  
73 -  
74 - } 61 + /**
  62 + * 响应消息类型
  63 + */
  64 + private static final byte RESPONSE = (byte) 0x02;
75 65
76 /** 66 /**
77 - * 协议头部固定长度(魔术字 + 版本号 + 消息类型 + 消息标志 + 消息长度) 67 + * 协议头部固定长度(魔术字 + 版本号 + 消息类型 + 消息长度)
78 */ 68 */
79 - private static final int HEADER_FIXED_LENGTH = 8; 69 + private static final int HEADER_FIXED_LENGTH = 7;
80 70
81 /** 71 /**
82 * 最小消息长度(头部 + 消息ID长度 + 方法名长度) 72 * 最小消息长度(头部 + 消息ID长度 + 方法名长度)
@@ -97,7 +87,7 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -97,7 +87,7 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
97 byte messageType = determineMessageType(message); 87 byte messageType = determineMessageType(message);
98 // 2. 构建消息体 88 // 2. 构建消息体
99 byte[] bodyData = buildMessageBody(message, messageType); 89 byte[] bodyData = buildMessageBody(message, messageType);
100 - // 3. 构建完整消息(不包含deviceId,由连接上下文管理) 90 + // 3. 构建完整消息
101 return buildCompleteMessage(message, messageType, bodyData); 91 return buildCompleteMessage(message, messageType, bodyData);
102 } catch (Exception e) { 92 } catch (Exception e) {
103 log.error("[encode][TCP 二进制消息编码失败,消息: {}]", message, e); 93 log.error("[encode][TCP 二进制消息编码失败,消息: {}]", message, e);
@@ -111,30 +101,59 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -111,30 +101,59 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
111 Assert.isTrue(bytes.length >= MIN_MESSAGE_LENGTH, "数据包长度不足"); 101 Assert.isTrue(bytes.length >= MIN_MESSAGE_LENGTH, "数据包长度不足");
112 try { 102 try {
113 Buffer buffer = Buffer.buffer(bytes); 103 Buffer buffer = Buffer.buffer(bytes);
114 - // 1. 解析协议头部  
115 - ProtocolHeader header = parseProtocolHeader(buffer);  
116 - // 2. 解析消息内容(不包含deviceId,由上层连接管理器设置)  
117 - return parseMessageContent(buffer, header); 104 + // 解析协议头部和消息内容
  105 + int index = 0;
  106 + // 1. 验证魔术字
  107 + byte magic = buffer.getByte(index++);
  108 + Assert.isTrue(magic == MAGIC_NUMBER, "无效的协议魔术字: " + magic);
  109 +
  110 + // 2. 验证版本号
  111 + byte version = buffer.getByte(index++);
  112 + Assert.isTrue(version == PROTOCOL_VERSION, "不支持的协议版本: " + version);
  113 +
  114 + // 3. 读取消息类型
  115 + byte messageType = buffer.getByte(index++);
  116 + // 直接验证消息类型,无需抽取方法
  117 + Assert.isTrue(messageType == REQUEST || messageType == RESPONSE,
  118 + "无效的消息类型: " + messageType);
  119 +
  120 + // 4. 读取消息长度
  121 + int messageLength = buffer.getInt(index);
  122 + index += 4;
  123 + Assert.isTrue(messageLength == buffer.length(),
  124 + "消息长度不匹配,期望: " + messageLength + ", 实际: " + buffer.length());
  125 +
  126 + // 5. 读取消息 ID
  127 + short messageIdLength = buffer.getShort(index);
  128 + index += 2;
  129 + String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name());
  130 + index += messageIdLength;
  131 +
  132 + // 6. 读取方法名
  133 + short methodLength = buffer.getShort(index);
  134 + index += 2;
  135 + String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name());
  136 + index += methodLength;
  137 +
  138 + // 7. 解析消息体
  139 + return parseMessageBody(buffer, index, messageType, messageId, method);
118 } catch (Exception e) { 140 } catch (Exception e) {
119 log.error("[decode][TCP 二进制消息解码失败,数据长度: {}]", bytes.length, e); 141 log.error("[decode][TCP 二进制消息解码失败,数据长度: {}]", bytes.length, e);
120 throw new RuntimeException("TCP 二进制消息解码失败: " + e.getMessage(), e); 142 throw new RuntimeException("TCP 二进制消息解码失败: " + e.getMessage(), e);
121 } 143 }
122 } 144 }
123 145
124 - // ==================== 编码相关方法 ====================  
125 -  
126 /** 146 /**
127 * 确定消息类型 147 * 确定消息类型
128 * 优化后的判断逻辑:有响应字段就是响应消息,否则就是请求消息 148 * 优化后的判断逻辑:有响应字段就是响应消息,否则就是请求消息
129 */ 149 */
130 private byte determineMessageType(IotDeviceMessage message) { 150 private byte determineMessageType(IotDeviceMessage message) {
131 // 判断是否为响应消息:有响应码或响应消息时为响应 151 // 判断是否为响应消息:有响应码或响应消息时为响应
132 - // TODO @haohao:感觉只判断 code 更稳妥点?msg 有可能空。。。  
133 - if (message.getCode() != null || StrUtil.isNotBlank(message.getMsg())) {  
134 - return MessageType.RESPONSE; 152 + if (message.getCode() != null) {
  153 + return RESPONSE;
135 } 154 }
136 // 默认为请求消息 155 // 默认为请求消息
137 - return MessageType.REQUEST; 156 + return REQUEST;
138 } 157 }
139 158
140 /** 159 /**
@@ -142,12 +161,12 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -142,12 +161,12 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
142 */ 161 */
143 private byte[] buildMessageBody(IotDeviceMessage message, byte messageType) { 162 private byte[] buildMessageBody(IotDeviceMessage message, byte messageType) {
144 Buffer bodyBuffer = Buffer.buffer(); 163 Buffer bodyBuffer = Buffer.buffer();
145 - if (messageType == MessageType.RESPONSE) { 164 + if (messageType == RESPONSE) {
146 // code 165 // code
147 bodyBuffer.appendInt(message.getCode() != null ? message.getCode() : 0); 166 bodyBuffer.appendInt(message.getCode() != null ? message.getCode() : 0);
148 // msg 167 // msg
149 String msg = message.getMsg() != null ? message.getMsg() : ""; 168 String msg = message.getMsg() != null ? message.getMsg() : "";
150 - byte[] msgBytes = msg.getBytes(StandardCharsets.UTF_8); 169 + byte[] msgBytes = StrUtil.utf8Bytes(msg);
151 bodyBuffer.appendShort((short) msgBytes.length); 170 bodyBuffer.appendShort((short) msgBytes.length);
152 bodyBuffer.appendBytes(msgBytes); 171 bodyBuffer.appendBytes(msgBytes);
153 // data 172 // data
@@ -155,11 +174,9 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -155,11 +174,9 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
155 bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getData())); 174 bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getData()));
156 } 175 }
157 } else { 176 } else {
158 - // params  
159 - // TODO @haohao:请求是不是只处理 message.getParams() 哈?  
160 - Object payload = message.getParams() != null ? message.getParams() : message.getData();  
161 - if (payload != null) {  
162 - bodyBuffer.appendBytes(JsonUtils.toJsonByte(payload)); 177 + // 请求消息只处理 params 参数
  178 + if (message.getParams() != null) {
  179 + bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getParams()));
163 } 180 }
164 } 181 }
165 return bodyBuffer.getBytes(); 182 return bodyBuffer.getBytes();
@@ -174,20 +191,17 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -174,20 +191,17 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
174 buffer.appendByte(MAGIC_NUMBER); 191 buffer.appendByte(MAGIC_NUMBER);
175 buffer.appendByte(PROTOCOL_VERSION); 192 buffer.appendByte(PROTOCOL_VERSION);
176 buffer.appendByte(messageType); 193 buffer.appendByte(messageType);
177 - buffer.appendByte((byte) 0x00); // 消息标志,预留字段 TODO @haohao:这个标识的作用是啥呀?  
178 - // 2. 预留消息长度位置(在 6. 更新消息长度) 194 + // 2. 预留消息长度位置(在 5. 更新消息长度)
179 int lengthPosition = buffer.length(); 195 int lengthPosition = buffer.length();
180 buffer.appendInt(0); 196 buffer.appendInt(0);
181 // 3. 写入消息 ID 197 // 3. 写入消息 ID
182 String messageId = StrUtil.isNotBlank(message.getRequestId()) ? message.getRequestId() 198 String messageId = StrUtil.isNotBlank(message.getRequestId()) ? message.getRequestId()
183 - // TODO @haohao:复用 IotDeviceMessageUtils 的 generateMessageId 哇?  
184 - : generateMessageId(message.getMethod());  
185 - // TODO @haohao:StrUtil.utf8Bytes()  
186 - byte[] messageIdBytes = messageId.getBytes(StandardCharsets.UTF_8); 199 + : IotDeviceMessageUtils.generateMessageId();
  200 + byte[] messageIdBytes = StrUtil.utf8Bytes(messageId);
187 buffer.appendShort((short) messageIdBytes.length); 201 buffer.appendShort((short) messageIdBytes.length);
188 buffer.appendBytes(messageIdBytes); 202 buffer.appendBytes(messageIdBytes);
189 // 4. 写入方法名 203 // 4. 写入方法名
190 - byte[] methodBytes = message.getMethod().getBytes(StandardCharsets.UTF_8); 204 + byte[] methodBytes = StrUtil.utf8Bytes(message.getMethod());
191 buffer.appendShort((short) methodBytes.length); 205 buffer.appendShort((short) methodBytes.length);
192 buffer.appendBytes(methodBytes); 206 buffer.appendBytes(methodBytes);
193 // 5. 写入消息体 207 // 5. 写入消息体
@@ -198,66 +212,6 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -198,66 +212,6 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
198 } 212 }
199 213
200 /** 214 /**
201 - * 生成消息 ID  
202 - */  
203 - private String generateMessageId(String method) {  
204 - return method + "_" + System.currentTimeMillis() + "_" + (int) (Math.random() * 1000);  
205 - }  
206 -  
207 - // ==================== 解码相关方法 ====================  
208 -  
209 - // TODO @haohao:是不是把 parseProtocolHeader、parseMessageContent 合并?  
210 - /**  
211 - * 解析协议头部  
212 - */  
213 - private ProtocolHeader parseProtocolHeader(Buffer buffer) {  
214 - int index = 0;  
215 - // 1. 验证魔术字  
216 - byte magic = buffer.getByte(index++);  
217 - Assert.isTrue(magic == MAGIC_NUMBER, "无效的协议魔术字: " + magic);  
218 - // 2. 验证版本号  
219 - byte version = buffer.getByte(index++);  
220 - Assert.isTrue(version == PROTOCOL_VERSION, "不支持的协议版本: " + version);  
221 -  
222 - // 3. 读取消息类型  
223 - byte messageType = buffer.getByte(index++);  
224 - Assert.isTrue(isValidMessageType(messageType), "无效的消息类型: " + messageType);  
225 - // 4. 读取消息标志(暂时跳过)  
226 - byte messageFlags = buffer.getByte(index++);  
227 -  
228 - // 5. 读取消息长度  
229 - int messageLength = buffer.getInt(index);  
230 - index += 4;  
231 -  
232 - Assert.isTrue(messageLength == buffer.length(),  
233 - "消息长度不匹配,期望: " + messageLength + ", 实际: " + buffer.length());  
234 -  
235 - return new ProtocolHeader(magic, version, messageType, messageFlags, messageLength, index);  
236 - }  
237 -  
238 - /**  
239 - * 解析消息内容  
240 - */  
241 - private IotDeviceMessage parseMessageContent(Buffer buffer, ProtocolHeader header) {  
242 - int index = header.getNextIndex();  
243 -  
244 - // 1. 读取消息 ID  
245 - short messageIdLength = buffer.getShort(index);  
246 - index += 2;  
247 - String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name());  
248 - index += messageIdLength;  
249 -  
250 - // 2. 读取方法名  
251 - short methodLength = buffer.getShort(index);  
252 - index += 2;  
253 - String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name());  
254 - index += methodLength;  
255 -  
256 - // 3. 解析消息体  
257 - return parseMessageBody(buffer, index, header.getMessageType(), messageId, method);  
258 - }  
259 -  
260 - /**  
261 * 解析消息体 215 * 解析消息体
262 */ 216 */
263 private IotDeviceMessage parseMessageBody(Buffer buffer, int startIndex, byte messageType, 217 private IotDeviceMessage parseMessageBody(Buffer buffer, int startIndex, byte messageType,
@@ -267,11 +221,11 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -267,11 +221,11 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
267 return IotDeviceMessage.of(messageId, method, null, null, null, null); 221 return IotDeviceMessage.of(messageId, method, null, null, null, null);
268 } 222 }
269 223
270 - if (messageType == MessageType.RESPONSE) { 224 + if (messageType == RESPONSE) {
271 // 响应消息:解析 code + msg + data 225 // 响应消息:解析 code + msg + data
272 return parseResponseMessage(buffer, startIndex, messageId, method); 226 return parseResponseMessage(buffer, startIndex, messageId, method);
273 } else { 227 } else {
274 - // 请求消息:解析 payload(可能是 params 或 data) 228 + // 请求消息:解析 payload
275 Object payload = parseJsonData(buffer, startIndex, buffer.length()); 229 Object payload = parseJsonData(buffer, startIndex, buffer.length());
276 return IotDeviceMessage.of(messageId, method, payload, null, null, null); 230 return IotDeviceMessage.of(messageId, method, payload, null, null, null);
277 } 231 }
@@ -303,7 +257,7 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -303,7 +257,7 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
303 } 257 }
304 258
305 /** 259 /**
306 - * 解析JSON数据 260 + * 解析 JSON 数据
307 */ 261 */
308 private Object parseJsonData(Buffer buffer, int startIndex, int endIndex) { 262 private Object parseJsonData(Buffer buffer, int startIndex, int endIndex) {
309 if (startIndex >= endIndex) { 263 if (startIndex >= endIndex) {
@@ -318,34 +272,14 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -318,34 +272,14 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
318 } 272 }
319 } 273 }
320 274
321 - // ==================== 辅助方法 ====================  
322 -  
323 - // TODO @haohao:这个貌似只用一次,可以考虑不抽小方法哈;  
324 /** 275 /**
325 - * 验证消息类型是否有效 276 + * 快速检测是否为二进制格式
  277 + *
  278 + * @param data 数据
  279 + * @return 是否为二进制格式
326 */ 280 */
327 - private boolean isValidMessageType(byte messageType) {  
328 - return messageType == MessageType.REQUEST || messageType == MessageType.RESPONSE; 281 + public static boolean isBinaryFormatQuick(byte[] data) {
  282 + return data != null && data.length >= 1 && data[0] == MAGIC_NUMBER;
329 } 283 }
330 284
331 - // ==================== 内部类 ====================  
332 -  
333 - /**  
334 - * 协议头部信息  
335 - */  
336 - @Data  
337 - @AllArgsConstructor  
338 - private static class ProtocolHeader {  
339 -  
340 - private byte magic;  
341 - private byte version;  
342 - private byte messageType;  
343 - private byte messageFlags;  
344 - private int messageLength;  
345 - /**  
346 - * 指向消息内容开始位置  
347 - */  
348 - private int nextIndex;  
349 -  
350 - }  
351 } 285 }
352 \ No newline at end of file 286 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java
@@ -28,46 +28,33 @@ public class IotTcpConnectionManager { @@ -28,46 +28,33 @@ public class IotTcpConnectionManager {
28 private final Map<NetSocket, ConnectionInfo> connectionMap = new ConcurrentHashMap<>(); 28 private final Map<NetSocket, ConnectionInfo> connectionMap = new ConcurrentHashMap<>();
29 29
30 /** 30 /**
31 - * 设备 ID -> NetSocket 的映射(用于快速查找) 31 + * 设备 ID -> NetSocket 的映射
32 */ 32 */
33 private final Map<Long, NetSocket> deviceSocketMap = new ConcurrentHashMap<>(); 33 private final Map<Long, NetSocket> deviceSocketMap = new ConcurrentHashMap<>();
34 34
35 /** 35 /**
36 - * NetSocket -> 设备 ID 的映射(用于连接断开时清理)  
37 - */  
38 - private final Map<NetSocket, Long> socketDeviceMap = new ConcurrentHashMap<>();  
39 -  
40 - /**  
41 * 注册设备连接(包含认证信息) 36 * 注册设备连接(包含认证信息)
42 * 37 *
43 - * @param socket TCP 连接  
44 - * @param deviceId 设备 ID  
45 - * @param authInfo 认证信息 38 + * @param socket TCP 连接
  39 + * @param deviceId 设备 ID
  40 + * @param connectionInfo 连接信息
46 */ 41 */
47 - public void registerConnection(NetSocket socket, Long deviceId, AuthInfo authInfo) { 42 + public void registerConnection(NetSocket socket, Long deviceId, ConnectionInfo connectionInfo) {
48 // 如果设备已有其他连接,先清理旧连接 43 // 如果设备已有其他连接,先清理旧连接
49 NetSocket oldSocket = deviceSocketMap.get(deviceId); 44 NetSocket oldSocket = deviceSocketMap.get(deviceId);
50 if (oldSocket != null && oldSocket != socket) { 45 if (oldSocket != null && oldSocket != socket) {
51 log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]", 46 log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]",
52 deviceId, oldSocket.remoteAddress()); 47 deviceId, oldSocket.remoteAddress());
53 oldSocket.close(); 48 oldSocket.close();
54 - // 清理所有相关映射 49 + // 清理旧连接的映射
55 connectionMap.remove(oldSocket); 50 connectionMap.remove(oldSocket);
56 - socketDeviceMap.remove(oldSocket);  
57 } 51 }
58 52
59 - // 注册新连接 - 更新所有映射关系  
60 - ConnectionInfo connectionInfo = new ConnectionInfo()  
61 - .setDeviceId(deviceId)  
62 - .setAuthInfo(authInfo)  
63 - .setAuthenticated(true);  
64 connectionMap.put(socket, connectionInfo); 53 connectionMap.put(socket, connectionInfo);
65 deviceSocketMap.put(deviceId, socket); 54 deviceSocketMap.put(deviceId, socket);
66 - // TODO @haohao:socketDeviceMap 和 connectionMap 会重复哇?connectionMap.get(socket).getDeviceId  
67 - socketDeviceMap.put(socket, deviceId);  
68 55
69 log.info("[registerConnection][注册设备连接,设备 ID: {},连接: {},product key: {},device name: {}]", 56 log.info("[registerConnection][注册设备连接,设备 ID: {},连接: {},product key: {},device name: {}]",
70 - deviceId, socket.remoteAddress(), authInfo.getProductKey(), authInfo.getDeviceName()); 57 + deviceId, socket.remoteAddress(), connectionInfo.getProductKey(), connectionInfo.getDeviceName());
71 } 58 }
72 59
73 /** 60 /**
@@ -77,29 +64,14 @@ public class IotTcpConnectionManager { @@ -77,29 +64,14 @@ public class IotTcpConnectionManager {
77 */ 64 */
78 public void unregisterConnection(NetSocket socket) { 65 public void unregisterConnection(NetSocket socket) {
79 ConnectionInfo connectionInfo = connectionMap.remove(socket); 66 ConnectionInfo connectionInfo = connectionMap.remove(socket);
80 - Long deviceId = socketDeviceMap.remove(socket);  
81 - if (connectionInfo != null && deviceId != null) { 67 + if (connectionInfo != null) {
  68 + Long deviceId = connectionInfo.getDeviceId();
82 deviceSocketMap.remove(deviceId); 69 deviceSocketMap.remove(deviceId);
83 log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]", 70 log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]",
84 deviceId, socket.remoteAddress()); 71 deviceId, socket.remoteAddress());
85 } 72 }
86 } 73 }
87 74
88 - // TODO @haohao:用不到,要不暂时清理哈。  
89 - /**  
90 - * 注销设备连接(通过设备 ID)  
91 - *  
92 - * @param deviceId 设备 ID  
93 - */  
94 - public void unregisterConnection(Long deviceId) {  
95 - NetSocket socket = deviceSocketMap.remove(deviceId);  
96 - if (socket != null) {  
97 - connectionMap.remove(socket);  
98 - socketDeviceMap.remove(socket);  
99 - log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]", deviceId, socket.remoteAddress());  
100 - }  
101 - }  
102 -  
103 /** 75 /**
104 * 检查连接是否已认证 76 * 检查连接是否已认证
105 */ 77 */
@@ -116,11 +88,10 @@ public class IotTcpConnectionManager { @@ -116,11 +88,10 @@ public class IotTcpConnectionManager {
116 } 88 }
117 89
118 /** 90 /**
119 - * 获取连接的认证信息 91 + * 获取连接信息
120 */ 92 */
121 - public AuthInfo getAuthInfo(NetSocket socket) {  
122 - ConnectionInfo info = connectionMap.get(socket);  
123 - return info != null ? info.getAuthInfo() : null; 93 + public ConnectionInfo getConnectionInfo(NetSocket socket) {
  94 + return connectionMap.get(socket);
124 } 95 }
125 96
126 /** 97 /**
@@ -159,30 +130,34 @@ public class IotTcpConnectionManager { @@ -159,30 +130,34 @@ public class IotTcpConnectionManager {
159 } 130 }
160 } 131 }
161 132
162 - // TODO @haohao:ConnectionInfo 和 AuthInfo 是不是可以融合哈?  
163 -  
164 /** 133 /**
165 - * 连接信息 134 + * 连接信息(包含认证信息)
166 */ 135 */
167 @Data 136 @Data
168 public static class ConnectionInfo { 137 public static class ConnectionInfo {
169 -  
170 - private Long deviceId;  
171 - private AuthInfo authInfo;  
172 - private boolean authenticated;  
173 -  
174 - }  
175 -  
176 - /**  
177 - * 认证信息  
178 - */  
179 - @Data  
180 - public static class AuthInfo {  
181 - 138 + /**
  139 + * 设备 ID
  140 + */
182 private Long deviceId; 141 private Long deviceId;
  142 + /**
  143 + * 产品 Key
  144 + */
183 private String productKey; 145 private String productKey;
  146 + /**
  147 + * 设备名称
  148 + */
184 private String deviceName; 149 private String deviceName;
  150 + /**
  151 + * 客户端 ID
  152 + */
185 private String clientId; 153 private String clientId;
186 - 154 + /**
  155 + * 消息编解码类型(认证后确定)
  156 + */
  157 + private String codecType;
  158 + /**
  159 + * 是否已认证
  160 + */
  161 + private boolean authenticated;
187 } 162 }
188 } 163 }
189 \ No newline at end of file 164 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.BooleanUtil;
4 import cn.hutool.core.util.IdUtil; 5 import cn.hutool.core.util.IdUtil;
5 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
6 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
7 -import cn.hutool.json.JSONObject;  
8 -import cn.hutool.json.JSONUtil;  
9 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 8 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  9 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
10 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 10 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
11 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 11 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
12 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; 12 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
@@ -21,12 +21,8 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessa @@ -21,12 +21,8 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessa
21 import io.vertx.core.Handler; 21 import io.vertx.core.Handler;
22 import io.vertx.core.buffer.Buffer; 22 import io.vertx.core.buffer.Buffer;
23 import io.vertx.core.net.NetSocket; 23 import io.vertx.core.net.NetSocket;
24 -import lombok.AllArgsConstructor;  
25 -import lombok.Data;  
26 import lombok.extern.slf4j.Slf4j; 24 import lombok.extern.slf4j.Slf4j;
27 25
28 -import java.nio.charset.StandardCharsets;  
29 -  
30 /** 26 /**
31 * TCP 上行消息处理器 27 * TCP 上行消息处理器
32 * 28 *
@@ -77,31 +73,55 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -77,31 +73,55 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
77 }); 73 });
78 74
79 // 设置消息处理器 75 // 设置消息处理器
80 - socket.handler(buffer -> processMessage(clientId, buffer, socket)); 76 + socket.handler(buffer -> {
  77 + try {
  78 + processMessage(clientId, buffer, socket);
  79 + } catch (Exception e) {
  80 + log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
  81 + clientId, socket.remoteAddress(), e.getMessage());
  82 + cleanupConnection(socket);
  83 + socket.close();
  84 + }
  85 + });
81 } 86 }
82 87
83 /** 88 /**
84 * 处理消息 89 * 处理消息
  90 + *
  91 + * @param clientId 客户端 ID
  92 + * @param buffer 消息
  93 + * @param socket 网络连接
  94 + * @throws Exception 消息解码失败时抛出异常
85 */ 95 */
86 - private void processMessage(String clientId, Buffer buffer, NetSocket socket) { 96 + private void processMessage(String clientId, Buffer buffer, NetSocket socket) throws Exception {
  97 + // 1. 基础检查
  98 + if (buffer == null || buffer.length() == 0) {
  99 + return;
  100 + }
  101 +
  102 + // 2. 获取消息格式类型
  103 + String codecType = getMessageCodecType(buffer, socket);
  104 +
  105 + // 3. 解码消息
  106 + IotDeviceMessage message;
87 try { 107 try {
88 - // 1.1 数据包基础检查  
89 - if (buffer.length() == 0) {  
90 - return;  
91 - }  
92 - // 1.2 解码消息  
93 - MessageInfo messageInfo = decodeMessage(buffer);  
94 - if (messageInfo == null) {  
95 - return; 108 + message = deviceMessageService.decodeDeviceMessage(buffer.getBytes(), codecType);
  109 + if (message == null) {
  110 + throw new Exception("解码后消息为空");
96 } 111 }
  112 + } catch (Exception e) {
  113 + // 消息格式错误时抛出异常,由上层处理连接断开
  114 + throw new Exception("消息解码失败: " + e.getMessage(), e);
  115 + }
97 116
98 - // 2. 根据消息类型路由处理  
99 - if (isAuthRequest(messageInfo.message)) { 117 + // 4. 根据消息类型路由处理
  118 + try {
  119 + if (AUTH_METHOD.equals(message.getMethod())) {
100 // 认证请求 120 // 认证请求
101 - handleAuthenticationRequest(clientId, messageInfo, socket); 121 + handleAuthenticationRequest(clientId, message, codecType, socket);
102 } else { 122 } else {
103 // 业务消息 123 // 业务消息
104 - handleBusinessRequest(clientId, messageInfo, socket); 124 + handleBusinessRequest(clientId, message, codecType, socket);
105 } 125 }
106 } catch (Exception e) { 126 } catch (Exception e) {
107 log.error("[processMessage][处理消息失败,客户端 ID: {}]", clientId, e); 127 log.error("[processMessage][处理消息失败,客户端 ID: {}]", clientId, e);
@@ -110,226 +130,158 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -110,226 +130,158 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
110 130
111 /** 131 /**
112 * 处理认证请求 132 * 处理认证请求
  133 + *
  134 + * @param clientId 客户端 ID
  135 + * @param message 消息信息
  136 + * @param codecType 消息编解码类型
  137 + * @param socket 网络连接
113 */ 138 */
114 - private void handleAuthenticationRequest(String clientId, MessageInfo messageInfo, NetSocket socket) { 139 + private void handleAuthenticationRequest(String clientId, IotDeviceMessage message, String codecType,
  140 + NetSocket socket) {
115 try { 141 try {
116 // 1.1 解析认证参数 142 // 1.1 解析认证参数
117 - IotDeviceMessage message = messageInfo.message;  
118 - AuthParams authParams = parseAuthParams(message.getParams()); 143 + IotDeviceAuthReqDTO authParams = JsonUtils.parseObject(message.getParams().toString(),
  144 + IotDeviceAuthReqDTO.class);
119 if (authParams == null) { 145 if (authParams == null) {
120 - sendError(socket, message.getRequestId(), "认证参数不完整", messageInfo.codecType); 146 + sendErrorResponse(socket, message.getRequestId(), "认证参数不完整", codecType);
121 return; 147 return;
122 } 148 }
123 // 1.2 执行认证 149 // 1.2 执行认证
124 - if (!authenticateDevice(authParams)) { 150 + if (!validateDeviceAuth(authParams)) {
125 log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]", 151 log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]",
126 - clientId, authParams.username);  
127 - sendError(socket, message.getRequestId(), "认证失败", messageInfo.codecType); 152 + clientId, authParams.getUsername());
  153 + sendErrorResponse(socket, message.getRequestId(), "认证失败", codecType);
128 return; 154 return;
129 } 155 }
130 156
131 // 2.1 解析设备信息 157 // 2.1 解析设备信息
132 - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.username); 158 + IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername());
133 if (deviceInfo == null) { 159 if (deviceInfo == null) {
134 - sendError(socket, message.getRequestId(), "解析设备信息失败", messageInfo.codecType); 160 + sendErrorResponse(socket, message.getRequestId(), "解析设备信息失败", codecType);
135 return; 161 return;
136 } 162 }
137 // 2.2 获取设备信息 163 // 2.2 获取设备信息
138 IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), 164 IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(),
139 deviceInfo.getDeviceName()); 165 deviceInfo.getDeviceName());
140 if (device == null) { 166 if (device == null) {
141 - sendError(socket, message.getRequestId(), "设备不存在", messageInfo.codecType); 167 + sendErrorResponse(socket, message.getRequestId(), "设备不存在", codecType);
142 return; 168 return;
143 } 169 }
144 170
145 - // 3. 注册连接并发送成功响应  
146 - registerConnection(socket, device, deviceInfo, authParams.clientId);  
147 - sendOnlineMessage(deviceInfo);  
148 - sendSuccess(socket, message.getRequestId(), "认证成功", messageInfo.codecType); 171 + // 3.1 注册连接
  172 + registerConnection(socket, device, clientId, codecType);
  173 + // 3.2 发送上线消息
  174 + sendOnlineMessage(device);
  175 + // 3.3 发送成功响应
  176 + sendSuccessResponse(socket, message.getRequestId(), "认证成功", codecType);
149 log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {}]", 177 log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {}]",
150 - device.getId(), deviceInfo.getDeviceName()); 178 + device.getId(), device.getDeviceName());
151 } catch (Exception e) { 179 } catch (Exception e) {
152 log.error("[handleAuthenticationRequest][认证处理异常,客户端 ID: {}]", clientId, e); 180 log.error("[handleAuthenticationRequest][认证处理异常,客户端 ID: {}]", clientId, e);
153 - sendError(socket, messageInfo.message.getRequestId(), "认证处理异常", messageInfo.codecType); 181 + sendErrorResponse(socket, message.getRequestId(), "认证处理异常", codecType);
154 } 182 }
155 } 183 }
156 184
157 /** 185 /**
158 * 处理业务请求 186 * 处理业务请求
  187 + *
  188 + * @param clientId 客户端 ID
  189 + * @param message 消息信息
  190 + * @param codecType 消息编解码类型
  191 + * @param socket 网络连接
159 */ 192 */
160 - private void handleBusinessRequest(String clientId, MessageInfo messageInfo, NetSocket socket) { 193 + private void handleBusinessRequest(String clientId, IotDeviceMessage message, String codecType, NetSocket socket) {
161 try { 194 try {
162 // 1. 检查认证状态 195 // 1. 检查认证状态
163 if (connectionManager.isNotAuthenticated(socket)) { 196 if (connectionManager.isNotAuthenticated(socket)) {
164 log.warn("[handleBusinessRequest][设备未认证,客户端 ID: {}]", clientId); 197 log.warn("[handleBusinessRequest][设备未认证,客户端 ID: {}]", clientId);
165 - sendError(socket, messageInfo.message.getRequestId(), "请先进行认证", messageInfo.codecType); 198 + sendErrorResponse(socket, message.getRequestId(), "请先进行认证", codecType);
166 return; 199 return;
167 } 200 }
168 201
169 // 2. 获取认证信息并处理业务消息 202 // 2. 获取认证信息并处理业务消息
170 - IotTcpConnectionManager.AuthInfo authInfo = connectionManager.getAuthInfo(socket);  
171 - processBusinessMessage(clientId, messageInfo.message, authInfo);  
172 - } catch (Exception e) {  
173 - log.error("[handleBusinessRequest][业务请求处理异常,客户端 ID: {}]", clientId, e);  
174 - }  
175 - } 203 + IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
176 204
177 - // TODO @haohao:processBusinessMessage 这个小方法,直接融合到 handleBusinessRequest 里?读起来更聚集点  
178 - /**  
179 - * 处理业务消息  
180 - */  
181 - private void processBusinessMessage(String clientId, IotDeviceMessage message,  
182 - IotTcpConnectionManager.AuthInfo authInfo) {  
183 - try {  
184 - message.setDeviceId(authInfo.getDeviceId());  
185 - message.setServerId(serverId);  
186 - // 发送到消息总线  
187 - deviceMessageService.sendDeviceMessage(message, authInfo.getProductKey(),  
188 - authInfo.getDeviceName(), serverId); 205 + // 3. 发送消息到消息总线
  206 + deviceMessageService.sendDeviceMessage(message, connectionInfo.getProductKey(),
  207 + connectionInfo.getDeviceName(), serverId);
189 } catch (Exception e) { 208 } catch (Exception e) {
190 - log.error("[processBusinessMessage][业务消息处理失败,客户端 ID: {},消息 ID: {}]",  
191 - clientId, message.getId(), e); 209 + log.error("[handleBusinessRequest][业务请求处理异常,客户端 ID: {}]", clientId, e);
192 } 210 }
193 } 211 }
194 212
195 /** 213 /**
196 - * 解码消息 214 + * 获取消息编解码类型
197 * 215 *
198 * @param buffer 消息 216 * @param buffer 消息
  217 + * @param socket 网络连接
  218 + * @return 消息编解码类型
199 */ 219 */
200 - private MessageInfo decodeMessage(Buffer buffer) {  
201 - if (buffer == null || buffer.length() == 0) {  
202 - return null;  
203 - }  
204 - // 1. 快速检测消息格式类型  
205 - // TODO @haohao:是不是进一步优化?socket 建立认证后,那条消息已经定义了所有消息的格式哈?  
206 - String codecType = detectMessageFormat(buffer);  
207 - try {  
208 - // 2. 使用检测到的格式进行解码  
209 - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(buffer.getBytes(), codecType);  
210 - if (message == null) {  
211 - return null;  
212 - }  
213 - return new MessageInfo(message, codecType);  
214 - } catch (Exception e) {  
215 - log.warn("[decodeMessage][消息解码失败,格式: {},数据长度: {},错误: {}]",  
216 - codecType, buffer.length(), e.getMessage());  
217 - // TODO @haohao:一般消息格式不对,应该抛出异常,断开连接居多?  
218 - return null;  
219 - }  
220 - }  
221 -  
222 - /**  
223 - * 检测消息格式类型  
224 - * 优化性能:避免不必要的字符串转换  
225 - */  
226 - private String detectMessageFormat(Buffer buffer) {  
227 - // TODO @haohao:是不是 IotTcpBinaryDeviceMessageCodec 提供一个 isBinaryFormat 方法哈?  
228 - // 默认使用 JSON  
229 - if (buffer.length() == 0) {  
230 - return CODEC_TYPE_JSON;  
231 - }  
232 -  
233 - // 1. 优先检测二进制格式(检查魔术字节 0x7E)  
234 - if (isBinaryFormat(buffer)) {  
235 - return CODEC_TYPE_BINARY;  
236 - }  
237 -  
238 - // 2. 检测 JSON 格式(检查前几个有效字符)  
239 - // TODO @haohao:这个检测去掉?直接 return CODEC_TYPE_JSON 更简洁一点。  
240 - if (isJsonFormat(buffer)) {  
241 - return CODEC_TYPE_JSON; 220 + private String getMessageCodecType(Buffer buffer, NetSocket socket) {
  221 + // 1. 如果已认证,优先使用缓存的编解码类型
  222 + IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
  223 + if (connectionInfo != null && connectionInfo.isAuthenticated() &&
  224 + StrUtil.isNotBlank(connectionInfo.getCodecType())) {
  225 + return connectionInfo.getCodecType();
242 } 226 }
243 227
244 - // 3. 默认尝试 JSON 格式  
245 - return CODEC_TYPE_JSON;  
246 - }  
247 -  
248 - /**  
249 - * 检测二进制格式  
250 - * 通过检查魔术字节快速识别,避免完整字符串转换  
251 - */  
252 - private boolean isBinaryFormat(Buffer buffer) {  
253 - // 二进制协议最小长度检查  
254 - if (buffer.length() < 8) {  
255 - return false;  
256 - }  
257 -  
258 - try {  
259 - // 检查魔术字节 0x7E(二进制协议的第一个字节)  
260 - byte firstByte = buffer.getByte(0);  
261 - return firstByte == (byte) 0x7E;  
262 - } catch (Exception e) {  
263 - return false;  
264 - }  
265 - }  
266 -  
267 - /**  
268 - * 检测 JSON 格式  
269 - * 只检查前几个有效字符,避免完整字符串转换  
270 - */  
271 - private boolean isJsonFormat(Buffer buffer) {  
272 - try {  
273 - // 检查前 64 个字节或整个缓冲区(取较小值)  
274 - int checkLength = Math.min(buffer.length(), 64);  
275 - String prefix = buffer.getString(0, checkLength, StandardCharsets.UTF_8.name());  
276 -  
277 - if (StrUtil.isBlank(prefix)) {  
278 - return false;  
279 - }  
280 -  
281 - String trimmed = prefix.trim();  
282 - // JSON 格式必须以 { 或 [ 开头  
283 - return trimmed.startsWith("{") || trimmed.startsWith("[");  
284 -  
285 - } catch (Exception e) {  
286 - return false;  
287 - } 228 + // 2. 未认证时检测消息格式类型
  229 + return IotTcpBinaryDeviceMessageCodec.isBinaryFormatQuick(buffer.getBytes()) ? CODEC_TYPE_BINARY
  230 + : CODEC_TYPE_JSON;
288 } 231 }
289 232
290 /** 233 /**
291 * 注册连接信息 234 * 注册连接信息
  235 + *
  236 + * @param socket 网络连接
  237 + * @param device 设备
  238 + * @param clientId 客户端 ID
  239 + * @param codecType 消息编解码类型
292 */ 240 */
293 private void registerConnection(NetSocket socket, IotDeviceRespDTO device, 241 private void registerConnection(NetSocket socket, IotDeviceRespDTO device,
294 - IotDeviceAuthUtils.DeviceInfo deviceInfo, String clientId) {  
295 - // TODO @haohao:AuthInfo 的创建,放在 connectionManager 里构建貌似会更收敛一点?  
296 - // 创建认证信息  
297 - IotTcpConnectionManager.AuthInfo authInfo = new IotTcpConnectionManager.AuthInfo() 242 + String clientId, String codecType) {
  243 + IotTcpConnectionManager.ConnectionInfo connectionInfo = new IotTcpConnectionManager.ConnectionInfo()
298 .setDeviceId(device.getId()) 244 .setDeviceId(device.getId())
299 - .setProductKey(deviceInfo.getProductKey())  
300 - .setDeviceName(deviceInfo.getDeviceName())  
301 - .setClientId(clientId); 245 + .setProductKey(device.getProductKey())
  246 + .setDeviceName(device.getDeviceName())
  247 + .setClientId(clientId)
  248 + .setCodecType(codecType)
  249 + .setAuthenticated(true);
302 // 注册连接 250 // 注册连接
303 - connectionManager.registerConnection(socket, device.getId(), authInfo); 251 + connectionManager.registerConnection(socket, device.getId(), connectionInfo);
304 } 252 }
305 253
306 /** 254 /**
307 * 发送设备上线消息 255 * 发送设备上线消息
  256 + *
  257 + * @param device 设备信息
308 */ 258 */
309 - private void sendOnlineMessage(IotDeviceAuthUtils.DeviceInfo deviceInfo) { 259 + private void sendOnlineMessage(IotDeviceRespDTO device) {
310 try { 260 try {
311 IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline(); 261 IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();
312 - deviceMessageService.sendDeviceMessage(onlineMessage, deviceInfo.getProductKey(),  
313 - deviceInfo.getDeviceName(), serverId); 262 + deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(),
  263 + device.getDeviceName(), serverId);
314 } catch (Exception e) { 264 } catch (Exception e) {
315 - log.error("[sendOnlineMessage][发送上线消息失败,设备: {}]", deviceInfo.getDeviceName(), e); 265 + log.error("[sendOnlineMessage][发送上线消息失败,设备: {}]", device.getDeviceName(), e);
316 } 266 }
317 } 267 }
318 268
319 /** 269 /**
320 * 清理连接 270 * 清理连接
  271 + *
  272 + * @param socket 网络连接
321 */ 273 */
322 private void cleanupConnection(NetSocket socket) { 274 private void cleanupConnection(NetSocket socket) {
323 try { 275 try {
324 - // 发送离线消息(如果已认证)  
325 - IotTcpConnectionManager.AuthInfo authInfo = connectionManager.getAuthInfo(socket);  
326 - if (authInfo != null) { 276 + // 1. 发送离线消息(如果已认证)
  277 + IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
  278 + if (connectionInfo != null) {
327 IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline(); 279 IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();
328 - deviceMessageService.sendDeviceMessage(offlineMessage, authInfo.getProductKey(),  
329 - authInfo.getDeviceName(), serverId); 280 + deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(),
  281 + connectionInfo.getDeviceName(), serverId);
330 } 282 }
331 283
332 - // 注销连接 284 + // 2. 注销连接
333 connectionManager.unregisterConnection(socket); 285 connectionManager.unregisterConnection(socket);
334 } catch (Exception e) { 286 } catch (Exception e) {
335 log.error("[cleanupConnection][清理连接失败]", e); 287 log.error("[cleanupConnection][清理连接失败]", e);
@@ -338,6 +290,12 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -338,6 +290,12 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
338 290
339 /** 291 /**
340 * 发送响应消息 292 * 发送响应消息
  293 + *
  294 + * @param socket 网络连接
  295 + * @param success 是否成功
  296 + * @param message 消息
  297 + * @param requestId 请求 ID
  298 + * @param codecType 消息编解码类型
341 */ 299 */
342 private void sendResponse(NetSocket socket, boolean success, String message, String requestId, String codecType) { 300 private void sendResponse(NetSocket socket, boolean success, String message, String requestId, String codecType) {
343 try { 301 try {
@@ -346,8 +304,9 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -346,8 +304,9 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
346 .put("message", message) 304 .put("message", message)
347 .build(); 305 .build();
348 306
  307 + int code = success ? 0 : 401;
349 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData, 308 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData,
350 - success ? 0 : 401, message); 309 + code, message);
351 310
352 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, codecType); 311 byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, codecType);
353 socket.write(Buffer.buffer(encodedData)); 312 socket.write(Buffer.buffer(encodedData));
@@ -357,94 +316,47 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -357,94 +316,47 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
357 } 316 }
358 } 317 }
359 318
360 - // ==================== 辅助方法 ====================  
361 -  
362 - /**  
363 - * 判断是否为认证请求  
364 - */  
365 - private boolean isAuthRequest(IotDeviceMessage message) {  
366 - return AUTH_METHOD.equals(message.getMethod());  
367 - }  
368 -  
369 - /**  
370 - * 解析认证参数  
371 - */  
372 - private AuthParams parseAuthParams(Object params) {  
373 - if (params == null) {  
374 - return null;  
375 - }  
376 - try {  
377 - JSONObject paramsJson = params instanceof JSONObject ? (JSONObject) params  
378 - : JSONUtil.parseObj(params.toString());  
379 - String clientId = paramsJson.getStr("clientId");  
380 - String username = paramsJson.getStr("username");  
381 - String password = paramsJson.getStr("password");  
382 - return StrUtil.hasBlank(clientId, username, password) ? null  
383 - : new AuthParams(clientId, username, password);  
384 - } catch (Exception e) {  
385 - log.warn("[parseAuthParams][解析认证参数失败]", e);  
386 - return null;  
387 - }  
388 - }  
389 -  
390 /** 319 /**
391 - * 认证设备 320 + * 验证设备认证信息
  321 + *
  322 + * @param authParams 认证参数
  323 + * @return 是否认证成功
392 */ 324 */
393 - private boolean authenticateDevice(AuthParams authParams) { 325 + private boolean validateDeviceAuth(IotDeviceAuthReqDTO authParams) {
394 try { 326 try {
395 CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO() 327 CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()
396 - .setClientId(authParams.clientId)  
397 - .setUsername(authParams.username)  
398 - .setPassword(authParams.password));  
399 - return result.isSuccess() && Boolean.TRUE.equals(result.getData()); 328 + .setClientId(authParams.getClientId()).setUsername(authParams.getUsername())
  329 + .setPassword(authParams.getPassword()));
  330 + result.checkError();
  331 + return BooleanUtil.isTrue(result.getData());
400 } catch (Exception e) { 332 } catch (Exception e) {
401 - log.error("[authenticateDevice][设备认证异常,username: {}]", authParams.username, e); 333 + log.error("[validateDeviceAuth][设备认证异常,username: {}]", authParams.getUsername(), e);
402 return false; 334 return false;
403 } 335 }
404 } 336 }
405 337
406 - // TODO @haohao:改成 sendErrorResponse sendSuccessResponse 更清晰点?  
407 -  
408 /** 338 /**
409 * 发送错误响应 339 * 发送错误响应
  340 + *
  341 + * @param socket 网络连接
  342 + * @param requestId 请求 ID
  343 + * @param errorMessage 错误消息
  344 + * @param codecType 消息编解码类型
410 */ 345 */
411 - private void sendError(NetSocket socket, String requestId, String errorMessage, String codecType) { 346 + private void sendErrorResponse(NetSocket socket, String requestId, String errorMessage, String codecType) {
412 sendResponse(socket, false, errorMessage, requestId, codecType); 347 sendResponse(socket, false, errorMessage, requestId, codecType);
413 } 348 }
414 349
415 /** 350 /**
416 * 发送成功响应 351 * 发送成功响应
  352 + *
  353 + * @param socket 网络连接
  354 + * @param requestId 请求 ID
  355 + * @param message 消息
  356 + * @param codecType 消息编解码类型
417 */ 357 */
418 - private void sendSuccess(NetSocket socket, String requestId, String message, String codecType) { 358 + private void sendSuccessResponse(NetSocket socket, String requestId, String message, String codecType) {
419 sendResponse(socket, true, message, requestId, codecType); 359 sendResponse(socket, true, message, requestId, codecType);
420 } 360 }
421 361
422 - // ==================== 内部类 ====================  
423 -  
424 - // TODO @haohao:IotDeviceAuthReqDTO 复用这个?  
425 - /**  
426 - * 认证参数  
427 - */  
428 - @Data  
429 - @AllArgsConstructor  
430 - private static class AuthParams {  
431 -  
432 - private final String clientId;  
433 - private final String username;  
434 - private final String password;  
435 -  
436 - }  
437 -  
438 - /**  
439 - * 消息信息  
440 - */  
441 - @Data  
442 - @AllArgsConstructor  
443 - private static class MessageInfo {  
444 -  
445 - private final IotDeviceMessage message;  
446 -  
447 - private final String codecType;  
448 -  
449 - }  
450 } 362 }
451 \ No newline at end of file 363 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-binary-packet-examples.md
@@ -9,7 +9,7 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è @@ -9,7 +9,7 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è
9 - **高效传输**:完全二进制格å¼ï¼Œå‡å°‘æ•°æ®ä¼ è¾“é‡ 9 - **高效传输**:完全二进制格å¼ï¼Œå‡å°‘æ•°æ®ä¼ è¾“é‡
10 - **版本控制**:内置å议版本å·ï¼Œæ”¯æŒåè®®å‡çº§ 10 - **版本控制**:内置å议版本å·ï¼Œæ”¯æŒåè®®å‡çº§
11 - **类型安全**:明确的消æ¯ç±»åž‹æ ‡è¯† 11 - **类型安全**:明确的消æ¯ç±»åž‹æ ‡è¯†
12 -- **扩展性**:预留标志ä½ï¼Œæ”¯æŒæœªæ¥åŠŸèƒ½æ‰©å±• 12 +- **简æ´è®¾è®¡**:去除冗余字段,å议更加精简
13 - **兼容性**:与现有 `IotDeviceMessage` 接å£å®Œå…¨å…¼å®¹ 13 - **兼容性**:与现有 `IotDeviceMessage` 接å£å®Œå…¨å…¼å®¹
14 14
15 ## 2. åè®®æ ¼å¼ 15 ## 2. å议格å¼
@@ -17,9 +17,9 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è @@ -17,9 +17,9 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è
17 ### 2.1 整体结构 17 ### 2.1 整体结构
18 18
19 ``` 19 ```
20 -+--------+--------+--------+--------+--------+--------+--------+--------+  
21 -| 魔术字 | ç‰ˆæœ¬å· | 消æ¯ç±»åž‹| æ¶ˆæ¯æ ‡å¿—| 消æ¯é•¿åº¦(4字节) |  
22 -+--------+--------+--------+--------+--------+--------+--------+--------+ 20 ++--------+--------+--------+---------------------------+--------+--------+
  21 +| 魔术字 | ç‰ˆæœ¬å· | 消æ¯ç±»åž‹| 消æ¯é•¿åº¦(4字节) |
  22 ++--------+--------+--------+---------------------------+--------+--------+
23 | æ¶ˆæ¯ ID 长度(2字节) | æ¶ˆæ¯ ID (å˜é•¿å­—符串) | 23 | æ¶ˆæ¯ ID 长度(2字节) | æ¶ˆæ¯ ID (å˜é•¿å­—符串) |
24 +--------+--------+--------+--------+--------+--------+--------+--------+ 24 +--------+--------+--------+--------+--------+--------+--------+--------+
25 | 方法å长度(2字节) | 方法å(å˜é•¿å­—符串) | 25 | 方法å长度(2字节) | 方法å(å˜é•¿å­—符串) |
@@ -35,7 +35,6 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è @@ -35,7 +35,6 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è
35 | 魔术字 | 1字节 | byte | `0x7E` - å议识别标识,用于数æ®åŒæ­¥ | 35 | 魔术字 | 1字节 | byte | `0x7E` - å议识别标识,用于数æ®åŒæ­¥ |
36 | ç‰ˆæœ¬å· | 1字节 | byte | `0x01` - å议版本å·ï¼Œæ”¯æŒç‰ˆæœ¬æŽ§åˆ¶ | 36 | ç‰ˆæœ¬å· | 1字节 | byte | `0x01` - å议版本å·ï¼Œæ”¯æŒç‰ˆæœ¬æŽ§åˆ¶ |
37 | 消æ¯ç±»åž‹ | 1字节 | byte | `0x01`=请求, `0x02`=å“应 | 37 | 消æ¯ç±»åž‹ | 1字节 | byte | `0x01`=请求, `0x02`=å“应 |
38 -| æ¶ˆæ¯æ ‡å¿— | 1字节 | byte | é¢„ç•™å­—æ®µï¼Œç”¨äºŽæœªæ¥æ‰©å±• |  
39 | 消æ¯é•¿åº¦ | 4字节 | int | 整个消æ¯çš„æ€»é•¿åº¦ï¼ˆåŒ…å«å¤´éƒ¨ï¼‰ | 38 | 消æ¯é•¿åº¦ | 4字节 | int | 整个消æ¯çš„æ€»é•¿åº¦ï¼ˆåŒ…å«å¤´éƒ¨ï¼‰ |
40 | æ¶ˆæ¯ ID 长度 | 2字节 | short | æ¶ˆæ¯ ID 字符串的字节长度 | 39 | æ¶ˆæ¯ ID 长度 | 2字节 | short | æ¶ˆæ¯ ID 字符串的字节长度 |
41 | æ¶ˆæ¯ ID | å˜é•¿ | string | 消æ¯å”¯ä¸€æ ‡è¯†ç¬¦ï¼ˆUTF-8ç¼–ç ï¼‰ | 40 | æ¶ˆæ¯ ID | å˜é•¿ | string | 消æ¯å”¯ä¸€æ ‡è¯†ç¬¦ï¼ˆUTF-8ç¼–ç ï¼‰ |
@@ -53,14 +52,12 @@ private static final byte MAGIC_NUMBER = (byte) 0x7E; @@ -53,14 +52,12 @@ private static final byte MAGIC_NUMBER = (byte) 0x7E;
53 private static final byte PROTOCOL_VERSION = (byte) 0x01; 52 private static final byte PROTOCOL_VERSION = (byte) 0x01;
54 53
55 // 消æ¯ç±»åž‹ 54 // 消æ¯ç±»åž‹
56 -public static class MessageType {  
57 - public static final byte REQUEST = 0x01; // è¯·æ±‚æ¶ˆæ¯  
58 - public static final byte RESPONSE = 0x02; // å“åº”æ¶ˆæ¯  
59 -} 55 +private static final byte REQUEST = (byte) 0x01; // 请求消æ¯
  56 +private static final byte RESPONSE = (byte) 0x02; // å“应消æ¯
60 57
61 // å议长度 58 // å议长度
62 -private static final int HEADER_FIXED_LENGTH = 8; // 固定头部长度  
63 -private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦ 59 +private static final int HEADER_FIXED_LENGTH = 7; // 固定头部长度
  60 +private static final int MIN_MESSAGE_LENGTH = 11; // æœ€å°æ¶ˆæ¯é•¿åº¦
64 ``` 61 ```
65 62
66 ## 3. 消æ¯ç±»åž‹å’Œæ ¼å¼ 63 ## 3. 消æ¯ç±»åž‹å’Œæ ¼å¼
@@ -86,8 +83,7 @@ private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦ @@ -86,8 +83,7 @@ private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦
86 7E // 魔术字 (0x7E) 83 7E // 魔术字 (0x7E)
87 01 // ç‰ˆæœ¬å· (0x01) 84 01 // ç‰ˆæœ¬å· (0x01)
88 01 // 消æ¯ç±»åž‹ (REQUEST) 85 01 // 消æ¯ç±»åž‹ (REQUEST)
89 -00 // æ¶ˆæ¯æ ‡å¿— (预留)  
90 -00 00 00 8A // 消æ¯é•¿åº¦ (138字节) 86 +00 00 00 89 // 消æ¯é•¿åº¦ (137字节)
91 00 19 // æ¶ˆæ¯ ID 长度 (25字节) 87 00 19 // æ¶ˆæ¯ ID 长度 (25字节)
92 61 75 74 68 5F 31 37 30 34 30 // æ¶ˆæ¯ ID: "auth_1704067200000_123" 88 61 75 74 68 5F 31 37 30 34 30 // æ¶ˆæ¯ ID: "auth_1704067200000_123"
93 36 37 32 30 30 30 30 30 5F 31 89 36 37 32 30 30 30 30 30 5F 31
@@ -144,8 +140,7 @@ private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦ @@ -144,8 +140,7 @@ private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦
144 7E // 魔术字 (0x7E) 140 7E // 魔术字 (0x7E)
145 01 // ç‰ˆæœ¬å· (0x01) 141 01 // ç‰ˆæœ¬å· (0x01)
146 02 // 消æ¯ç±»åž‹ (RESPONSE) 142 02 // 消æ¯ç±»åž‹ (RESPONSE)
147 -00 // æ¶ˆæ¯æ ‡å¿— (预留)  
148 -00 00 00 A5 // 消æ¯é•¿åº¦ (165字节) 143 +00 00 00 A4 // 消æ¯é•¿åº¦ (164字节)
149 00 22 // æ¶ˆæ¯ ID 长度 (34字节) 144 00 22 // æ¶ˆæ¯ ID 长度 (34字节)
150 61 75 74 68 5F 72 65 73 70 6F // æ¶ˆæ¯ ID: "auth_response_1704067200000_123" 145 61 75 74 68 5F 72 65 73 70 6F // æ¶ˆæ¯ ID: "auth_response_1704067200000_123"
151 6E 73 65 5F 31 37 30 34 30 36 146 6E 73 65 5F 31 37 30 34 30 36
@@ -175,19 +170,19 @@ public static final String TYPE = &quot;TCP_BINARY&quot;; @@ -175,19 +170,19 @@ public static final String TYPE = &quot;TCP_BINARY&quot;;
175 - **æ•°æ®ç´§å‡‘**:二进制格å¼ï¼Œç›¸æ¯” JSON å‡å°‘ 30-50% 的数æ®é‡ 170 - **æ•°æ®ç´§å‡‘**:二进制格å¼ï¼Œç›¸æ¯” JSON å‡å°‘ 30-50% 的数æ®é‡
176 - **è§£æžé«˜æ•ˆ**:直接二进制æ“作,å‡å°‘字符串转æ¢å¼€é”€ 171 - **è§£æžé«˜æ•ˆ**:直接二进制æ“作,å‡å°‘字符串转æ¢å¼€é”€
177 - **类型安全**:明确的消æ¯ç±»åž‹å’Œå­—段定义 172 - **类型安全**:明确的消æ¯ç±»åž‹å’Œå­—段定义
178 -- **扩展性强**ï¼šé¢„ç•™æ ‡å¿—ä½æ”¯æŒæœªæ¥åŠŸèƒ½æ‰©å±• 173 +- **设计简æ´**:去除冗余字段,å议更加精简高效
179 - **版本控制**ï¼šå†…ç½®ç‰ˆæœ¬å·æ”¯æŒåè®®å‡çº§ 174 - **版本控制**ï¼šå†…ç½®ç‰ˆæœ¬å·æ”¯æŒåè®®å‡çº§
180 175
181 ## 6. 与 JSON å议对比 176 ## 6. 与 JSON å议对比
182 177
183 -| 特性 | 二进制åè®® | JSONåè®® |  
184 -|------|------------|----------|  
185 -| æ•°æ®å¤§å° | å°ï¼ˆèŠ‚çœ30-50%) | 大 |  
186 -| è§£æžæ€§èƒ½ | 高 | 中等 |  
187 -| 网络开销 | 低 | 高 |  
188 -| å¯è¯»æ€§ | å·® | 优秀 |  
189 -| 调试难度 | 高 | 低 |  
190 -| 扩展性 | 良好(有预留ä½ï¼‰ | 优秀 | 178 +| 特性 | 二进制åè®® | JSONåè®® |
  179 +|------|-------------|--------|
  180 +| æ•°æ®å¤§å° | å°ï¼ˆèŠ‚çœ30-50%) | 大 |
  181 +| è§£æžæ€§èƒ½ | 高 | 中等 |
  182 +| 网络开销 | 低 | 高 |
  183 +| å¯è¯»æ€§ | å·® | 优秀 |
  184 +| 调试难度 | 高 | 低 |
  185 +| 扩展性 | 良好 | 优秀 |
191 186
192 **推è场景**: 187 **推è场景**:
193 - ✅ **高频数æ®ä¼ è¾“**:传感器数æ®å®žæ—¶ä¸ŠæŠ¥ 188 - ✅ **高频数æ®ä¼ è¾“**:传感器数æ®å®žæ—¶ä¸ŠæŠ¥