Commit 4cefea68809b5ca38903c6254357ac985b974810

Authored by 安浩浩
1 parent 4746281d

【功能完善】IoT: 添加 MQTT 消息处理器,重构设备属性和事件上报逻辑,优化消息处理流程

yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDevicePropertySetVertxHandler.java
... ... @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
16 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
17 17  
18 18 /**
19   - * IoT 设备服务设置 Vertx Handler
  19 + * IoT 设置设备属性 Vertx Handler
20 20 *
21 21 * 芋道源码
22 22 */
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotPluginEmqxAutoConfiguration.java
1 1 package cn.iocoder.yudao.module.iot.plugin.emqx.config;
2 2  
3 3 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
4   -import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
5 4 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
7 5 import cn.iocoder.yudao.module.iot.plugin.emqx.downstream.IotDeviceDownstreamHandlerImpl;
8 6 import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.IotDeviceUpstreamServer;
9 7 import org.springframework.boot.context.properties.EnableConfigurationProperties;
... ... @@ -21,10 +19,8 @@ public class IotPluginEmqxAutoConfiguration {
21 19  
22 20 @Bean(initMethod = "start", destroyMethod = "stop")
23 21 public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,
24   - IotPluginCommonProperties commonProperties,
25   - IotPluginEmqxProperties emqxProperties,
26   - IotDeviceDownstreamServer deviceDownstreamServer) {
27   - return new IotDeviceUpstreamServer(commonProperties, emqxProperties, deviceUpstreamApi, deviceDownstreamServer);
  22 + IotPluginEmqxProperties emqxProperties) {
  23 + return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi);
28 24 }
29 25  
30 26 @Bean
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
... ... @@ -14,6 +14,9 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
14 14  
15 15 @Override
16 16 public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
  17 + // 设备服务调用
  18 + // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}
  19 + // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}_reply
17 20 return CommonResult.success(true);
18 21 }
19 22  
... ... @@ -24,6 +27,9 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
24 27  
25 28 @Override
26 29 public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
  30 + // 设置设备属性 标准 JSON
  31 + // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set
  32 + // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply
27 33 return CommonResult.success(true);
28 34 }
29 35  
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/IotDeviceUpstreamServer.java
1 1 package cn.iocoder.yudao.module.iot.plugin.emqx.upstream;
2 2  
3 3 import cn.hutool.core.util.IdUtil;
4   -import cn.hutool.json.JSONObject;
5   -import cn.hutool.json.JSONUtil;
6 4 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
7   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
8   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
9   -import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
10   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
11   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
12 5 import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties;
13 6 import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler;
  7 +import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceMqttMessageHandler;
  8 +import io.netty.handler.codec.mqtt.MqttQoS;
