Commit 44b1950e4a4825bbe8488d257ee0cb99cd568ea2

Authored by YunaiV
1 parent e89fc2bf

feat(iot):【协议改造】tcp 初步改造(50%)

Showing 20 changed files with 1037 additions and 905 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.config; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.config;
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolManager; 4 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolManager;
5 -import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;  
6 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapDownstreamSubscriber; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapDownstreamSubscriber;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
@@ -12,10 +11,6 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscr @@ -12,10 +11,6 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscr
12 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol; 11 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; 12 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
14 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler; 13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler;
15 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber;  
16 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol;  
17 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;  
18 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler;  
19 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpDownstreamSubscriber; 14 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpDownstreamSubscriber;
20 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol; 15 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol;
21 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager; 16 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
@@ -24,6 +19,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketDownst @@ -24,6 +19,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketDownst
24 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol; 19 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol;
25 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager; 20 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
26 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router.IotWebSocketDownstreamHandler; 21 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router.IotWebSocketDownstreamHandler;
  22 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
27 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; 23 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
28 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 24 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
29 import io.vertx.core.Vertx; 25 import io.vertx.core.Vertx;
@@ -85,44 +81,6 @@ public class IotGatewayConfiguration { @@ -85,44 +81,6 @@ public class IotGatewayConfiguration {
85 } 81 }
86 82
87 /** 83 /**
88 - * IoT 网关 TCP 协议配置类  
89 - */  
90 - @Configuration  
91 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.tcp", name = "enabled", havingValue = "true")  
92 - @Slf4j  
93 - public static class TcpProtocolConfiguration {  
94 -  
95 - @Bean(name = "tcpVertx", destroyMethod = "close")  
96 - public Vertx tcpVertx() {  
97 - return Vertx.vertx();  
98 - }  
99 -  
100 - @Bean  
101 - public IotTcpUpstreamProtocol iotTcpUpstreamProtocol(IotGatewayProperties gatewayProperties,  
102 - IotDeviceService deviceService,  
103 - IotDeviceMessageService messageService,  
104 - IotTcpConnectionManager connectionManager,  
105 - @Qualifier("tcpVertx") Vertx tcpVertx) {  
106 - return new IotTcpUpstreamProtocol(gatewayProperties.getProtocol().getTcp(),  
107 - deviceService, messageService, connectionManager, tcpVertx);  
108 - }  
109 -  
110 - @Bean  
111 - public IotTcpDownstreamHandler iotTcpDownstreamHandler(IotDeviceMessageService messageService,  
112 - IotTcpConnectionManager connectionManager) {  
113 - return new IotTcpDownstreamHandler(messageService, connectionManager);  
114 - }  
115 -  
116 - @Bean  
117 - public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler,  
118 - IotTcpDownstreamHandler downstreamHandler,  
119 - IotMessageBus messageBus) {  
120 - return new IotTcpDownstreamSubscriber(protocolHandler, downstreamHandler, messageBus);  
121 - }  
122 -  
123 - }  
124 -  
125 - /**  
126 * IoT 网关 MQTT 协议配置类 84 * IoT 网关 MQTT 协议配置类
127 */ 85 */
128 @Configuration 86 @Configuration
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
@@ -2,6 +2,9 @@ package cn.iocoder.yudao.module.iot.gateway.config; @@ -2,6 +2,9 @@ package cn.iocoder.yudao.module.iot.gateway.config;
2 2
3 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; 3 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
4 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig; 4 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
  6 +import io.vertx.core.net.KeyCertOptions;
  7 +import io.vertx.core.net.TrustOptions;
