Commit e89fc2bfbd1ac7d88ff59bb4a580cecc564a20fb

Authored by YunaiV
1 parent f4ba6e75

feat(iot):【协议改造】http 初步改造

Showing 40 changed files with 1467 additions and 633 deletions
yudao-module-iot/yudao-module-iot-biz/pom.xml
... ... @@ -101,18 +101,6 @@
101 101 <artifactId>spring-boot-starter-amqp</artifactId>
102 102 <optional>true</optional>
103 103 </dependency>
104   -
105   - <!-- IoT 网络组件:接收来自设备的上行数据 -->
106   - <!-- <dependency>-->
107   - <!-- <groupId>cn.iocoder.boot</groupId>-->
108   - <!-- <artifactId>yudao-module-iot-net-component-http</artifactId>-->
109   - <!-- <version>${revision}</version>-->
110   - <!-- </dependency>-->
111   - <!-- <dependency>-->
112   - <!-- <groupId>cn.iocoder.boot</groupId>-->
113   - <!-- <artifactId>yudao-module-iot-net-component-emqx</artifactId>-->
114   - <!-- <version>${revision}</version>-->
115   - <!-- </dependency>-->
116 104 </dependencies>
117 105  
118 106 </project>
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotProtocolTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.enums;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.Getter;
  6 +import lombok.RequiredArgsConstructor;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * IoT 协议类型枚举
  12 + *
  13 + * 用于定义传输层协议类型
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@RequiredArgsConstructor
  18 +@Getter
  19 +public enum IotProtocolTypeEnum implements ArrayValuable<String> {
  20 +
  21 + TCP("tcp"),
  22 + UDP("udp"),
  23 + WEBSOCKET("websocket"),
  24 + HTTP("http"),
  25 + MQTT("mqtt"),
  26 + EMQX("emqx"),
  27 + COAP("coap"),
  28 + MODBUS_TCP("modbus_tcp");
  29 +
  30 + public static final String[] ARRAYS = Arrays.stream(values()).map(IotProtocolTypeEnum::getType).toArray(String[]::new);
  31 +
  32 + /**
  33 + * 类型
  34 + */
  35 + private final String type;
  36 +
  37 + @Override
  38 + public String[] array() {
  39 + return ARRAYS;
  40 + }
  41 +
  42 + public static IotProtocolTypeEnum of(String type) {
  43 + return ArrayUtil.firstMatch(e -> e.getType().equals(type), values());
  44 + }
  45 +
  46 +}
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotSerializeTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.enums;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.Getter;
  6 +import lombok.RequiredArgsConstructor;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * IoT 序列化类型枚举
  12 + *
  13 + * 用于定义设备消息的序列化格式
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@RequiredArgsConstructor
  18 +@Getter
  19 +public enum IotSerializeTypeEnum implements ArrayValuable<String> {
  20 +
  21 + JSON("json"),
  22 + BINARY("binary");
  23 +
  24 + public static final String[] ARRAYS = Arrays.stream(values()).map(IotSerializeTypeEnum::getType).toArray(String[]::new);
  25 +
  26 + /**
  27 + * 类型
  28 + */
  29 + private final String type;
  30 +
  31 + @Override
  32 + public String[] array() {
  33 + return ARRAYS;
  34 + }
  35 +
  36 + public static IotSerializeTypeEnum of(String type) {
  37 + return ArrayUtil.firstMatch(e -> e.getType().equals(type), values());
  38 + }
  39 +
  40 +}
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java
... ... @@ -24,4 +24,14 @@ public interface IotMessageBus {
24 24 */
25 25 void register(IotMessageSubscriber<?> subscriber);
26 26  
  27 + /**
  28 + * 取消注册消息订阅者
  29 + *
  30 + * @param subscriber 订阅者
  31 + */
  32 + default void unregister(IotMessageSubscriber<?> subscriber) {
  33 + // TODO 芋艿:暂时不实现,需求量不大,但是
  34 + // throw new UnsupportedOperationException("取消注册消息订阅者功能,尚未实现");
  35 + }
  36 +
27 37 }
28 38 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java
... ... @@ -26,4 +26,16 @@ public interface IotMessageSubscriber&lt;T&gt; {
26 26 */
27 27 void onMessage(T message);
28 28  
  29 + /**
  30 + * 启动订阅
  31 + */
  32 + default void start() {
  33 + }
  34 +
  35 + /**
  36 + * 停止订阅
  37 + */
  38 + default void stop() {
  39 + }
  40 +
29 41 }
30 42 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
... ... @@ -60,7 +60,7 @@ public class IotDeviceMessage {
60 60 */
61 61 private String serverId;
62 62  
63   - // ========== codec(编解码)字段 ==========
  63 + // ========== serialize(序列化)相关字段 ==========
64 64  
65 65 /**
66 66 * 请求编号
... ... @@ -94,7 +94,7 @@ public class IotDeviceMessage {
94 94 */
95 95 private String msg;
96 96  
97   - // ========== 基础方法:只传递"codec(编解码)字段" ==========
  97 + // ========== 基础方法:只传递"serialize(序列化)相关字段" ==========
98 98  
99 99 public static IotDeviceMessage requestOf(String method) {
100 100 return requestOf(null, method, null);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
1 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolManager;
  5 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
4 6 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapDownstreamSubscriber;
5 7 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;
6 8 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
7 9 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;
8 10 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;
9   -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber;
10   -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
11 11 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber;
12 12 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
13 13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
... ... @@ -15,12 +15,15 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstrea
15 15 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber;
16 16 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol;
17 17 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
  18 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler;
18 19 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpDownstreamSubscriber;
19 20 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol;
20 21 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
  22 +import cn.iocoder.yudao.module.iot.gateway.protocol.udp.router.IotUdpDownstreamHandler;
21 23 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketDownstreamSubscriber;
22 24 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol;
23 25 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
  26 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router.IotWebSocketDownstreamHandler;
24 27 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
25 28 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
26 29 import io.vertx.core.Vertx;
... ... @@ -31,35 +34,22 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
31 34 import org.springframework.context.annotation.Bean;
32 35 import org.springframework.context.annotation.Configuration;
33 36  
  37 +
34 38 @Configuration
35 39 @EnableConfigurationProperties(IotGatewayProperties.class)
36 40 @Slf4j
37 41 public class IotGatewayConfiguration {
38 42  
39   - /**
40   - * IoT 网关 HTTP 协议配置类
41   - */
42   - @Configuration
43   - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.http", name = "enabled", havingValue = "true")
44   - @Slf4j
45   - public static class HttpProtocolConfiguration {
46   -
47   - @Bean(name = "httpVertx", destroyMethod = "close")
48   - public Vertx httpVertx() {
49   - return Vertx.vertx();
50   - }
51   -
52   - @Bean
53   - public IotHttpUpstreamProtocol iotHttpUpstreamProtocol(IotGatewayProperties gatewayProperties,
54   - @Qualifier("httpVertx") Vertx httpVertx) {
55   - return new IotHttpUpstreamProtocol(gatewayProperties.getProtocol().getHttp(), httpVertx);
56   - }
  43 + @Bean
  44 + public IotMessageSerializerManager iotMessageSerializerManager() {
  45 + return new IotMessageSerializerManager();
  46 + }
57 47  
58   - @Bean
59   - public IotHttpDownstreamSubscriber iotHttpDownstreamSubscriber(IotHttpUpstreamProtocol httpUpstreamProtocol,
60   - IotMessageBus messageBus) {
61   - return new IotHttpDownstreamSubscriber(httpUpstreamProtocol, messageBus);
62   - }
  48 + @Bean
  49 + public IotProtocolManager iotProtocolManager(IotGatewayProperties gatewayProperties,
  50 + IotMessageSerializerManager serializerManager,
  51 + IotMessageBus messageBus) {
  52 + return new IotProtocolManager(gatewayProperties, serializerManager, messageBus);
63 53 }
64 54  
65 55 /**
... ... @@ -118,11 +108,16 @@ public class IotGatewayConfiguration {
118 108 }
119 109  
120 110 @Bean
  111 + public IotTcpDownstreamHandler iotTcpDownstreamHandler(IotDeviceMessageService messageService,
  112 + IotTcpConnectionManager connectionManager) {
  113 + return new IotTcpDownstreamHandler(messageService, connectionManager);
  114 + }
  115 +
  116 + @Bean
121 117 public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler,
122   - IotDeviceMessageService messageService,
123   - IotTcpConnectionManager connectionManager,
  118 + IotTcpDownstreamHandler downstreamHandler,
124 119 IotMessageBus messageBus) {
125   - return new IotTcpDownstreamSubscriber(protocolHandler, messageService, connectionManager, messageBus);
  120 + return new IotTcpDownstreamSubscriber(protocolHandler, downstreamHandler, messageBus);
126 121 }
127 122  
128 123 }
... ... @@ -188,11 +183,17 @@ public class IotGatewayConfiguration {
188 183 }
189 184  
190 185 @Bean
  186 + public IotUdpDownstreamHandler iotUdpDownstreamHandler(IotDeviceMessageService messageService,
  187 + IotUdpSessionManager sessionManager,
  188 + IotUdpUpstreamProtocol protocol) {
  189 + return new IotUdpDownstreamHandler(messageService, sessionManager, protocol);
  190 + }
  191 +
  192 + @Bean
191 193 public IotUdpDownstreamSubscriber iotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocolHandler,
192   - IotDeviceMessageService messageService,
193   - IotUdpSessionManager sessionManager,
  194 + IotUdpDownstreamHandler downstreamHandler,
194 195 IotMessageBus messageBus) {
195   - return new IotUdpDownstreamSubscriber(protocolHandler, messageService, sessionManager, messageBus);
  196 + return new IotUdpDownstreamSubscriber(protocolHandler, downstreamHandler, messageBus);
196 197 }
197 198  
198 199 }
... ... @@ -242,11 +243,16 @@ public class IotGatewayConfiguration {
242 243 }
243 244  
244 245 @Bean
  246 + public IotWebSocketDownstreamHandler iotWebSocketDownstreamHandler(IotDeviceMessageService messageService,
  247 + IotWebSocketConnectionManager connectionManager) {
  248 + return new IotWebSocketDownstreamHandler(messageService, connectionManager);
  249 + }
  250 +
  251 + @Bean
245 252 public IotWebSocketDownstreamSubscriber iotWebSocketDownstreamSubscriber(IotWebSocketUpstreamProtocol protocolHandler,
246   - IotDeviceMessageService messageService,
247   - IotWebSocketConnectionManager connectionManager,
  253 + IotWebSocketDownstreamHandler downstreamHandler,
248 254 IotMessageBus messageBus) {
249   - return new IotWebSocketDownstreamSubscriber(protocolHandler, messageService, connectionManager, messageBus);
  255 + return new IotWebSocketDownstreamSubscriber(protocolHandler, downstreamHandler, messageBus);
250 256 }
251 257  
252 258 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
1 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig;
  5 +import jakarta.validation.Valid;
3 6 import jakarta.validation.constraints.NotEmpty;
4 7 import jakarta.validation.constraints.NotNull;
5 8 import lombok.Data;
... ... @@ -24,10 +27,15 @@ public class IotGatewayProperties {
24 27 private TokenProperties token;
25 28  
26 29 /**
27   - * 协议配置
  30 + * 协议配置(旧版,保持兼容)
28 31 */
29 32 private ProtocolProperties protocol;
30 33  
  34 + /**
  35 + * 协议实例列表(新版)
  36 + */
  37 + private List<ProtocolInstanceProperties> protocols;
  38 +
31 39 @Data
32 40 public static class RpcProperties {
33 41  
... ... @@ -89,11 +97,6 @@ public class IotGatewayProperties {
89 97 private MqttProperties mqtt;
90 98  
91 99 /**
92   - * MQTT WebSocket 组件配置
93   - */
94   - private MqttWsProperties mqttWs;
95   -
96   - /**
97 100 * UDP 组件配置
98 101 */
99 102 private UdpProperties udp;
... ... @@ -423,102 +426,6 @@ public class IotGatewayProperties {
423 426 }
424 427  
425 428 @Data
426   - public static class MqttWsProperties {
427   -
428   - /**
429   - * 是否开启
430   - */
431   - @NotNull(message = "是否开启不能为空")
432   - private Boolean enabled;
433   -
434   - /**
435   - * WebSocket 服务器端口(默认:8083)
436   - */
437   - private Integer port = 8083;
438   -
439   - /**
440   - * WebSocket 路径(默认:/mqtt)
441   - */
442   - @NotEmpty(message = "WebSocket 路径不能为空")
443   - private String path = "/mqtt";
444   -
445   - /**
446   - * 最大消息大小(字节)
447   - */
448   - private Integer maxMessageSize = 8192;
449   -
450   - /**
451   - * 连接超时时间(秒)
452   - */
453   - private Integer connectTimeoutSeconds = 60;
454   -
455   - /**
456   - * 保持连接超时时间(秒)
457   - */
458   - private Integer keepAliveTimeoutSeconds = 300;
459   -
460   - /**
461   - * 是否启用 SSL(wss://)
462   - */
463   - private Boolean sslEnabled = false;
464   -
465   - /**
466   - * SSL 配置
467   - */
468   - private SslOptions sslOptions = new SslOptions();
469   -
470   - /**
471   - * WebSocket 子协议(通常为 "mqtt" 或 "mqttv3.1")
472   - */
473   - @NotEmpty(message = "WebSocket 子协议不能为空")
474   - private String subProtocol = "mqtt";
475   -
476   - /**
477   - * 最大帧大小(字节)
478   - */
479   - private Integer maxFrameSize = 65536;
480   -
481   - /**
482   - * SSL 配置选项
483   - */
484   - @Data
485   - public static class SslOptions {
486   -
487   - /**
488   - * 密钥证书选项
489   - */
490   - private io.vertx.core.net.KeyCertOptions keyCertOptions;
491   -
492   - /**
493   - * 信任选项
494   - */
495   - private io.vertx.core.net.TrustOptions trustOptions;
496   -
497   - /**
498   - * SSL 证书路径
499   - */
500   - private String certPath;
501   -
502   - /**
503   - * SSL 私钥路径
504   - */
505   - private String keyPath;
506   -
507   - /**
508   - * 信任存储路径
509   - */
510   - private String trustStorePath;
511   -
512   - /**
513   - * 信任存储密码
514   - */
515   - private String trustStorePassword;
516   -
517   - }
518   -
519   - }
520   -
521   - @Data
522 429 public static class UdpProperties {
523 430  
524 431 /**
... ... @@ -643,4 +550,125 @@ public class IotGatewayProperties {
643 550  
644 551 }
645 552  
  553 + // TODO @AI:【暂时忽略】改成 ProtocolProperties
  554 + /**
  555 + * 协议实例配置
  556 + */
  557 + @Data
  558 + public static class ProtocolInstanceProperties {
  559 +
  560 + /**
  561 + * 协议实例 ID,如 "http-alink"、"tcp-binary"
  562 + */
  563 + @NotEmpty(message = "协议实例 ID 不能为空")
  564 + private String id;
  565 + /**
  566 + * 是否启用
  567 + */
  568 + @NotNull(message = "是否启用不能为空")
  569 + private Boolean enabled = true;
  570 + /**
  571 + * 协议类型
  572 + *
  573 + * @see cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum
  574 + */
  575 + @NotEmpty(message = "协议类型不能为空")
  576 + private String type;
  577 + /**
  578 + * 服务端口
  579 + */
  580 + @NotNull(message = "服务端口不能为空")
  581 + private Integer port;
  582 + /**
  583 + * 序列化类型(可选)
  584 + *
  585 + * @see cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum
  586 + *
  587 + * 为什么是可选的呢?
  588 + * 1. {@link IotProtocolTypeEnum#HTTP}、${@link IotProtocolTypeEnum#COAP} 协议,目前强制是 JSON 格式
  589 + * 2. {@link IotProtocolTypeEnum#EMQX} 协议,目前支持根据产品(设备)配置的序列化类型来解析
  590 + */
  591 + private String serialize;
  592 +
  593 + /**
  594 + * HTTP 协议配置
  595 + */
  596 + @Valid
  597 + private IotHttpConfig http;
  598 +
  599 + // TODO @AI:后续改下;
  600 + /**
  601 + * TCP 协议配置(后续扩展)
  602 + */
  603 + @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 + }
  671 +
  672 + }
  673 +
646 674 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  4 +
  5 +/**
  6 + * IoT 协议接口
  7 + *
  8 + * 定义传输层协议的生命周期管理
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface IotProtocol {
  13 +
  14 + /**
  15 + * 获取协议实例 ID
  16 + *
  17 + * @return 协议实例 ID,如 "http-alink"、"tcp-binary"
  18 + */
  19 + String getId();
  20 +
  21 + /**
  22 + * 获取服务器 ID(用于消息追踪,全局唯一)
  23 + *
  24 + * @return 服务器 ID
  25 + */
  26 + String getServerId();
  27 +
  28 + /**
  29 + * 获取协议类型
  30 + *
  31 + * @return 协议类型枚举
  32 + */
  33 + IotProtocolTypeEnum getType();
  34 +
  35 + /**
  36 + * 启动协议服务
  37 + */
  38 + void start();
  39 +
  40 + /**
  41 + * 停止协议服务
  42 + */
  43 + void stop();
  44 +
  45 + /**
  46 + * 检查协议服务是否正在运行
  47 + *
  48 + * @return 是否正在运行
  49 + */
  50 + boolean isRunning();
  51 +
  52 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolDownstreamSubscriber.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  5 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  8 +import lombok.AllArgsConstructor;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +/**
  12 + * IoT 协议下行消息订阅者抽象类
  13 + *
  14 + * 负责接收来自消息总线的下行消息,并委托给子类进行业务处理
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@AllArgsConstructor
  19 +@Slf4j
  20 +public abstract class IotProtocolDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
  21 +
  22 + private final IotProtocol protocol;
  23 +
  24 + private final IotMessageBus messageBus;
  25 +
  26 + @Override
  27 + public String getTopic() {
  28 + return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
  29 + }
  30 +
  31 + /**
  32 + * 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
  33 + */
  34 + @Override
  35 + public String getGroup() {
  36 + return getTopic();
  37 + }
  38 +
  39 + @Override
  40 + public void start() {
  41 + messageBus.register(this);
  42 + log.info("[start][{} 下行消息订阅成功,Topic:{}]", protocol.getType().name(), getTopic());
  43 + }
  44 +
  45 + @Override
  46 + public void stop() {
  47 + messageBus.unregister(this);
  48 + log.info("[stop][{} 下行消息订阅已停止,Topic:{}]", protocol.getType().name(), getTopic());
  49 + }
  50 +
  51 + @Override
  52 + public void onMessage(IotDeviceMessage message) {
  53 + log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",
  54 + message.getId(), message.getMethod(), message.getDeviceId());
  55 + try {
  56 + // 1. 校验
  57 + String method = message.getMethod();
  58 + if (StrUtil.isBlank(method)) {
  59 + log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]",
  60 + message.getId(), message.getDeviceId());
  61 + return;
  62 + }
  63 +
  64 + // 2. 处理下行消息
  65 + handleMessage(message);
  66 + } catch (Exception e) {
  67 + log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]",
  68 + message.getId(), message.getMethod(), message.getDeviceId(), e);
  69 + }
  70 + }
  71 +
  72 + /**
  73 + * 处理下行消息
  74 + *
  75 + * @param message 下行消息
  76 + */
  77 + protected abstract void handleMessage(IotDeviceMessage message);
  78 +
  79 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolManager.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.BooleanUtil;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  7 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
  9 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.context.SmartLifecycle;
  12 +
  13 +import java.util.ArrayList;
  14 +import java.util.List;
  15 +
  16 +/**
  17 + * IoT 协议管理器
  18 + *
  19 + * 负责根据配置创建和管理协议实例
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +@Slf4j
  24 +public class IotProtocolManager implements SmartLifecycle {
  25 +
  26 + private final IotGatewayProperties gatewayProperties;
  27 +
  28 + private final IotMessageSerializerManager serializerManager;
  29 +
  30 + private final IotMessageBus messageBus;
  31 +
  32 + private final List<IotProtocol> protocols = new ArrayList<>();
  33 +
  34 + private volatile boolean running = false;
  35 +
  36 + public IotProtocolManager(IotGatewayProperties gatewayProperties,
  37 + IotMessageSerializerManager serializerManager,
  38 + IotMessageBus messageBus) {
  39 + this.gatewayProperties = gatewayProperties;
  40 + this.serializerManager = serializerManager;
  41 + this.messageBus = messageBus;
  42 + }
  43 +
  44 + @Override
  45 + public void start() {
  46 + if (running) {
  47 + return;
  48 + }
  49 + List<IotGatewayProperties.ProtocolInstanceProperties> protocolConfigs = gatewayProperties.getProtocols();
  50 + if (CollUtil.isEmpty(protocolConfigs)) {
  51 + log.info("[start][没有配置协议实例,跳过启动]");
  52 + return;
  53 + }
  54 +
  55 + for (IotGatewayProperties.ProtocolInstanceProperties config : protocolConfigs) {
  56 + if (BooleanUtil.isFalse(config.getEnabled())) {
  57 + log.info("[start][协议实例 {} 未启用,跳过]", config.getId());
  58 + continue;
  59 + }
  60 + IotProtocol protocol = createProtocol(config);
  61 + if (protocol == null) {
  62 + continue;
  63 + }
  64 + protocol.start();
  65 + protocols.add(protocol);
  66 + }
  67 + running = true;
  68 + log.info("[start][协议管理器启动完成,共启动 {} 个协议实例]", protocols.size());
  69 + }
  70 +
  71 + @Override
  72 + public void stop() {
  73 + if (!running) {
  74 + return;
  75 + }
  76 + for (IotProtocol protocol : protocols) {
  77 + try {
  78 + protocol.stop();
  79 + } catch (Exception e) {
  80 + log.error("[stop][协议实例 {} 停止失败]", protocol.getId(), e);
  81 + }
  82 + }
  83 + protocols.clear();
  84 + running = false;
  85 + log.info("[stop][协议管理器已停止]");
  86 + }
  87 +
  88 + @Override
  89 + public boolean isRunning() {
  90 + return running;
  91 + }
  92 +
  93 + /**
  94 + * 创建协议实例
  95 + *
  96 + * @param config 协议实例配置
  97 + * @return 协议实例
  98 + */
  99 + @SuppressWarnings({"SwitchStatementWithTooFewBranches", "EnhancedSwitchMigration"})
  100 + private IotProtocol createProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
  101 + IotProtocolTypeEnum protocolType = IotProtocolTypeEnum.of(config.getType());
  102 + if (protocolType == null) {
  103 + log.error("[createProtocol][协议实例 {} 的协议类型 {} 不存在]", config.getId(), config.getType());
  104 + return null;
  105 + }
  106 + switch (protocolType) {
  107 + case HTTP:
  108 + return createHttpProtocol(config);
  109 + // TODO 后续添加其他协议类型
  110 + default:
  111 + throw new IllegalArgumentException(String.format(
  112 + "[createProtocol][协议实例 %s 的协议类型 %s 暂不支持]", config.getId(), protocolType));
  113 + }
  114 + }
  115 +
  116 + /**
  117 + * 创建 HTTP 协议实例
  118 + *
  119 + * @param config 协议实例配置
  120 + * @return HTTP 协议实例
  121 + */
  122 + private IotHttpProtocol createHttpProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
  123 + return new IotHttpProtocol(config, messageBus);
  124 + }
  125 +
  126 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapDownstreamSubscriber.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7   -import jakarta.annotation.PostConstruct;