14 9 import io.vertx.core.Vertx;
15 10 import io.vertx.core.http.HttpServer;
16 11 import io.vertx.ext.web.Router;
... ... @@ -19,47 +14,28 @@ import io.vertx.mqtt.MqttClient;
19 14 import io.vertx.mqtt.MqttClientOptions;
20 15 import lombok.extern.slf4j.Slf4j;
21 16  
22   -import java.time.LocalDateTime;
23   -
24 17 /**
25 18 * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
26 19 * <p>
27 20 * 协议:HTTP、MQTT
28   - * 参考:<a href=
29   - * "https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.97a72915vRck44#section-g4j-5zg-12b">...</a>
30 21 *
31 22 * @author haohao
32 23 */
33 24 @Slf4j
34 25 public class IotDeviceUpstreamServer {
35 26  
36   - // 设备上报属性 标准 JSON
37   - // 请求Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
38   - // 响应Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
39   - // 设备上报事件 标准 JSON
40   - // 请求Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post
41   - // 响应Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply
42   -
43   - private static final String SYS_TOPIC_PREFIX = "/sys/";
44   - private static final String PROPERTY_POST_TOPIC = "/thing/event/property/post";
45   - private static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/";
46   - private static final String EVENT_POST_TOPIC_SUFFIX = "/post";
47   -
48 27 private static final int RECONNECT_DELAY = 5000; // 重连延迟时间(毫秒)
49   - private static final int QOS_LEVEL = 1;
50 28  
51 29 private final Vertx vertx;
52 30 private final HttpServer server;
53 31 private final MqttClient client;
54 32 private final IotPluginEmqxProperties emqxProperties;
55   - private final IotDeviceUpstreamApi deviceUpstreamApi;
  33 + private final IotDeviceMqttMessageHandler mqttMessageHandler;
56 34  
57   - public IotDeviceUpstreamServer(IotPluginCommonProperties commonProperties,
58   - IotPluginEmqxProperties emqxProperties,
59   - IotDeviceUpstreamApi deviceUpstreamApi,
60   - IotDeviceDownstreamServer deviceDownstreamServer) {
  35 + public IotDeviceUpstreamServer(IotPluginEmqxProperties emqxProperties,
  36 + IotDeviceUpstreamApi deviceUpstreamApi) {
61 37 this.emqxProperties = emqxProperties;
62   - this.deviceUpstreamApi = deviceUpstreamApi;
  38 +
63 39 // 创建 Vertx 实例
64 40 this.vertx = Vertx.vertx();
65 41 // 创建 Router 实例
... ... @@ -77,6 +53,7 @@ public class IotDeviceUpstreamServer {
77 53 .setPassword(emqxProperties.getMqttPassword())
78 54 .setSsl(emqxProperties.isMqttSsl());
79 55 client = MqttClient.create(vertx, options);
  56 + this.mqttMessageHandler = new IotDeviceMqttMessageHandler(deviceUpstreamApi, client);
80 57 }
81 58  
82 59 /**
... ... @@ -108,109 +85,7 @@ public class IotDeviceUpstreamServer {
108 85 * 设置 MQTT 消息处理器
109 86 */
110 87 private void setupMessageHandler() {
111   - client.publishHandler(message -> {
112   - String topic = message.topicName();
113   - String payload = message.payload().toString();
114   - log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload);
115   -
116   - try {
117   - handleMessage(topic, payload);
118   - } catch (Exception e) {
119   - log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e);
120   - }
121   - });
122   - }
123   -
124   - /**
125   - * 处理 MQTT 消息
126   - */
127   - private void handleMessage(String topic, String payload) {
128   - // 校验前缀
129   - if (!topic.startsWith(SYS_TOPIC_PREFIX)) {
130   - log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
131   - return;
132   - }
133   -
134   - // 处理设备属性上报消息
135   - if (topic.endsWith(PROPERTY_POST_TOPIC)) {
136   - log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic);
137   - handlePropertyPost(topic, payload);
138   - return;
139   - }
140   -
141   - // 处理设备事件上报消息
142   - if (topic.contains(EVENT_POST_TOPIC_PREFIX) && topic.endsWith(EVENT_POST_TOPIC_SUFFIX)) {
143   - log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic);
144   - handleEventPost(topic, payload);
145   - return;
146   - }
147   -
148   - // 未知消息类型
149   - log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
150   - }
151   -
152   - /**
153   - * 处理设备属性上报
154   - */
155   - private void handlePropertyPost(String topic, String payload) {
156   - // /sys/${productKey}/${deviceName}/thing/event/property/post
157   - // 解析消息内容
158   - JSONObject jsonObject = JSONUtil.parseObj(payload);
159   - String[] topicParts = topic.split("/");
160   -
161   - // 构建设备属性上报请求对象
162   - IotDevicePropertyReportReqDTO reportReqDTO = buildPropertyReportDTO(jsonObject, topicParts);
163   -
164   - // 调用上游 API 处理设备上报数据
165   - deviceUpstreamApi.reportDeviceProperty(reportReqDTO);
166   - log.info("[handlePropertyPost][处理设备上行消息成功][topic: {}][reportReqDTO: {}]",
167   - topic, JSONUtil.toJsonStr(reportReqDTO));
168   - }
169   -
170   - /**
171   - * 处理设备事件上报
172   - */
173   - private void handleEventPost(String topic, String payload) {
174   - // /sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post
175   - // 解析消息内容
176   - JSONObject jsonObject = JSONUtil.parseObj(payload);
177   - String[] topicParts = topic.split("/");
178   -
179   - // 构建设备事件上报请求对象
180   - IotDeviceEventReportReqDTO reportReqDTO = buildEventReportDTO(jsonObject, topicParts);
181   -
182   - // 调用上游 API 处理设备上报数据
183   - deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
184   - log.info("[handleEventPost][处理设备上行消息成功][topic: {}][reportReqDTO: {}]",
185   - topic, JSONUtil.toJsonStr(reportReqDTO));
186   - }
187   -
188   - /**
189   - * 构建设备属性上报请求对象
190   - */
191   - private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject,
192   - String[] topicParts) {
193   - return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
194   - .setRequestId(jsonObject.getStr("id"))
195   - .setProcessId(IotPluginCommonUtils.getProcessId())
196   - .setReportTime(LocalDateTime.now())
197   - .setProductKey(topicParts[2])
198   - .setDeviceName(topicParts[3]))
199   - .setProperties(jsonObject.getJSONObject("params"));
200   - }
201   -
202   - /**
203   - * 构建设备事件上报请求对象
204   - */
205   - private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) {
206   - return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO()
207   - .setRequestId(jsonObject.getStr("id"))
208   - .setProcessId(IotPluginCommonUtils.getProcessId())
209   - .setReportTime(LocalDateTime.now())
210   - .setProductKey(topicParts[2])
211   - .setDeviceName(topicParts[3]))
212   - .setIdentifier(topicParts[4])
213   - .setParams(jsonObject.getJSONObject("params"));
  88 + client.publishHandler(mqttMessageHandler::handle);
