Commit c658ac69c0e584d948b16898b9e23e97a354f95c

Authored by haohao
1 parent af0ff1ce

reactor:【IoT 物联网】合并 MQTT 上行协议与 HTTP 认证协议,优化消息处理逻辑

Showing 11 changed files with 367 additions and 973 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
... ... @@ -4,7 +4,6 @@ import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 4 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber;
5 5 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
6 6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber;
7   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttHttpAuthProtocol;
8 7 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
9 8 import lombok.extern.slf4j.Slf4j;
10 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
... ... @@ -45,8 +44,11 @@ public class IotGatewayConfiguration {
45 44 @Slf4j
46 45 public static class MqttProtocolConfiguration {
47 46  
  47 + /**
  48 + * MQTT 统一协议:集成上行协议和HTTP认证协议
  49 + */
48 50 @Bean
49   - public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties) {
  51 + public IotMqttUpstreamProtocol iotMqttUnifiedProtocol(IotGatewayProperties gatewayProperties) {
50 52 return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getEmqx());
51 53 }
52 54  
... ... @@ -55,14 +57,6 @@ public class IotGatewayConfiguration {
55 57 IotMessageBus messageBus) {
56 58 return new IotMqttDownstreamSubscriber(mqttUpstreamProtocol, messageBus);
57 59 }
58   -
59   - /**
60   - * MQTT HTTP 认证协议:提供 HTTP 认证接口供 EMQX 调用
61   - */
62   - @Bean
63   - public IotMqttHttpAuthProtocol mqttHttpAuthProtocol(IotGatewayProperties gatewayProperties) {
64   - return new IotMqttHttpAuthProtocol(gatewayProperties.getProtocol().getEmqx());
65   - }
66 60 }
67 61  
68 62 }
... ...
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 +import cn.hutool.core.util.StrUtil;
3 4 import cn.hutool.json.JSONObject;
4   -import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
5   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
6 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
7 6 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
8 7 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
9 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
10 9 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
11 10 import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
  11 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
12 12 import jakarta.annotation.PostConstruct;
13 13 import jakarta.annotation.Resource;
14 14 import lombok.RequiredArgsConstructor;
... ... @@ -27,7 +27,7 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi
27 27 private final IotMessageBus messageBus;
28 28  
29 29 @Resource
30   - private IotDeviceCommonApi deviceApi;
  30 + private IotDeviceService deviceService;
31 31  
32 32 @PostConstruct
33 33 public void init() {
... ... @@ -56,137 +56,87 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDevi
56 56 return;
57 57 }
58 58  
59   - // TODO @haohao:看看怎么融合下
60   - if (method.startsWith("thing.service.property.")) {
61   - handlePropertyMessage(message);
62   - } else if (method.startsWith("thing.service.") && !method.contains("property") && !method.contains("config")
63   - && !method.contains("ota")) {
64   - handleServiceMessage(message);
65   - } else if (method.startsWith("thing.service.config.")) {
66   - handleConfigMessage(message);
67   - } else if (method.startsWith("thing.service.ota.")) {
68   - handleOtaMessage(message);
69   - } else {
70   - log.warn("[onMessage][未知的消息方法:{}]", method);
71   - }
  59 + // 处理下行消息
  60 + handleDownstreamMessage(message);