5 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
6 import jakarta.validation.constraints.NotEmpty; 9 import jakarta.validation.constraints.NotEmpty;
7 import jakarta.validation.constraints.NotNull; 10 import jakarta.validation.constraints.NotNull;
@@ -77,21 +80,11 @@ public class IotGatewayProperties { @@ -77,21 +80,11 @@ public class IotGatewayProperties {
77 public static class ProtocolProperties { 80 public static class ProtocolProperties {
78 81
79 /** 82 /**
80 - * HTTP 组件配置  
81 - */  
82 - private HttpProperties http;  
83 -  
84 - /**  
85 * EMQX 组件配置 83 * EMQX 组件配置
86 */ 84 */
87 private EmqxProperties emqx; 85 private EmqxProperties emqx;
88 86
89 /** 87 /**
90 - * TCP 组件配置  
91 - */  
92 - private TcpProperties tcp;  
93 -  
94 - /**  
95 * MQTT 组件配置 88 * MQTT 组件配置
96 */ 89 */
97 private MqttProperties mqtt; 90 private MqttProperties mqtt;
@@ -114,36 +107,6 @@ public class IotGatewayProperties { @@ -114,36 +107,6 @@ public class IotGatewayProperties {
114 } 107 }
115 108
116 @Data 109 @Data
117 - public static class HttpProperties {  
118 -  
119 - /**  
120 - * 是否开启  
121 - */  
122 - @NotNull(message = "是否开启不能为空")  
123 - private Boolean enabled;  
124 - /**  
125 - * 服务端口  
126 - */  
127 - private Integer serverPort;  
128 -  
129 - /**  
130 - * 是否开启 SSL  
131 - */  
132 - @NotNull(message = "是否开启 SSL 不能为空")  
133 - private Boolean sslEnabled = false;  
134 -  
135 - /**  
136 - * SSL 证书路径  
137 - */  
138 - private String sslKeyPath;  
139 - /**  
140 - * SSL 证书路径  
141 - */  
142 - private String sslCertPath;  
143 -  
144 - }  
145 -  
146 - @Data  
147 public static class EmqxProperties { 110 public static class EmqxProperties {
148 111
149 /** 112 /**
@@ -313,47 +276,6 @@ public class IotGatewayProperties { @@ -313,47 +276,6 @@ public class IotGatewayProperties {
313 } 276 }
314 277
315 @Data 278 @Data
316 - public static class TcpProperties {  
317 -  
318 - /**  
319 - * 是否开启  
320 - */  
321 - @NotNull(message = "是否开启不能为空")  
322 - private Boolean enabled;  
323 -  
324 - /**  
325 - * 服务器端口  
326 - */  
327 - private Integer port = 8091;  
328 -  
329 - /**  
330 - * 心跳超时时间(毫秒)  
331 - */  
332 - private Long keepAliveTimeoutMs = 30000L;  
333 -  
334 - /**  
335 - * 最大连接数  
336 - */  
337 - private Integer maxConnections = 1000;  
338 -  
339 - /**  
340 - * 是否启用SSL  
341 - */  
342 - private Boolean sslEnabled = false;  
343 -  
344 - /**  
345 - * SSL证书路径  
346 - */  
347 - private String sslCertPath;  
348 -  
349 - /**  
350 - * SSL私钥路径  
351 - */  
352 - private String sslKeyPath;  
353 -  
354 - }  
355 -  
356 - @Data  
357 public static class MqttProperties { 279 public static class MqttProperties {
358 280
359 /** 281 /**
@@ -381,6 +303,7 @@ public class IotGatewayProperties { @@ -381,6 +303,7 @@ public class IotGatewayProperties {
381 */ 303 */
382 private Integer keepAliveTimeoutSeconds = 300; 304 private Integer keepAliveTimeoutSeconds = 300;
383 305
  306 + // TODO @AI:所有跟 ssl 相关的参数,是不是可以统一?放到 protocol 层级?ProtocolInstanceProperties【优先级:低】暂时不用规划;
384 /** 307 /**
385 * 是否启用 SSL 308 * 是否启用 SSL
386 */ 309 */
@@ -399,11 +322,11 @@ public class IotGatewayProperties { @@ -399,11 +322,11 @@ public class IotGatewayProperties {
399 /** 322 /**
400 * 密钥证书选项 323 * 密钥证书选项
401 */ 324 */
402 - private io.vertx.core.net.KeyCertOptions keyCertOptions; 325 + private KeyCertOptions keyCertOptions;
403 /** 326 /**
404 * 信任选项 327 * 信任选项
405 */ 328 */
406 - private io.vertx.core.net.TrustOptions trustOptions; 329 + private TrustOptions trustOptions;
407 /** 330 /**
408 * SSL 证书路径 331 * SSL 证书路径
409 */ 332 */
@@ -596,78 +519,11 @@ public class IotGatewayProperties { @@ -596,78 +519,11 @@ public class IotGatewayProperties {
596 @Valid 519 @Valid
597 private IotHttpConfig http; 520 private IotHttpConfig http;
598 521
599 - // TODO @AI:后续改下;  
600 /** 522 /**
601 - * TCP 协议配置(后续扩展) 523 + * TCP 协议配置
602 */ 524 */
603 @Valid 525 @Valid
604 - private TcpInstanceConfig tcp;  
605 -  
606 - }  
607 -  
608 - /**  
609 - * TCP 协议实例配置(后续扩展)  
610 - */  
611 - @Data  
612 - public static class TcpInstanceConfig {  
613 -  
614 - /**  
615 - * 最大连接数  
616 - */  
617 - private Integer maxConnections = 1000;  
618 -  
619 - /**  
620 - * 心跳超时时间(毫秒)  
621 - */  
622 - private Long keepAliveTimeoutMs = 30000L;  
623 -  
624 - /**  
625 - * 是否启用 SSL  
626 - */  
627 - private Boolean sslEnabled = false;  
628 -  
629 - /**  
630 - * SSL 证书路径  
631 - */  
632 - private String sslCertPath;  
633 -  
634 - /**  
635 - * SSL 私钥路径  
636 - */  
637 - private String sslKeyPath;  
638 -  
639 - /**  
640 - * 拆包配置  
641 - */  
642 - private CodecConfig codec;  
643 -  
644 - /**  
645 - * TCP 拆包配置  
646 - */  
647 - @Data  
648 - public static class CodecConfig {  
649 -  
650 - /**  
651 - * 拆包类型:LENGTH_FIELD / DELIMITER  
652 - */  
653 - private String type;  
654 -  
655 - /**  
656 - * LENGTH_FIELD: 偏移量  
657 - */  
658 - private Integer lengthFieldOffset;  
659 -  
660 - /**  
661 - * LENGTH_FIELD: 长度字段长度  
662 - */  
663 - private Integer lengthFieldLength;  
664 -  
665 - /**  
666 - * DELIMITER: 分隔符  
667 - */  
668 - private String delimiter;  
669 -  
670 - } 526 + private IotTcpConfig tcp;
671 527
672 } 528 }
673 529
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolManager.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
6 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 6 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
7 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; 7 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpProtocol;
9 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager; 10 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
10 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
11 import org.springframework.context.SmartLifecycle; 12 import org.springframework.context.SmartLifecycle;
@@ -14,9 +15,7 @@ import java.util.ArrayList; @@ -14,9 +15,7 @@ import java.util.ArrayList;
14 import java.util.List; 15 import java.util.List;
15 16
16 /** 17 /**
17 - * IoT 协议管理器  
18 - *  
19 - * 负责根据配置创建和管理协议实例 18 + * IoT 协议管理器:负责根据配置创建和管理协议实例
20 * 19 *
21 * @author 芋道源码 20 * @author 芋道源码
22 */ 21 */
@@ -96,7 +95,7 @@ public class IotProtocolManager implements SmartLifecycle { @@ -96,7 +95,7 @@ public class IotProtocolManager implements SmartLifecycle {
96 * @param config 协议实例配置 95 * @param config 协议实例配置
97 * @return 协议实例 96 * @return 协议实例
98 */ 97 */
99 - @SuppressWarnings({"SwitchStatementWithTooFewBranches", "EnhancedSwitchMigration"}) 98 + @SuppressWarnings({"EnhancedSwitchMigration"})
100 private IotProtocol createProtocol(IotGatewayProperties.ProtocolInstanceProperties config) { 99 private IotProtocol createProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
101 IotProtocolTypeEnum protocolType = IotProtocolTypeEnum.of(config.getType()); 100 IotProtocolTypeEnum protocolType = IotProtocolTypeEnum.of(config.getType());
102 if (protocolType == null) { 101 if (protocolType == null) {
@@ -106,6 +105,8 @@ public class IotProtocolManager implements SmartLifecycle { @@ -106,6 +105,8 @@ public class IotProtocolManager implements SmartLifecycle {
106 switch (protocolType) { 105 switch (protocolType) {
107 case HTTP: 106 case HTTP:
108 return createHttpProtocol(config); 107 return createHttpProtocol(config);
  108 + case TCP:
  109 + return createTcpProtocol(config);
109 // TODO 后续添加其他协议类型 110 // TODO 后续添加其他协议类型
110 default: 111 default:
111 throw new IllegalArgumentException(String.format( 112 throw new IllegalArgumentException(String.format(
@@ -123,4 +124,14 @@ public class IotProtocolManager implements SmartLifecycle { @@ -123,4 +124,14 @@ public class IotProtocolManager implements SmartLifecycle {
123 return new IotHttpProtocol(config, messageBus); 124 return new IotHttpProtocol(config, messageBus);
124 } 125 }
125 126
  127 + /**
  128 + * 创建 TCP 协议实例
  129 + *
  130 + * @param config 协议实例配置
  131 + * @return TCP 协议实例
  132 + */
  133 + private IotTcpProtocol createTcpProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
  134 + return new IotTcpProtocol(config, messageBus, serializerManager);
  135 + }
  136 +
126 } 137 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpProtocol.java
@@ -34,36 +34,32 @@ public class IotHttpProtocol implements IotProtocol { @@ -34,36 +34,32 @@ public class IotHttpProtocol implements IotProtocol {
34 */ 34 */
35 private final ProtocolInstanceProperties properties; 35 private final ProtocolInstanceProperties properties;
36 /** 36 /**
37 - * 消息总线  
38 - */  
39 - private final IotMessageBus messageBus;  
40 - /**  
41 * 服务器 ID(用于消息追踪,全局唯一) 37 * 服务器 ID(用于消息追踪,全局唯一)
42 */ 38 */
43 @Getter 39 @Getter
44 private final String serverId; 40 private final String serverId;
45 41
46 /** 42 /**
47 - * Vert.x 实例(每个 Protocol 自己管理) 43 + * 运行状态
  44 + */
  45 + private volatile boolean running = false;
  46 +
  47 + /**
  48 + * Vert.x 实例
48 */ 49 */
49 private Vertx vertx; 50 private Vertx vertx;
50 /** 51 /**
51 * HTTP 服务器 52 * HTTP 服务器
52 */ 53 */
53 private HttpServer httpServer; 54 private HttpServer httpServer;
  55 +
54 /** 56 /**
55 * 下行消息订阅者 57 * 下行消息订阅者
56 */ 58 */
57 private IotHttpDownstreamSubscriber downstreamSubscriber; 59 private IotHttpDownstreamSubscriber downstreamSubscriber;
58 60
59 - /**  
60 - * 运行状态  
61 - */  
62 - private volatile boolean running = false;  
63 -  
64 public IotHttpProtocol(ProtocolInstanceProperties properties, IotMessageBus messageBus) { 61 public IotHttpProtocol(ProtocolInstanceProperties properties, IotMessageBus messageBus) {
65 this.properties = properties; 62 this.properties = properties;
66 - this.messageBus = messageBus;  
67 this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); 63 this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
68 this.downstreamSubscriber = new IotHttpDownstreamSubscriber(this, messageBus); 64 this.downstreamSubscriber = new IotHttpDownstreamSubscriber(this, messageBus);
69 } 65 }
@@ -121,7 +117,6 @@ public class IotHttpProtocol implements IotProtocol { @@ -121,7 +117,6 @@ public class IotHttpProtocol implements IotProtocol {
121 getId(), properties.getPort(), serverId); 117 getId(), properties.getPort(), serverId);
122 118
123 // 2. 启动下行消息订阅者 119 // 2. 启动下行消息订阅者
124 - this.downstreamSubscriber = new IotHttpDownstreamSubscriber(this, messageBus);  
125 this.downstreamSubscriber.start(); 120 this.downstreamSubscriber.start();
126 } catch (Exception e) { 121 } catch (Exception e) {
127 log.error("[start][IoT HTTP 协议 {} 启动失败]", getId(), e); 122 log.error("[start][IoT HTTP 协议 {} 启动失败]", getId(), e);
@@ -132,9 +127,6 @@ public class IotHttpProtocol implements IotProtocol { @@ -132,9 +127,6 @@ public class IotHttpProtocol implements IotProtocol {
132 } 127 }
133 throw e; 128 throw e;
134 } 129 }
135 -  
136 - // 2. 启动下行消息订阅者  
137 - this.downstreamSubscriber.start();  
138 } 130 }
139 131
140 @Override 132 @Override
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
  2 +
  3 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpCodecTypeEnum;
  4 +import jakarta.validation.Valid;
  5 +import jakarta.validation.constraints.Min;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +/**
  10 + * IoT TCP 协议配置
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +public class IotTcpConfig {
  16 +
  17 + /**
  18 + * 最大连接数
  19 + */
  20 + @NotNull(message = "最大连接数不能为空")
  21 + @Min(value = 1, message = "最大连接数必须大于 0")
  22 + private Integer maxConnections = 1000;
  23 + /**
  24 + * 心跳超时时间(毫秒)
  25 + */
  26 + @NotNull(message = "心跳超时时间不能为空")
  27 + @Min(value = 1000, message = "心跳超时时间必须大于 1000 毫秒")
  28 + private Long keepAliveTimeoutMs = 30000L;
  29 +
  30 + /**
  31 + * 是否启用 SSL
  32 + */
  33 + @NotNull(message = "是否启用 SSL 不能为空")
  34 + private Boolean sslEnabled = false;
  35 + /**
  36 + * SSL 证书路径
  37 + */
  38 + private String sslCertPath;
  39 + /**
  40 + * SSL 私钥路径
  41 + */
  42 + private String sslKeyPath;
  43 +
  44 + /**
  45 + * 拆包配置
  46 + */
  47 + @Valid
  48 + private CodecConfig codec;
  49 +
  50 + /**
  51 + * TCP 拆包配置
  52 + */
  53 + @Data
  54 + public static class CodecConfig {
  55 +
  56 + /**
  57 + * 拆包类型
  58 + *
  59 + * @see IotTcpCodecTypeEnum
  60 + */
  61 + @NotNull(message = "拆包类型不能为空")
  62 + private String type;
  63 +
  64 + /**
  65 + * LENGTH_FIELD: 长度字段偏移量
  66 + * <p>
  67 + * 表示长度字段在消息中的起始位置(从0开始)
  68 + */
  69 + private Integer lengthFieldOffset;
  70 + /**
  71 + * LENGTH_FIELD: 长度字段长度(字节数)
  72 + * <p>
  73 + * 常见值:1(最大255)、2(最大65535)、4(最大2GB)
  74 + */
  75 + private Integer lengthFieldLength;
  76 + /**
  77 + * LENGTH_FIELD: 长度调整值
  78 + * <p>
  79 + * 用于调整长度字段的值,例如长度字段包含头部长度时需要减去头部长度
  80 + */
  81 + private Integer lengthAdjustment = 0;
  82 + /**
  83 + * LENGTH_FIELD: 跳过的初始字节数
  84 + * <p>
  85 + * 解码后跳过的字节数,通常等于 lengthFieldOffset + lengthFieldLength
  86 + */
  87 + private Integer initialBytesToStrip = 0;
  88 +
  89 + /**
  90 + * DELIMITER: 分隔符
  91 + * <p>
  92 + * 支持转义字符:\n(换行)、\r(回车)、\r\n(回车换行)
  93 + */
  94 + private String delimiter;
  95 +
  96 + /**
  97 + * FIXED_LENGTH: 固定消息长度(字节)
  98 + * <p>
  99 + * 每条消息的固定长度
  100 + */
  101 + private Integer fixedLength;
  102 +
  103 + /**
  104 + * 最大帧长度(字节)
  105 + * <p>
  106 + * 防止内存溢出,默认 1MB
  107 + */
  108 + @NotNull(message = "最大帧长度不能为空")
  109 + @Min(value = 1, message = "最大帧长度必须大于 0")
  110 + private Integer maxFrameLength = 1048576;
  111 +
  112 + }
  113 +
  114 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  7 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  8 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolInstanceProperties;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.downstream.IotTcpDownstreamHandler;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.downstream.IotTcpDownstreamSubscriber;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.upstream.IotTcpUpstreamHandler;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
  15 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  16 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
  17 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
  18 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  19 +import io.vertx.core.Vertx;
  20 +import io.vertx.core.net.NetServer;
  21 +import io.vertx.core.net.NetServerOptions;
  22 +import io.vertx.core.net.PemKeyCertOptions;
  23 +import lombok.Getter;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +
  26 +/**
  27 + * IoT TCP 协议实现
  28 + * <p>
  29 + * 基于 Vert.x 实现 TCP 服务器,接收设备上行消息
  30 + *
  31 + * @author 芋道源码
  32 + */
  33 +@Slf4j
  34 +public class IotTcpProtocol implements IotProtocol {
  35 +
  36 + /**
  37 + * 协议配置
  38 + */
  39 + private final ProtocolInstanceProperties properties;
  40 + /**
  41 + * 服务器 ID(用于消息追踪,全局唯一)
  42 + */
  43 + @Getter
  44 + private final String serverId;
  45 +
  46 + /**
  47 + * 运行状态
  48 + */
  49 + private volatile boolean running = false;
  50 +
  51 + /**
  52 + * Vert.x 实例
  53 + */
  54 + private Vertx vertx;
  55 + /**
  56 + * TCP 服务器
  57 + */
  58 + private NetServer tcpServer;
  59 +
  60 + /**
  61 + * 下行消息订阅者
  62 + */
  63 + private final IotTcpDownstreamSubscriber downstreamSubscriber;
  64 +
  65 + /**
  66 + * 消息序列化器
  67 + */
  68 + private final IotMessageSerializer serializer;
  69 +
  70 + /**
  71 + * TCP 帧编解码器
  72 + */
  73 + private final IotTcpFrameCodec frameCodec;
  74 +
  75 + public IotTcpProtocol(ProtocolInstanceProperties properties, IotMessageBus messageBus,
  76 + IotMessageSerializerManager serializerManager) {
  77 + this.properties = properties;
  78 + this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
  79 +
  80 + // 初始化序列化器
  81 + IotSerializeTypeEnum serializeType = IotSerializeTypeEnum.of(properties.getSerialize());
  82 + if (serializeType == null) {
  83 + serializeType = IotSerializeTypeEnum.JSON; // 默认 JSON
  84 + }
  85 + this.serializer = serializerManager.get(serializeType);
  86 +
  87 + // 初始化帧编解码器
  88 + IotTcpConfig tcpConfig = properties.getTcp();
  89 + IotTcpConfig.CodecConfig codecConfig = tcpConfig != null ? tcpConfig.getCodec() : null;
  90 + this.frameCodec = IotTcpFrameCodec.create(codecConfig);
  91 +
  92 + // 初始化下行消息订阅者
  93 + IotTcpConnectionManager connectionManager = SpringUtil.getBean(IotTcpConnectionManager.class);
  94 + IotTcpDownstreamHandler downstreamHandler = new IotTcpDownstreamHandler(connectionManager, frameCodec, serializer);
  95 + this.downstreamSubscriber = new IotTcpDownstreamSubscriber(this, downstreamHandler, messageBus);
  96 + }
  97 +
  98 + @Override
  99 + public String getId() {
  100 + return properties.getId();
  101 + }
  102 +
  103 + @Override
  104 + public IotProtocolTypeEnum getType() {
  105 + return IotProtocolTypeEnum.TCP;
  106 + }
  107 +
  108 + @Override
  109 + public void start() {
  110 + if (running) {
  111 + log.warn("[start][IoT TCP 协议 {} 已经在运行中]", getId());
  112 + return;
  113 + }
  114 +
  115 + // 1.1 创建 Vertx 实例(每个 Protocol 独立管理)
  116 + this.vertx = Vertx.vertx();
  117 +
  118 + // 1.2 创建服务器选项
  119 + IotTcpConfig tcpConfig = properties.getTcp();
  120 + NetServerOptions options = new NetServerOptions()
  121 + .setPort(properties.getPort())
  122 + .setTcpKeepAlive(true)
  123 + .setTcpNoDelay(true)
  124 + .setReuseAddress(true);
  125 + if (tcpConfig != null && Boolean.TRUE.equals(tcpConfig.getSslEnabled())) {
  126 + PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
  127 + .setKeyPath(tcpConfig.getSslKeyPath())
  128 + .setCertPath(tcpConfig.getSslCertPath());
  129 + options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
  130 + }
  131 +
  132 + // 1.3 创建服务器并设置连接处理器
  133 + tcpServer = vertx.createNetServer(options);
  134 + IotDeviceService deviceService = SpringUtil.getBean(IotDeviceService.class);
  135 + IotDeviceMessageService messageService = SpringUtil.getBean(IotDeviceMessageService.class);
  136 + IotTcpConnectionManager connectionManager = SpringUtil.getBean(IotTcpConnectionManager.class);
  137 + tcpServer.connectHandler(socket -> {
  138 + IotTcpUpstreamHandler handler = new IotTcpUpstreamHandler(this, messageService, deviceService,
  139 + connectionManager, frameCodec, serializer);
  140 + handler.handle(socket);
  141 + });
  142 +
  143 + // 1.4 启动 TCP 服务器
  144 + try {
  145 + tcpServer.listen().result();
  146 + running = true;
  147 + log.info("[start][IoT TCP 协议 {} 启动成功,端口:{},serverId:{}]",
  148 + getId(), properties.getPort(), serverId);
  149 +
  150 + // 2. 启动下行消息订阅者
  151 + this.downstreamSubscriber.start();
  152 + } catch (Exception e) {
  153 + log.error("[start][IoT TCP 协议 {} 启动失败]", getId(), e);
  154 + // 启动失败时关闭 Vertx
  155 + if (vertx != null) {
  156 + vertx.close();
  157 + vertx = null;
  158 + }
  159 + throw e;
  160 + }
  161 + }
  162 +
  163 + @Override
  164 + public void stop() {
  165 + if (!running) {
  166 + return;
  167 + }
  168 + // 1. 停止下行消息订阅者
  169 + try {
  170 + downstreamSubscriber.stop();
  171 + log.info("[stop][IoT TCP 协议 {} 下行消息订阅者已停止]", getId());
  172 + } catch (Exception e) {
  173 + log.error("[stop][IoT TCP 协议 {} 下行消息订阅者停止失败]", getId(), e);
  174 + }
  175 +
  176 + // 2.1 关闭 TCP 服务器
  177 + if (tcpServer != null) {
  178 + try {
  179 + tcpServer.close().result();
  180 + log.info("[stop][IoT TCP 协议 {} 服务器已停止]", getId());
  181 + } catch (Exception e) {
  182 + log.error("[stop][IoT TCP 协议 {} 服务器停止失败]", getId(), e);
  183 + }
  184 + tcpServer = null;
  185 + }
  186 + // 2.2 关闭 Vertx 实例
  187 + if (vertx != null) {
  188 + try {
  189 + vertx.close().result();
  190 + log.info("[stop][IoT TCP 协议 {} Vertx 已关闭]", getId());
  191 + } catch (Exception e) {
  192 + log.error("[stop][IoT TCP 协议 {} Vertx 关闭失败]", getId(), e);
  193 + }
  194 + vertx = null;
  195 + }
  196 + running = false;
  197 + log.info("[stop][IoT TCP 协议 {} 已停止]", getId());
  198 + }
  199 +
  200 + @Override
  201 + public boolean isRunning() {
  202 + return running;
  203 + }
  204 +
  205 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpUpstreamProtocol.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;  
2 -  
3 -import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;  
4 -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;  
5 -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;  
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;  
7 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;  
8 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpUpstreamHandler;  
9 -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;  
10 -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;  
11 -import io.vertx.core.Vertx;  
12 -import io.vertx.core.net.NetServer;  
13 -import io.vertx.core.net.NetServerOptions;  
14 -import io.vertx.core.net.PemKeyCertOptions;  
15 -import jakarta.annotation.PostConstruct;  
16 -import jakarta.annotation.PreDestroy;  
17 -import lombok.Getter;  
18 -import lombok.extern.slf4j.Slf4j;  
19 -  
20 -/**  
21 - * IoT 网关 TCP 协议:接收设备上行消息  
22 - *  
23 - * @author 芋道源码  
24 - */  
25 -@Slf4j  
26 -public class IotTcpUpstreamProtocol implements IotProtocol {  
27 -  
28 - private static final String ID = "tcp";  
29 -  
30 - private final IotGatewayProperties.TcpProperties tcpProperties;  
31 -  
32 - private final IotDeviceService deviceService;  
33 -  
34 - private final IotDeviceMessageService messageService;  
35 -  
36 - private final IotTcpConnectionManager connectionManager;  
37 -  
38 - private final Vertx vertx;  
39 -  
40 - @Getter  
41 - private final String serverId;  
42 -  
43 - private NetServer tcpServer;  
44 -  
45 - private volatile boolean running = false;  
46 -  
47 - public IotTcpUpstreamProtocol(IotGatewayProperties.TcpProperties tcpProperties,  
48 - IotDeviceService deviceService,  
49 - IotDeviceMessageService messageService,  
50 - IotTcpConnectionManager connectionManager,  
51 - Vertx vertx) {  
52 - this.tcpProperties = tcpProperties;  
53 - this.deviceService = deviceService;  
54 - this.messageService = messageService;  
55 - this.connectionManager = connectionManager;  
56 - this.vertx = vertx;  
57 - this.serverId = IotDeviceMessageUtils.generateServerId(tcpProperties.getPort());  
58 - }  
59 -  
60 - @Override  
61 - public String getId() {  
62 - return ID;  
63 - }  
64 -  
65 - @Override  
66 - public IotProtocolTypeEnum getType() {  
67 - return IotProtocolTypeEnum.TCP;  
68 - }  
69 -  
70 - @Override  
71 - @PostConstruct  
72 - public void start() {  
73 - // 创建服务器选项  
74 - NetServerOptions options = new NetServerOptions()  
75 - .setPort(tcpProperties.getPort())  
76 - .setTcpKeepAlive(true)  
77 - .setTcpNoDelay(true)  
78 - .setReuseAddress(true);  
79 - // 配置 SSL(如果启用)  
80 - if (Boolean.TRUE.equals(tcpProperties.getSslEnabled())) {  
81 - PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()  
82 - .setKeyPath(tcpProperties.getSslKeyPath())  
83 - .setCertPath(tcpProperties.getSslCertPath());  
84 - options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);  
85 - }  
86 -  
87 - // 创建服务器并设置连接处理器  
88 - tcpServer = vertx.createNetServer(options);  
89 - tcpServer.connectHandler(socket -> {  
90 - IotTcpUpstreamHandler handler = new IotTcpUpstreamHandler(this, messageService, deviceService,  
91 - connectionManager);  
92 - handler.handle(socket);  
93 - });  
94 -  
95 - // 启动服务器  
96 - try {  
97 - tcpServer.listen().result();  
98 - running = true;  
99 - log.info("[start][IoT 网关 TCP 协议启动成功,端口:{}]", tcpProperties.getPort());  
100 - } catch (Exception e) {  
101 - log.error("[start][IoT 网关 TCP 协议启动失败]", e);  
102 - throw e;  
103 - }  
104 - }  
105 -  
106 - @Override  
107 - @PreDestroy  
108 - public void stop() {  
109 - if (tcpServer != null) {  
110 - try {  
111 - tcpServer.close().result();  
112 - running = false;  
113 - log.info("[stop][IoT 网关 TCP 协议已停止]");  
114 - } catch (Exception e) {  
115 - log.error("[stop][IoT 网关 TCP 协议停止失败]", e);  
116 - }  
117 - }  
118 - }  
119 -  
120 - @Override  
121 - public boolean isRunning() {  
122 - return running;  
123 - }  
124 -  
125 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/IotTcpCodecTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.delimiter.IotTcpDelimiterFrameCodec;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.length.IotTcpFixedLengthFrameCodec;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.length.IotTcpLengthFieldFrameCodec;
  8 +import lombok.AllArgsConstructor;
  9 +import lombok.Getter;
  10 +
  11 +import java.util.function.Function;
  12 +
  13 +/**
  14 + * IoT TCP 拆包类型枚举
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@AllArgsConstructor
  19 +@Getter
  20 +public enum IotTcpCodecTypeEnum {
  21 +
  22 + /**
  23 + * 基于固定长度的拆包
  24 + * <p>
  25 + * 消息格式:固定长度的消息体
  26 + * 需要配置:fixedLength(固定长度)
  27 + */
  28 + FIXED_LENGTH("fixed_length", IotTcpFixedLengthFrameCodec::new),
  29 +
  30 + /**
  31 + * 基于分隔符的拆包
  32 + * <p>
  33 + * 消息格式:消息内容 + 分隔符
  34 + * 需要配置:delimiter(分隔符)
  35 + */
  36 + DELIMITER("delimiter", IotTcpDelimiterFrameCodec::new),
  37 +
  38 + /**
  39 + * 基于长度字段的拆包
  40 + * <p>
  41 + * 消息格式:[长度字段][消息体]
  42 + * 需要配置:lengthFieldOffset(长度字段偏移量)、lengthFieldLength(长度字段长度)
  43 + */
  44 + LENGTH_FIELD("length_field", IotTcpLengthFieldFrameCodec::new),
  45 + ;
  46 +
  47 + /**
  48 + * 类型标识
  49 + */
  50 + private final String type;
  51 +
  52 + /**
  53 + * Codec 创建工厂
  54 + */
  55 + private final Function<IotTcpConfig.CodecConfig, IotTcpFrameCodec> codecFactory;
  56 +
  57 + /**
  58 + * 根据类型获取枚举
  59 + *
  60 + * @param type 类型标识
  61 + * @return 枚举值
  62 + */
  63 + public static IotTcpCodecTypeEnum of(String type) {
  64 + return ArrayUtil.firstMatch(e -> e.getType().equalsIgnoreCase(type), values());
  65 + }
  66 +
  67 + /**
  68 + * 创建 Codec 实例
  69 + *
  70 + * @param config 拆包配置
  71 + * @return Codec 实例
  72 + */
  73 + public IotTcpFrameCodec createCodec(IotTcpConfig.CodecConfig config) {
  74 + return codecFactory.apply(config);
  75 + }
  76 +
  77 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/IotTcpFrameCodec.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec;
  2 +
  3 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
  4 +import io.vertx.core.Handler;
  5 +import io.vertx.core.buffer.Buffer;
  6 +import io.vertx.core.parsetools.RecordParser;
  7 +
  8 +/**
  9 + * IoT TCP 帧编解码器接口
  10 + * <p>
  11 + * 用于解决 TCP 粘包/拆包问题,提供解码(拆包)和编码(加帧)能力
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public interface IotTcpFrameCodec {
  16 +
  17 + /**
  18 + * 获取编解码器类型
  19 + *
  20 + * @return 编解码器类型
  21 + */
  22 + IotTcpCodecTypeEnum getType();
  23 +
  24 + /**
  25 + * 创建解码器(RecordParser)
  26 + * <p>
  27 + * 每个连接调用一次,返回的 parser 需绑定到 socket.handler()
  28 + *
  29 + * @param handler 消息处理器,当收到完整的消息帧后回调
  30 + * @return RecordParser 实例
  31 + */
  32 + RecordParser createDecodeParser(Handler<Buffer> handler);
  33 +
  34 + /**
  35 + * 编码消息(加帧)
  36 + * <p>
  37 + * 根据不同的编解码类型添加帧头/分隔符
  38 + *
  39 + * @param data 原始数据
  40 + * @return 编码后的数据(带帧头/分隔符)
  41 + */
  42 + Buffer encode(byte[] data);
  43 +
  44 + // TODO @AI:还是搞个 facory 类 ,更好理解;
  45 + // ========== 静态工厂方法 ==========
  46 +
  47 + /**
  48 + * 根据配置创建编解码器
  49 + *
  50 + * @param config 拆包配置
  51 + * @return 编解码器实例,如果配置为空则返回 null
  52 + */
  53 + static IotTcpFrameCodec create(IotTcpConfig.CodecConfig config) {
  54 + if (config == null) {
  55 + return null;
  56 + }
  57 + IotTcpCodecTypeEnum type = IotTcpCodecTypeEnum.of(config.getType());
  58 + if (type == null) {
  59 + return null;
  60 + }
  61 + return type.createCodec(config);
  62 + }
  63 +
  64 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/delimiter/IotTcpDelimiterFrameCodec.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.delimiter;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpCodecTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
  7 +import io.vertx.core.Handler;
  8 +import io.vertx.core.buffer.Buffer;
  9 +import io.vertx.core.parsetools.RecordParser;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +
  12 +/**
  13 + * IoT TCP 分隔符帧编解码器
  14 + * <p>
  15 + * 基于分隔符的拆包策略,消息格式:消息内容 + 分隔符
  16 + * <p>
  17 + * 支持的分隔符:
  18 + * <ul>
  19 + * <li>\n - 换行符</li>
  20 + * <li>\r - 回车符</li>
  21 + * <li>\r\n - 回车换行</li>
  22 + * <li>自定义字符串</li>
  23 + * </ul>
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Slf4j
  28 +public class IotTcpDelimiterFrameCodec implements IotTcpFrameCodec {
  29 +
  30 + private final IotTcpConfig.CodecConfig config;
  31 +
  32 + /**
  33 + * 解析后的分隔符字节数组
  34 + */
  35 + private final byte[] delimiterBytes;
  36 +
  37 + /**
  38 + * 最大帧长度
  39 + */
  40 + // TODO @AI:最大帧数要不去掉;简洁一点;包括其他地方的配置项;
  41 + private final int maxFrameLength;
  42 +
  43 + public IotTcpDelimiterFrameCodec(IotTcpConfig.CodecConfig config) {
  44 + this.config = config;
  45 + // TODO @AI:禁止为空;
  46 + this.delimiterBytes = parseDelimiter(config.getDelimiter());
  47 + this.maxFrameLength = config.getMaxFrameLength() != null ? config.getMaxFrameLength() : 1048576;
  48 + }
  49 +
  50 + @Override
  51 + public IotTcpCodecTypeEnum getType() {
  52 + return IotTcpCodecTypeEnum.DELIMITER;
  53 + }
  54 +
  55 + @Override
  56 + public RecordParser createDecodeParser(Handler<Buffer> handler) {
  57 + RecordParser parser = RecordParser.newDelimited(Buffer.buffer(delimiterBytes));
  58 +
  59 + parser.handler(buffer -> {
  60 + // 检查帧长度是否超过限制
  61 + if (buffer.length() > maxFrameLength) {
  62 + log.warn("[createDecodeParser][帧长度超过限制,length: {}, maxFrameLength: {}]",
  63 + buffer.length(), maxFrameLength);
  64 + return;
  65 + }
  66 + // 处理完整消息(不包含分隔符)
  67 + handler.handle(buffer);
  68 + });
  69 +
  70 + // TODO @AI:异常处理;
  71 + parser.exceptionHandler(ex -> log.error("[createDecodeParser][解析异常]", ex));
  72 + return parser;
  73 + }
  74 +
  75 + @Override
  76 + public Buffer encode(byte[] data) {
  77 + Buffer buffer = Buffer.buffer();
  78 + buffer.appendBytes(data);
  79 + buffer.appendBytes(delimiterBytes);
  80 + return buffer;
  81 + }
  82 +
  83 + /**
  84 + * 解析分隔符字符串为字节数组
  85 + * <p>
  86 + * 支持转义字符:\n、\r、\r\n、\t
  87 + *
  88 + * @param delimiter 分隔符字符串
  89 + * @return 分隔符字节数组
  90 + */
  91 + private byte[] parseDelimiter(String delimiter) {
  92 + if (StrUtil.isBlank(delimiter)) {
  93 + // 默认使用换行符
  94 + return new byte[]{'\n'};
  95 + }
  96 +
  97 + // 处理转义字符
  98 + // TODO @AI:是否必要?不调整感觉也没问题?用户自己写对就 ok 了是哇?
  99 + String parsed = delimiter
  100 + .replace("\\r\\n", "\r\n")
  101 + .replace("\\r", "\r")
  102 + .replace("\\n", "\n")
  103 + .replace("\\t", "\t");
  104 + return parsed.getBytes();
  105 + }
  106 +
  107 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpFixedLengthFrameCodec.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.length;
  2 +
  3 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpCodecTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
  6 +import io.vertx.core.Handler;
  7 +import io.vertx.core.buffer.Buffer;
  8 +import io.vertx.core.parsetools.RecordParser;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +/**
  12 + * IoT TCP 定长帧编解码器
  13 + * <p>
  14 + * 基于固定长度的拆包策略,每条消息固定字节数
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Slf4j
  19 +public class IotTcpFixedLengthFrameCodec implements IotTcpFrameCodec {
  20 +
  21 + /**
  22 + * 固定消息长度
  23 + */
  24 + private final int fixedLength;
  25 +
  26 + public IotTcpFixedLengthFrameCodec(IotTcpConfig.CodecConfig config) {
  27 + // TODO @AI:config.getFixedLength() 禁止为空;
  28 + this.fixedLength = config.getFixedLength() != null ? config.getFixedLength() : 1024;
  29 + }
  30 +
  31 + @Override
  32 + public IotTcpCodecTypeEnum getType() {
  33 + return IotTcpCodecTypeEnum.FIXED_LENGTH;
  34 + }
  35 +
  36 + @Override
  37 + public RecordParser createDecodeParser(Handler<Buffer> handler) {
  38 + RecordParser parser = RecordParser.newFixed(fixedLength);
  39 + parser.handler(handler);
  40 + // TODO @AI:解析失败,是不是要抛出异常?因为要 close 掉连接;
  41 + parser.exceptionHandler(ex -> log.error("[createDecodeParser][解析异常]", ex));
  42 + return parser;
  43 + }
  44 +
  45 + @Override
  46 + public Buffer encode(byte[] data) {
  47 + Buffer buffer = Buffer.buffer(fixedLength);
  48 + buffer.appendBytes(data);
  49 + // 如果数据不足固定长度,填充 0
  50 + // TODO @AI:这里的填充是合理的么?RecordParser.newFixed(fixedLength) 有填充的逻辑么?
  51 + if (data.length < fixedLength) {
  52 + byte[] padding = new byte[fixedLength - data.length];
  53 + buffer.appendBytes(padding);
  54 + }
  55 + return buffer;
  56 + }
  57 +
  58 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpLengthFieldFrameCodec.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.length;
  2 +
  3 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpCodecTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
  6 +import io.vertx.core.Handler;
  7 +import io.vertx.core.buffer.Buffer;
  8 +import io.vertx.core.parsetools.RecordParser;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +/**
  12 + * IoT TCP 长度字段帧编解码器
  13 + * <p>
  14 + * 基于长度字段的拆包策略,消息格式:[长度字段][消息体]
  15 + * <p>
  16 + * 参数说明:
  17 + * <ul>
  18 + * <li>lengthFieldOffset: 长度字段在消息中的偏移量</li>
  19 + * <li>lengthFieldLength: 长度字段的字节数(1/2/4)</li>
  20 + * <li>lengthAdjustment: 长度调整值,用于调整长度字段的实际含义</li>
  21 + * <li>initialBytesToStrip: 解码后跳过的字节数</li>
  22 + * </ul>
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +@Slf4j
  27 +public class IotTcpLengthFieldFrameCodec implements IotTcpFrameCodec {
  28 +
  29 + private final int lengthFieldOffset;
  30 + private final int lengthFieldLength;
  31 + private final int lengthAdjustment;
  32 + private final int initialBytesToStrip;
  33 + // TODO @AI:去掉 maxFrameLength 相关字段;
  34 + private final int maxFrameLength;
  35 +
  36 + /**
  37 + * 头部长度 = 长度字段偏移量 + 长度字段长度
  38 + */
  39 + private final int headerLength;
  40 +
  41 + public IotTcpLengthFieldFrameCodec(IotTcpConfig.CodecConfig config) {
  42 + // TODO @AI: 增加参数校验;不要 default 逻辑;
  43 + this.lengthFieldOffset = config.getLengthFieldOffset() != null ? config.getLengthFieldOffset() : 0;
  44 + this.lengthFieldLength = config.getLengthFieldLength() != null ? config.getLengthFieldLength() : 4;
  45 + this.lengthAdjustment = config.getLengthAdjustment() != null ? config.getLengthAdjustment() : 0;
  46 + this.initialBytesToStrip = config.getInitialBytesToStrip() != null ? config.getInitialBytesToStrip() : 0;
  47 + this.maxFrameLength = config.getMaxFrameLength() != null ? config.getMaxFrameLength() : 1048576;
  48 + this.headerLength = lengthFieldOffset + lengthFieldLength;
  49 + }
  50 +
  51 + @Override
  52 + public IotTcpCodecTypeEnum getType() {
  53 + return IotTcpCodecTypeEnum.LENGTH_FIELD;
  54 + }
  55 +
  56 + @Override
  57 + public RecordParser createDecodeParser(Handler<Buffer> handler) {
  58 + // 创建状态机:先读取头部,再读取消息体
  59 + RecordParser parser = RecordParser.newFixed(headerLength);
  60 + // 使用数组保存状态和头部数据
  61 + // TODO @AI:bodyLength 只使用第 0 位,是不是 atomicInteger 更合适?
  62 + final int[] bodyLength = {-1};
  63 + final Buffer[] headerBuffer = {null};
  64 +
  65 + // 处理读取到的数据
  66 + parser.handler(buffer -> {
  67 + if (bodyLength[0] == -1) {
  68 + // 阶段 1: 读取头部,解析长度字段
  69 + headerBuffer[0] = buffer.copy();
  70 + int length = readLength(buffer, lengthFieldOffset, lengthFieldLength);
  71 + int frameBodyLength = length + lengthAdjustment;
  72 + // 检查帧长度是否超过限制
  73 + if (frameBodyLength < 0 || frameBodyLength > maxFrameLength - headerLength) {
  74 + log.warn("[createDecodeParser][帧长度异常,length: {}, frameBodyLength: {}, maxFrameLength: {}]",
  75 + length, frameBodyLength, maxFrameLength);
  76 + return;
  77 + }
  78 +
  79 + if (frameBodyLength == 0) {
  80 + // 消息体为空,直接处理
  81 + // TODO @AI:消息体为空,是不是不合理哈?应该抛出异常?
  82 + Buffer frame = processFrame(headerBuffer[0], null);
  83 + handler.handle(frame);
  84 + } else {
  85 + // 切换到读取消息体模式
  86 + bodyLength[0] = frameBodyLength;
  87 + parser.fixedSizeMode(frameBodyLength);
  88 + }
  89 + } else {
  90 + // 阶段 2: 读取消息体,组装完整帧
  91 + Buffer frame = processFrame(headerBuffer[0], buffer);
  92 + // 重置状态,准备读取下一帧
  93 + bodyLength[0] = -1;
  94 + headerBuffer[0] = null;
  95 + parser.fixedSizeMode(headerLength);
  96 +
  97 + // 处理完整消息
  98 + handler.handle(frame);
  99 + }
  100 + });
  101 +
  102 + parser.exceptionHandler(ex -> log.error("[createDecodeParser][解析异常]", ex));
  103 + return parser;
  104 + }
  105 +
  106 + @Override
  107 + public Buffer encode(byte[] data) {
  108 + Buffer buffer = Buffer.buffer();
  109 + // 计算要写入的长度值
  110 + int lengthValue = data.length - lengthAdjustment;
  111 + // 写入偏移量前的填充字节(如果有)
  112 + for (int i = 0; i < lengthFieldOffset; i++) {
  113 + buffer.appendByte((byte) 0);
  114 + }
  115 + // 写入长度字段
  116 + writeLength(buffer, lengthValue, lengthFieldLength);
  117 + // 写入消息体
  118 + buffer.appendBytes(data);
  119 + return buffer;
  120 + }
  121 +
  122 + /**
  123 + * 从 Buffer 中读取长度字段
  124 + */
  125 + // TODO @AI:兼容 JDK8
  126 + private int readLength(Buffer buffer, int offset, int length) {
  127 + return switch (length) {
  128 + case 1 -> buffer.getUnsignedByte(offset);
  129 + case 2 -> buffer.getUnsignedShort(offset);
  130 + case 4 -> buffer.getInt(offset);
  131 + default -> throw new IllegalArgumentException("不支持的长度字段长度: " + length);
  132 + };
  133 + }
  134 +
  135 + /**
  136 + * 向 Buffer 中写入长度字段
  137 + */
  138 + // TODO @AI:兼容 JDK8
  139 + private void writeLength(Buffer buffer, int length, int fieldLength) {
  140 + switch (fieldLength) {
  141 + case 1 -> buffer.appendByte((byte) length);
  142 + case 2 -> buffer.appendShort((short) length);
  143 + case 4 -> buffer.appendInt(length);
  144 + default -> throw new IllegalArgumentException("不支持的长度字段长度: " + fieldLength);
  145 + }
  146 + }
  147 +
  148 + /**
  149 + * 处理帧数据(根据 initialBytesToStrip 跳过指定字节)
  150 + */
  151 + private Buffer processFrame(Buffer header, Buffer body) {
  152 + Buffer fullFrame = Buffer.buffer();
  153 + if (header != null) {
  154 + fullFrame.appendBuffer(header);
  155 + }
  156 + if (body != null) {
  157 + fullFrame.appendBuffer(body);
  158 + }
  159 + // 根据 initialBytesToStrip 跳过指定字节
  160 + if (initialBytesToStrip > 0 && initialBytesToStrip < fullFrame.length()) {
  161 + return fullFrame.slice(initialBytesToStrip, fullFrame.length());
  162 + }
  163 + return fullFrame;
  164 + }
  165 +
  166 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpDownstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/downstream/IotTcpDownstreamHandler.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.downstream;
2 2
3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
4 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
5 -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 6 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  7 +import io.vertx.core.buffer.Buffer;
6 import lombok.RequiredArgsConstructor; 8 import lombok.RequiredArgsConstructor;
7 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
8 10
@@ -15,32 +17,43 @@ import lombok.extern.slf4j.Slf4j; @@ -15,32 +17,43 @@ import lombok.extern.slf4j.Slf4j;
15 @RequiredArgsConstructor 17 @RequiredArgsConstructor
16 public class IotTcpDownstreamHandler { 18 public class IotTcpDownstreamHandler {
17 19
18 - private final IotDeviceMessageService deviceMessageService;  
19 -  
20 private final IotTcpConnectionManager connectionManager; 20 private final IotTcpConnectionManager connectionManager;
21 21
22 /** 22 /**
  23 + * TCP 帧编解码器(处理粘包/拆包)
  24 + */
  25 + private final IotTcpFrameCodec codec;
  26 + /**
  27 + * 消息序列化器(处理业务消息序列化/反序列化)
  28 + */
  29 + private final IotMessageSerializer serializer;
  30 +
  31 + /**
23 * 处理下行消息 32 * 处理下行消息
24 */ 33 */
25 public void handle(IotDeviceMessage message) { 34 public void handle(IotDeviceMessage message) {
26 try { 35 try {
27 log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]", 36 log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]",
28 message.getDeviceId(), message.getMethod(), message.getId()); 37 message.getDeviceId(), message.getMethod(), message.getId());
29 -  
30 - // 1. 获取连接信息(包含 codecType) 38 + // 1. 检查设备连接
31 IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfoByDeviceId( 39 IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfoByDeviceId(
32 message.getDeviceId()); 40 message.getDeviceId());
33 if (connectionInfo == null) { 41 if (connectionInfo == null) {
34 - log.error("[handle][连接信息不存在,设备 ID: {}]", message.getDeviceId()); 42 + // TODO @AI:是不是把消息 id 也打印进去?类似上面的日志
  43 + log.warn("[handle][连接信息不存在,设备 ID: {}]", message.getDeviceId());
35 return; 44 return;
36 } 45 }
37 46
38 - // 2. 使用连接时的 codecType 编码消息,并发送到设备  
39 - byte[] bytes = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getCodecType());  
40 - boolean success = connectionManager.sendToDevice(message.getDeviceId(), bytes); 47 + // 2. 序列化 + 帧编码
  48 + byte[] serializedData = serializer.serialize(message);
  49 + Buffer frameData = codec.encode(serializedData);
  50 +
  51 + // 3. 发送到设备
  52 + boolean success = connectionManager.sendToDevice(message.getDeviceId(), frameData.getBytes());
  53 + // TODO @AI:不成功,直接抛出异常;反正下面的日志也会打印失败的
41 if (success) { 54 if (success) {
42 log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},数据长度: {} 字节]", 55 log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},数据长度: {} 字节]",
43 - message.getDeviceId(), message.getMethod(), message.getId(), bytes.length); 56 + message.getDeviceId(), message.getMethod(), message.getId(), frameData.length());
44 } else { 57 } else {
45 log.error("[handle][下行消息发送失败,设备 ID: {},方法: {},消息 ID: {}]", 58 log.error("[handle][下行消息发送失败,设备 ID: {},方法: {},消息 ID: {}]",
46 message.getDeviceId(), message.getMethod(), message.getId()); 59 message.getDeviceId(), message.getMethod(), message.getId());
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/downstream/IotTcpDownstreamSubscriber.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.downstream;
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler;  
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
9 /** 9 /**
@@ -16,7 +16,7 @@ public class IotTcpDownstreamSubscriber extends IotProtocolDownstreamSubscriber @@ -16,7 +16,7 @@ public class IotTcpDownstreamSubscriber extends IotProtocolDownstreamSubscriber
16 16
17 private final IotTcpDownstreamHandler downstreamHandler; 17 private final IotTcpDownstreamHandler downstreamHandler;
18 18
19 - public IotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocol, 19 + public IotTcpDownstreamSubscriber(IotProtocol protocol,
20 IotTcpDownstreamHandler downstreamHandler, 20 IotTcpDownstreamHandler downstreamHandler,
21 IotMessageBus messageBus) { 21 IotMessageBus messageBus) {
22 super(protocol, messageBus); 22 super(protocol, messageBus);
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/upstream/IotTcpUpstreamHandler.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.upstream;
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.BooleanUtil;
5 import cn.hutool.core.util.IdUtil; 5 import cn.hutool.core.util.IdUtil;
6 -import cn.hutool.core.util.StrUtil;  
7 import cn.hutool.extra.spring.SpringUtil; 6 import cn.hutool.extra.spring.SpringUtil;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
9 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -16,15 +15,16 @@ import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; @@ -16,15 +15,16 @@ import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
16 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; 15 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
17 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; 16 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
18 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 17 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
19 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec;  
20 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec;  
21 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol; 18 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  19 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
22 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; 20 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
  21 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
23 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; 22 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
24 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 23 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
25 import io.vertx.core.Handler; 24 import io.vertx.core.Handler;
26 import io.vertx.core.buffer.Buffer; 25 import io.vertx.core.buffer.Buffer;
27 import io.vertx.core.net.NetSocket; 26 import io.vertx.core.net.NetSocket;
  27 +import io.vertx.core.parsetools.RecordParser;
28 import lombok.extern.slf4j.Slf4j; 28 import lombok.extern.slf4j.Slf4j;
29 29
30 import java.util.Map; 30 import java.util.Map;
@@ -37,9 +37,6 @@ import java.util.Map; @@ -37,9 +37,6 @@ import java.util.Map;
37 @Slf4j 37 @Slf4j
38 public class IotTcpUpstreamHandler implements Handler<NetSocket> { 38 public class IotTcpUpstreamHandler implements Handler<NetSocket> {
39 39
40 - private static final String CODEC_TYPE_JSON = IotTcpJsonDeviceMessageCodec.TYPE;  
41 - private static final String CODEC_TYPE_BINARY = IotTcpBinaryDeviceMessageCodec.TYPE;  
42 -  
43 private static final String AUTH_METHOD = "auth"; 40 private static final String AUTH_METHOD = "auth";
44 41
45 private final IotDeviceMessageService deviceMessageService; 42 private final IotDeviceMessageService deviceMessageService;
@@ -52,15 +49,29 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -52,15 +49,29 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
52 49
53 private final String serverId; 50 private final String serverId;
54 51
55 - public IotTcpUpstreamHandler(IotTcpUpstreamProtocol protocol, 52 + /**
  53 + * TCP 帧编解码器(处理粘包/拆包)
  54 + */
  55 + private final IotTcpFrameCodec codec;
  56 + /**
  57 + * 消息序列化器(处理业务消息序列化/反序列化)
  58 + */
  59 + private final IotMessageSerializer serializer;
  60 +
  61 + public IotTcpUpstreamHandler(IotProtocol protocol,
56 IotDeviceMessageService deviceMessageService, 62 IotDeviceMessageService deviceMessageService,
57 IotDeviceService deviceService, 63 IotDeviceService deviceService,
58 - IotTcpConnectionManager connectionManager) { 64 + IotTcpConnectionManager connectionManager,
  65 + IotTcpFrameCodec codec,
  66 + IotMessageSerializer serializer) {
  67 + this.serverId = protocol.getServerId();
  68 + this.codec = codec;
  69 + this.serializer = serializer;
  70 + this.connectionManager = connectionManager;
  71 + // TODO @AI:都通过 springutil 获取下;
59 this.deviceMessageService = deviceMessageService; 72 this.deviceMessageService = deviceMessageService;
60 this.deviceService = deviceService; 73 this.deviceService = deviceService;
61 - this.connectionManager = connectionManager;  
62 this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); 74 this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
63 - this.serverId = protocol.getServerId();  
64 } 75 }
65 76
66 @Override 77 @Override
@@ -78,18 +89,32 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -78,18 +89,32 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
78 cleanupConnection(socket); 89 cleanupConnection(socket);
79 }); 90 });
80 91
81 - // 设置消息处理器  
82 - socket.handler(buffer -> {  
83 - // TODO @AI:TODO @芋艿:这里应该有拆粘包的问题; 92 + // 设置消息处理器(带拆包支持)
  93 + Handler<Buffer> messageHandler = buffer -> {
  94 + // TODO @AI:需要跟 AI 讨论。哪些情况关闭;哪些情况,发送异常消息;
84 try { 95 try {
85 processMessage(clientId, buffer, socket); 96 processMessage(clientId, buffer, socket);
86 } catch (Exception e) { 97 } catch (Exception e) {
  98 + // TODO @AI:这里能合并到 exceptionHandler 么?还是怎么搞好点;
87 log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]", 99 log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
88 clientId, socket.remoteAddress(), e.getMessage()); 100 clientId, socket.remoteAddress(), e.getMessage());
89 cleanupConnection(socket); 101 cleanupConnection(socket);
90 socket.close(); 102 socket.close();
91 } 103 }
92 - }); 104 + };
  105 +
  106 + // 根据是否配置了 FrameCodec 来决定是否使用拆包器
  107 + // TODO @AI:必须配置!
  108 + if (codec != null) {
  109 + // 使用拆包器处理粘包/拆包
  110 + RecordParser parser = codec.createDecodeParser(messageHandler);
  111 + socket.handler(parser);
  112 + log.debug("[handle][启用 {} 拆包器,客户端 ID: {}]", codec.getType(), clientId);
  113 + } else {
  114 + // 未配置拆包器,直接处理原始数据(可能存在粘包问题)
  115 + socket.handler(messageHandler);
  116 + log.debug("[handle][未配置拆包器,客户端 ID: {}]", clientId);
  117 + }
93 } 118 }
94 119
95 /** 120 /**
@@ -102,43 +127,42 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -102,43 +127,42 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
102 */ 127 */
103 private void processMessage(String clientId, Buffer buffer, NetSocket socket) throws Exception { 128 private void processMessage(String clientId, Buffer buffer, NetSocket socket) throws Exception {
104 // 1. 基础检查 129 // 1. 基础检查
  130 + // TODO @AI:不太应该为空?!
105 if (buffer == null || buffer.length() == 0) { 131 if (buffer == null || buffer.length() == 0) {
106 return; 132 return;
107 } 133 }
108 134
109 - // 2. 获取消息格式类型  
110 - String codecType = getMessageCodecType(buffer, socket);  
111 -  
112 - // 3. 解码消息 135 + // 2. 反序列化消息
113 IotDeviceMessage message; 136 IotDeviceMessage message;
114 try { 137 try {
115 - message = deviceMessageService.decodeDeviceMessage(buffer.getBytes(), codecType); 138 + message = serializer.deserialize(buffer.getBytes());
116 if (message == null) { 139 if (message == null) {
117 - throw new Exception("解码后消息为空"); 140 + throw new IllegalArgumentException("反序列化后消息为空");
118 } 141 }
119 } catch (Exception e) { 142 } catch (Exception e) {
120 - // 消息格式错误时抛出异常,由上层处理连接断开  
121 - throw new Exception("消息解码失败: " + e.getMessage(), e); 143 + // TODO @AI:是不是不用 try catch?
  144 + throw new Exception("消息反序列化失败: " + e.getMessage(), e);
122 } 145 }
123 146
124 - // 4. 根据消息类型路由处理 147 + // 3. 根据消息类型路由处理
125 try { 148 try {
126 if (AUTH_METHOD.equals(message.getMethod())) { 149 if (AUTH_METHOD.equals(message.getMethod())) {
127 // 认证请求 150 // 认证请求
128 - handleAuthenticationRequest(clientId, message, codecType, socket); 151 + handleAuthenticationRequest(clientId, message, socket);
129 } else if (IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod().equals(message.getMethod())) { 152 } else if (IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod().equals(message.getMethod())) {
130 // 设备动态注册请求 153 // 设备动态注册请求
131 - handleRegisterRequest(clientId, message, codecType, socket); 154 + handleRegisterRequest(clientId, message, socket);
132 } else { 155 } else {
133 // 业务消息 156 // 业务消息
134 - handleBusinessRequest(clientId, message, codecType, socket); 157 + handleBusinessRequest(clientId, message, socket);
135 } 158 }
136 } catch (Exception e) { 159 } catch (Exception e) {
137 - log.error("[processMessage][处理消息失败,客户端 ID: {},消息方法: {}]",  
138 - clientId, message.getMethod(), e); 160 + // TODO @AI:如果参数不正确,不断开连接;
  161 + log.error("[processMessage][处理消息失败,客户端 ID: {},消息方法: {}]", clientId, message.getMethod(), e);
139 // 发送错误响应,避免客户端一直等待 162 // 发送错误响应,避免客户端一直等待
  163 + // TODO @AI:发送失败,是不是不用 try catch?
140 try { 164 try {
141 - sendErrorResponse(socket, message.getRequestId(), "消息处理失败", codecType); 165 + sendErrorResponse(socket, message.getRequestId(), "消息处理失败");
142 } catch (Exception responseEx) { 166 } catch (Exception responseEx) {
143 log.error("[processMessage][发送错误响应失败,客户端 ID: {}]", clientId, responseEx); 167 log.error("[processMessage][发送错误响应失败,客户端 ID: {}]", clientId, responseEx);
144 } 168 }
@@ -148,74 +172,73 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -148,74 +172,73 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
148 /** 172 /**
149 * 处理认证请求 173 * 处理认证请求
150 * 174 *
151 - * @param clientId 客户端 ID  
152 - * @param message 消息信息  
153 - * @param codecType 消息编解码类型  
154 - * @param socket 网络连接 175 + * @param clientId 客户端 ID
  176 + * @param message 消息信息
  177 + * @param socket 网络连接
155 */ 178 */
156 - private void handleAuthenticationRequest(String clientId, IotDeviceMessage message, String codecType,  
157 - NetSocket socket) { 179 + private void handleAuthenticationRequest(String clientId, IotDeviceMessage message, NetSocket socket) {
158 try { 180 try {
159 // 1.1 解析认证参数 181 // 1.1 解析认证参数
  182 + // TODO @AI:直接 JsonUtils.convertObject(params, IotDeviceAuthReqDTO.class);然后,校验参数,不正确抛出 invalid exception;和 http 那一样;
160 IotDeviceAuthReqDTO authParams = parseAuthParams(message.getParams()); 183 IotDeviceAuthReqDTO authParams = parseAuthParams(message.getParams());
161 if (authParams == null) { 184 if (authParams == null) {
162 log.warn("[handleAuthenticationRequest][认证参数解析失败,客户端 ID: {}]", clientId); 185 log.warn("[handleAuthenticationRequest][认证参数解析失败,客户端 ID: {}]", clientId);
163 - sendErrorResponse(socket, message.getRequestId(), "认证参数不完整", codecType); 186 + sendErrorResponse(socket, message.getRequestId(), "认证参数不完整");
164 return; 187 return;
165 } 188 }
166 // 1.2 执行认证 189 // 1.2 执行认证
167 if (!validateDeviceAuth(authParams)) { 190 if (!validateDeviceAuth(authParams)) {
168 log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]", 191 log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]",
169 clientId, authParams.getUsername()); 192 clientId, authParams.getUsername());
170 - sendErrorResponse(socket, message.getRequestId(), "认证失败", codecType); 193 + sendErrorResponse(socket, message.getRequestId(), "认证失败");
171 return; 194 return;
172 } 195 }
173 196
174 // 2.1 解析设备信息 197 // 2.1 解析设备信息
175 IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername()); 198 IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername());
  199 + // TODO @AI:这里就断言 deviceInfo 不为空了?!
176 if (deviceInfo == null) { 200 if (deviceInfo == null) {
177 - sendErrorResponse(socket, message.getRequestId(), "解析设备信息失败", codecType); 201 + sendErrorResponse(socket, message.getRequestId(), "解析设备信息失败");
178 return; 202 return;
179 } 203 }
180 // 2.2 获取设备信息 204 // 2.2 获取设备信息
181 - IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(),  
182 - deviceInfo.getDeviceName()); 205 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
  206 + // TODO @AI:这里就断言 device 不为空了?!
183 if (device == null) { 207 if (device == null) {
184 - sendErrorResponse(socket, message.getRequestId(), "设备不存在", codecType); 208 + sendErrorResponse(socket, message.getRequestId(), "设备不存在");
185 return; 209 return;
186 } 210 }
187 211
188 // 3.1 注册连接 212 // 3.1 注册连接
189 - registerConnection(socket, device, clientId, codecType); 213 + registerConnection(socket, device, clientId);
190 // 3.2 发送上线消息 214 // 3.2 发送上线消息
191 sendOnlineMessage(device); 215 sendOnlineMessage(device);
192 // 3.3 发送成功响应 216 // 3.3 发送成功响应
193 - sendSuccessResponse(socket, message.getRequestId(), "认证成功", codecType); 217 + sendSuccessResponse(socket, message.getRequestId(), "认证成功");
194 log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {}]", 218 log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {}]",
195 device.getId(), device.getDeviceName()); 219 device.getId(), device.getDeviceName());
196 } catch (Exception e) { 220 } catch (Exception e) {
  221 + // TODO @AI:最大化去掉 try catch;(这个方法里的)
197 log.error("[handleAuthenticationRequest][认证处理异常,客户端 ID: {}]", clientId, e); 222 log.error("[handleAuthenticationRequest][认证处理异常,客户端 ID: {}]", clientId, e);
198 - sendErrorResponse(socket, message.getRequestId(), "认证处理异常", codecType); 223 + sendErrorResponse(socket, message.getRequestId(), "认证处理异常");
199 } 224 }
200 } 225 }
201 226
202 /** 227 /**
203 * 处理设备动态注册请求(一型一密,不需要认证) 228 * 处理设备动态注册请求(一型一密,不需要认证)
204 * 229 *
205 - * @param clientId 客户端 ID  
206 - * @param message 消息信息  
207 - * @param codecType 消息编解码类型  
208 - * @param socket 网络连接 230 + * @param clientId 客户端 ID
  231 + * @param message 消息信息
  232 + * @param socket 网络连接
209 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> 233 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
210 */ 234 */
211 - private void handleRegisterRequest(String clientId, IotDeviceMessage message, String codecType,  
212 - NetSocket socket) { 235 + private void handleRegisterRequest(String clientId, IotDeviceMessage message, NetSocket socket) {
213 try { 236 try {
214 // 1. 解析注册参数 237 // 1. 解析注册参数
215 IotDeviceRegisterReqDTO params = parseRegisterParams(message.getParams()); 238 IotDeviceRegisterReqDTO params = parseRegisterParams(message.getParams());
216 if (params == null) { 239 if (params == null) {
217 log.warn("[handleRegisterRequest][注册参数解析失败,客户端 ID: {}]", clientId); 240 log.warn("[handleRegisterRequest][注册参数解析失败,客户端 ID: {}]", clientId);
218 - sendErrorResponse(socket, message.getRequestId(), "注册参数不完整", codecType); 241 + sendErrorResponse(socket, message.getRequestId(), "注册参数不完整");
219 return; 242 return;
220 } 243 }
221 244
@@ -223,34 +246,33 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -223,34 +246,33 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
223 CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); 246 CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params);
224 if (result.isError()) { 247 if (result.isError()) {
225 log.warn("[handleRegisterRequest][注册失败,客户端 ID: {},错误: {}]", clientId, result.getMsg()); 248 log.warn("[handleRegisterRequest][注册失败,客户端 ID: {},错误: {}]", clientId, result.getMsg());
226 - sendErrorResponse(socket, message.getRequestId(), result.getMsg(), codecType); 249 + sendErrorResponse(socket, message.getRequestId(), result.getMsg());
227 return; 250 return;
228 } 251 }
229 252
230 // 3. 发送成功响应(包含 deviceSecret) 253 // 3. 发送成功响应(包含 deviceSecret)
231 - sendRegisterSuccessResponse(socket, message.getRequestId(), result.getData(), codecType); 254 + sendRegisterSuccessResponse(socket, message.getRequestId(), result.getData());
232 log.info("[handleRegisterRequest][注册成功,客户端 ID: {},设备名: {}]", 255 log.info("[handleRegisterRequest][注册成功,客户端 ID: {},设备名: {}]",
233 clientId, params.getDeviceName()); 256 clientId, params.getDeviceName());
234 } catch (Exception e) { 257 } catch (Exception e) {
235 log.error("[handleRegisterRequest][注册处理异常,客户端 ID: {}]", clientId, e); 258 log.error("[handleRegisterRequest][注册处理异常,客户端 ID: {}]", clientId, e);
236 - sendErrorResponse(socket, message.getRequestId(), "注册处理异常", codecType); 259 + sendErrorResponse(socket, message.getRequestId(), "注册处理异常");
237 } 260 }
238 } 261 }
239 262
240 /** 263 /**
241 * 处理业务请求 264 * 处理业务请求
242 * 265 *
243 - * @param clientId 客户端 ID  
244 - * @param message 消息信息  
245 - * @param codecType 消息编解码类型  
246 - * @param socket 网络连接 266 + * @param clientId 客户端 ID
  267 + * @param message 消息信息
  268 + * @param socket 网络连接
247 */ 269 */
248 - private void handleBusinessRequest(String clientId, IotDeviceMessage message, String codecType, NetSocket socket) { 270 + private void handleBusinessRequest(String clientId, IotDeviceMessage message, NetSocket socket) {
249 try { 271 try {
250 // 1. 检查认证状态 272 // 1. 检查认证状态
251 if (connectionManager.isNotAuthenticated(socket)) { 273 if (connectionManager.isNotAuthenticated(socket)) {
252 log.warn("[handleBusinessRequest][设备未认证,客户端 ID: {}]", clientId); 274 log.warn("[handleBusinessRequest][设备未认证,客户端 ID: {}]", clientId);
253 - sendErrorResponse(socket, message.getRequestId(), "请先进行认证", codecType); 275 + sendErrorResponse(socket, message.getRequestId(), "请先进行认证");
254 return; 276 return;
255 } 277 }
256 278
@@ -268,41 +290,18 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -268,41 +290,18 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
268 } 290 }
269 291
270 /** 292 /**
271 - * 获取消息编解码类型  
272 - *  
273 - * @param buffer 消息  
274 - * @param socket 网络连接  
275 - * @return 消息编解码类型  
276 - */  
277 - private String getMessageCodecType(Buffer buffer, NetSocket socket) {  
278 - // 1. 如果已认证,优先使用缓存的编解码类型  
279 - IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);  
280 - if (connectionInfo != null  
281 - && StrUtil.isNotBlank(connectionInfo.getCodecType())) {  
282 - return connectionInfo.getCodecType();  
283 - }  
284 -  
285 - // 2. 未认证时检测消息格式类型  
286 - return IotTcpBinaryDeviceMessageCodec.isBinaryFormatQuick(buffer.getBytes()) ? CODEC_TYPE_BINARY  
287 - : CODEC_TYPE_JSON;  
288 - }  
289 -  
290 - /**  
291 * 注册连接信息 293 * 注册连接信息
292 * 294 *
293 - * @param socket 网络连接  
294 - * @param device 设备  
295 - * @param clientId 客户端 ID  
296 - * @param codecType 消息编解码类型 295 + * @param socket 网络连接
  296 + * @param device 设备
  297 + * @param clientId 客户端 ID
297 */ 298 */
298 - private void registerConnection(NetSocket socket, IotDeviceRespDTO device,  
299 - String clientId, String codecType) { 299 + private void registerConnection(NetSocket socket, IotDeviceRespDTO device, String clientId) {
300 IotTcpConnectionManager.ConnectionInfo connectionInfo = new IotTcpConnectionManager.ConnectionInfo() 300 IotTcpConnectionManager.ConnectionInfo connectionInfo = new IotTcpConnectionManager.ConnectionInfo()
301 .setDeviceId(device.getId()) 301 .setDeviceId(device.getId())
302 .setProductKey(device.getProductKey()) 302 .setProductKey(device.getProductKey())
303 .setDeviceName(device.getDeviceName()) 303 .setDeviceName(device.getDeviceName())
304 - .setClientId(clientId)  
305 - .setCodecType(codecType); 304 + .setClientId(clientId);
306 // 注册连接 305 // 注册连接
307 connectionManager.registerConnection(socket, device.getId(), connectionInfo); 306 connectionManager.registerConnection(socket, device.getId(), connectionInfo);
308 } 307 }
@@ -351,10 +350,10 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -351,10 +350,10 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
351 * @param success 是否成功 350 * @param success 是否成功
352 * @param message 消息 351 * @param message 消息
353 * @param requestId 请求 ID 352 * @param requestId 请求 ID
354 - * @param codecType 消息编解码类型  
355 */ 353 */
356 - private void sendResponse(NetSocket socket, boolean success, String message, String requestId, String codecType) { 354 + private void sendResponse(NetSocket socket, boolean success, String message, String requestId) {
357 try { 355 try {
  356 + // TODO @AI:是不是不用
358 Object responseData = MapUtil.builder() 357 Object responseData = MapUtil.builder()
359 .put("success", success) 358 .put("success", success)
360 .put("message", message) 359 .put("message", message)
@@ -364,14 +363,17 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -364,14 +363,17 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
364 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData, 363 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData,
365 code, message); 364 code, message);
366 365
367 - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, codecType);  
368 - socket.write(Buffer.buffer(encodedData)); 366 + // 序列化 + 帧编码
  367 + byte[] serializedData = serializer.serialize(responseMessage);
  368 + Buffer frameData = codec.encode(serializedData);
  369 + socket.write(frameData);
369 370
370 } catch (Exception e) { 371 } catch (Exception e) {
371 log.error("[sendResponse][发送响应失败,requestId: {}]", requestId, e); 372 log.error("[sendResponse][发送响应失败,requestId: {}]", requestId, e);
372 } 373 }
373 } 374 }
374 375
  376 + // TODO @AI:合并到 handleAuthenticationRequest 里;