8   -import lombok.RequiredArgsConstructor;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
9 6 import lombok.extern.slf4j.Slf4j;
10 7  
11 8 /**
... ... @@ -13,34 +10,17 @@ import lombok.extern.slf4j.Slf4j;
13 10 *
14 11 * @author 芋道源码
15 12 */
16   -@RequiredArgsConstructor
17 13 @Slf4j
18   -public class IotCoapDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
  14 +public class IotCoapDownstreamSubscriber extends IotProtocolDownstreamSubscriber {
19 15  
20   - private final IotCoapUpstreamProtocol protocol;
21   -
22   - private final IotMessageBus messageBus;
23   -
24   - @PostConstruct
25   - public void init() {
26   - messageBus.register(this);
27   - }
28   -
29   - @Override
30   - public String getTopic() {
31   - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
32   - }
33   -
34   - @Override
35   - public String getGroup() {
36   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
37   - return getTopic();
  16 + public IotCoapDownstreamSubscriber(IotCoapUpstreamProtocol protocol, IotMessageBus messageBus) {
  17 + super(protocol, messageBus);
38 18 }
39 19  
40 20 @Override
41   - public void onMessage(IotDeviceMessage message) {
  21 + protected void handleMessage(IotDeviceMessage message) {
42 22 // 如需支持,可通过 CoAP Observe 模式实现(设备订阅资源,服务器推送变更)
43   - log.warn("[onMessage][IoT 网关 CoAP 协议暂不支持下行消息,忽略消息:{}]", message);
  23 + log.warn("[handleMessage][IoT 网关 CoAP 协议暂不支持下行消息,忽略消息:{}]", message);
44 24 }
45 25  
46 26 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapUpstreamProtocol.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
3 4 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
4 5 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
5 7 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapAuthHandler;
6 8 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapAuthResource;
7 9 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapRegisterHandler;
... ... @@ -30,7 +32,9 @@ import java.util.concurrent.TimeUnit;
30 32 * @author 芋道源码
31 33 */
32 34 @Slf4j
33   -public class IotCoapUpstreamProtocol {
  35 +public class IotCoapUpstreamProtocol implements IotProtocol {
  36 +
  37 + private static final String ID = "coap";
34 38  
35 39 private final IotGatewayProperties.CoapProperties coapProperties;
36 40  
... ... @@ -39,11 +43,24 @@ public class IotCoapUpstreamProtocol {
39 43 @Getter
40 44 private final String serverId;
41 45  
  46 + private volatile boolean running = false;
  47 +
42 48 public IotCoapUpstreamProtocol(IotGatewayProperties.CoapProperties coapProperties) {
43 49 this.coapProperties = coapProperties;
44 50 this.serverId = IotDeviceMessageUtils.generateServerId(coapProperties.getPort());
45 51 }
46 52  
  53 + @Override
  54 + public String getId() {
  55 + return ID;
  56 + }
  57 +
  58 + @Override
  59 + public IotProtocolTypeEnum getType() {
  60 + return IotProtocolTypeEnum.COAP;
  61 + }
  62 +
  63 + @Override
47 64 @PostConstruct
48 65 public void start() {
49 66 try {
... ... @@ -73,6 +90,7 @@ public class IotCoapUpstreamProtocol {
73 90  
74 91 // 3. 启动服务器
75 92 coapServer.start();
  93 + running = true;
76 94 log.info("[start][IoT 网关 CoAP 协议启动成功,端口:{},资源:/auth, /auth/register/device, /topic]", coapProperties.getPort());
77 95 } catch (Exception e) {
78 96 log.error("[start][IoT 网关 CoAP 协议启动失败]", e);
... ... @@ -80,11 +98,13 @@ public class IotCoapUpstreamProtocol {
80 98 }
81 99 }
82 100  
  101 + @Override
83 102 @PreDestroy
84 103 public void stop() {
85 104 if (coapServer != null) {
86 105 try {
87 106 coapServer.stop();
  107 + running = false;
88 108 log.info("[stop][IoT 网关 CoAP 协议已停止]");
89 109 } catch (Exception e) {
90 110 log.error("[stop][IoT 网关 CoAP 协议停止失败]", e);
... ... @@ -92,4 +112,9 @@ public class IotCoapUpstreamProtocol {
92 112 }
93 113 }
94 114  
  115 + @Override
  116 + public boolean isRunning() {
  117 + return running;
  118 + }
  119 +
95 120 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapAuthHandler.java
... ... @@ -24,8 +24,6 @@ import java.util.Map;
24 24 /**
25 25 * IoT 网关 CoAP 协议的【认证】处理器
26 26 *
27   - * 参考 {@link cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAuthHandler}
28   - *
29 27 * @author 芋道源码
30 28 */
31 29 @Slf4j
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapRegisterHandler.java
... ... @@ -22,7 +22,6 @@ import java.util.Map;
22 22 *
23 23 * @author 芋道源码
24 24 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
25   - * @see cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpRegisterHandler
26 25 */
27 26 @Slf4j
28 27 public class IotCoapRegisterHandler {
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxDownstreamSubscriber.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.emqx;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
7 7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router.IotEmqxDownstreamHandler;
8   -import jakarta.annotation.PostConstruct;
9 8 import lombok.extern.slf4j.Slf4j;
10 9  
11 10 /**
... ... @@ -14,55 +13,18 @@ import lombok.extern.slf4j.Slf4j;
14 13 * @author 芋道源码
15 14 */
16 15 @Slf4j
17   -public class IotEmqxDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
  16 +public class IotEmqxDownstreamSubscriber extends IotProtocolDownstreamSubscriber {
18 17  
19 18 private final IotEmqxDownstreamHandler downstreamHandler;
20 19  
21   - private final IotMessageBus messageBus;
22   -
23   - private final IotEmqxUpstreamProtocol protocol;
24   -
25 20 public IotEmqxDownstreamSubscriber(IotEmqxUpstreamProtocol protocol, IotMessageBus messageBus) {
26   - this.protocol = protocol;
27   - this.messageBus = messageBus;
  21 + super(protocol, messageBus);
28 22 this.downstreamHandler = new IotEmqxDownstreamHandler(protocol);
29 23 }
30 24  
31   - @PostConstruct
32   - public void init() {
33   - messageBus.register(this);
34   - }
35   -
36 25 @Override
37   - public String getTopic() {
38   - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
39   - }
40   -
41   - @Override
42   - public String getGroup() {
43   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
44   - return getTopic();
45   - }
46   -
47   - @Override
48   - public void onMessage(IotDeviceMessage message) {
49   - log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",
50   - message.getId(), message.getMethod(), message.getDeviceId());
51   - try {
52   - // 1. 校验
53   - String method = message.getMethod();
54   - if (method == null) {
55   - log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]",
56   - message.getId(), message.getDeviceId());
57   - return;
58   - }
59   -
60   - // 2. 处理下行消息
61   - downstreamHandler.handle(message);
62   - } catch (Exception e) {
63   - log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]",
64   - message.getId(), message.getMethod(), message.getDeviceId(), e);
65   - }
  26 + protected void handleMessage(IotDeviceMessage message) {
  27 + downstreamHandler.handle(message);
66 28 }
67 29  
68   -}
69 30 \ No newline at end of file
  31 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxUpstreamProtocol.java
... ... @@ -2,8 +2,10 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.emqx;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
5 6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
6 7 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
7 9 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router.IotEmqxUpstreamHandler;
8 10 import io.netty.handler.codec.mqtt.MqttQoS;
9 11 import io.vertx.core.Vertx;
... ... @@ -28,11 +30,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
28 30 * @author 芋道源码
29 31 */
30 32 @Slf4j
31   -public class IotEmqxUpstreamProtocol {
  33 +public class IotEmqxUpstreamProtocol implements IotProtocol {
  34 +
  35 + private static final String ID = "emqx";
32 36  
33 37 private final IotGatewayProperties.EmqxProperties emqxProperties;
34 38  
35   - private volatile boolean isRunning = false;
  39 + private volatile boolean running = false;
36 40  
37 41 private final Vertx vertx;
38 42  
... ... @@ -50,9 +54,20 @@ public class IotEmqxUpstreamProtocol {
50 54 this.vertx = vertx;
51 55 }
52 56  
  57 + @Override
  58 + public String getId() {
  59 + return ID;
  60 + }
  61 +
  62 + @Override
  63 + public IotProtocolTypeEnum getType() {
  64 + return IotProtocolTypeEnum.EMQX;
  65 + }
  66 +
  67 + @Override
53 68 @PostConstruct
54 69 public void start() {
55   - if (isRunning) {
  70 + if (running) {
56 71 return;
57 72 }
58 73  
... ... @@ -61,7 +76,7 @@ public class IotEmqxUpstreamProtocol {
61 76 startMqttClient();
62 77  
63 78 // 2. 标记服务为运行状态
64   - isRunning = true;
  79 + running = true;
65 80 log.info("[start][IoT 网关 EMQX 协议启动成功]");
66 81 } catch (Exception e) {
67 82 log.error("[start][IoT 网关 EMQX 协议服务启动失败,应用将关闭]", e);
... ... @@ -88,9 +103,10 @@ public class IotEmqxUpstreamProtocol {
88 103 }
89 104 }
90 105  
  106 + @Override
91 107 @PreDestroy
92 108 public void stop() {
93   - if (!isRunning) {
  109 + if (!running) {
94 110 return;
95 111 }
96 112  
... ... @@ -98,10 +114,15 @@ public class IotEmqxUpstreamProtocol {
98 114 stopMqttClient();
99 115  
100 116 // 2. 标记服务为停止状态
101   - isRunning = false;
  117 + running = false;
102 118 log.info("[stop][IoT 网关 MQTT 协议服务已停止]");
103 119 }
104 120  
  121 + @Override
  122 + public boolean isRunning() {
  123 + return running;
  124 + }
  125 +
105 126 /**
106 127 * 启动 MQTT 客户端
107 128 */
... ... @@ -185,7 +206,7 @@ public class IotEmqxUpstreamProtocol {
185 206 * 延迟重连
186 207 */
187 208 private void reconnectWithDelay() {
188   - if (!isRunning) {
  209 + if (!running) {
189 210 return;
190 211 }
191 212 if (mqttClient != null && mqttClient.isConnected()) {
... ... @@ -195,7 +216,7 @@ public class IotEmqxUpstreamProtocol {
195 216 long delay = emqxProperties.getReconnectDelayMs();
196 217 log.info("[reconnectWithDelay][将在 {} 毫秒后尝试重连 MQTT Broker]", delay);
197 218 vertx.setTimer(delay, timerId -> {
198   - if (!isRunning) {
  219 + if (!running) {
199 220 return;
200 221 }
201 222 if (mqttClient != null && mqttClient.isConnected()) {
... ... @@ -305,7 +326,7 @@ public class IotEmqxUpstreamProtocol {
305 326 private void setupMqttHandlers() {
306 327 // 1. 设置断开重连监听器
307 328 mqttClient.closeHandler(closeEvent -> {
308   - if (!isRunning) {
  329 + if (!running) {
309 330 return;
310 331 }
311 332 log.warn("[closeHandler][MQTT 连接已断开, 准备重连]");
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.http;
  2 +
  3 +import lombok.Data;
  4 +
  5 +/**
  6 + * IoT HTTP 协议配置
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +@Data
  11 +public class IotHttpConfig {
  12 +
  13 + /**
  14 + * 是否启用 SSL
  15 + */
  16 + private Boolean sslEnabled = false;
  17 +
  18 + /**
  19 + * SSL 证书路径
  20 + */
  21 + private String sslCertPath;
  22 +
  23 + /**
  24 + * SSL 私钥路径
  25 + */
  26 + private String sslKeyPath;
  27 +
  28 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpDownstreamSubscriber.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.http;
2   -
3   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7   -import jakarta.annotation.PostConstruct;
8   -import lombok.RequiredArgsConstructor;
9   -import lombok.extern.slf4j.Slf4j;
10   -
11   -/**
12   - * IoT 网关 HTTP 订阅者:接收下行给设备的消息
13   - *
14   - * @author 芋道源码
15   - */
16   -@RequiredArgsConstructor
17   -@Slf4j
18   -public class IotHttpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
19   -
20   - private final IotHttpUpstreamProtocol protocol;
21   -
22   - private final IotMessageBus messageBus;
23   -
24   - @PostConstruct
25   - public void init() {
26   - messageBus.register(this);
27   - }
28   -
29   - @Override
30   - public String getTopic() {
31   - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
32   - }
33   -
34   - @Override
35   - public String getGroup() {
36   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
37   - return getTopic();
38   - }
39   -
40   - @Override
41   - public void onMessage(IotDeviceMessage message) {
42   - log.info("[onMessage][IoT 网关 HTTP 协议不支持下行消息,忽略消息:{}]", message);
43   - }
44   -
45   -}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.http;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  4 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  5 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  6 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolInstanceProperties;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.downstream.IotHttpDownstreamSubscriber;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpAuthHandler;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpRegisterHandler;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpRegisterSubHandler;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpUpstreamHandler;
  13 +import io.vertx.core.Vertx;
  14 +import io.vertx.core.http.HttpServer;
  15 +import io.vertx.core.http.HttpServerOptions;
  16 +import io.vertx.core.net.PemKeyCertOptions;
  17 +import io.vertx.ext.web.Router;
  18 +import io.vertx.ext.web.handler.BodyHandler;
  19 +import lombok.Getter;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +
  22 +/**
  23 + * IoT HTTP 协议实现
  24 + * <p>
  25 + * 基于 Vert.x 实现 HTTP 服务器,接收设备上行消息
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Slf4j
  30 +public class IotHttpProtocol implements IotProtocol {
  31 +
  32 + /**
  33 + * 协议配置
  34 + */
  35 + private final ProtocolInstanceProperties properties;
  36 + /**
  37 + * 消息总线
  38 + */
  39 + private final IotMessageBus messageBus;
  40 + /**
  41 + * 服务器 ID(用于消息追踪,全局唯一)
  42 + */
  43 + @Getter
  44 + private final String serverId;
  45 +
  46 + /**
  47 + * Vert.x 实例(每个 Protocol 自己管理)
  48 + */
  49 + private Vertx vertx;
  50 + /**
  51 + * HTTP 服务器
  52 + */
  53 + private HttpServer httpServer;
  54 + /**
  55 + * 下行消息订阅者
  56 + */
  57 + private IotHttpDownstreamSubscriber downstreamSubscriber;
  58 +
  59 + /**
  60 + * 运行状态
  61 + */
  62 + private volatile boolean running = false;
  63 +
  64 + public IotHttpProtocol(ProtocolInstanceProperties properties, IotMessageBus messageBus) {
  65 + this.properties = properties;
  66 + this.messageBus = messageBus;
  67 + this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
  68 + this.downstreamSubscriber = new IotHttpDownstreamSubscriber(this, messageBus);
  69 + }
  70 +
  71 + @Override
  72 + public String getId() {
  73 + return properties.getId();
  74 + }
  75 +
  76 + @Override
  77 + public IotProtocolTypeEnum getType() {
  78 + return IotProtocolTypeEnum.HTTP;
  79 + }
  80 +
  81 + @Override
  82 + public void start() {
  83 + if (running) {
  84 + log.warn("[start][IoT HTTP 协议 {} 已经在运行中]", getId());
  85 + return;
  86 + }
  87 +
  88 + // 1.1 创建 Vertx 实例(每个 Protocol 独立管理)
  89 + this.vertx = Vertx.vertx();
  90 +
  91 + // 1.2 创建路由
  92 + Router router = Router.router(vertx);
  93 + router.route().handler(BodyHandler.create());
  94 +
  95 + // 1.3 创建处理器,添加路由处理器
  96 + IotHttpAuthHandler authHandler = new IotHttpAuthHandler(this);
  97 + router.post(IotHttpAuthHandler.PATH).handler(authHandler);
  98 + IotHttpRegisterHandler registerHandler = new IotHttpRegisterHandler();
  99 + router.post(IotHttpRegisterHandler.PATH).handler(registerHandler);
  100 + IotHttpRegisterSubHandler registerSubHandler = new IotHttpRegisterSubHandler();
  101 + router.post(IotHttpRegisterSubHandler.PATH).handler(registerSubHandler);
  102 + IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(this);
  103 + router.post(IotHttpUpstreamHandler.PATH).handler(upstreamHandler);
  104 +
  105 + // 1.4 启动 HTTP 服务器
  106 + IotHttpConfig httpConfig = properties.getHttp();
  107 + HttpServerOptions options = new HttpServerOptions().setPort(properties.getPort());
  108 + if (httpConfig != null && Boolean.TRUE.equals(httpConfig.getSslEnabled())) {
  109 + PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
  110 + .setKeyPath(httpConfig.getSslKeyPath())
  111 + .setCertPath(httpConfig.getSslCertPath());
  112 + options = options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
  113 + }
  114 + try {
  115 + httpServer = vertx.createHttpServer(options)
  116 + .requestHandler(router)
  117 + .listen()
  118 + .result();
  119 + running = true;
  120 + log.info("[start][IoT HTTP 协议 {} 启动成功,端口:{},serverId:{}]",
  121 + getId(), properties.getPort(), serverId);
  122 +
  123 + // 2. 启动下行消息订阅者
  124 + this.downstreamSubscriber = new IotHttpDownstreamSubscriber(this, messageBus);
  125 + this.downstreamSubscriber.start();
  126 + } catch (Exception e) {
  127 + log.error("[start][IoT HTTP 协议 {} 启动失败]", getId(), e);
  128 + // 启动失败时关闭 Vertx
  129 + if (vertx != null) {
  130 + vertx.close();
  131 + vertx = null;
  132 + }
  133 + throw e;
  134 + }
  135 +
  136 + // 2. 启动下行消息订阅者
  137 + this.downstreamSubscriber.start();
  138 + }
  139 +
  140 + @Override
  141 + public void stop() {
  142 + if (!running) {
  143 + return;
  144 + }
  145 + // 1. 停止下行消息订阅者
  146 + if (downstreamSubscriber != null) {
  147 + try {
  148 + downstreamSubscriber.stop();
  149 + log.info("[stop][IoT HTTP 协议 {} 下行消息订阅者已停止]", getId());
  150 + } catch (Exception e) {
  151 + log.error("[stop][IoT HTTP 协议 {} 下行消息订阅者停止失败]", getId(), e);
  152 + }
  153 + downstreamSubscriber = null;
  154 + }
  155 +
  156 + // 2.1 关闭 HTTP 服务器
  157 + if (httpServer != null) {
  158 + try {
  159 + httpServer.close().result();
  160 + log.info("[stop][IoT HTTP 协议 {} 服务器已停止]", getId());
  161 + } catch (Exception e) {
  162 + log.error("[stop][IoT HTTP 协议 {} 服务器停止失败]", getId(), e);
  163 + }
  164 + httpServer = null;
  165 + }
  166 + // 2.2 关闭 Vertx 实例
  167 + if (vertx != null) {
  168 + try {
  169 + vertx.close().result();
  170 + log.info("[stop][IoT HTTP 协议 {} Vertx 已关闭]", getId());
  171 + } catch (Exception e) {
  172 + log.error("[stop][IoT HTTP 协议 {} Vertx 关闭失败]", getId(), e);
  173 + }
  174 + vertx = null;
  175 + }
  176 + running = false;
  177 + log.info("[stop][IoT HTTP 协议 {} 已停止]", getId());
  178 + }
  179 +
  180 + @Override
  181 + public boolean isRunning() {
  182 + return running;
  183 + }
  184 +
  185 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpUpstreamProtocol.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.http;
2   -
3   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
4   -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
5   -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAuthHandler;
6   -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpRegisterHandler;
7   -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpRegisterSubHandler;
8   -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpUpstreamHandler;
9   -import io.vertx.core.Vertx;
10   -import io.vertx.core.http.HttpServer;
11   -import io.vertx.core.http.HttpServerOptions;
12   -import io.vertx.core.net.PemKeyCertOptions;
13   -import io.vertx.ext.web.Router;
14   -import io.vertx.ext.web.handler.BodyHandler;
15   -import jakarta.annotation.PostConstruct;
16   -import jakarta.annotation.PreDestroy;
17   -import lombok.Getter;
18   -import lombok.extern.slf4j.Slf4j;
19   -
20   -/**
21   - * IoT 网关 HTTP 协议:接收设备上行消息
22   - *
23   - * @author 芋道源码
24   - */
25   -@Slf4j
26   -public class IotHttpUpstreamProtocol {
27   -
28   - private final IotGatewayProperties.HttpProperties httpProperties;
29   -
30   - private final Vertx vertx;
31   -
32   - private HttpServer httpServer;
33   -
34   - @Getter
35   - private final String serverId;
36   -
37   - public IotHttpUpstreamProtocol(IotGatewayProperties.HttpProperties httpProperties, Vertx vertx) {
38   - this.httpProperties = httpProperties;
39   - this.vertx = vertx;
40   - this.serverId = IotDeviceMessageUtils.generateServerId(httpProperties.getServerPort());
41   - }
42   -
43   - @PostConstruct
44   - public void start() {
45   - // 创建路由
46   - Router router = Router.router(vertx);
47   - router.route().handler(BodyHandler.create());
48   -
49   - // 创建处理器,添加路由处理器
50   - IotHttpAuthHandler authHandler = new IotHttpAuthHandler(this);
51   - router.post(IotHttpAuthHandler.PATH).handler(authHandler);
52   - IotHttpRegisterHandler registerHandler = new IotHttpRegisterHandler();
53   - router.post(IotHttpRegisterHandler.PATH).handler(registerHandler);
54   - IotHttpRegisterSubHandler registerSubHandler = new IotHttpRegisterSubHandler();
55   - router.post(IotHttpRegisterSubHandler.PATH).handler(registerSubHandler);
56   - IotHttpUpstreamHandler upstreamHandler = new IotHttpUpstreamHandler(this);
57   - router.post(IotHttpUpstreamHandler.PATH).handler(upstreamHandler);
58   -
59   - // 启动 HTTP 服务器
60   - HttpServerOptions options = new HttpServerOptions()
61   - .setPort(httpProperties.getServerPort());
62   - if (Boolean.TRUE.equals(httpProperties.getSslEnabled())) {
63   - PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions().setKeyPath(httpProperties.getSslKeyPath())
64   - .setCertPath(httpProperties.getSslCertPath());
65   - options = options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
66   - }
67   - try {
68   - httpServer = vertx.createHttpServer(options)
69   - .requestHandler(router)
70   - .listen()
71   - .result();
72   - log.info("[start][IoT 网关 HTTP 协议启动成功,端口:{}]", httpProperties.getServerPort());
73   - } catch (Exception e) {
74   - log.error("[start][IoT 网关 HTTP 协议启动失败]", e);
75   - throw e;
76   - }
77   - }
78   -
79   - @PreDestroy
80   - public void stop() {
81   - if (httpServer != null) {
82   - try {
83   - httpServer.close().result();
84   - log.info("[stop][IoT 网关 HTTP 协议已停止]");
85   - } catch (Exception e) {
86   - log.error("[stop][IoT 网关 HTTP 协议停止失败]", e);
87   - }
88   - }
89   - }
90   -
91   -}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/downstream/IotHttpDownstreamSubscriber.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.downstream;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +
  9 +/**
  10 + * IoT 网关 HTTP 订阅者:接收下行给设备的消息
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +
  15 +@Slf4j
  16 +public class IotHttpDownstreamSubscriber extends IotProtocolDownstreamSubscriber {
  17 +
  18 + public IotHttpDownstreamSubscriber(IotProtocol protocol, IotMessageBus messageBus) {
  19 + super(protocol, messageBus);
  20 + }
  21 +
  22 + @Override
  23 + protected void handleMessage(IotDeviceMessage message) {
  24 + log.info("[handleMessage][IoT 网关 HTTP 协议不支持下行消息,忽略消息:{}]", message);
  25 + }
  26 +
  27 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAuthHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpAuthHandler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.hutool.core.map.MapUtil;
... ... @@ -10,10 +10,10 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
10 10 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
11 11 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
12 12 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
13   -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAbstractHandler;
14 15 import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
15 16 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
16   -import io.vertx.core.json.JsonObject;
17 17 import io.vertx.ext.web.RoutingContext;
18 18  
19 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -32,7 +32,7 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
32 32  
33 33 public static final String PATH = "/auth";
34 34  
35   - private final IotHttpUpstreamProtocol protocol;
  35 + private final String serverId;
36 36  
37 37 private final IotDeviceTokenService deviceTokenService;
38 38  
... ... @@ -40,8 +40,8 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
40 40  
41 41 private final IotDeviceMessageService deviceMessageService;
42 42  
43   - public IotHttpAuthHandler(IotHttpUpstreamProtocol protocol) {
44   - this.protocol = protocol;
  43 + public IotHttpAuthHandler(IotHttpProtocol protocol) {
  44 + this.serverId = protocol.getServerId();
45 45 this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
46 46 this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
47 47 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
... ... @@ -50,32 +50,25 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
50 50 @Override
51 51 public CommonResult<Object> handle0(RoutingContext context) {
52 52 // 1. 解析参数
53   - JsonObject body = context.body().asJsonObject();
54   - if (body == null) {
55   - throw invalidParamException("请求体不能为空");
56   - }
57   - String clientId = body.getString("clientId");
58   - if (StrUtil.isEmpty(clientId)) {
  53 + IotDeviceAuthReqDTO request = deserializeRequest(context, IotDeviceAuthReqDTO.class);
  54 + if (StrUtil.isEmpty(request.getClientId())) {
59 55 throw invalidParamException("clientId 不能为空");
60 56 }
61   - String username = body.getString("username");
62   - if (StrUtil.isEmpty(username)) {
  57 + if (StrUtil.isEmpty(request.getUsername())) {
63 58 throw invalidParamException("username 不能为空");
64 59 }
65   - String password = body.getString("password");
66   - if (StrUtil.isEmpty(password)) {
  60 + if (StrUtil.isEmpty(request.getPassword())) {
67 61 throw invalidParamException("password 不能为空");
68 62 }
69 63  
70 64 // 2.1 执行认证
71   - CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()
72   - .setClientId(clientId).setUsername(username).setPassword(password));
  65 + CommonResult<Boolean> result = deviceApi.authDevice(request);
73 66 result.checkError();
74 67 if (!BooleanUtil.isTrue(result.getData())) {
75 68 throw exception(DEVICE_AUTH_FAIL);
76 69 }
77 70 // 2.2 生成 Token
78   - IotDeviceIdentity deviceInfo = deviceTokenService.parseUsername(username);
  71 + IotDeviceIdentity deviceInfo = deviceTokenService.parseUsername(request.getUsername());
79 72 Assert.notNull(deviceInfo, "设备信息不能为空");
80 73 String token = deviceTokenService.createToken(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
81 74 Assert.notBlank(token, "生成 token 不能为空位");
... ... @@ -83,7 +76,7 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
83 76 // 3. 执行上线
84 77 IotDeviceMessage message = IotDeviceMessage.buildStateUpdateOnline();
85 78 deviceMessageService.sendDeviceMessage(message,
86   - deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId());
  79 + deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId);
87 80  
88 81 // 构建响应数据
89 82 return success(MapUtil.of("token", token));
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpRegisterHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpRegisterHandler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import cn.hutool.extra.spring.SpringUtil;
... ... @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7 7 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
8 8 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
9   -import io.vertx.core.json.JsonObject;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAbstractHandler;
10 10 import io.vertx.ext.web.RoutingContext;
11 11  
12 12 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
... ... @@ -33,27 +33,19 @@ public class IotHttpRegisterHandler extends IotHttpAbstractHandler {
33 33 @Override
34 34 public CommonResult<Object> handle0(RoutingContext context) {
35 35 // 1. 解析参数
36   - JsonObject body = context.body().asJsonObject();
37   - if (body == null) {
38   - throw invalidParamException("请求体不能为空");
39   - }
40   - String productKey = body.getString("productKey");
41   - if (StrUtil.isEmpty(productKey)) {
  36 + IotDeviceRegisterReqDTO request = deserializeRequest(context, IotDeviceRegisterReqDTO.class);
  37 + if (StrUtil.isEmpty(request.getProductKey())) {
42 38 throw invalidParamException("productKey 不能为空");
43 39 }
44   - String deviceName = body.getString("deviceName");
45   - if (StrUtil.isEmpty(deviceName)) {
  40 + if (StrUtil.isEmpty(request.getDeviceName())) {
46 41 throw invalidParamException("deviceName 不能为空");
47 42 }
48   - String productSecret = body.getString("productSecret");
49   - if (StrUtil.isEmpty(productSecret)) {
  43 + if (StrUtil.isEmpty(request.getProductSecret())) {
50 44 throw invalidParamException("productSecret 不能为空");
51 45 }
52 46  
53 47 // 2. 调用动态注册
54   - IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO()
55   - .setProductKey(productKey).setDeviceName(deviceName).setProductSecret(productSecret);
56   - CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(reqDTO);
  48 + CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(request);
57 49 result.checkError();
58 50  
59 51 // 3. 返回结果
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpRegisterSubHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpRegisterSubHandler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.extra.spring.SpringUtil;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7 7 import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
  8 +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
8 9 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
9   -import io.vertx.core.json.JsonObject;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAbstractHandler;
10 11 import io.vertx.ext.web.RoutingContext;
  12 +import lombok.Data;
11 13  
12 14 import java.util.List;
13 15  
... ... @@ -39,29 +41,32 @@ public class IotHttpRegisterSubHandler extends IotHttpAbstractHandler {
39 41  
40 42 @Override
41 43 public CommonResult<Object> handle0(RoutingContext context) {
42   - // 1. 解析通用参数
  44 + // 1.1 解析通用参数
43 45 String productKey = context.pathParam("productKey");
44 46 String deviceName = context.pathParam("deviceName");
45   -
46   - // 2. 解析子设备列表
47   - JsonObject body = context.body().asJsonObject();
48   - if (body == null) {
49   - throw invalidParamException("请求体不能为空");
50   - }
51   - if (body.getJsonArray("params") == null) {
  47 + // 1.2 解析子设备列表
  48 + SubDeviceRegisterRequest request = deserializeRequest(context, SubDeviceRegisterRequest.class);
  49 + if (CollUtil.isEmpty(request.getParams())) {
52 50 throw invalidParamException("params 不能为空");
53 51 }
54   - List<cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO> subDevices = JsonUtils.parseArray(
55   - body.getJsonArray("params").toString(), cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO.class);
56 52  
57   - // 3. 调用子设备动态注册
  53 + // 2. 调用子设备动态注册
58 54 IotSubDeviceRegisterFullReqDTO reqDTO = new IotSubDeviceRegisterFullReqDTO()
59   - .setGatewayProductKey(productKey).setGatewayDeviceName(deviceName).setSubDevices(subDevices);
  55 + .setGatewayProductKey(productKey)
  56 + .setGatewayDeviceName(deviceName)
  57 + .setSubDevices(request.getParams());
60 58 CommonResult<List<IotSubDeviceRegisterRespDTO>> result = deviceApi.registerSubDevices(reqDTO);
61 59 result.checkError();
62 60  
63   - // 4. 返回结果
  61 + // 3. 返回结果
64 62 return success(result.getData());
65 63 }
66 64  
  65 + @Data
  66 + public static class SubDeviceRegisterRequest {
  67 +
  68 + private List<IotSubDeviceRegisterReqDTO> params;
  69 +
  70 + }
  71 +
67 72 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpUpstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpUpstreamHandler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream;
2 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.hutool.core.map.MapUtil;
... ... @@ -6,31 +6,28 @@ import cn.hutool.core.text.StrPool;
6 6 import cn.hutool.extra.spring.SpringUtil;
7 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
8 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9   -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAbstractHandler;
10 11 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
11 12 import io.vertx.ext.web.RoutingContext;
12   -import lombok.RequiredArgsConstructor;
13 13 import lombok.extern.slf4j.Slf4j;
14 14  
15   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
16   -
17 15 /**
18 16 * IoT 网关 HTTP 协议的【上行】处理器
19 17 *
20 18 * @author 芋道源码
21 19 */
22   -@RequiredArgsConstructor
23 20 @Slf4j
24 21 public class IotHttpUpstreamHandler extends IotHttpAbstractHandler {
25 22  
26 23 public static final String PATH = "/topic/sys/:productKey/:deviceName/*";
27 24  
28   - private final IotHttpUpstreamProtocol protocol;
  25 + private final String serverId;
29 26  
30 27 private final IotDeviceMessageService deviceMessageService;
31 28  
32   - public IotHttpUpstreamHandler(IotHttpUpstreamProtocol protocol) {
33   - this.protocol = protocol;
  29 + public IotHttpUpstreamHandler(IotHttpProtocol protocol) {
  30 + this.serverId = protocol.getServerId();
34 31 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
35 32 }
36 33  
... ... @@ -41,20 +38,15 @@ public class IotHttpUpstreamHandler extends IotHttpAbstractHandler {
41 38 String deviceName = context.pathParam("deviceName");
42 39 String method = context.pathParam("*").replaceAll(StrPool.SLASH, StrPool.DOT);
43 40  
44   - // 2.1 解析消息
45   - if (context.body().buffer() == null) {
46   - throw invalidParamException("请求体不能为空");
47   - }
48   - byte[] bytes = context.body().buffer().getBytes();
49   - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(bytes,
50   - productKey, deviceName);
  41 + // 2.1 根据 Content-Type 反序列化消息
  42 + IotDeviceMessage message = deserializeRequest(context, IotDeviceMessage.class);
51 43 Assert.equals(method, message.getMethod(), "method 不匹配");
52 44 // 2.2 发送消息
53 45 deviceMessageService.sendDeviceMessage(message,
54   - productKey, deviceName, protocol.getServerId());
  46 + productKey, deviceName, serverId);
55 47  
56 48 // 3. 返回结果
57 49 return CommonResult.success(MapUtil.of("messageId", message.getId()));
58 50 }
59 51  
60   -}
61 52 \ No newline at end of file
  53 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.http.router;
2 2  
3 3 import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.ArrayUtil;
4 5 import cn.hutool.core.util.ObjUtil;
5 6 import cn.hutool.core.util.StrUtil;
6 7 import cn.hutool.extra.spring.SpringUtil;
... ... @@ -9,11 +10,13 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
9 10 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
10 11 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
11 12 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpAuthHandler;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpRegisterHandler;
  15 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
12 16 import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
13 17 import io.vertx.core.Handler;
14 18 import io.vertx.core.http.HttpHeaders;
15 19 import io.vertx.ext.web.RoutingContext;
16   -import lombok.RequiredArgsConstructor;
17 20 import lombok.extern.slf4j.Slf4j;
18 21 import org.springframework.http.MediaType;
19 22  
... ... @@ -27,12 +30,13 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
27 30 *
28 31 * @author 芋道源码
29 32 */
30   -@RequiredArgsConstructor
31 33 @Slf4j
32 34 public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> {
33 35  
34 36 private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
35 37  
  38 + private final IotMessageSerializerManager serializerManager = SpringUtil.getBean(IotMessageSerializerManager.class);
  39 +
36 40 @Override
37 41 public final void handle(RoutingContext context) {
38 42 try {
... ... @@ -83,12 +87,26 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt;
83 87 }
84 88 }
85 89  
  90 + // ========== 序列化相关方法 ==========
  91 +
  92 + protected static <T> T deserializeRequest(RoutingContext context, Class<T> clazz) {
  93 + byte[] body = context.body().buffer() != null ? context.body().buffer().getBytes() : null;
  94 + if (ArrayUtil.isEmpty(body)) {
  95 + throw invalidParamException("请求体不能为空");
  96 + }
  97 + return JsonUtils.parseObject(body, clazz);
  98 + }
  99 +
  100 + private static String serializeResponse(Object data) {
  101 + return JsonUtils.toJsonString(data);
  102 + }
  103 +
86 104 @SuppressWarnings("deprecation")
87 105 public static void writeResponse(RoutingContext context, Object data) {
88 106 context.response()
89 107 .setStatusCode(200)
90 108 .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
91   - .end(JsonUtils.toJsonString(data));
  109 + .end(serializeResponse(data));
92 110 }
93 111  
94 112 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
7 6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler;
8   -import jakarta.annotation.PostConstruct;
9 7 import lombok.extern.slf4j.Slf4j;
10 8  
11 9 /**
... ... @@ -16,64 +14,27 @@ import lombok.extern.slf4j.Slf4j;
16 14 * @author 芋道源码
17 15 */
18 16 @Slf4j
19   -public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
20   -
21   - private final IotMqttUpstreamProtocol upstreamProtocol;
  17 +public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber {
22 18  
23 19 private final IotMqttDownstreamHandler downstreamHandler;
24 20  
25   - private final IotMessageBus messageBus;
26   -
27   - public IotMqttDownstreamSubscriber(IotMqttUpstreamProtocol upstreamProtocol,
  21 + public IotMqttDownstreamSubscriber(IotMqttUpstreamProtocol protocol,
28 22 IotMqttDownstreamHandler downstreamHandler,
29 23 IotMessageBus messageBus) {
30   - this.upstreamProtocol = upstreamProtocol;
  24 + super(protocol, messageBus);
31 25 this.downstreamHandler = downstreamHandler;
32   - this.messageBus = messageBus;
33   - }
34   -
35   - @PostConstruct
36   - public void subscribe() {
37   - messageBus.register(this);
38   - log.info("[subscribe][MQTT 协议下行消息订阅成功,主题:{}]", getTopic());
39   - }
40   -
41   - @Override
42   - public String getTopic() {
43   - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(upstreamProtocol.getServerId());
44   - }
45   -
46   - @Override
47   - public String getGroup() {
48   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
49   - return getTopic();
50 26 }
51 27  
52 28 @Override
53   - public void onMessage(IotDeviceMessage message) {
54   - log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",
55   - message.getId(), message.getMethod(), message.getDeviceId());
56   - try {
57   - // 1. 校验
58   - String method = message.getMethod();
59   - if (method == null) {
60   - log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]",
61   - message.getId(), message.getDeviceId());
62   - return;
63   - }
64   -
65   - // 2. 委托给下行处理器处理业务逻辑
66   - boolean success = downstreamHandler.handleDownstreamMessage(message);
67   - if (success) {
68   - log.debug("[onMessage][下行消息处理成功, messageId: {}, method: {}, deviceId: {}]",
69   - message.getId(), message.getMethod(), message.getDeviceId());
70   - } else {
71   - log.warn("[onMessage][下行消息处理失败, messageId: {}, method: {}, deviceId: {}]",
72   - message.getId(), message.getMethod(), message.getDeviceId());
73   - }
74   - } catch (Exception e) {
75   - log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]",
76   - message.getId(), message.getMethod(), message.getDeviceId(), e);
  29 + protected void handleMessage(IotDeviceMessage message) {
  30 + boolean success = downstreamHandler.handleDownstreamMessage(message);
  31 + if (success) {
  32 + log.debug("[handleMessage][下行消息处理成功, messageId: {}, method: {}, deviceId: {}]",
  33 + message.getId(), message.getMethod(), message.getDeviceId());
  34 + } else {
  35 + log.warn("[handleMessage][下行消息处理失败, messageId: {}, method: {}, deviceId: {}]",
  36 + message.getId(), message.getMethod(), message.getDeviceId());
77 37 }
78 38 }
  39 +
79 40 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
3 4 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
4 5 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
5 7 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
6 8 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamHandler;
7 9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
... ... @@ -19,7 +21,11 @@ import lombok.extern.slf4j.Slf4j;
19 21 * @author 芋道源码
20 22 */
21 23 @Slf4j
22   -public class IotMqttUpstreamProtocol {
  24 +public class IotMqttUpstreamProtocol implements IotProtocol {
  25 +
  26 + private static final String ID = "mqtt";
  27 +
  28 + private volatile boolean running = false;
23 29  
24 30 private final IotGatewayProperties.MqttProperties mqttProperties;
25 31  
... ... @@ -45,7 +51,23 @@ public class IotMqttUpstreamProtocol {
45 51 this.serverId = IotDeviceMessageUtils.generateServerId(mqttProperties.getPort());
46 52 }
47 53  
  54 + @Override
  55 + public String getId() {
  56 + return ID;
  57 + }
  58 +
  59 + @Override
  60 + public IotProtocolTypeEnum getType() {
  61 + return IotProtocolTypeEnum.MQTT;
  62 + }
  63 +
  64 + @Override
  65 + public boolean isRunning() {
  66 + return running;
  67 + }
  68 +
48 69 // TODO @haohao:这里的编写,是不是和 tcp 对应的,风格保持一致哈;
  70 + @Override
49 71 @PostConstruct
50 72 public void start() {
51 73 // 创建服务器选项
... ... @@ -71,6 +93,7 @@ public class IotMqttUpstreamProtocol {
71 93 // 启动服务器
72 94 try {
73 95 mqttServer.listen().result();
  96 + running = true;
74 97 log.info("[start][IoT 网关 MQTT 协议启动成功,端口:{}]", mqttProperties.getPort());
75 98 } catch (Exception e) {
76 99 log.error("[start][IoT 网关 MQTT 协议启动失败]", e);
... ... @@ -78,11 +101,13 @@ public class IotMqttUpstreamProtocol {
78 101 }
79 102 }
80 103  
  104 + @Override
81 105 @PreDestroy
82 106 public void stop() {
83 107 if (mqttServer != null) {
84 108 try {
85 109 mqttServer.close().result();
  110 + running = false;
86 111 log.info("[stop][IoT 网关 MQTT 协议已停止]");
87 112 } catch (Exception e) {
88 113 log.error("[stop][IoT 网关 MQTT 协议停止失败]", e);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7   -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
8 6 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10   -import jakarta.annotation.PostConstruct;
11   -import lombok.RequiredArgsConstructor;
12 7 import lombok.extern.slf4j.Slf4j;
13 8  
14 9 /**
... ... @@ -17,48 +12,20 @@ import lombok.extern.slf4j.Slf4j;
17 12 * @author 芋道源码
18 13 */
19 14 @Slf4j
20   -@RequiredArgsConstructor
21   -public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
  15 +public class IotTcpDownstreamSubscriber extends IotProtocolDownstreamSubscriber {
22 16  
23   - private final IotTcpUpstreamProtocol protocol;
  17 + private final IotTcpDownstreamHandler downstreamHandler;
24 18  
25   - private final IotDeviceMessageService messageService;
26   -
27   - private final IotTcpConnectionManager connectionManager;
28   -
29   - private final IotMessageBus messageBus;
30   -
31   - private IotTcpDownstreamHandler downstreamHandler;
32   -
33   - @PostConstruct
34   - public void init() {
35   - // 初始化下游处理器
36   - this.downstreamHandler = new IotTcpDownstreamHandler(messageService, connectionManager);
37   - // 注册下游订阅者
38   - messageBus.register(this);
39   - log.info("[init][TCP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]",
40   - protocol.getServerId(), getTopic());
41   - }
42   -
43   - @Override
44   - public String getTopic() {
45   - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
46   - }
47   -
48   - @Override
49   - public String getGroup() {
50   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
51   - return getTopic();
  19 + public IotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocol,
  20 + IotTcpDownstreamHandler downstreamHandler,
  21 + IotMessageBus messageBus) {
  22 + super(protocol, messageBus);
  23 + this.downstreamHandler = downstreamHandler;
52 24 }
53 25  
54 26 @Override
55   - public void onMessage(IotDeviceMessage message) {
56   - try {
57   - downstreamHandler.handle(message);
58   - } catch (Exception e) {
59   - log.error("[onMessage][处理下行消息失败,设备 ID: {},方法: {},消息 ID: {}]",
60   - message.getDeviceId(), message.getMethod(), message.getId(), e);
61   - }
  27 + protected void handleMessage(IotDeviceMessage message) {
  28 + downstreamHandler.handle(message);
62 29 }
63 30  
64   -}
65 31 \ No newline at end of file
  32 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpUpstreamProtocol.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
3 4 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
4 5 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
5 7 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
6 8 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpUpstreamHandler;
7 9 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
... ... @@ -21,7 +23,9 @@ import lombok.extern.slf4j.Slf4j;
21 23 * @author 芋道源码
22 24 */
23 25 @Slf4j
24   -public class IotTcpUpstreamProtocol {
  26 +public class IotTcpUpstreamProtocol implements IotProtocol {
  27 +
  28 + private static final String ID = "tcp";
25 29  
26 30 private final IotGatewayProperties.TcpProperties tcpProperties;
27 31  
... ... @@ -38,6 +42,8 @@ public class IotTcpUpstreamProtocol {
38 42  
39 43 private NetServer tcpServer;
40 44  
  45 + private volatile boolean running = false;
  46 +
41 47 public IotTcpUpstreamProtocol(IotGatewayProperties.TcpProperties tcpProperties,
42 48 IotDeviceService deviceService,
43 49 IotDeviceMessageService messageService,
... ... @@ -51,6 +57,17 @@ public class IotTcpUpstreamProtocol {
51 57 this.serverId = IotDeviceMessageUtils.generateServerId(tcpProperties.getPort());
52 58 }
53 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
54 71 @PostConstruct
55 72 public void start() {
56 73 // 创建服务器选项
... ... @@ -78,6 +95,7 @@ public class IotTcpUpstreamProtocol {
78 95 // 启动服务器
79 96 try {
80 97 tcpServer.listen().result();
  98 + running = true;
81 99 log.info("[start][IoT 网关 TCP 协议启动成功,端口:{}]", tcpProperties.getPort());
82 100 } catch (Exception e) {
83 101 log.error("[start][IoT 网关 TCP 协议启动失败]", e);
... ... @@ -85,15 +103,23 @@ public class IotTcpUpstreamProtocol {
85 103 }
86 104 }
87 105  
  106 + @Override
88 107 @PreDestroy
89 108 public void stop() {
90 109 if (tcpServer != null) {
91 110 try {
92 111 tcpServer.close().result();
  112 + running = false;
93 113 log.info("[stop][IoT 网关 TCP 协议已停止]");
94 114 } catch (Exception e) {
95 115 log.error("[stop][IoT 网关 TCP 协议停止失败]", e);
96 116 }
97 117 }
98 118 }
99   -}
100 119 \ No newline at end of file
  120 +
  121 + @Override
  122 + public boolean isRunning() {
  123 + return running;
  124 + }
  125 +
  126 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpDownstreamSubscriber.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7   -import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
8 6 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.router.IotUdpDownstreamHandler;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10   -import jakarta.annotation.PostConstruct;
11   -import lombok.RequiredArgsConstructor;
12 7 import lombok.extern.slf4j.Slf4j;
13 8  
14 9 /**
... ... @@ -17,48 +12,20 @@ import lombok.extern.slf4j.Slf4j;
17 12 * @author 芋道源码
18 13 */
19 14 @Slf4j
20   -@RequiredArgsConstructor
21   -public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
  15 +public class IotUdpDownstreamSubscriber extends IotProtocolDownstreamSubscriber {
22 16  
23   - private final IotUdpUpstreamProtocol protocol;
  17 + private final IotUdpDownstreamHandler downstreamHandler;
24 18  
25   - private final IotDeviceMessageService messageService;
26   -
27   - private final IotUdpSessionManager sessionManager;
28   -
29   - private final IotMessageBus messageBus;
30   -
31   - private IotUdpDownstreamHandler downstreamHandler;
32   -
33   - @PostConstruct
34   - public void init() {
35   - // 初始化下游处理器
36   - this.downstreamHandler = new IotUdpDownstreamHandler(messageService, sessionManager, protocol);
37   - // 注册下游订阅者
38   - messageBus.register(this);
39   - log.info("[init][UDP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]",
40   - protocol.getServerId(), getTopic());
41   - }
42   -
43   - @Override
44   - public String getTopic() {
45   - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
46   - }
47   -
48   - @Override
49   - public String getGroup() {
50   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
51   - return getTopic();
  19 + public IotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocol,
  20 + IotUdpDownstreamHandler downstreamHandler,
  21 + IotMessageBus messageBus) {
  22 + super(protocol, messageBus);
  23 + this.downstreamHandler = downstreamHandler;
52 24 }
53 25  
54 26 @Override
55   - public void onMessage(IotDeviceMessage message) {
56   - try {
57   - downstreamHandler.handle(message);
58   - } catch (Exception e) {
59   - log.error("[onMessage][处理下行消息失败,设备 ID: {},方法: {},消息 ID: {}]",
60   - message.getDeviceId(), message.getMethod(), message.getId(), e);
61   - }
  27 + protected void handleMessage(IotDeviceMessage message) {
  28 + downstreamHandler.handle(message);
62 29 }
63 30  
64 31 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpUpstreamProtocol.java
... ... @@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
5 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7 8 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
8 10 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
9 11 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.router.IotUdpUpstreamHandler;
10 12 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
... ... @@ -30,7 +32,9 @@ import java.util.List;
30 32 * @author 芋道源码
31 33 */
32 34 @Slf4j
33   -public class IotUdpUpstreamProtocol {
  35 +public class IotUdpUpstreamProtocol implements IotProtocol {
  36 +
  37 + private static final String ID = "udp";
34 38  
35 39 private final IotGatewayProperties.UdpProperties udpProperties;
36 40  
... ... @@ -55,6 +59,8 @@ public class IotUdpUpstreamProtocol {
55 59  
56 60 private IotUdpUpstreamHandler upstreamHandler;
57 61  
  62 + private volatile boolean running = false;
  63 +
58 64 public IotUdpUpstreamProtocol(IotGatewayProperties.UdpProperties udpProperties,
59 65 IotDeviceService deviceService,
60 66 IotDeviceMessageService messageService,
... ... @@ -68,6 +74,17 @@ public class IotUdpUpstreamProtocol {
68 74 this.serverId = IotDeviceMessageUtils.generateServerId(udpProperties.getPort());
69 75 }
70 76  
  77 + @Override
  78 + public String getId() {
  79 + return ID;
  80 + }
  81 +
  82 + @Override
  83 + public IotProtocolTypeEnum getType() {
  84 + return IotProtocolTypeEnum.UDP;
  85 + }
  86 +
  87 + @Override
71 88 @PostConstruct
72 89 public void start() {
73 90 // 1. 初始化上行消息处理器
... ... @@ -90,6 +107,7 @@ public class IotUdpUpstreamProtocol {
90 107 }
91 108 // 设置数据包处理器
92 109 udpSocket.handler(packet -> upstreamHandler.handle(packet, udpSocket));
  110 + running = true;
93 111 log.info("[start][IoT 网关 UDP 协议启动成功,端口:{},接收缓冲区:{} 字节,发送缓冲区:{} 字节]",
94 112 udpProperties.getPort(), udpProperties.getReceiveBufferSize(),
95 113 udpProperties.getSendBufferSize());
... ... @@ -99,6 +117,7 @@ public class IotUdpUpstreamProtocol {
99 117 });
100 118 }
101 119  
  120 + @Override
102 121 @PreDestroy
103 122 public void stop() {
104 123 // 1. 取消会话清理定时器
... ... @@ -112,6 +131,7 @@ public class IotUdpUpstreamProtocol {
112 131 if (udpSocket != null) {
113 132 try {
114 133 udpSocket.close().result();
  134 + running = false;
115 135 log.info("[stop][IoT 网关 UDP 协议已停止]");
116 136 } catch (Exception e) {
117 137 log.error("[stop][IoT 网关 UDP 协议停止失败]", e);
... ... @@ -119,6 +139,11 @@ public class IotUdpUpstreamProtocol {
119 139 }
120 140 }
121 141  
  142 + @Override
  143 + public boolean isRunning() {
  144 + return running;
  145 + }
  146 +
122 147 /**
123 148 * 启动会话清理定时器
124 149 */
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketDownstreamSubscriber.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7   -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
8 6 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router.IotWebSocketDownstreamHandler;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10   -import jakarta.annotation.PostConstruct;
11   -import lombok.RequiredArgsConstructor;
12 7 import lombok.extern.slf4j.Slf4j;
13 8  
14 9 /**
... ... @@ -17,48 +12,20 @@ import lombok.extern.slf4j.Slf4j;
17 12 * @author 芋道源码
18 13 */
19 14 @Slf4j
20   -@RequiredArgsConstructor
21   -public class IotWebSocketDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
  15 +public class IotWebSocketDownstreamSubscriber extends IotProtocolDownstreamSubscriber {
22 16  
23   - private final IotWebSocketUpstreamProtocol protocol;
  17 + private final IotWebSocketDownstreamHandler downstreamHandler;
24 18  
25   - private final IotDeviceMessageService messageService;
26   -
27   - private final IotWebSocketConnectionManager connectionManager;
28   -
29   - private final IotMessageBus messageBus;
30   -
31   - private IotWebSocketDownstreamHandler downstreamHandler;
32   -
33   - @PostConstruct
34   - public void init() {
35   - // 初始化下游处理器
36   - this.downstreamHandler = new IotWebSocketDownstreamHandler(messageService, connectionManager);
37   - // 注册下游订阅者
38   - messageBus.register(this);
39   - log.info("[init][WebSocket 下游订阅者初始化完成,服务器 ID: {},Topic: {}]",
40   - protocol.getServerId(), getTopic());
41   - }
42   -
43   - @Override
44   - public String getTopic() {
45   - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
46   - }
47   -
48   - @Override
49   - public String getGroup() {
50   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
51   - return getTopic();
  19 + public IotWebSocketDownstreamSubscriber(IotWebSocketUpstreamProtocol protocol,
  20 + IotWebSocketDownstreamHandler downstreamHandler,
  21 + IotMessageBus messageBus) {
  22 + super(protocol, messageBus);
  23 + this.downstreamHandler = downstreamHandler;
52 24 }
53 25  
54 26 @Override
55   - public void onMessage(IotDeviceMessage message) {
56   - try {
57   - downstreamHandler.handle(message);
58   - } catch (Exception e) {
59   - log.error("[onMessage][处理下行消息失败,设备 ID: {},方法: {},消息 ID: {}]",
60   - message.getDeviceId(), message.getMethod(), message.getId(), e);
61   - }
  27 + protected void handleMessage(IotDeviceMessage message) {
  28 + downstreamHandler.handle(message);
62 29 }
63 30  
64 31 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketUpstreamProtocol.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
2 2  
3 3 import cn.hutool.core.util.ObjUtil;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
4 5 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
5 6 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
6 8 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
7 9 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router.IotWebSocketUpstreamHandler;
8 10 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
... ... @@ -22,7 +24,9 @@ import lombok.extern.slf4j.Slf4j;
22 24 * @author 芋道源码
23 25 */
24 26 @Slf4j
25   -public class IotWebSocketUpstreamProtocol {
  27 +public class IotWebSocketUpstreamProtocol implements IotProtocol {
  28 +
  29 + private static final String ID = "websocket";
26 30  
27 31 private final IotGatewayProperties.WebSocketProperties wsProperties;
28 32  
... ... @@ -39,6 +43,8 @@ public class IotWebSocketUpstreamProtocol {
39 43  
40 44 private HttpServer httpServer;
41 45  
  46 + private volatile boolean running = false;
  47 +
42 48 public IotWebSocketUpstreamProtocol(IotGatewayProperties.WebSocketProperties wsProperties,
43 49 IotDeviceService deviceService,
44 50 IotDeviceMessageService messageService,
... ... @@ -52,6 +58,17 @@ public class IotWebSocketUpstreamProtocol {
52 58 this.serverId = IotDeviceMessageUtils.generateServerId(wsProperties.getPort());
53 59 }
54 60  
  61 + @Override
  62 + public String getId() {
  63 + return ID;
  64 + }
  65 +
  66 + @Override
  67 + public IotProtocolTypeEnum getType() {
  68 + return IotProtocolTypeEnum.WEBSOCKET;
  69 + }
  70 +
  71 + @Override
55 72 @PostConstruct
56 73 @SuppressWarnings("deprecation")
57 74 public void start() {
... ... @@ -88,6 +105,7 @@ public class IotWebSocketUpstreamProtocol {
88 105 // 3. 启动服务器
89 106 try {
90 107 httpServer.listen().result();
  108 + running = true;
91 109 log.info("[start][IoT 网关 WebSocket 协议启动成功,端口:{},路径:{}]", wsProperties.getPort(), wsProperties.getPath());
92 110 } catch (Exception e) {
93 111 log.error("[start][IoT 网关 WebSocket 协议启动失败]", e);
... ... @@ -95,11 +113,13 @@ public class IotWebSocketUpstreamProtocol {
95 113 }
96 114 }
97 115  
  116 + @Override
98 117 @PreDestroy
99 118 public void stop() {
100 119 if (httpServer != null) {
101 120 try {
102 121 httpServer.close().result();
  122 + running = false;
103 123 log.info("[stop][IoT 网关 WebSocket 协议已停止]");
104 124 } catch (Exception e) {
105 125 log.error("[stop][IoT 网关 WebSocket 协议停止失败]", e);
... ... @@ -107,4 +127,9 @@ public class IotWebSocketUpstreamProtocol {
107 127 }
108 128 }
109 129  
  130 + @Override
  131 + public boolean isRunning() {
  132 + return running;
  133 + }
  134 +
110 135 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/serialize/IotMessageSerializer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.serialize;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +
  6 +/**
  7 + * IoT 设备消息序列化器接口
  8 + *
  9 + * 用于序列化和反序列化设备消息
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public interface IotMessageSerializer {
  14 +
  15 + /**
  16 + * 序列化消息
  17 + *
  18 + * @param message 消息
  19 + * @return 编码后的消息内容
  20 + */
  21 + byte[] serialize(IotDeviceMessage message);
  22 +
  23 + /**
  24 + * 反序列化消息
  25 + *
  26 + * @param bytes 消息内容
  27 + * @return 解码后的消息内容
  28 + */
  29 + IotDeviceMessage deserialize(byte[] bytes);
  30 +
  31 + /**
  32 + * 获取序列化类型
  33 + *
  34 + * @return 序列化类型枚举
  35 + */
  36 + IotSerializeTypeEnum getType();
  37 +
  38 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/serialize/IotMessageSerializerManager.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.serialize;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
  4 +import cn.iocoder.yudao.module.iot.gateway.serialize.binary.IotBinarySerializer;
  5 +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +
  8 +import java.util.EnumMap;
  9 +import java.util.Map;
  10 +
  11 +/**
  12 + * IoT 序列化器管理器
  13 + *
  14 + * 负责根据枚举创建和管理序列化器实例
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Slf4j
  19 +public class IotMessageSerializerManager {
  20 +
  21 + private final Map<IotSerializeTypeEnum, IotMessageSerializer> serializerMap = new EnumMap<>(IotSerializeTypeEnum.class);
  22 +
  23 + public IotMessageSerializerManager() {
  24 + // 遍历枚举,创建对应的序列化器
  25 + for (IotSerializeTypeEnum type : IotSerializeTypeEnum.values()) {
  26 + IotMessageSerializer serializer = createSerializer(type);
  27 + serializerMap.put(type, serializer);
  28 + log.info("[IotSerializerManager][序列化器 {} 创建成功]", type);
  29 + }
  30 + }
  31 +
  32 + /**
  33 + * 根据类型创建序列化器
  34 + *
  35 + * @param type 序列化类型
  36 + * @return 序列化器实例
  37 + */
  38 + @SuppressWarnings("EnhancedSwitchMigration")
  39 + private IotMessageSerializer createSerializer(IotSerializeTypeEnum type) {
  40 + switch (type) {
  41 + case JSON:
  42 + return new IotJsonSerializer();
  43 + case BINARY:
  44 + return new IotBinarySerializer();
  45 + default:
  46 + throw new IllegalArgumentException("未知的序列化类型:" + type);
  47 + }
  48 + }
  49 +
  50 + /**
  51 + * 获取序列化器
  52 + *
  53 + * @param type 序列化类型
  54 + * @return 序列化器实例
  55 + */
  56 + public IotMessageSerializer get(IotSerializeTypeEnum type) {
  57 + return serializerMap.get(type);
  58 + }
  59 +
  60 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/serialize/binary/IotBinarySerializer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.serialize.binary;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  6 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
  7 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  8 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  9 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  10 +import io.vertx.core.buffer.Buffer;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +import java.nio.charset.StandardCharsets;
  14 +
  15 +/**
  16 + * 二进制格式的消息序列化器
  17 + *
  18 + * 二进制协议格式(所有数值使用大端序):
  19 + *
  20 + * <pre>
  21 + * +--------+--------+--------+---------------------------+--------+--------+
  22 + * | 魔术字 | 版本号 | 消息类型| 消息长度(4 字节) |
  23 + * +--------+--------+--------+---------------------------+--------+--------+
  24 + * | 消息 ID 长度(2 字节) | 消息 ID (变长字符串) |
  25 + * +--------+--------+--------+--------+--------+--------+--------+--------+
  26 + * | 方法名长度(2 字节) | 方法名(变长字符串) |
  27 + * +--------+--------+--------+--------+--------+--------+--------+--------+
  28 + * | 消息体数据(变长) |
  29 + * +--------+--------+--------+--------+--------+--------+--------+--------+
  30 + * </pre>
  31 + *
  32 + * 消息体格式:
  33 + * - 请求消息:params 数据(JSON)
  34 + * - 响应消息:code (4字节) + msg 长度(2字节) + msg 字符串 + data 数据(JSON)
  35 + *
  36 + * @author 芋道源码
  37 + */
  38 +@Slf4j
  39 +public class IotBinarySerializer implements IotMessageSerializer {
  40 +
  41 + /**
  42 + * 协议魔术字,用于协议识别
  43 + */
  44 + private static final byte MAGIC_NUMBER = (byte) 0x7E;
  45 +
  46 + /**
  47 + * 协议版本号
  48 + */
  49 + private static final byte PROTOCOL_VERSION = (byte) 0x01;
  50 +
  51 + /**
  52 + * 请求消息类型
  53 + */
  54 + private static final byte REQUEST = (byte) 0x01;
  55 +
  56 + /**
  57 + * 响应消息类型
  58 + */
  59 + private static final byte RESPONSE = (byte) 0x02;
  60 +
  61 + /**
  62 + * 协议头部固定长度(魔术字 + 版本号 + 消息类型 + 消息长度)
  63 + */
  64 + private static final int HEADER_FIXED_LENGTH = 7;
  65 +
  66 + /**
  67 + * 最小消息长度(头部 + 消息ID长度 + 方法名长度)
  68 + */
  69 + private static final int MIN_MESSAGE_LENGTH = HEADER_FIXED_LENGTH + 4;
  70 +
  71 + @Override
  72 + public IotSerializeTypeEnum getType() {
  73 + return IotSerializeTypeEnum.BINARY;
  74 + }
  75 +
  76 + @Override
  77 + public byte[] serialize(IotDeviceMessage message) {
  78 + Assert.notNull(message, "消息不能为空");
  79 + Assert.notBlank(message.getMethod(), "消息方法不能为空");
  80 + try {
  81 + // 1. 确定消息类型
  82 + byte messageType = determineMessageType(message);
  83 + // 2. 构建消息体
  84 + byte[] bodyData = buildMessageBody(message, messageType);
  85 + // 3. 构建完整消息
  86 + return buildCompleteMessage(message, messageType, bodyData);
  87 + } catch (Exception e) {
  88 + log.error("[encode][二进制消息编码失败,消息: {}]", message, e);
  89 + throw new RuntimeException("二进制消息编码失败: " + e.getMessage(), e);
  90 + }
  91 + }
  92 +
  93 + @Override
  94 + public IotDeviceMessage deserialize(byte[] bytes) {
  95 + Assert.notNull(bytes, "待解码数据不能为空");
  96 + Assert.isTrue(bytes.length >= MIN_MESSAGE_LENGTH, "数据包长度不足");
  97 + try {
  98 + Buffer buffer = Buffer.buffer(bytes);
  99 + int index = 0;
  100 +
  101 + // 1. 验证魔术字
  102 + byte magic = buffer.getByte(index++);
  103 + Assert.isTrue(magic == MAGIC_NUMBER, "无效的协议魔术字: " + magic);
  104 +
  105 + // 2. 验证版本号
  106 + byte version = buffer.getByte(index++);
  107 + Assert.isTrue(version == PROTOCOL_VERSION, "不支持的协议版本: " + version);
  108 +
  109 + // 3. 读取消息类型
  110 + byte messageType = buffer.getByte(index++);
  111 + Assert.isTrue(messageType == REQUEST || messageType == RESPONSE, "无效的消息类型: " + messageType);
  112 +
  113 + // 4. 读取消息长度
  114 + int messageLength = buffer.getInt(index);
  115 + index += 4;
  116 + Assert.isTrue(messageLength == buffer.length(),
  117 + "消息长度不匹配,期望: " + messageLength + ", 实际: " + buffer.length());
  118 +
  119 + // 5. 读取消息 ID
  120 + short messageIdLength = buffer.getShort(index);
  121 + index += 2;
  122 + String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name());
  123 + index += messageIdLength;
  124 +
  125 + // 6. 读取方法名
  126 + short methodLength = buffer.getShort(index);
  127 + index += 2;
  128 + String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name());
  129 + index += methodLength;
  130 +
  131 + // 7. 解析消息体
  132 + return parseMessageBody(buffer, index, messageType, messageId, method);
  133 + } catch (Exception e) {
  134 + log.error("[decode][二进制消息解码失败,数据长度: {}]", bytes.length, e);
  135 + throw new RuntimeException("二进制消息解码失败: " + e.getMessage(), e);
  136 + }
  137 + }
  138 +
  139 + /**
  140 + * 快速检测是否为二进制格式
  141 + *
  142 + * @param data 数据
  143 + * @return 是否为二进制格式
  144 + */
  145 + public static boolean isBinaryFormat(byte[] data) {
  146 + return data != null && data.length >= 1 && data[0] == MAGIC_NUMBER;
  147 + }
  148 +
  149 + private byte determineMessageType(IotDeviceMessage message) {
  150 + if (message.getCode() != null) {
  151 + return RESPONSE;
  152 + }
  153 + return REQUEST;
  154 + }
  155 +
  156 + private byte[] buildMessageBody(IotDeviceMessage message, byte messageType) {
  157 + Buffer bodyBuffer = Buffer.buffer();
  158 + if (messageType == RESPONSE) {
  159 + // code
  160 + bodyBuffer.appendInt(message.getCode() != null ? message.getCode() : 0);
  161 + // msg
  162 + String msg = message.getMsg() != null ? message.getMsg() : "";
  163 + byte[] msgBytes = StrUtil.utf8Bytes(msg);
  164 + bodyBuffer.appendShort((short) msgBytes.length);
  165 + bodyBuffer.appendBytes(msgBytes);
  166 + // data
  167 + if (message.getData() != null) {
  168 + bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getData()));
  169 + }
  170 + } else {
  171 + // 请求消息只处理 params 参数
  172 + if (message.getParams() != null) {
  173 + bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getParams()));
  174 + }
  175 + }
  176 + return bodyBuffer.getBytes();
  177 + }
  178 +
  179 + private byte[] buildCompleteMessage(IotDeviceMessage message, byte messageType, byte[] bodyData) {
  180 + Buffer buffer = Buffer.buffer();
  181 + // 1. 写入协议头部
  182 + buffer.appendByte(MAGIC_NUMBER);
  183 + buffer.appendByte(PROTOCOL_VERSION);
  184 + buffer.appendByte(messageType);
  185 + // 2. 预留消息长度位置
  186 + int lengthPosition = buffer.length();
  187 + buffer.appendInt(0);
  188 + // 3. 写入消息 ID
  189 + String messageId = StrUtil.isNotBlank(message.getRequestId()) ? message.getRequestId()
  190 + : IotDeviceMessageUtils.generateMessageId();
  191 + byte[] messageIdBytes = StrUtil.utf8Bytes(messageId);
  192 + buffer.appendShort((short) messageIdBytes.length);
  193 + buffer.appendBytes(messageIdBytes);
  194 + // 4. 写入方法名
  195 + byte[] methodBytes = StrUtil.utf8Bytes(message.getMethod());
  196 + buffer.appendShort((short) methodBytes.length);
  197 + buffer.appendBytes(methodBytes);
  198 + // 5. 写入消息体
  199 + buffer.appendBytes(bodyData);
  200 + // 6. 更新消息长度
  201 + buffer.setInt(lengthPosition, buffer.length());
  202 + return buffer.getBytes();
  203 + }
  204 +
  205 + private IotDeviceMessage parseMessageBody(Buffer buffer, int startIndex, byte messageType,
  206 + String messageId, String method) {
  207 + if (startIndex >= buffer.length()) {
  208 + return IotDeviceMessage.of(messageId, method, null, null, null, null);
  209 + }
  210 +
  211 + if (messageType == RESPONSE) {
  212 + return parseResponseMessage(buffer, startIndex, messageId, method);
  213 + } else {
  214 + Object payload = parseJsonData(buffer, startIndex, buffer.length());
  215 + return IotDeviceMessage.of(messageId, method, payload, null, null, null);
  216 + }
  217 + }
  218 +
  219 + private IotDeviceMessage parseResponseMessage(Buffer buffer, int startIndex, String messageId, String method) {
  220 + int index = startIndex;
  221 +
  222 + // 1. 读取响应码
  223 + Integer code = buffer.getInt(index);
  224 + index += 4;
  225 +
  226 + // 2. 读取响应消息
  227 + short msgLength = buffer.getShort(index);
  228 + index += 2;
  229 + String msg = msgLength > 0 ? buffer.getString(index, index + msgLength, StandardCharsets.UTF_8.name()) : null;
  230 + index += msgLength;
  231 +
  232 + // 3. 读取响应数据
  233 + Object data = null;
  234 + if (index < buffer.length()) {
  235 + data = parseJsonData(buffer, index, buffer.length());
  236 + }
  237 +
  238 + return IotDeviceMessage.of(messageId, method, null, data, code, msg);
  239 + }
  240 +
  241 + private Object parseJsonData(Buffer buffer, int startIndex, int endIndex) {
  242 + if (startIndex >= endIndex) {
  243 + return null;
  244 + }
  245 + try {
  246 + String jsonStr = buffer.getString(startIndex, endIndex, StandardCharsets.UTF_8.name());
  247 + return JsonUtils.parseObject(jsonStr, Object.class);
  248 + } catch (Exception e) {
  249 + log.warn("[parseJsonData][JSON 解析失败,返回原始字符串]", e);
  250 + return buffer.getString(startIndex, endIndex, StandardCharsets.UTF_8.name());
  251 + }
  252 + }
  253 +
  254 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/serialize/json/IotJsonSerializer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.serialize.json;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  7 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  8 +
  9 +/**
  10 + * JSON 格式的消息序列化器
  11 + *
  12 + * 直接使用 JsonUtils 序列化/反序列化 {@link IotDeviceMessage},不包装额外字段
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public class IotJsonSerializer implements IotMessageSerializer {
  17 +
  18 + @Override
  19 + public IotSerializeTypeEnum getType() {
  20 + return IotSerializeTypeEnum.JSON;
  21 + }
  22 +
  23 + @Override
  24 + public byte[] serialize(IotDeviceMessage message) {
  25 + Assert.notNull(message, "消息不能为空");
  26 + return JsonUtils.toJsonByte(message);
  27 + }
  28 +
  29 + @Override
  30 + public IotDeviceMessage deserialize(byte[] bytes) {
  31 + Assert.notNull(bytes, "待解码数据不能为空");
  32 + IotDeviceMessage message = JsonUtils.parseObject(bytes, IotDeviceMessage.class);
  33 + Assert.notNull(message, "消息解码失败");
  34 + return message;
  35 + }
  36 +
  37 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
... ... @@ -42,14 +42,20 @@ yudao:
42 42 secret: yudaoIotGatewayTokenSecret123456789 # Token 密钥,至少32位
43 43 expiration: 7d
44 44  
45   - # 协议配置
46   - protocol:
  45 + # 协议实例列表(新版配置方式)
  46 + protocols:
47 47 # ====================================
48 48 # 针对引入的 HTTP 组件的配置
49 49 # ====================================
50   - http:
  50 + - id: http-json
  51 + type: http
  52 + port: 8092
51 53 enabled: true
52   - server-port: 8092
  54 + http:
  55 + ssl-enabled: false
  56 +
  57 + # 协议配置(旧版,保持兼容)
  58 + protocol:
53 59 # ====================================
54 60 # 针对引入的 EMQX 组件的配置
55 61 # ====================================
... ...