214 89 }
215 90  
216 91 /**
... ... @@ -244,7 +119,7 @@ public class IotDeviceUpstreamServer {
244 119 private void subscribeToTopics() {
245 120 String[] topics = emqxProperties.getMqttTopics().split(",");
246 121 for (String topic : topics) {
247   - client.subscribe(topic, QOS_LEVEL)
  122 + client.subscribe(topic, MqttQoS.AT_LEAST_ONCE.value())
248 123 .onSuccess(v -> log.info("[subscribeToTopics][成功订阅主题: {}]", topic))
249 124 .onFailure(err -> log.error("[subscribeToTopics][订阅主题失败: {}]", topic, err));
250 125 }
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/router/IotDeviceMqttMessageHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router;
  2 +
  3 +import cn.hutool.json.JSONObject;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  6 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
  7 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
  8 +import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
  9 +import io.netty.handler.codec.mqtt.MqttQoS;
  10 +import io.vertx.core.buffer.Buffer;
  11 +import io.vertx.mqtt.MqttClient;
  12 +import io.vertx.mqtt.messages.MqttPublishMessage;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +
  15 +import java.time.LocalDateTime;
  16 +
  17 +/**
  18 + * IoT 设备 MQTT 消息处理器
  19 + * <p>
  20 + * 参考:
  21 + * <p>
  22 + * "<a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.97a72915vRck44#section-g4j-5zg-12b">...</a>">
  23 + */
  24 +@Slf4j
  25 +public class IotDeviceMqttMessageHandler {
  26 +
  27 + // 设备上报属性 标准 JSON
  28 + // 请求Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
  29 + // 响应Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
  30 + // 设备上报事件 标准 JSON
  31 + // 请求Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post
  32 + // 响应Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply
  33 +
  34 + private static final String SYS_TOPIC_PREFIX = "/sys/";
  35 + private static final String PROPERTY_POST_TOPIC = "/thing/event/property/post";
  36 + private static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/";
  37 + private static final String EVENT_POST_TOPIC_SUFFIX = "/post";
  38 +
  39 + private final IotDeviceUpstreamApi deviceUpstreamApi;
  40 + private final MqttClient mqttClient;
  41 +
  42 + public IotDeviceMqttMessageHandler(IotDeviceUpstreamApi deviceUpstreamApi, MqttClient mqttClient) {
  43 + this.deviceUpstreamApi = deviceUpstreamApi;
  44 + this.mqttClient = mqttClient;
  45 + }
  46 +
  47 + public void handle(MqttPublishMessage message) {
  48 + String topic = message.topicName();
  49 + String payload = message.payload().toString();
  50 + log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload);
  51 +
  52 + try {
  53 + handleMessage(topic, payload);
  54 + } catch (Exception e) {
  55 + log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e);
  56 + }
  57 + }
  58 +
  59 + private void handleMessage(String topic, String payload) {
  60 + // 校验前缀
  61 + if (!topic.startsWith(SYS_TOPIC_PREFIX)) {
  62 + log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
  63 + return;
  64 + }
  65 +
  66 + // 处理设备属性上报消息
  67 + if (topic.endsWith(PROPERTY_POST_TOPIC)) {
  68 + log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic);
  69 + handlePropertyPost(topic, payload);
  70 + return;
  71 + }
  72 +
  73 + // 处理设备事件上报消息
  74 + if (topic.contains(EVENT_POST_TOPIC_PREFIX) && topic.endsWith(EVENT_POST_TOPIC_SUFFIX)) {
  75 + log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic);
  76 + handleEventPost(topic, payload);
  77 + return;
  78 + }
  79 +
  80 + // 未知消息类型
  81 + log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
  82 + }
  83 +
  84 + /**
  85 + * 处理设备属性上报消息
  86 + *
  87 + * @param topic 主题
  88 + * @param payload 消息内容
  89 + */
  90 + private void handlePropertyPost(String topic, String payload) {
  91 + // 解析消息内容
  92 + JSONObject jsonObject = JSONUtil.parseObj(payload);
  93 + String[] topicParts = topic.split("/");
  94 +
  95 + // 构建设备属性上报请求对象
  96 + IotDevicePropertyReportReqDTO reportReqDTO = buildPropertyReportDTO(jsonObject, topicParts);
  97 +
  98 + // 调用上游 API 处理设备上报数据
  99 + deviceUpstreamApi.reportDeviceProperty(reportReqDTO);
  100 + log.info("[handlePropertyPost][处理设备上行消息成功][topic: {}][reportReqDTO: {}]",
  101 + topic, JSONUtil.toJsonStr(reportReqDTO));
  102 +
  103 + // 发送响应消息
  104 + String replyTopic = topic + "_reply";
  105 + JSONObject response = new JSONObject()
  106 + .set("id", jsonObject.getStr("id"))
  107 + .set("code", 200)
  108 + .set("data", new JSONObject())
  109 + .set("message", "success")
  110 + .set("method", "thing.event.property.post");
  111 +
  112 + mqttClient.publish(replyTopic,
  113 + Buffer.buffer(response.toString()),
  114 + MqttQoS.AT_LEAST_ONCE,
  115 + false,
  116 + false);
  117 + log.info("[handlePropertyPost][发送响应消息成功][topic: {}][response: {}]",
  118 + replyTopic, response.toString());
  119 + }
  120 +
  121 + /**
  122 + * 处理设备事件上报消息
  123 + *
  124 + * @param topic 主题
  125 + * @param payload 消息内容
  126 + */
  127 + private void handleEventPost(String topic, String payload) {
  128 + // 解析消息内容
  129 + JSONObject jsonObject = JSONUtil.parseObj(payload);
  130 + String[] topicParts = topic.split("/");
  131 +
  132 + // 构建设备事件上报请求对象
  133 + IotDeviceEventReportReqDTO reportReqDTO = buildEventReportDTO(jsonObject, topicParts);
  134 +
  135 + // 调用上游 API 处理设备上报数据
  136 + deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
  137 + log.info("[handleEventPost][处理设备上行消息成功][topic: {}][reportReqDTO: {}]",
  138 + topic, JSONUtil.toJsonStr(reportReqDTO));
  139 +
  140 + // 发送响应消息
  141 + String replyTopic = topic + "_reply";
  142 + String eventIdentifier = topicParts[6]; // 从 topic 中获取事件标识符
  143 + JSONObject response = new JSONObject()
  144 + .set("id", jsonObject.getStr("id"))
  145 + .set("code", 200)
  146 + .set("data", new JSONObject())
  147 + .set("message", "success")
  148 + .set("method", "thing.event." + eventIdentifier + ".post");
  149 +
  150 + mqttClient.publish(replyTopic,
  151 + Buffer.buffer(response.toString()),
  152 + MqttQoS.AT_LEAST_ONCE,
  153 + false,
  154 + false);
  155 + log.info("[handleEventPost][发送响应消息成功][topic: {}][response: {}]",
  156 + replyTopic, response.toString());
  157 + }
  158 +
  159 + /**
  160 + * 构建设备属性上报请求对象
  161 + *
  162 + * @param jsonObject 消息内容
  163 + * @param topicParts 主题部分
  164 + * @return 设备属性上报请求对象
  165 + */
  166 + private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject,
  167 + String[] topicParts) {
  168 + return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
  169 + .setRequestId(jsonObject.getStr("id"))
  170 + .setProcessId(IotPluginCommonUtils.getProcessId())
  171 + .setReportTime(LocalDateTime.now())
  172 + .setProductKey(topicParts[2])
  173 + .setDeviceName(topicParts[3]))
  174 + .setProperties(jsonObject.getJSONObject("params"));
  175 + }
  176 +
  177 + /**
  178 + * 构建设备事件上报请求对象
  179 + *
  180 + * @param jsonObject 消息内容
  181 + * @param topicParts 主题部分
  182 + * @return 设备事件上报请求对象
  183 + */
  184 + private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) {
  185 + return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO()
  186 + .setRequestId(jsonObject.getStr("id"))
  187 + .setProcessId(IotPluginCommonUtils.getProcessId())
  188 + .setReportTime(LocalDateTime.now())
  189 + .setProductKey(topicParts[2])
  190 + .setDeviceName(topicParts[3]))
  191 + .setIdentifier(topicParts[4])
  192 + .setParams(jsonObject.getJSONObject("params"));
  193 + }
  194 +}
0 195 \ No newline at end of file
... ...