375 /** 377 /**
376 * 验证设备认证信息 378 * 验证设备认证信息
377 * 379 *
@@ -397,10 +399,9 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -397,10 +399,9 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
397 * @param socket 网络连接 399 * @param socket 网络连接
398 * @param requestId 请求 ID 400 * @param requestId 请求 ID
399 * @param errorMessage 错误消息 401 * @param errorMessage 错误消息
400 - * @param codecType 消息编解码类型  
401 */ 402 */
402 - private void sendErrorResponse(NetSocket socket, String requestId, String errorMessage, String codecType) {  
403 - sendResponse(socket, false, errorMessage, requestId, codecType); 403 + private void sendErrorResponse(NetSocket socket, String requestId, String errorMessage) {
  404 + sendResponse(socket, false, errorMessage, requestId);
404 } 405 }
405 406
406 /** 407 /**
@@ -409,11 +410,10 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -409,11 +410,10 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
409 * @param socket 网络连接 410 * @param socket 网络连接
410 * @param requestId 请求 ID 411 * @param requestId 请求 ID
411 * @param message 消息 412 * @param message 消息
412 - * @param codecType 消息编解码类型  
413 */ 413 */
414 @SuppressWarnings("SameParameterValue") 414 @SuppressWarnings("SameParameterValue")
415 - private void sendSuccessResponse(NetSocket socket, String requestId, String message, String codecType) {  
416 - sendResponse(socket, true, message, requestId, codecType); 415 + private void sendSuccessResponse(NetSocket socket, String requestId, String message) {
  416 + sendResponse(socket, true, message, requestId);
417 } 417 }
418 418
419 /** 419 /**
@@ -489,17 +489,17 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -489,17 +489,17 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
489 * @param socket 网络连接 489 * @param socket 网络连接
490 * @param requestId 请求 ID 490 * @param requestId 请求 ID
491 * @param registerResp 注册响应 491 * @param registerResp 注册响应
492 - * @param codecType 消息编解码类型  
493 */ 492 */
494 private void sendRegisterSuccessResponse(NetSocket socket, String requestId, 493 private void sendRegisterSuccessResponse(NetSocket socket, String requestId,
495 - IotDeviceRegisterRespDTO registerResp, String codecType) { 494 + IotDeviceRegisterRespDTO registerResp) {
496 try { 495 try {
497 // 1. 构建响应消息(参考 HTTP 返回格式,直接返回 IotDeviceRegisterRespDTO) 496 // 1. 构建响应消息(参考 HTTP 返回格式,直接返回 IotDeviceRegisterRespDTO)
498 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, 497 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId,
499 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerResp, 0, null); 498 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerResp, 0, null);
500 - // 2. 发送响应  
501 - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, codecType);  
502 - socket.write(Buffer.buffer(encodedData)); 499 + // 2. 序列化 + 帧编码
  500 + byte[] serializedData = serializer.serialize(responseMessage);
  501 + Buffer frameData = codec.encode(serializedData);
  502 + socket.write(frameData);
503 } catch (Exception e) { 503 } catch (Exception e) {
504 log.error("[sendRegisterSuccessResponse][发送注册成功响应失败,requestId: {}]", requestId, e); 504 log.error("[sendRegisterSuccessResponse][发送注册成功响应失败,requestId: {}]", requestId, e);
505 } 505 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java
@@ -81,6 +81,7 @@ public class IotTcpConnectionManager { @@ -81,6 +81,7 @@ public class IotTcpConnectionManager {
81 return info != null; 81 return info != null;
82 } 82 }
83 83
  84 + // TODO @AI:是不是可以去掉;因为现在只有认证成功的,才会注册连接;