72 61 } catch (Exception e) {
73 62 log.error("[onMessage][处理下行消息失败:{}]", message, e);
74 63 }
75 64 }
76 65  
77 66 /**
78   - * 处理属性相关消息
  67 + * 处理下行消息
79 68 *
80 69 * @param message 设备消息
81 70 */
82   - private void handlePropertyMessage(IotDeviceMessage message) {
83   - String method = message.getMethod();
84   -
85   - // 通过 deviceId 获取设备信息
86   - // TODO @haohao:通过缓存拿;
87   - IotDeviceGetReqDTO reqDTO = new IotDeviceGetReqDTO();
88   - reqDTO.setId(message.getDeviceId());
89   - IotDeviceRespDTO deviceInfo = deviceApi.getDevice(reqDTO).getData();
  71 + private void handleDownstreamMessage(IotDeviceMessage message) {
  72 + // 1. 获取设备信息(使用缓存)
  73 + IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());
90 74 if (deviceInfo == null) {
91   - log.warn("[handlePropertyMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
  75 + log.warn("[handleDownstreamMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
92 76 return;
93 77 }
94 78  
95   - String productKey = deviceInfo.getProductKey();
96   - String deviceName = deviceInfo.getDeviceName();
97   -
98   - if ("thing.service.property.set".equals(method)) {
99   - // 属性设置
100   - String topic = IotDeviceTopicEnum.buildPropertySetTopic(productKey, deviceName);
101   - JSONObject payload = buildDownstreamPayload(message, method);
102   - protocol.publishMessage(topic, payload.toString());
103   - log.info("[handlePropertyMessage][发送属性设置消息][topic: {}]", topic);
104   - } else if ("thing.service.property.get".equals(method)) {
105   - // 属性获取
106   - String topic = IotDeviceTopicEnum.buildPropertyGetTopic(productKey, deviceName);
107   - JSONObject payload = buildDownstreamPayload(message, method);
108   - protocol.publishMessage(topic, payload.toString());
109   - log.info("[handlePropertyMessage][发送属性获取消息][topic: {}]", topic);
110   - } else {
111   - log.warn("[handlePropertyMessage][未知的属性操作:{}]", method);
112   - }
113   - }
114   -
115   - /**
116   - * 处理服务调用消息
117   - *
118   - * @param message 设备消息
119   - */
120   - private void handleServiceMessage(IotDeviceMessage message) {
121   - String method = message.getMethod();
122   -
123   - // 通过 deviceId 获取设备信息
124   - IotDeviceGetReqDTO reqDTO = new IotDeviceGetReqDTO();
125   - reqDTO.setId(message.getDeviceId());
126   - IotDeviceRespDTO deviceInfo = deviceApi.getDevice(reqDTO).getData();
127   - if (deviceInfo == null) {
128   - log.warn("[handleServiceMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
  79 + // 2. 根据方法构建主题
  80 + String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName());
  81 + if (StrUtil.isBlank(topic)) {
  82 + log.warn("[handleDownstreamMessage][未知的消息方法:{}]", message.getMethod());
129 83 return;
130 84 }
131 85  
132   - String productKey = deviceInfo.getProductKey();
133   - String deviceName = deviceInfo.getDeviceName();
134   -
135   - // 从方法中提取服务标识符
136   - String serviceIdentifier = method.substring("thing.service.".length());
  86 + // 3. 构建载荷
  87 + JSONObject payload = buildDownstreamPayload(message, message.getMethod());
137 88  
138   - String topic = IotDeviceTopicEnum.buildServiceTopic(productKey, deviceName, serviceIdentifier);
139   - JSONObject payload = buildDownstreamPayload(message, method);
  89 + // 4. 发送消息
140 90 protocol.publishMessage(topic, payload.toString());
141   - log.info("[handleServiceMessage][发送服务调用消息][topic: {}]", topic);
  91 + log.info("[handleDownstreamMessage][发送下行消息成功][method: {}][topic: {}]", message.getMethod(), topic);
142 92 }
143 93  
144 94 /**
145   - * 处理配置设置消息
  95 + * 根据方法构建主题
146 96 *
147   - * @param message 设备消息
  97 + * @param method 消息方法
  98 + * @param productKey 产品标识
  99 + * @param deviceName 设备名称
  100 + * @return 构建的主题,如果方法不支持返回 null
148 101 */
149   - private void handleConfigMessage(IotDeviceMessage message) {
150   - // 通过 deviceId 获取设备信息
151   - IotDeviceGetReqDTO reqDTO = new IotDeviceGetReqDTO();
152   - reqDTO.setId(message.getDeviceId());
153   - IotDeviceRespDTO deviceInfo = deviceApi.getDevice(reqDTO).getData();
154   - if (deviceInfo == null) {
155   - log.warn("[handleConfigMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
156   - return;
  102 + private String buildTopicByMethod(String method, String productKey, String deviceName) {
  103 + if (StrUtil.isBlank(method)) {
  104 + return null;
157 105 }
158 106  
159   - String productKey = deviceInfo.getProductKey();
160   - String deviceName = deviceInfo.getDeviceName();
  107 + // 属性相关操作
  108 + if (method.startsWith("thing.service.property.")) {
  109 + if ("thing.service.property.set".equals(method)) {
  110 + return IotDeviceTopicEnum.buildPropertySetTopic(productKey, deviceName);
  111 + } else if ("thing.service.property.get".equals(method)) {
  112 + return IotDeviceTopicEnum.buildPropertyGetTopic(productKey, deviceName);
  113 + }
  114 + return null;
  115 + }
161 116  
162   - String topic = IotDeviceTopicEnum.buildConfigSetTopic(productKey, deviceName);
163   - JSONObject payload = buildDownstreamPayload(message, message.getMethod());
164   - protocol.publishMessage(topic, payload.toString());
165   - log.info("[handleConfigMessage][发送配置设置消息][topic: {}]", topic);
166   - }
  117 + // 配置设置操作
  118 + if (method.startsWith("thing.service.config.")) {
  119 + return IotDeviceTopicEnum.buildConfigSetTopic(productKey, deviceName);
  120 + }
167 121  
168   - /**
169   - * 处理 OTA 升级消息
170   - *
171   - * @param message 设备消息
172   - */
173   - private void handleOtaMessage(IotDeviceMessage message) {
174   - // 通过 deviceId 获取设备信息
175   - IotDeviceGetReqDTO reqDTO = new IotDeviceGetReqDTO();
176   - reqDTO.setId(message.getDeviceId());
177   - IotDeviceRespDTO deviceInfo = deviceApi.getDevice(reqDTO).getData();
178   - if (deviceInfo == null) {
179   - log.warn("[handleOtaMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
180   - return;
  122 + // OTA 升级操作
  123 + if (method.startsWith("thing.service.ota.")) {
  124 + return IotDeviceTopicEnum.buildOtaUpgradeTopic(productKey, deviceName);
181 125 }
182 126  
183   - String productKey = deviceInfo.getProductKey();
184   - String deviceName = deviceInfo.getDeviceName();
  127 + // 一般服务调用操作
  128 + if (method.startsWith("thing.service.")) {
  129 + // 排除属性、配置、OTA相关的服务调用
  130 + if (method.contains("property") || method.contains("config") || method.contains("ota")) {
  131 + return null; // 已在上面处理
  132 + }
  133 + // 从方法中提取服务标识符
  134 + String serviceIdentifier = method.substring("thing.service.".length());
  135 + return IotDeviceTopicEnum.buildServiceTopic(productKey, deviceName, serviceIdentifier);
  136 + }
185 137  
186   - String topic = IotDeviceTopicEnum.buildOtaUpgradeTopic(productKey, deviceName);
187   - JSONObject payload = buildDownstreamPayload(message, message.getMethod());
188   - protocol.publishMessage(topic, payload.toString());
189   - log.info("[handleOtaMessage][发送 OTA 升级消息][topic: {}]", topic);
  138 + // 不支持的方法
  139 + return null;
190 140 }
191 141  
192 142 /**
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttHttpAuthProtocol.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2   -
3   -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
4   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttHttpAuthHandler;
5   -import io.vertx.core.AbstractVerticle;
6   -import io.vertx.core.Vertx;
7   -import io.vertx.core.http.HttpServer;
8   -import io.vertx.ext.web.Router;
9   -import io.vertx.ext.web.handler.BodyHandler;
10   -import jakarta.annotation.PostConstruct;
11   -import jakarta.annotation.PreDestroy;
12   -import lombok.RequiredArgsConstructor;
13   -import lombok.extern.slf4j.Slf4j;
14   -
15   -// TODO @haohao:可以融合到 IotMqttUpstreamProtocol 么?主要考虑,一个协议,一个 protocol;
16   -/**
17   - * IoT 网关 MQTT HTTP 认证协议:提供 HTTP 认证接口供 EMQX 调用
18   - *
19   - * @author 芋道源码
20   - */
21   -@RequiredArgsConstructor
22   -@Slf4j
23   -public class IotMqttHttpAuthProtocol extends AbstractVerticle {
24   -
25   - private final IotGatewayProperties.EmqxProperties emqxProperties;
26   -
27   - private HttpServer httpServer;
28   - private Vertx vertx;
29   -
30   - @Override
31   - @PostConstruct
32   - public void start() {
33   - log.info("[start][开始启动 MQTT HTTP 认证协议服务]");
34   -
35   - // 创建 Vertx 实例
36   - this.vertx = Vertx.vertx();
37   -
38   - // 创建路由
39   - Router router = Router.router(vertx);
40   - router.route().handler(BodyHandler.create());
41   -
42   - // 创建 MQTT 认证处理器
43   - IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler();
44   -
45   - // 添加 MQTT 认证路由
46   - router.post("/mqtt/auth/authenticate").handler(authHandler::authenticate);
47   - router.post("/mqtt/auth/connected").handler(authHandler::connected);
48   - router.post("/mqtt/auth/disconnected").handler(authHandler::disconnected);
49   -
50   - // 启动 HTTP 服务器,使用独立端口
51   - int authPort = getAuthPort();
52   - try {
53   - httpServer = vertx.createHttpServer()
54   - .requestHandler(router)
55   - .listen(authPort)
56   - .result();
57   - log.info("[start][MQTT HTTP 认证协议启动成功,端口:{}]", authPort);
58   - } catch (Exception e) {
59   - log.error("[start][MQTT HTTP 认证协议启动失败]", e);
60   - throw e;
61   - }
62   - }
63   -
64   - @Override
65   - @PreDestroy
66   - public void stop() {
67   - if (httpServer != null) {
68   - try {
69   - httpServer.close().result();
70   - log.info("[stop][MQTT HTTP 认证协议已停止]");
71   - } catch (Exception e) {
72   - log.error("[stop][MQTT HTTP 认证协议停止失败]", e);
73   - }
74   - }
75   -
76   - if (vertx != null) {
77   - try {
78   - vertx.close();
79   - log.info("[stop][Vertx 已关闭]");
80   - } catch (Exception e) {
81   - log.warn("[stop][关闭 Vertx 异常]", e);
82   - }
83   - }
84   - }
85   -
86   - // TODO @haohao:这里不搞默认,必须填写哈;
87   - /**
88   - * 获取认证服务端口
89   - * 从配置中获取,默认使用 8090 端口
90   - */
91   - private int getAuthPort() {
92   - return emqxProperties.getHttpAuthPort() != null ? emqxProperties.getHttpAuthPort() : 8090;
93   - }
94   -
95   - public String getServerId() {
96   - return "mqtt_auth_gateway_" + getAuthPort();
97   - }
98   -}
99 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.http deleted
1   -### TODO 芋艿:这个文件是不是要留着;
2   -### IoT MQTT HTTP 认证服务测试文件
3   -### 用于测试独立的 MQTT HTTP 认证服务接口
4   -### 服务由 IotMqttHttpAuthProtocol 启动,运行在配置的端口上(默认 8090)
5   -### 注意:这些接口通常由 EMQX 调用,也可以用于手动测试
6   -
7   -### 配置说明:
8   -### 在 application-local.yaml 中可以配置端口:
9   -### yudao:
10   -### iot:
11   -### gateway:
12   -### protocol:
13   -### emqx:
14   -### http-auth-port: 8090 # 可以修改为其他端口
15   -
16   -### 1. MQTT 设备认证接口 => 成功认证
17   -POST http://localhost:8090/mqtt/auth/authenticate
18   -Content-Type: application/json
19   -
20   -{
21   - "clientid": "test_product.test_device",
22   - "username": "test_device&test_product",
23   - "password": "test_device_secret_hmac_password"
24   -}
25   -
26   -### 2. MQTT 设备认证接口 => 参数不完整(clientid 为空)
27   -POST http://localhost:8090/mqtt/auth/authenticate
28   -Content-Type: application/json
29   -
30   -{
31   - "clientid": "",
32   - "username": "test_device&test_product",
33   - "password": "test_device_secret"
34   -}
35   -
36   -### 3. MQTT 设备认证接口 => 参数不完整(username 为空)
37   -POST http://localhost:8090/mqtt/auth/authenticate
38   -Content-Type: application/json
39   -
40   -{
41   - "clientid": "test_product.test_device",
42   - "username": "",
43   - "password": "test_device_secret"
44   -}
45   -
46   -### 4. MQTT 设备认证接口 => 参数不完整(password 为空)
47   -POST http://localhost:8090/mqtt/auth/authenticate
48   -Content-Type: application/json
49   -
50   -{
51   - "clientid": "test_product.test_device",
52   - "username": "test_device&test_product",
53   - "password": ""
54   -}
55   -
56   -### 5. MQTT 设备认证接口 => 认证失败(错误的设备密钥)
57   -POST http://localhost:8090/mqtt/auth/authenticate
58   -Content-Type: application/json
59   -
60   -{
61   - "clientid": "test_product.test_device",
62   - "username": "test_device&test_product",
63   - "password": "wrong_password"
64   -}
65   -
66   -### 6. EMQX 客户端连接事件钩子 => 设备上线
67   -POST http://localhost:8090/mqtt/auth/connected
68   -Content-Type: application/json
69   -
70   -{
71   - "clientid": "test_product.test_device",
72   - "username": "test_device&test_product",
73   - "timestamp": 1703808000000,
74   - "peername": "192.168.1.100:52036",
75   - "sockname": "127.0.0.1:1883"
76   -}
77   -
78   -### 7. EMQX 客户端连接事件钩子 => 用户名为空
79   -POST http://localhost:8090/mqtt/auth/connected
80   -Content-Type: application/json
81   -
82   -{
83   - "clientid": "test_product.test_device",
84   - "username": "",
85   - "timestamp": 1703808000000,
86   - "peername": "192.168.1.100:52036",
87   - "sockname": "127.0.0.1:1883"
88   -}
89   -
90   -### 8. EMQX 客户端断开连接事件钩子 => 设备下线(正常断开)
91   -POST http://localhost:8090/mqtt/auth/disconnected
92   -Content-Type: application/json
93   -
94   -{
95   - "clientid": "test_product.test_device",
96   - "username": "test_device&test_product",
97   - "reason": "normal",
98   - "timestamp": 1703808060000
99   -}
100   -
101   -### 9. 使用自定义端口测试(如果配置了不同端口)
102   -### 假设配置了端口 8091:
103   -### POST http://localhost:8091/mqtt/auth/authenticate
104   -### Content-Type: application/json
105   -###
106   -### {
107   -### "clientid": "test_product.test_device",
108   -### "username": "test_device&test_product",
109   -### "password": "test_device_secret_hmac_password"
110   -### }
111   -
112   -### EMQX 配置参考:
113   -### authentication = [
114   -### {
115   -### mechanism = http
116   -### method = post
117   -### url = "http://localhost:8090/mqtt/auth/authenticate" # 使用配置的端口
118   -### body = {
119   -### clientid = "${clientid}"
120   -### username = "${username}"
121   -### password = "${password}"
122   -### }
123   -### headers = {
124   -### "content-type" = "application/json"
125   -### }
126   -### }
127   -### ]
128   -###
129   -### webhooks = [
130   -### {
131   -### name = "client_connected"
132   -### url = "http://localhost:8090/mqtt/auth/connected" # 使用配置的端口
133   -### events = ["client.connected"]
134   -### },
135   -### {
136   -### name = "client_disconnected"
137   -### url = "http://localhost:8090/mqtt/auth/disconnected" # 使用配置的端口
138   -### events = ["client.disconnected"]
139   -### }
140   -### ]
141 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java
... ... @@ -6,15 +6,18 @@ import cn.hutool.core.util.ObjUtil;
6 6 import cn.hutool.core.util.StrUtil;
7 7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8 8 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
9   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamRouter;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttHttpAuthHandler;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamHandler;
10 11 import io.netty.handler.codec.mqtt.MqttQoS;
11 12 import io.vertx.core.Vertx;
12 13 import io.vertx.core.buffer.Buffer;
  14 +import io.vertx.core.http.HttpServer;
  15 +import io.vertx.ext.web.Router;
  16 +import io.vertx.ext.web.handler.BodyHandler;
13 17 import io.vertx.mqtt.MqttClient;
14 18 import io.vertx.mqtt.MqttClientOptions;
15 19 import jakarta.annotation.PostConstruct;
16 20 import jakarta.annotation.PreDestroy;
17   -import lombok.RequiredArgsConstructor;
18 21 import lombok.extern.slf4j.Slf4j;
19 22  
20 23 import java.util.List;
... ... @@ -22,11 +25,14 @@ import java.util.concurrent.CompletableFuture;
22 25 import java.util.concurrent.TimeUnit;
23 26  
24 27 /**
25   - * IoT 网关 MQTT 协议:接收设备上行消息
  28 + * IoT 网关 MQTT 统一协议
  29 + * <p>
  30 + * 集成了 MQTT 上行协议和 HTTP 认证协议的功能:
  31 + * 1. MQTT 客户端:连接 EMQX,处理设备上行和下行消息
  32 + * 2. HTTP 认证服务:为 EMQX 提供设备认证接口
26 33 *
27 34 * @author 芋道源码
28 35 */
29   -@RequiredArgsConstructor
30 36 @Slf4j
31 37 public class IotMqttUpstreamProtocol {
32 38  
... ... @@ -37,30 +43,143 @@ public class IotMqttUpstreamProtocol {
37 43  
38 44 private final IotGatewayProperties.EmqxProperties emqxProperties;
39 45  
  46 + // 共享资源
40 47 private Vertx vertx;
  48 +
  49 + // MQTT 客户端相关
41 50 private MqttClient mqttClient;
42   - private IotMqttUpstreamRouter messageRouter;
  51 + private IotMqttUpstreamHandler upstreamHandler;
  52 +
  53 + // HTTP 认证服务相关
  54 + private HttpServer httpAuthServer;
  55 + private IotMqttHttpAuthHandler authHandler;
43 56  
44 57 /**
45 58 * 服务运行状态标志
46 59 */
47 60 private volatile boolean isRunning = false;
48 61  
  62 + /**
  63 + * 构造函数
  64 + */
  65 + public IotMqttUpstreamProtocol(IotGatewayProperties.EmqxProperties emqxProperties) {
  66 + this.emqxProperties = emqxProperties;
  67 + }
  68 +
49 69 @PostConstruct
50 70 public void start() {
51 71 if (isRunning) {
52   - log.warn("[start][MQTT 协议服务已经在运行中,请勿重复启动]");
  72 + log.warn("[start][MQTT 统一协议服务已经在运行中,请勿重复启动]");
  73 + return;
  74 + }
  75 + log.info("[start][开始启动 MQTT 统一协议服务]");
  76 +
  77 + try {
  78 + // 1. 创建共享的 Vertx 实例
  79 + this.vertx = Vertx.vertx();
  80 + log.info("[start][共享 Vertx 实例创建成功]");
  81 +
  82 + // 2. 启动 HTTP 认证服务
  83 + startHttpAuthServer();
  84 +
  85 + // 3. 启动 MQTT 客户端
  86 + startMqttClient();
  87 +
  88 + isRunning = true;
  89 + log.info("[start][MQTT 统一协议服务启动完成]");
  90 + } catch (Exception e) {
  91 + log.error("[start][MQTT 统一协议服务启动失败]", e);
  92 + // 启动失败时清理资源
  93 + stop();
  94 + throw e;
  95 + }
  96 + }
  97 +
  98 + @PreDestroy
  99 + public void stop() {
  100 + if (!isRunning) {
  101 + log.warn("[stop][MQTT 统一协议服务已经停止,无需再次停止]");
53 102 return;
54 103 }
55   - log.info("[start][开始启动 MQTT 协议服务]");
  104 + log.info("[stop][开始停止 MQTT 统一协议服务]");
  105 +
  106 + // 1. 停止 MQTT 客户端
  107 + stopMqttClient();
  108 +
  109 + // 2. 停止 HTTP 认证服务
  110 + stopHttpAuthServer();
  111 +
  112 + // 3. 关闭 Vertx 实例
  113 + if (vertx != null) {
  114 + try {
  115 + vertx.close();
  116 + log.info("[stop][Vertx 实例已关闭]");
  117 + } catch (Exception e) {
  118 + log.warn("[stop][关闭 Vertx 实例失败]", e);
  119 + }
  120 + }
  121 +
  122 + isRunning = false;
  123 + log.info("[stop][MQTT 统一协议服务已停止]");
  124 + }
  125 +
  126 + /**
  127 + * 启动 HTTP 认证服务
  128 + */
  129 + private void startHttpAuthServer() {
  130 + log.info("[startHttpAuthServer][开始启动 HTTP 认证服务]");
  131 +
  132 + // 创建路由
  133 + Router router = Router.router(vertx);
  134 + router.route().handler(BodyHandler.create());
  135 +
  136 + // 创建认证处理器
  137 + this.authHandler = new IotMqttHttpAuthHandler();
  138 +
  139 + // 添加认证路由
  140 + router.post("/mqtt/auth/authenticate").handler(authHandler::authenticate);
  141 + router.post("/mqtt/auth/connected").handler(authHandler::connected);
  142 + router.post("/mqtt/auth/disconnected").handler(authHandler::disconnected);
  143 +
  144 + // 启动 HTTP 服务器
  145 + int authPort = emqxProperties.getHttpAuthPort();
  146 + try {
  147 + httpAuthServer = vertx.createHttpServer()
  148 + .requestHandler(router)
  149 + .listen(authPort)
  150 + .result();
  151 + log.info("[startHttpAuthServer][HTTP 认证服务启动成功,端口:{}]", authPort);
  152 + } catch (Exception e) {
  153 + log.error("[startHttpAuthServer][HTTP 认证服务启动失败]", e);
  154 + throw e;
  155 + }
  156 + }
  157 +
  158 + /**
  159 + * 停止 HTTP 认证服务
  160 + */
  161 + private void stopHttpAuthServer() {
  162 + if (httpAuthServer != null) {
  163 + try {
  164 + httpAuthServer.close().result();
  165 + log.info("[stopHttpAuthServer][HTTP 认证服务已停止]");
  166 + } catch (Exception e) {
  167 + log.error("[stopHttpAuthServer][HTTP 认证服务停止失败]", e);
  168 + }
  169 + }
  170 + }
56 171  
57   - // 初始化组件
58   - this.vertx = Vertx.vertx();
59   - this.messageRouter = new IotMqttUpstreamRouter(this);
  172 + /**
  173 + * 启动 MQTT 客户端
  174 + */
  175 + private void startMqttClient() {
  176 + log.info("[startMqttClient][开始启动 MQTT 客户端]");
  177 +
  178 + // 初始化消息处理器
  179 + this.upstreamHandler = new IotMqttUpstreamHandler();
60 180  
61 181 // 创建 MQTT 客户端
62 182 MqttClientOptions options = new MqttClientOptions()
63   - // TODO @haohao:clientid 建议也配置文件;想通名字,会冲突哇?
64 183 .setClientId("yudao-iot-gateway-" + IdUtil.fastSimpleUUID())
65 184 .setUsername(emqxProperties.getMqttUsername())
66 185 .setPassword(emqxProperties.getMqttPassword())
... ... @@ -72,14 +191,10 @@ public class IotMqttUpstreamProtocol {
72 191 connectMqtt();
73 192 }
74 193  
75   - @PreDestroy
76   - public void stop() {
77   - if (!isRunning) {
78   - log.warn("[stop][MQTT 协议服务已经停止,无需再次停止]");
79   - return;
80   - }
81   - log.info("[stop][开始停止 MQTT 协议服务]");
82   -
  194 + /**
  195 + * 停止 MQTT 客户端
  196 + */
  197 + private void stopMqttClient() {
83 198 // 1. 取消 MQTT 主题订阅
84 199 if (mqttClient != null && mqttClient.isConnected()) {
85 200 List<String> topicList = emqxProperties.getMqttTopics();
... ... @@ -87,9 +202,9 @@ public class IotMqttUpstreamProtocol {
87 202 for (String topic : topicList) {
88 203 try {
89 204 mqttClient.unsubscribe(topic);
90   - log.debug("[stop][取消订阅主题: {}]", topic);
  205 + log.debug("[stopMqttClient][取消订阅主题: {}]", topic);
91 206 } catch (Exception e) {
92   - log.warn("[stop][取消订阅主题异常: {}]", topic, e);
  207 + log.warn("[stopMqttClient][取消订阅主题异常: {}]", topic, e);
93 208 }
94 209 }
95 210 }
... ... @@ -99,33 +214,20 @@ public class IotMqttUpstreamProtocol {
99 214 try {
100 215 if (mqttClient != null && mqttClient.isConnected()) {
101 216 mqttClient.disconnect();
  217 + log.info("[stopMqttClient][MQTT 客户端已断开]");
102 218 }
103 219 } catch (Exception e) {
104   - log.warn("[stop][关闭 MQTT 客户端异常]", e);
  220 + log.warn("[stopMqttClient][关闭 MQTT 客户端异常]", e);
105 221 }
106   -
107   - // 3. 关闭 Vertx
108   - try {
109   - if (vertx != null) {
110   - vertx.close();
111   - }
112   - } catch (Exception e) {
113   - log.warn("[stop][关闭 Vertx 异常]", e);
114   - }
115   -
116   - // 4. 更新状态
117   - isRunning = false;
118   - log.info("[stop][MQTT 协议服务已停止]");
119 222 }
120 223  
121 224 /**
122 225 * 连接 MQTT Broker 并订阅主题
123 226 */
124 227 private void connectMqtt() {
125   - // 检查必要的 MQTT 配置
126   - // TODO @haohao:这些通过配置文件的 validate 去做哈,简化下;
127 228 String host = emqxProperties.getMqttHost();
128 229 Integer port = emqxProperties.getMqttPort();
  230 +
129 231 if (StrUtil.isBlank(host)) {
130 232 String msg = "[connectMqtt][MQTT Host 为空,无法连接]";
131 233 log.error(msg);
... ... @@ -133,7 +235,7 @@ public class IotMqttUpstreamProtocol {
133 235 }
134 236 if (port == null) {
135 237 log.warn("[connectMqtt][MQTT Port 为 null,使用默认端口 1883]");
136   - port = 1883; // 默认 MQTT 端口
  238 + port = 1883;
137 239 }
138 240  
139 241 final Integer finalPort = port;
... ... @@ -152,7 +254,6 @@ public class IotMqttUpstreamProtocol {
152 254 // 订阅主题
153 255 subscribeToTopics();
154 256 })
155   - // TODO @haohao:这个要不要改成,必须连接成功?不做重试;不然启动也蛮危险的?
156 257 .exceptionally(error -> {
157 258 log.error("[connectMqtt][连接 MQTT Broker 失败]", error);
158 259 reconnectWithDelay();
... ... @@ -162,10 +263,9 @@ public class IotMqttUpstreamProtocol {
162 263 // 等待连接完成
163 264 try {
164 265 connectFuture.get(10, TimeUnit.SECONDS);
165   - isRunning = true;
166   - log.info("[connectMqtt][MQTT 协议服务启动完成]");
  266 + log.info("[connectMqtt][MQTT 客户端启动完成]");
167 267 } catch (Exception e) {
168   - log.error("[connectMqtt][MQTT 协议服务启动失败]", e);
  268 + log.error("[connectMqtt][MQTT 客户端启动失败]", e);
169 269 }
170 270 }
171 271  
... ... @@ -173,7 +273,7 @@ public class IotMqttUpstreamProtocol {
173 273 * 设置 MQTT 消息处理器
174 274 */
175 275 private void setupMessageHandler() {
176   - mqttClient.publishHandler(messageRouter::route);
  276 + mqttClient.publishHandler(upstreamHandler::handle);
177 277 log.debug("[setupMessageHandler][MQTT 消息处理器设置完成]");
178 278 }
179 279  
... ... @@ -182,76 +282,54 @@ public class IotMqttUpstreamProtocol {
182 282 */
183 283 private void subscribeToTopics() {
184 284 List<String> topicList = emqxProperties.getMqttTopics();
185   - // TODO @haohao:建议 topicList 直接 validate 校验
186 285 if (CollUtil.isEmpty(topicList)) {
187   - log.warn("[subscribeToTopics][未配置 MQTT 主题,使用默认主题]");
188   - topicList = List.of("/sys/#"); // 默认订阅所有系统主题
  286 + log.warn("[subscribeToTopics][没有配置要订阅的主题]");
  287 + return;
189 288 }
190 289  
191 290 for (String topic : topicList) {
192   - // TODO @haohao:直接 validate 校验;嘿嘿,主要保证核心逻辑,简单点
193   - if (StrUtil.isBlank(topic)) {
194   - log.warn("[subscribeToTopics][跳过空主题]");
195   - continue;
196   - }
197   -
198   - mqttClient.subscribe(topic, DEFAULT_QOS.value())
199   - .onSuccess(ack -> log.info("[subscribeToTopics][订阅主题成功: {}]", topic))
200   - .onFailure(err -> log.error("[subscribeToTopics][订阅主题失败: {}]", topic, err));
  291 + mqttClient.subscribe(topic, DEFAULT_QOS.value(), subscribeResult -> {
  292 + if (subscribeResult.succeeded()) {
  293 + log.info("[subscribeToTopics][订阅主题成功: {}]", topic);
  294 + } else {
  295 + log.error("[subscribeToTopics][订阅主题失败: {}]", topic, subscribeResult.cause());
  296 + }
  297 + });
201 298 }
202 299 }
203 300  
204 301 /**
205   - * 重连 MQTT 客户端
  302 + * 延迟重连
206 303 */
207 304 private void reconnectWithDelay() {
208   - if (!isRunning) {
209   - log.info("[reconnectWithDelay][服务已停止,不再尝试重连]");
210   - return;
211   - }
212   -
213   - // 默认重连延迟 5 秒
214   - int reconnectDelayMs = 5000;
215   - vertx.setTimer(reconnectDelayMs, id -> {
216   - log.info("[reconnectWithDelay][开始重新连接 MQTT]");
217   - connectMqtt();
  305 + vertx.setTimer(5000, timerId -> {
  306 + if (isRunning && (mqttClient == null || !mqttClient.isConnected())) {
  307 + log.info("[reconnectWithDelay][开始重连 MQTT Broker]");
  308 + connectMqtt();
  309 + }
218 310 });
219 311 }
220 312  
221 313 /**
222   - * 发布消息到 MQTT
  314 + * 发布消息到 MQTT Broker
223 315 *
224 316 * @param topic 主题
225 317 * @param payload 消息内容
226 318 */
227 319 public void publishMessage(String topic, String payload) {
228   - if (mqttClient == null || !mqttClient.isConnected()) {
229   - log.warn("[publishMessage][MQTT 客户端未连接,无法发送消息][topic: {}]", topic);
230   - return;
  320 + if (mqttClient != null && mqttClient.isConnected()) {
  321 + mqttClient.publish(topic, Buffer.buffer(payload), DEFAULT_QOS, false, false);
  322 + log.debug("[publishMessage][发布消息成功][topic: {}]", topic);
  323 + } else {
  324 + log.warn("[publishMessage][MQTT 客户端未连接,无法发布消息][topic: {}]", topic);
231 325 }
232   -
233   - mqttClient.publish(topic, Buffer.buffer(payload), DEFAULT_QOS, false, false)
234   - .onSuccess(v -> log.debug("[publishMessage][发送消息成功][topic: {}]", topic))
235   - .onFailure(err -> log.error("[publishMessage][发送消息失败][topic: {}]", topic, err));
236 326 }
237 327  
238 328 /**
239 329 * 获取服务器 ID
240   - *
241   - * @return 服务器 ID
242 330 */
243 331 public String getServerId() {
244 332 return IotDeviceMessageUtils.generateServerId(emqxProperties.getMqttPort());
245 333 }
246 334  
247   - // TODO @haohao:这个要删除哇?
248   - /**
249   - * 获取 MQTT 客户端
250   - *
251   - * @return MQTT 客户端
252   - */
253   - public MqttClient getMqttClient() {
254   - return mqttClient;
255   - }
256   -
257 335 }
258 336 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttAbstractHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
2   -
3   -import lombok.extern.slf4j.Slf4j;
4   -
5   -/**
6   - * IoT 网关 MQTT 协议的路由处理器抽象基类
7   - * <p>
8   - * 提供通用的处理方法,所有 MQTT 消息处理器都应继承此类
9   - *
10   - * @author 芋道源码
11   - */
12   -@Slf4j
13   -public abstract class IotMqttAbstractHandler {
14   -
15   - /**
16   - * 处理 MQTT 消息
17   - *
18   - * @param topic 主题
19   - * @param payload 消息内容
20   - */
21   - public abstract void handle(String topic, String payload);
22   -
23   - /**
24   - * 解析主题,获取主题各部分
25   - *
26   - * @param topic 主题
27   - * @return 主题各部分数组,如果解析失败返回 null
28   - */
29   - protected String[] parseTopic(String topic) {
30   - String[] topicParts = topic.split("/");
31   - if (topicParts.length < 7) {
32   - log.warn("[parseTopic][主题格式不正确][topic: {}]", topic);
33   - return null;
34   - }
35   - return topicParts;
36   - }
37   -
38   -}
39 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttEventHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
2   -
3   -import cn.hutool.json.JSONObject;
4   -import cn.hutool.json.JSONUtil;
5   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
7   -import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
8   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10   -import lombok.RequiredArgsConstructor;
11   -import lombok.extern.slf4j.Slf4j;
12   -
13   -import java.nio.charset.StandardCharsets;
14   -
15   -/**
16   - * IoT 网关 MQTT 事件处理器
17   - * <p>
18   - * 处理设备事件相关的 MQTT 消息
19   - *
20   - * @author 芋道源码
21   - */
22   -@RequiredArgsConstructor
23   -@Slf4j
24   -public class IotMqttEventHandler extends IotMqttAbstractHandler {
25   -
26   - private final IotMqttUpstreamProtocol protocol;
27   - private final IotDeviceMessageProducer deviceMessageProducer;
28   - private final IotDeviceMessageService deviceMessageService;
29   -
30   - @Override
31   - public void handle(String topic, String payload) {
32   - try {
33   - log.info("[handle][接收到设备事件上报][topic: {}]", topic);
34   -
35   - // 解析消息内容
36   - String[] topicParts = parseTopic(topic);
37   - if (topicParts == null) {
38   - return;
39   - }
40   -
41   - // 构建设备消息
42   - String productKey = topicParts[2];
43   - String deviceName = topicParts[3];
44   - String eventIdentifier = getEventIdentifier(topicParts, topic);
45   - if (eventIdentifier == null) {
46   - return;
47   - }
48   -
49   - // 使用 IotDeviceMessageService 解码消息
50   - byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
51   - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
52   - messageBytes, productKey, deviceName);
53   -
54   - // 发送消息
55   - deviceMessageProducer.sendDeviceMessage(message);
56   - log.info("[handle][处理设备事件上报成功][topic: {}]", topic);
57   -
58   - // 发送响应消息
59   - // TODO @haohao:这里应该只 ack 哈;reply 在 biz 业务处理了。handleUpstreamDeviceMessage
60   - String method = "thing.event." + eventIdentifier + ".post";
61   - sendResponse(topic, JSONUtil.parseObj(payload), method);
62   - } catch (Exception e) {
63   - log.error("[handle][处理设备事件上报失败][topic: {}][payload: {}]", topic, payload, e);
64   - }
65   - }
66   -
67   - /**
68   - * 从主题部分中获取事件标识符
69   - *
70   - * @param topicParts 主题各部分
71   - * @param topic 原始主题,用于日志
72   - * @return 事件标识符,如果获取失败返回 null
73   - */
74   - private String getEventIdentifier(String[] topicParts, String topic) {
75   - try {
76   - return topicParts[6];
77   - } catch (ArrayIndexOutOfBoundsException e) {
78   - log.warn("[getEventIdentifier][无法从主题中获取事件标识符][topic: {}]", topic);
79   - return null;
80   - }
81   - }
82   -
83   - /**
84   - * 发送响应消息
85   - *
86   - * @param topic 原始主题
87   - * @param jsonObject 原始消息 JSON 对象
88   - * @param method 响应方法
89   - */
90   - private void sendResponse(String topic, JSONObject jsonObject, String method) {
91   - try {
92   - String replyTopic = IotDeviceTopicEnum.getReplyTopic(topic);
93   -
94   - // 构建响应消息
95   - JSONObject response = new JSONObject();
96   - response.set("id", jsonObject.getStr("id"));
97   - response.set("code", 200);
98   - response.set("method", method);
99   - response.set("data", new JSONObject());
100   -
101   - // 发送响应
102   - protocol.publishMessage(replyTopic, response.toString());
103   - log.debug("[sendResponse][发送响应消息成功][topic: {}]", replyTopic);
104   - } catch (Exception e) {
105   - log.error("[sendResponse][发送响应消息失败][topic: {}]", topic, e);
106   - }
107   - }
108   -
109   -}
110 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttPropertyHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
2   -
3   -import cn.hutool.json.JSONObject;
4   -import cn.hutool.json.JSONUtil;
5   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
7   -import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
8   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10   -import lombok.RequiredArgsConstructor;
11   -import lombok.extern.slf4j.Slf4j;
12   -
13   -import java.nio.charset.StandardCharsets;
14   -
15   -/**
16   - * IoT 网关 MQTT 属性处理器
17   - * <p>
18   - * 处理设备属性相关的 MQTT 消息
19   - *
20   - * @author 芋道源码
21   - */
22   -@RequiredArgsConstructor
23   -@Slf4j
24   -public class IotMqttPropertyHandler extends IotMqttAbstractHandler {
25   -
26   - private final IotMqttUpstreamProtocol protocol;
27   - private final IotDeviceMessageProducer deviceMessageProducer;
28   - private final IotDeviceMessageService deviceMessageService;
29   -
30   - @Override
31   - public void handle(String topic, String payload) {
32   - if (topic.endsWith(IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic())) {
33   - // 属性上报
34   - handlePropertyPost(topic, payload);
35   - } else if (topic.contains(IotDeviceTopicEnum.PROPERTY_SET_TOPIC.getTopic())) {
36   - // 属性设置响应
37   - handlePropertySetReply(topic, payload);
38   - } else if (topic.contains(IotDeviceTopicEnum.PROPERTY_GET_TOPIC.getTopic())) {
39   - // 属性获取响应
40   - handlePropertyGetReply(topic, payload);
41   - } else {
42   - log.warn("[handle][未知的属性主题][topic: {}]", topic);
43   - }
44   - }
45   -
46   - /**
47   - * 处理设备属性上报消息
48   - *
49   - * @param topic 主题
50   - * @param payload 消息内容
51   - */
52   - private void handlePropertyPost(String topic, String payload) {
53   - try {
54   - log.info("[handlePropertyPost][接收到设备属性上报][topic: {}]", topic);
55   -
56   - // 解析主题获取设备信息
57   - String[] topicParts = parseTopic(topic);
58   - if (topicParts == null) {
59   - return;
60   - }
61   -
62   - String productKey = topicParts[2];
63   - String deviceName = topicParts[3];
64   -
65   - // 使用 IotDeviceMessageService 解码消息
66   - byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
67   - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
68   - messageBytes, productKey, deviceName);
69   -
70   - // 发送消息
71   - deviceMessageProducer.sendDeviceMessage(message);
72   - log.info("[handlePropertyPost][处理设备属性上报成功][topic: {}]", topic);
73   -
74   - // 发送响应消息
75   - sendResponse(topic, JSONUtil.parseObj(payload), "thing.event.property.post");
76   - } catch (Exception e) {
77   - log.error("[handlePropertyPost][处理设备属性上报失败][topic: {}][payload: {}]", topic, payload, e);
78   - }
79   - }
80   -
81   - /**
82   - * 处理属性设置响应消息
83   - *
84   - * @param topic 主题
85   - * @param payload 消息内容
86   - */
87   - private void handlePropertySetReply(String topic, String payload) {
88   - try {
89   - log.info("[handlePropertySetReply][接收到属性设置响应][topic: {}]", topic);
90   -
91   - // 解析主题获取设备信息
92   - String[] topicParts = parseTopic(topic);
93   - if (topicParts == null) {
94   - return;
95   - }
96   -
97   - String productKey = topicParts[2];
98   - String deviceName = topicParts[3];
99   -
100   - // 使用 IotDeviceMessageService 解码消息
101   - byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
102   - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
103   - messageBytes, productKey, deviceName);
104   -
105   - // 发送消息
106   - deviceMessageProducer.sendDeviceMessage(message);
107   - log.info("[handlePropertySetReply][处理属性设置响应成功][topic: {}]", topic);
108   - } catch (Exception e) {
109   - log.error("[handlePropertySetReply][处理属性设置响应失败][topic: {}][payload: {}]", topic, payload, e);
110   - }
111   - }
112   -
113   - /**
114   - * 处理属性获取响应消息
115   - *
116   - * @param topic 主题
117   - * @param payload 消息内容
118   - */
119   - private void handlePropertyGetReply(String topic, String payload) {
120   - try {
121   - log.info("[handlePropertyGetReply][接收到属性获取响应][topic: {}]", topic);
122   -
123   - // 解析主题获取设备信息
124   - String[] topicParts = parseTopic(topic);
125   - if (topicParts == null) {
126   - return;
127   - }
128   -
129   - String productKey = topicParts[2];
130   - String deviceName = topicParts[3];
131   -
132   - // 使用 IotDeviceMessageService 解码消息
133   - byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
134   - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
135   - messageBytes, productKey, deviceName);
136   -
137   - // 发送消息
138   - deviceMessageProducer.sendDeviceMessage(message);
139   - log.info("[handlePropertyGetReply][处理属性获取响应成功][topic: {}]", topic);
140   - } catch (Exception e) {
141   - log.error("[handlePropertyGetReply][处理属性获取响应失败][topic: {}][payload: {}]", topic, payload, e);
142   - }
143   - }
144   -
145   - /**
146   - * 发送响应消息
147   - *
148   - * @param topic 原始主题
149   - * @param jsonObject 原始消息 JSON 对象
150   - * @param method 响应方法
151   - */
152   - private void sendResponse(String topic, JSONObject jsonObject, String method) {
153   - try {
154   - String replyTopic = IotDeviceTopicEnum.getReplyTopic(topic);
155   -
156   - // 构建响应消息
157   - JSONObject response = new JSONObject();
158   - response.set("id", jsonObject.getStr("id"));
159   - response.set("code", 200);
160   - response.set("method", method);
161   - response.set("data", new JSONObject());
162   -
163   - // 发送响应
164   - protocol.publishMessage(replyTopic, response.toString());
165   - log.debug("[sendResponse][发送响应消息成功][topic: {}]", replyTopic);
166   - } catch (Exception e) {
167   - log.error("[sendResponse][发送响应消息失败][topic: {}]", topic, e);
168   - }
169   - }
170   -
171   -}
172 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttServiceHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
2   -
3   -import cn.hutool.json.JSONObject;
4   -import cn.hutool.json.JSONUtil;
5   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
7   -import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
8   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10   -import lombok.RequiredArgsConstructor;
11   -import lombok.extern.slf4j.Slf4j;
12   -
13   -import java.nio.charset.StandardCharsets;
14   -
15   -/**
16   - * IoT 网关 MQTT 服务处理器
17   - * <p>
18   - * 处理设备服务调用相关的 MQTT 消息
19   - *
20   - * @author 芋道源码
21   - */
22   -@RequiredArgsConstructor
23   -@Slf4j
24   -public class IotMqttServiceHandler extends IotMqttAbstractHandler {
25   -
26   - private final IotMqttUpstreamProtocol protocol;
27   - private final IotDeviceMessageProducer deviceMessageProducer;
28   - private final IotDeviceMessageService deviceMessageService;
29   -
30   - @Override
31   - public void handle(String topic, String payload) {
32   - try {
33   - log.info("[handle][接收到设备服务调用响应][topic: {}]", topic);
34   -
35   - // 解析消息内容
36   - String[] topicParts = parseTopic(topic);
37   - if (topicParts == null) {
38   - return;
39   - }
40   -
41   - // 构建设备消息
42   - String productKey = topicParts[2];
43   - String deviceName = topicParts[3];
44   - String serviceIdentifier = getServiceIdentifier(topicParts, topic);
45   - if (serviceIdentifier == null) {
46   - return;
47   - }
48   -
49   - // 使用 IotDeviceMessageService 解码消息
50   - byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
51   - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
52   - messageBytes, productKey, deviceName);
53   -
54   - // 发送消息
55   - deviceMessageProducer.sendDeviceMessage(message);
56   - log.info("[handle][处理设备服务调用响应成功][topic: {}]", topic);
57   -
58   - // 发送响应消息
59   - String method = "thing.service." + serviceIdentifier;
60   - sendResponse(topic, JSONUtil.parseObj(payload), method);
61   - } catch (Exception e) {
62   - log.error("[handle][处理设备服务调用响应失败][topic: {}][payload: {}]", topic, payload, e);
63   - }
64   - }
65   -
66   - /**
67   - * 从主题部分中获取服务标识符
68   - *
69   - * @param topicParts 主题各部分
70   - * @param topic 原始主题,用于日志
71   - * @return 服务标识符,如果获取失败返回 null
72   - */
73   - private String getServiceIdentifier(String[] topicParts, String topic) {
74   - try {
75   - // 服务主题格式:/sys/{productKey}/{deviceName}/thing/service/{serviceIdentifier}
76   - return topicParts[6];
77   - } catch (ArrayIndexOutOfBoundsException e) {
78   - log.warn("[getServiceIdentifier][无法从主题中获取服务标识符][topic: {}]", topic);
79   - return null;
80   - }
81   - }
82   -
83   - /**
84   - * 发送响应消息
85   - *
86   - * @param topic 原始主题
87   - * @param jsonObject 原始消息 JSON 对象
88   - * @param method 响应方法
89   - */
90   - private void sendResponse(String topic, JSONObject jsonObject, String method) {
91   - try {
92   - String replyTopic = IotDeviceTopicEnum.getReplyTopic(topic);
93   -
94   - // 构建响应消息
95   - JSONObject response = new JSONObject();
96   - response.set("id", jsonObject.getStr("id"));
97   - response.set("code", 200);
98   - response.set("method", method);
99   - response.set("data", new JSONObject());
100   -
101   - // 发送响应
102   - protocol.publishMessage(replyTopic, response.toString());
103   - log.debug("[sendResponse][发送响应消息成功][topic: {}]", replyTopic);
104   - } catch (Exception e) {
105   - log.error("[sendResponse][发送响应消息失败][topic: {}]", topic, e);
106   - }
107   - }
108   -
109   -}
110 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
  5 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  6 +import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
  7 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  8 +import io.vertx.mqtt.messages.MqttPublishMessage;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +import java.nio.charset.StandardCharsets;
  12 +
  13 +/**
  14 + * IoT 网关 MQTT 协议的【上行】处理器
  15 + * <p>
  16 + * 处理设备上行消息,包括事件上报、属性上报、服务调用响应等
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@Slf4j
  21 +public class IotMqttUpstreamHandler {
  22 +
  23 + private final IotDeviceMessageProducer deviceMessageProducer;
  24 + private final IotDeviceMessageService deviceMessageService;
  25 +
  26 + public IotMqttUpstreamHandler() {
  27 + this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
  28 + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  29 + }
  30 +
  31 + /**
  32 + * 处理 MQTT 发布消息
  33 + */
  34 + public void handle(MqttPublishMessage message) {
  35 + String topic = message.topicName();
  36 + String payload = message.payload().toString(StandardCharsets.UTF_8);
  37 +
  38 + if (StrUtil.isBlank(topic)) {
  39 + log.warn("[handle][主题为空,忽略消息]");
  40 + return;
  41 + }
  42 +
  43 + if (StrUtil.isBlank(payload)) {
  44 + log.warn("[handle][消息内容为空][topic: {}]", topic);
  45 + return;
  46 + }
  47 +
  48 + log.debug("[handle][收到 MQTT 消息][topic: {}]", topic);
  49 + handle(topic, payload);
  50 + }
  51 +
  52 + /**
  53 + * 处理 MQTT 消息
  54 + *
  55 + * @param topic 主题
  56 + * @param payload 消息内容
  57 + */
  58 + public void handle(String topic, String payload) {
  59 + try {
  60 + // 1. 识别并验证消息类型
  61 + String messageType = getMessageType(topic);
  62 + if (messageType == null) {
  63 + log.warn("[handle][未知的消息类型][topic: {}]", topic);
  64 + return;
  65 + }
  66 +
  67 + // 2. 处理消息
  68 + processMessage(topic, payload, messageType);
  69 +
  70 + } catch (Exception e) {
  71 + log.error("[handle][处理消息失败][topic: {}][payload: {}]", topic, payload, e);
  72 + }
  73 + }
  74 +
  75 + /**
  76 + * 处理消息的统一逻辑
  77 + */
  78 + private void processMessage(String topic, String payload, String messageType) {
  79 + log.info("[processMessage][接收到{}][topic: {}]", messageType, topic);
  80 +
  81 + // 解析主题获取设备信息
  82 + String[] topicParts = parseTopic(topic);
  83 + if (topicParts == null) {
  84 + return;
  85 + }
  86 +
  87 + String productKey = topicParts[2];
  88 + String deviceName = topicParts[3];
  89 +
  90 + // 解码消息
  91 + byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
  92 + IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
  93 + messageBytes, productKey, deviceName);
  94 +
  95 + // 发送消息到队列
  96 + deviceMessageProducer.sendDeviceMessage(message);
  97 +
  98 + // 记录成功日志
  99 + log.info("[processMessage][处理{}成功][topic: {}]", messageType, topic);
  100 + }
  101 +
  102 + /**
  103 + * 识别消息类型
  104 + *
  105 + * @param topic 主题
  106 + * @return 消息类型描述,如果不支持返回 null
  107 + */
  108 + private String getMessageType(String topic) {
  109 + // 设备事件上报: /sys/{productKey}/{deviceName}/thing/event/{eventIdentifier}/post
  110 + if (topic.contains("/thing/event/") && topic.endsWith("/post")) {
  111 + return "设备事件上报";
  112 + }
  113 +
  114 + // 设备属性操作: /sys/{productKey}/{deviceName}/thing/property/post
  115 + // 或属性响应: /sys/{productKey}/{deviceName}/thing/service/property/set_reply
  116 + if (topic.endsWith("/thing/property/post") ||
  117 + topic.contains("/thing/service/property/set") ||
  118 + topic.contains("/thing/service/property/get")) {
  119 + return "设备属性操作";
  120 + }
  121 +
  122 + // 设备服务调用: /sys/{productKey}/{deviceName}/thing/service/{serviceIdentifier}
  123 + if (topic.contains("/thing/service/") && !topic.contains("/property/")) {
  124 + return "设备服务调用";
  125 + }
  126 +
  127 + // 不支持的消息类型
  128 + return null;
  129 + }
  130 +
  131 + /**
  132 + * 解析主题,获取主题各部分
  133 + *
  134 + * @param topic 主题
  135 + * @return 主题各部分数组,如果解析失败返回 null
  136 + */
  137 + private String[] parseTopic(String topic) {
  138 + String[] topicParts = topic.split("/");
  139 + if (topicParts.length < 7) {
  140 + log.warn("[parseTopic][主题格式不正确][topic: {}]", topic);
  141 + return null;
  142 + }
  143 + return topicParts;
  144 + }
  145 +}
0 146 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamRouter.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.hutool.extra.spring.SpringUtil;
5   -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
6   -import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
7   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
8   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
9   -import io.vertx.mqtt.messages.MqttPublishMessage;
10   -import lombok.RequiredArgsConstructor;
11   -import lombok.extern.slf4j.Slf4j;
12   -
13   -/**
14   - * IoT 网关 MQTT 上行路由器
15   - * <p>
16   - * 根据消息主题路由到不同的处理器
17   - *
18   - * @author 芋道源码
19   - */
20   -@RequiredArgsConstructor
21   -@Slf4j
22   -public class IotMqttUpstreamRouter {
23   -
24   - private final IotMqttUpstreamProtocol protocol;
25   - private final IotDeviceMessageProducer deviceMessageProducer;
26   - private final IotDeviceMessageService deviceMessageService;
27   -
28   - // 处理器实例
29   - private IotMqttPropertyHandler propertyHandler;
30   - private IotMqttEventHandler eventHandler;
31   - private IotMqttServiceHandler serviceHandler;
32   -
33   - public IotMqttUpstreamRouter(IotMqttUpstreamProtocol protocol) {
34   - this.protocol = protocol;
35   - this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
36   - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
37   - // 初始化处理器
38   - this.propertyHandler = new IotMqttPropertyHandler(protocol, deviceMessageProducer, deviceMessageService);
39   - this.eventHandler = new IotMqttEventHandler(protocol, deviceMessageProducer, deviceMessageService);
40   - this.serviceHandler = new IotMqttServiceHandler(protocol, deviceMessageProducer, deviceMessageService);
41   - }
42   -
43   - /**
44   - * 路由 MQTT 消息
45   - *
46   - * @param message MQTT 发布消息
47   - */
48   - public void route(MqttPublishMessage message) {
49   - String topic = message.topicName();
50   - String payload = message.payload().toString();
51   - log.info("[route][接收到 MQTT 消息][topic: {}][payload: {}]", topic, payload);
52   -
53   - try {
54   - if (StrUtil.isEmpty(payload)) {
55   - log.warn("[route][消息内容为空][topic: {}]", topic);
56   - return;
57   - }
58   -
59   - // 根据主题路由到相应的处理器
60   - if (isPropertyTopic(topic)) {
61   - propertyHandler.handle(topic, payload);
62   - } else if (isEventTopic(topic)) {
63   - eventHandler.handle(topic, payload);
64   - } else if (isServiceTopic(topic)) {
65   - serviceHandler.handle(topic, payload);
66   - } else {
67   - log.warn("[route][未知的消息类型][topic: {}]", topic);
68   - }
69   - } catch (Exception e) {
70   - log.error("[route][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, payload, e);
71   - }
72   - }
73   -
74   - /**
75   - * 判断是否为属性相关主题
76   - *
77   - * @param topic 主题
78   - * @return 是否为属性主题
79   - */
80   - private boolean isPropertyTopic(String topic) {
81   - return topic.endsWith(IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic()) ||
82   - topic.contains(IotDeviceTopicEnum.PROPERTY_SET_TOPIC.getTopic()) ||
83   - topic.contains(IotDeviceTopicEnum.PROPERTY_GET_TOPIC.getTopic());
84   - }
85   -
86   - /**
87   - * 判断是否为事件相关主题
88   - *
89   - * @param topic 主题
90   - * @return 是否为事件主题
91   - */
92   - private boolean isEventTopic(String topic) {
93   - return topic.contains(IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic()) &&
94   - topic.endsWith(IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic());
95   - }
96   -
97   - /**
98   - * 判断是否为服务相关主题
99   - *
100   - * @param topic 主题
101   - * @return 是否为服务主题
102   - */
103   - private boolean isServiceTopic(String topic) {
104   - return topic.contains(IotDeviceTopicEnum.SERVICE_TOPIC_PREFIX.getTopic()) &&
105   - !isPropertyTopic(topic); // 排除属性相关的服务调用
106   - }
107   -
108   -}
109 0 \ No newline at end of file