84 /** 85 /**
85 * 检查连接是否未认证 86 * 检查连接是否未认证
86 */ 87 */
@@ -148,10 +149,6 @@ public class IotTcpConnectionManager { @@ -148,10 +149,6 @@ public class IotTcpConnectionManager {
148 * 客户端 ID 149 * 客户端 ID
149 */ 150 */
150 private String clientId; 151 private String clientId;
151 - /**  
152 - * 消息编解码类型(认证后确定)  
153 - */  
154 - private String codecType;  
155 152
156 } 153 }
157 154
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
@@ -42,7 +42,7 @@ yudao: @@ -42,7 +42,7 @@ yudao:
42 secret: yudaoIotGatewayTokenSecret123456789 # Token 密钥,至少32位 42 secret: yudaoIotGatewayTokenSecret123456789 # Token 密钥,至少32位
43 expiration: 7d 43 expiration: 7d
44 44
45 - # 协议实例列表(新版配置方式) 45 + # 协议实例列表
46 protocols: 46 protocols:
47 # ==================================== 47 # ====================================
48 # 针对引入的 HTTP 组件的配置 48 # 针对引入的 HTTP 组件的配置
@@ -53,6 +53,22 @@ yudao: @@ -53,6 +53,22 @@ yudao:
53 enabled: true 53 enabled: true
54 http: 54 http:
55 ssl-enabled: false 55 ssl-enabled: false
  56 + # ====================================
  57 + # 针对引入的 TCP 组件的配置
  58 + # ====================================
  59 + - id: tcp-json
  60 + type: tcp
  61 + port: 8091
  62 + enabled: true
  63 + serialize: json
  64 + tcp:
  65 + max-connections: 1000
  66 + keep-alive-timeout-ms: 30000
  67 + ssl-enabled: false
  68 + codec:
  69 + type: delimiter # 拆包类型:length_field / delimiter / fixed_length
  70 + delimiter: "\\n" # 分隔符(支持转义:\\n=换行, \\r=回车, \\t=制表符)
  71 + max-frame-length: 1048576 # 最大帧长度(字节)
56 72
57 # 协议配置(旧版,保持兼容) 73 # 协议配置(旧版,保持兼容)
58 protocol: 74 protocol:
@@ -91,17 +107,6 @@ yudao: @@ -91,17 +107,6 @@ yudao:
91 trust-store-path: "classpath:certs/trust.jks" # 信任的 CA 证书库路径 107 trust-store-path: "classpath:certs/trust.jks" # 信任的 CA 证书库路径
92 trust-store-password: "your-truststore-password" # 信任的 CA 证书库密码 108 trust-store-password: "your-truststore-password" # 信任的 CA 证书库密码
93 # ==================================== 109 # ====================================
94 - # 针对引入的 TCP 组件的配置  
95 - # ====================================  
96 - tcp:  
97 - enabled: false  
98 - port: 8091  
99 - keep-alive-timeout-ms: 30000  
100 - max-connections: 1000  
101 - ssl-enabled: false  
102 - ssl-cert-path: "classpath:certs/client.jks"  
103 - ssl-key-path: "classpath:certs/client.jks"  
104 - # ====================================  
105 # 针对引入的 UDP 组件的配置 110 # 针对引入的 UDP 组件的配置
106 # ==================================== 111 # ====================================
107 udp: 112 udp:
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java
@@ -10,32 +10,40 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; @@ -10,32 +10,40 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
10 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; 10 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
11 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; 11 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
12 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 12 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
13 -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;  
14 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec;  
15 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec; 13 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  14 +import cn.iocoder.yudao.module.iot.gateway.serialize.binary.IotBinarySerializer;
  15 +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
16 import lombok.extern.slf4j.Slf4j; 16 import lombok.extern.slf4j.Slf4j;
17 import org.junit.jupiter.api.Disabled; 17 import org.junit.jupiter.api.Disabled;
18 import org.junit.jupiter.api.Test; 18 import org.junit.jupiter.api.Test;
19 19
20 -import java.io.InputStream; 20 +import java.io.BufferedReader;
  21 +import java.io.InputStreamReader;
21 import java.io.OutputStream; 22 import java.io.OutputStream;
22 import java.net.Socket; 23 import java.net.Socket;
  24 +import java.nio.charset.StandardCharsets;
23 25
24 /** 26 /**
25 * IoT 直连设备 TCP 协议集成测试(手动测试) 27 * IoT 直连设备 TCP 协议集成测试(手动测试)
26 * 28 *
27 * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP 协议直接连接平台 29 * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP 协议直接连接平台
28 * 30 *
29 - * <p>支持两种编解码格式: 31 + * <p>支持两种序列化格式:
30 * <ul> 32 * <ul>
31 - * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>  
32 - * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li> 33 + * <li>{@link IotJsonSerializer} - JSON 格式</li>
  34 + * <li>{@link IotBinarySerializer} - 二进制格式</li>
  35 + * </ul>
  36 + *
  37 + * <p>TCP 拆包配置(需与 application.yaml 中的 codec 配置一致):
  38 + * <ul>
  39 + * <li>type: delimiter - 基于分隔符拆包</li>
  40 + * <li>delimiter: \n - 换行符作为分隔符</li>
33 * </ul> 41 * </ul>
34 * 42 *
35 * <p>使用步骤: 43 * <p>使用步骤:
36 * <ol> 44 * <ol>
37 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li> 45 * <li>启动 yudao-module-iot-gateway 服务(TCP 端口 8091)</li>
38 - * <li>修改 {@link #CODEC} 选择测试的编解码格式</li> 46 + * <li>修改 {@link #SERIALIZER} 选择测试的序列化格式(Delimiter 模式只支持 JSON)</li>
39 * <li>运行以下测试方法: 47 * <li>运行以下测试方法:
40 * <ul> 48 * <ul>
41 * <li>{@link #testAuth()} - 设备认证</li> 49 * <li>{@link #testAuth()} - 设备认证</li>
@@ -58,10 +66,16 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -58,10 +66,16 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
58 private static final int SERVER_PORT = 8091; 66 private static final int SERVER_PORT = 8091;
59 private static final int TIMEOUT_MS = 5000; 67 private static final int TIMEOUT_MS = 5000;
60 68
61 - // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== 69 + // TODO @AI:这里可以通过 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec 么?例如说:使用 vertx vertx tcp client???从而更好的复用解码逻辑;
  70 + /**
  71 + * 分隔符(需与 application.yaml 中的 delimiter 配置一致)
  72 + */
  73 + private static final String DELIMITER = "\n";
62 74
63 -// private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();  
64 - private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); 75 + // ===================== 序列化器选择(Delimiter 模式推荐使用 JSON) =====================
  76 +
  77 + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer();
  78 +// private static final IotMessageSerializer SERIALIZER = new IotBinarySerializer();
65 79
66 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) ===================== 80 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) =====================
67 81
@@ -82,18 +96,19 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -82,18 +96,19 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
82 .setClientId(authInfo.getClientId()) 96 .setClientId(authInfo.getClientId())
83 .setUsername(authInfo.getUsername()) 97 .setUsername(authInfo.getUsername())
84 .setPassword(authInfo.getPassword()); 98 .setPassword(authInfo.getPassword());
85 - IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);  
86 - // 1.2 编码  
87 - byte[] payload = CODEC.encode(request);  
88 - log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length); 99 + IotDeviceMessage request = IotDeviceMessage.requestOf("auth", authReqDTO);
  100 + // 1.2 序列化
  101 + // TODO @AI:是不是把 SERIALIZER 放到 sendAndReceive 里;
  102 + byte[] payload = SERIALIZER.serialize(request);
  103 + log.info("[testAuth][Serializer: {}, 请求消息: {}, 数据包长度: {} 字节]", SERIALIZER.getType(), request, payload.length);
89 104
90 // 2.1 发送请求 105 // 2.1 发送请求
91 try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { 106 try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {
92 socket.setSoTimeout(TIMEOUT_MS); 107 socket.setSoTimeout(TIMEOUT_MS);
93 byte[] responseBytes = sendAndReceive(socket, payload); 108 byte[] responseBytes = sendAndReceive(socket, payload);
94 - // 2.2 解码响应 109 + // 2.2 反序列化响应
95 if (responseBytes != null) { 110 if (responseBytes != null) {
96 - IotDeviceMessage response = CODEC.decode(responseBytes); 111 + IotDeviceMessage response = SERIALIZER.deserialize(responseBytes);
97 log.info("[testAuth][响应消息: {}]", response); 112 log.info("[testAuth][响应消息: {}]", response);
98 } else { 113 } else {
99 log.warn("[testAuth][未收到响应]"); 114 log.warn("[testAuth][未收到响应]");
@@ -119,17 +134,17 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -119,17 +134,17 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
119 registerReqDTO.setProductSecret("test-product-secret"); 134 registerReqDTO.setProductSecret("test-product-secret");
120 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), 135 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(),
121 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null); 136 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null);
122 - // 1.2 编码  
123 - byte[] payload = CODEC.encode(request);  
124 - log.info("[testDeviceRegister][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length); 137 + // 1.2 序列化
  138 + byte[] payload = SERIALIZER.serialize(request);
  139 + log.info("[testDeviceRegister][Serializer: {}, 请求消息: {}, 数据包长度: {} 字节]", SERIALIZER.getType(), request, payload.length);
125 140
126 // 2.1 发送请求 141 // 2.1 发送请求
127 try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { 142 try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) {
128 socket.setSoTimeout(TIMEOUT_MS); 143 socket.setSoTimeout(TIMEOUT_MS);
129 byte[] responseBytes = sendAndReceive(socket, payload); 144 byte[] responseBytes = sendAndReceive(socket, payload);
130 - // 2.2 解码响应 145 + // 2.2 反序列化响应
131 if (responseBytes != null) { 146 if (responseBytes != null) {
132 - IotDeviceMessage response = CODEC.decode(responseBytes); 147 + IotDeviceMessage response = SERIALIZER.deserialize(responseBytes);
133 log.info("[testDeviceRegister][响应消息: {}]", response); 148 log.info("[testDeviceRegister][响应消息: {}]", response);
134 log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]"); 149 log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
135 } else { 150 } else {
@@ -161,15 +176,15 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -161,15 +176,15 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
161 .put("height", "2") 176 .put("height", "2")
162 .build()), 177 .build()),
163 null, null, null); 178 null, null, null);
164 - // 2.2 编码  
165 - byte[] payload = CODEC.encode(request);  
166 - log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request); 179 + // 2.2 序列化
  180 + byte[] payload = SERIALIZER.serialize(request);
  181 + log.info("[testPropertyPost][Serializer: {}, 请求消息: {}]", SERIALIZER.getType(), request);
167 182
168 // 3.1 发送请求 183 // 3.1 发送请求
169 byte[] responseBytes = sendAndReceive(socket, payload); 184 byte[] responseBytes = sendAndReceive(socket, payload);
170 - // 3.2 解码响应 185 + // 3.2 反序列化响应
171 if (responseBytes != null) { 186 if (responseBytes != null) {
172 - IotDeviceMessage response = CODEC.decode(responseBytes); 187 + IotDeviceMessage response = SERIALIZER.deserialize(responseBytes);
173 log.info("[testPropertyPost][响应消息: {}]", response); 188 log.info("[testPropertyPost][响应消息: {}]", response);
174 } else { 189 } else {
175 log.warn("[testPropertyPost][未收到响应]"); 190 log.warn("[testPropertyPost][未收到响应]");
@@ -200,15 +215,15 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -200,15 +215,15 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
200 MapUtil.<String, Object>builder().put("rice", 3).build(), 215 MapUtil.<String, Object>builder().put("rice", 3).build(),
201 System.currentTimeMillis()), 216 System.currentTimeMillis()),
202 null, null, null); 217 null, null, null);
203 - // 2.2 编码  
204 - byte[] payload = CODEC.encode(request);  
205 - log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request); 218 + // 2.2 序列化
  219 + byte[] payload = SERIALIZER.serialize(request);
  220 + log.info("[testEventPost][Serializer: {}, 请求消息: {}]", SERIALIZER.getType(), request);
206 221
207 // 3.1 发送请求 222 // 3.1 发送请求
208 byte[] responseBytes = sendAndReceive(socket, payload); 223 byte[] responseBytes = sendAndReceive(socket, payload);
209 - // 3.2 解码响应 224 + // 3.2 反序列化响应
210 if (responseBytes != null) { 225 if (responseBytes != null) {
211 - IotDeviceMessage response = CODEC.decode(responseBytes); 226 + IotDeviceMessage response = SERIALIZER.deserialize(responseBytes);
212 log.info("[testEventPost][响应消息: {}]", response); 227 log.info("[testEventPost][响应消息: {}]", response);
213 } else { 228 } else {
214 log.warn("[testEventPost][未收到响应]"); 229 log.warn("[testEventPost][未收到响应]");
@@ -231,41 +246,44 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { @@ -231,41 +246,44 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
231 .setUsername(authInfo.getUsername()) 246 .setUsername(authInfo.getUsername())
232 .setPassword(authInfo.getPassword()); 247 .setPassword(authInfo.getPassword());
233 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null); 248 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
234 - byte[] payload = CODEC.encode(request); 249 + byte[] payload = SERIALIZER.serialize(request);
235 byte[] responseBytes = sendAndReceive(socket, payload); 250 byte[] responseBytes = sendAndReceive(socket, payload);
236 if (responseBytes != null) { 251 if (responseBytes != null) {
237 log.info("[authenticate][响应数据长度: {} 字节,首字节: 0x{}, HEX: {}]", 252 log.info("[authenticate][响应数据长度: {} 字节,首字节: 0x{}, HEX: {}]",
238 responseBytes.length, 253 responseBytes.length,
239 String.format("%02X", responseBytes[0]), 254 String.format("%02X", responseBytes[0]),
240 HexUtil.encodeHexStr(responseBytes)); 255 HexUtil.encodeHexStr(responseBytes));
241 - return CODEC.decode(responseBytes); 256 + return SERIALIZER.deserialize(responseBytes);
242 } 257 }
243 return null; 258 return null;
244 } 259 }
245 260
246 /** 261 /**
247 - * 发送 TCP 请求并接收响应 262 + * 发送 TCP 请求并接收响应(支持 Delimiter 分隔符协议)
  263 + * <p>
  264 + * 发送格式:[消息体][分隔符]
  265 + * 接收格式:[消息体][分隔符]
248 * 266 *
249 * @param socket TCP Socket 267 * @param socket TCP Socket
250 - * @param payload 请求数据  
251 - * @return 响应数据 268 + * @param payload 请求数据(消息体,不含分隔符)
  269 + * @return 响应数据(消息体,不含分隔符)
252 */ 270 */
253 private byte[] sendAndReceive(Socket socket, byte[] payload) throws Exception { 271 private byte[] sendAndReceive(Socket socket, byte[] payload) throws Exception {
254 - // 1. 发送请求  
255 OutputStream out = socket.getOutputStream(); 272 OutputStream out = socket.getOutputStream();
256 - InputStream in = socket.getInputStream(); 273 + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
  274 +
  275 + // 1. 发送请求(添加分隔符后缀)
257 out.write(payload); 276 out.write(payload);
  277 + out.write(DELIMITER.getBytes(StandardCharsets.UTF_8));
258 out.flush(); 278 out.flush();
  279 + log.info("[sendAndReceive][发送数据: {} 字节(不含分隔符)]", payload.length);
259 280
260 - // 2.1 等待一小段时间让服务器处理  
261 - Thread.sleep(100);  
262 - // 2.2 接收响应  
263 - byte[] buffer = new byte[4096]; 281 + // 2. 接收响应(读取到分隔符为止)
264 try { 282 try {
265 - int length = in.read(buffer);  
266 - if (length > 0) {  
267 - byte[] response = new byte[length];  
268 - System.arraycopy(buffer, 0, response, 0, length); 283 + String responseLine = in.readLine();
  284 + if (responseLine != null) {
  285 + byte[] response = responseLine.getBytes(StandardCharsets.UTF_8);
  286 + log.info("[sendAndReceive][接收数据: {} 字节]", response.length);
269 return response; 287 return response;
270 } 288 }
271 return null; 289 return null;
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-binary-packet-examples.md deleted
1 -# TCP 二进制协议数据包格式说明  
2 -  
3 -## 1. 协议概述  
4 -  
5 -TCP 二进制协议是一种高效的自定义协议格式,采用紧凑的二进制格式传输数据,适用于对带宽和性能要求较高的 IoT 场景。  
6 -  
7 -### 1.1 协议特点  
8 -  
9 -- **高效传输**:完全二进制格式,减少数据传输量  
10 -- **版本控制**:内置协议版本号,支持协议升级  
11 -- **类型安全**:明确的消息类型标识  
12 -- **简洁设计**:去除冗余字段,协议更加精简  
13 -- **兼容性**:与现有 `IotDeviceMessage` 接口完全兼容  
14 -  
15 -## 2. 协议格式  
16 -  
17 -### 2.1 整体结构  
18 -  
19 -```  
20 -+--------+--------+--------+---------------------------+--------+--------+  
21 -| 魔术字 | 版本号 | 消息类型| 消息长度(4字节) |  
22 -+--------+--------+--------+---------------------------+--------+--------+  
23 -| 消息 ID 长度(2字节) | 消息 ID (变长字符串) |  
24 -+--------+--------+--------+--------+--------+--------+--------+--------+  
25 -| 方法名长度(2字节) | 方法名(变长字符串) |  
26 -+--------+--------+--------+--------+--------+--------+--------+--------+  
27 -| 消息体数据(变长) |  
28 -+--------+--------+--------+--------+--------+--------+--------+--------+  
29 -```  
30 -  
31 -### 2.2 字段详细说明  
32 -  
33 -| 字段 | 长度 | 类型 | 说明 |  
34 -|------|------|------|------|  
35 -| 魔术字 | 1字节 | byte | `0x7E` - 协议识别标识,用于数据同步 |  
36 -| 版本号 | 1字节 | byte | `0x01` - 协议版本号,支持版本控制 |  
37 -| 消息类型 | 1字节 | byte | `0x01`=请求, `0x02`=响应 |  
38 -| 消息长度 | 4字节 | int | 整个消息的总长度(包含头部) |  
39 -| 消息 ID 长度 | 2字节 | short | 消息 ID 字符串的字节长度 |  
40 -| 消息 ID | 变长 | string | 消息唯一标识符(UTF-8编码) |  
41 -| 方法名长度 | 2字节 | short | 方法名字符串的字节长度 |  
42 -| 方法名 | 变长 | string | 消息方法名(UTF-8编码) |  
43 -| 消息体 | 变长 | binary | 根据消息类型的不同数据格式 |  
44 -  
45 -**⚠️ 重要说明**:deviceId 不包含在协议中,由服务器根据连接上下文自动设置  
46 -  
47 -### 2.3 协议常量定义  
48 -  
49 -```java  
50 -// 协议标识  
51 -private static final byte MAGIC_NUMBER = (byte) 0x7E;  
52 -private static final byte PROTOCOL_VERSION = (byte) 0x01;  
53 -  
54 -// 消息类型  
55 -private static final byte REQUEST = (byte) 0x01; // 请求消息  
56 -private static final byte RESPONSE = (byte) 0x02; // 响应消息  
57 -  
58 -// 协议长度  
59 -private static final int HEADER_FIXED_LENGTH = 7; // 固定头部长度  
60 -private static final int MIN_MESSAGE_LENGTH = 11; // 最小消息长度  
61 -```  
62 -  
63 -## 3. 消息类型和格式  
64 -  
65 -### 3.1 请求消息 (REQUEST - 0x01)  
66 -  
67 -请求消息用于设备向服务器发送数据或请求。  
68 -  
69 -#### 3.1.1 消息体格式  
70 -```  
71 -消息体 = params 数据(JSON格式)  
72 -```  
73 -  
74 -#### 3.1.2 示例:设备认证请求  
75 -  
76 -**消息内容:**  
77 -- 消息 ID: `auth_1704067200000_123`  
78 -- 方法名: `auth`  
79 -- 参数: `{"clientId":"device_001","username":"productKey_deviceName","password":"device_password"}`  
80 -  
81 -**二进制数据包结构:**  
82 -```  
83 -7E // 魔术字 (0x7E)  
84 -01 // 版本号 (0x01)  
85 -01 // 消息类型 (REQUEST)  
86 -00 00 00 89 // 消息长度 (137字节)  
87 -00 19 // 消息 ID 长度 (25字节)  
88 -61 75 74 68 5F 31 37 30 34 30 // 消息 ID: "auth_1704067200000_123"  
89 -36 37 32 30 30 30 30 30 5F 31  
90 -32 33  
91 -00 04 // 方法名长度 (4字节)  
92 -61 75 74 68 // 方法名: "auth"  
93 -7B 22 63 6C 69 65 6E 74 49 64 // JSON参数数据  
94 -22 3A 22 64 65 76 69 63 65 5F // {"clientId":"device_001",  
95 -30 30 31 22 2C 22 75 73 65 72 // "username":"productKey_deviceName",  
96 -6E 61 6D 65 22 3A 22 70 72 6F // "password":"device_password"}  
97 -64 75 63 74 4B 65 79 5F 64 65  
98 -76 69 63 65 4E 61 6D 65 22 2C  
99 -22 70 61 73 73 77 6F 72 64 22  
100 -3A 22 64 65 76 69 63 65 5F 70  
101 -61 73 73 77 6F 72 64 22 7D  
102 -```  
103 -  
104 -#### 3.1.3 示例:属性数据上报  
105 -  
106 -**消息内容:**  
107 -- 消息 ID: `property_1704067200000_456`  
108 -- 方法名: `thing.property.post`  
109 -- 参数: `{"temperature":25.5,"humidity":60.2,"pressure":1013.25}`  
110 -  
111 -### 3.2 响应消息 (RESPONSE - 0x02)  
112 -  
113 -响应消息用于服务器向设备回复请求结果。  
114 -  
115 -#### 3.2.1 消息体格式  
116 -```  
117 -消息体 = 响应码(4字节) + 响应消息长度(2字节) + 响应消息(UTF-8) + 响应数据(JSON)  
118 -```  
119 -  
120 -#### 3.2.2 字段说明  
121 -  
122 -| 字段 | 长度 | 类型 | 说明 |  
123 -|------|------|------|------|  
124 -| 响应码 | 4字节 | int | HTTP状态码风格,0=成功,其他=错误 |  
125 -| 响应消息长度 | 2字节 | short | 响应消息字符串的字节长度 |  
126 -| 响应消息 | 变长 | string | 响应提示信息(UTF-8编码) |  
127 -| 响应数据 | 变长 | binary | JSON格式的响应数据(可选) |  
128 -  
129 -#### 3.2.3 示例:认证成功响应  
130 -  
131 -**消息内容:**  
132 -- 消息 ID: `auth_response_1704067200000_123`  
133 -- 方法名: `auth`  
134 -- 响应码: `0`  
135 -- 响应消息: `认证成功`  
136 -- 响应数据: `{"success":true,"message":"认证成功"}`  
137 -  
138 -**二进制数据包结构:**  
139 -```  
140 -7E // 魔术字 (0x7E)  
141 -01 // 版本号 (0x01)  
142 -02 // 消息类型 (RESPONSE)  
143 -00 00 00 A4 // 消息长度 (164字节)  
144 -00 22 // 消息 ID 长度 (34字节)  
145 -61 75 74 68 5F 72 65 73 70 6F // 消息 ID: "auth_response_1704067200000_123"  
146 -6E 73 65 5F 31 37 30 34 30 36  
147 -37 32 30 30 30 30 30 5F 31 32  
148 -33  
149 -00 04 // 方法名长度 (4字节)  
150 -61 75 74 68 // 方法名: "auth"  
151 -00 00 00 00 // 响应码 (0 = 成功)  
152 -00 0C // 响应消息长度 (12字节)  
153 -E8 AE A4 E8 AF 81 E6 88 90 E5 // 响应消息: "认证成功" (UTF-8)  
154 -8A 9F  
155 -7B 22 73 75 63 63 65 73 73 22 // JSON响应数据  
156 -3A 74 72 75 65 2C 22 6D 65 73 // {"success":true,"message":"认证成功"}  
157 -73 61 67 65 22 3A 22 E8 AE A4  
158 -E8 AF 81 E6 88 90 E5 8A 9F 22  
159 -7D  
160 -```  
161 -  
162 -## 4. 编解码器标识  
163 -  
164 -```java  
165 -public static final String TYPE = "TCP_BINARY";  
166 -```  
167 -  
168 -## 5. 协议优势  
169 -  
170 -- **数据紧凑**:二进制格式,相比 JSON 减少 30-50% 的数据量  
171 -- **解析高效**:直接二进制操作,减少字符串转换开销  
172 -- **类型安全**:明确的消息类型和字段定义  
173 -- **设计简洁**:去除冗余字段,协议更加精简高效  
174 -- **版本控制**:内置版本号支持协议升级  
175 -  
176 -## 6. 与 JSON 协议对比  
177 -  
178 -| 特性 | 二进制协议 | JSON协议 |  
179 -|------|-------------|--------|  
180 -| 数据大小 | 小(节省30-50%) | 大 |  
181 -| 解析性能 | 高 | 中等 |  
182 -| 网络开销 | 低 | 高 |  
183 -| 可读性 | 差 | 优秀 |  
184 -| 调试难度 | 高 | 低 |  
185 -| 扩展性 | 良好 | 优秀 |  
186 -  
187 -**推荐场景**:  
188 -- ✅ **高频数据传输**:传感器数据实时上报  
189 -- ✅ **带宽受限环境**:移动网络、卫星通信  
190 -- ✅ **性能要求高**:需要低延迟、高吞吐的场景  
191 -- ✅ **设备资源有限**:嵌入式设备、低功耗设备  
192 -- ❌ **开发调试阶段**:调试困难,建议使用 JSON 协议  
193 -- ❌ **快速原型开发**:开发效率低  
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-json-packet-examples.md deleted
1 -# TCP JSON 格式协议说明  
2 -  
3 -## 1. 协议概述  
4 -  
5 -TCP JSON 格式协议采用纯 JSON 格式进行数据传输,具有以下特点:  
6 -  
7 -- **标准化**:使用标准 JSON 格式,易于解析和处理  
8 -- **可读性**:人类可读,便于调试和维护  
9 -- **扩展性**:可以轻松添加新字段,向后兼容  
10 -- **跨平台**:JSON 格式支持所有主流编程语言  
11 -- **安全优化**:移除冗余的 deviceId 字段,提高安全性  
12 -  
13 -## 2. 消息格式  
14 -  
15 -### 2.1 基础消息结构  
16 -  
17 -```json  
18 -{  
19 - "id": "消息唯一标识",  
20 - "method": "消息方法",  
21 - "params": {  
22 - // 请求参数  
23 - },  
24 - "data": {  
25 - // 响应数据  
26 - },  
27 - "code": 响应码,  
28 - "msg": "响应消息",  
29 - "timestamp": 时间戳  
30 -}  
31 -```  
32 -  
33 -**⚠️ 重要说明**:  
34 -- **不包含 deviceId 字段**:由服务器通过 TCP 连接上下文自动确定设备 ID  
35 -- **避免伪造攻击**:防止设备伪造其他设备的 ID 发送消息  
36 -  
37 -### 2.2 字段详细说明  
38 -  
39 -| 字段名 | 类型 | 必填 | 用途 | 说明 |  
40 -|--------|------|------|------|------|  
41 -| id | String | 是 | 所有消息 | 消息唯一标识 |  
42 -| method | String | 是 | 所有消息 | 消息方法,如 `auth`、`thing.property.post` |  
43 -| params | Object | 否 | 请求消息 | 请求参数,具体内容根据method而定 |  
44 -| data | Object | 否 | 响应消息 | 响应数据,服务器返回的结果数据 |  
45 -| code | Integer | 否 | 响应消息 | 响应码,0=成功,其他=错误 |  
46 -| msg | String | 否 | 响应消息 | 响应提示信息 |  
47 -| timestamp | Long | 是 | 所有消息 | 时间戳(毫秒),编码时自动生成 |  
48 -  
49 -### 2.3 消息分类  
50 -  
51 -#### 2.3.1 请求消息(上行)  
52 -- **特征**:包含 `params` 字段,不包含 `code`、`msg` 字段  
53 -- **方向**:设备 → 服务器  
54 -- **用途**:设备认证、数据上报、状态更新等  
55 -  
56 -#### 2.3.2 响应消息(下行)  
57 -- **特征**:包含 `code`、`msg` 字段,可能包含 `data` 字段  
58 -- **方向**:服务器 → 设备  
59 -- **用途**:认证结果、指令响应、错误提示等  
60 -  
61 -## 3. 消息示例  
62 -  
63 -### 3.1 设备认证 (auth)  
64 -  
65 -#### 认证请求格式  
66 -**消息方向**:设备 → 服务器  
67 -  
68 -```json  
69 -{  
70 - "id": "auth_1704067200000_123",  
71 - "method": "auth",  
72 - "params": {  
73 - "clientId": "device_001",  
74 - "username": "productKey_deviceName",  
75 - "password": "设备密码"  
76 - },  
77 - "timestamp": 1704067200000  
78 -}  
79 -```  
80 -  
81 -**认证参数说明:**  
82 -  
83 -| 字段名 | 类型 | 必填 | 说明 |  
84 -|--------|------|------|------|  
85 -| clientId | String | 是 | 客户端唯一标识,用于连接管理 |  
86 -| username | String | 是 | 设备用户名,格式为 `productKey_deviceName` |  
87 -| password | String | 是 | 设备密码,在设备管理平台配置 |  
88 -  
89 -#### 认证响应格式  
90 -**消息方向**:服务器 → 设备  
91 -  
92 -**认证成功响应:**  
93 -```json  
94 -{  
95 - "id": "response_auth_1704067200000_123",  
96 - "method": "auth",  
97 - "data": {  
98 - "success": true,  
99 - "message": "认证成功"  
100 - },  
101 - "code": 0,  
102 - "msg": "认证成功",  
103 - "timestamp": 1704067200001  
104 -}  
105 -```  
106 -  
107 -**认证失败响应:**  
108 -```json  
109 -{  
110 - "id": "response_auth_1704067200000_123",  
111 - "method": "auth",  
112 - "data": {  
113 - "success": false,  
114 - "message": "认证失败:用户名或密码错误"  
115 - },  
116 - "code": 401,  
117 - "msg": "认证失败",  
118 - "timestamp": 1704067200001  
119 -}  
120 -```  
121 -  
122 -### 3.2 属性数据上报 (thing.property.post)  
123 -  
124 -**消息方向**:设备 → 服务器  
125 -  
126 -**示例:温度传感器数据上报**  
127 -```json  
128 -{  
129 - "id": "property_1704067200000_456",  
130 - "method": "thing.property.post",  
131 - "params": {  
132 - "temperature": 25.5,  
133 - "humidity": 60.2,  
134 - "pressure": 1013.25,  
135 - "battery": 85,  
136 - "signal_strength": -65  
137 - },  
138 - "timestamp": 1704067200000  
139 -}  
140 -```  
141 -  
142 -### 3.3 设备状态更新 (thing.state.update)  
143 -  
144 -**消息方向**:设备 → 服务器  
145 -  
146 -**示例:心跳请求**  
147 -```json  
148 -{  
149 - "id": "heartbeat_1704067200000_321",  
150 - "method": "thing.state.update",  
151 - "params": {  
152 - "state": "online",  
153 - "uptime": 86400,  
154 - "memory_usage": 65.2,  
155 - "cpu_usage": 12.8  
156 - },  
157 - "timestamp": 1704067200000  
158 -}  
159 -```  
160 -  
161 -## 4. 编解码器标识  
162 -  
163 -```java  
164 -public static final String TYPE = "TCP_JSON";  
165 -```  
166 -  
167 -## 5. 协议优势  
168 -  
169 -- **开发效率高**:JSON 格式,开发和调试简单  
170 -- **跨语言支持**:所有主流语言都支持 JSON  
171 -- **可读性优秀**:可以直接查看消息内容  
172 -- **扩展性强**:可以轻松添加新字段  
173 -- **安全性高**:移除 deviceId 字段,防止伪造攻击  
174 -  
175 -## 6. 与二进制协议对比  
176 -  
177 -| 特性 | JSON协议 | 二进制协议 |  
178 -|------|----------|------------|  
179 -| 开发难度 | 低 | 高 |  
180 -| 调试难度 | 低 | 高 |  
181 -| 可读性 | 优秀 | 差 |  
182 -| 数据大小 | 中等 | 小(节省30-50%) |  
183 -| 解析性能 | 中等 | 高 |  
184 -| 学习成本 | 低 | 高 |  
185 -  
186 -**推荐场景**:  
187 -- ✅ **开发调试阶段**:调试友好,开发效率高  
188 -- ✅ **快速原型开发**:实现简单,快速迭代  
189 -- ✅ **多语言集成**:广泛的语言支持  
190 -- ❌ **高频数据传输**:建议使用二进制协议  
191 -- ❌ **带宽受限环境**:建议使用二进制协议  
192 \ No newline at end of file 0 \ No newline at end of file