Commit 569eef4a743610606762e3ffd9f077a4d320ef25

Authored by haohao
1 parent b06556da

feat:【IoT 物联网】更新设备消息处理逻辑,重构 MQTT 下行消息处理器,优化主题构建和消息发布流程

Showing 13 changed files with 344 additions and 749 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java
@@ -22,7 +22,7 @@ import java.time.LocalDateTime; @@ -22,7 +22,7 @@ import java.time.LocalDateTime;
22 22
23 /** 23 /**
24 * 针对 {@link IotDeviceMessage} 的业务处理器:调用 method 对应的逻辑。例如说: 24 * 针对 {@link IotDeviceMessage} 的业务处理器:调用 method 对应的逻辑。例如说:
25 - * 1. {@link IotDeviceMessageMethodEnum#PROPERTY_REPORT} 属性上报时,记录设备属性 25 + * 1. {@link IotDeviceMessageMethodEnum#PROPERTY_POST} 属性上报时,记录设备属性
26 * 26 *
27 * @author alwayssuper 27 * @author alwayssuper
28 */ 28 */
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java
@@ -126,7 +126,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -126,7 +126,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
126 126
127 @Override 127 @Override
128 public void handleUpstreamDeviceMessage(IotDeviceMessage message, IotDeviceDO device) { 128 public void handleUpstreamDeviceMessage(IotDeviceMessage message, IotDeviceDO device) {
129 - // 1. 理消息 129 + // 1. 处理消息
130 Object replyData = null; 130 Object replyData = null;
131 ServiceException serviceException = null; 131 ServiceException serviceException = null;
132 try { 132 try {
@@ -175,8 +175,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -175,8 +175,7 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
175 } 175 }
176 176
177 // 属性上报 177 // 属性上报
178 - if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.PROPERTY_REPORT.getMethod()) ||  
179 - Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())) { 178 + if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())) {
180 devicePropertyService.saveDeviceProperty(device, message); 179 devicePropertyService.saveDeviceProperty(device, message);
181 return null; 180 return null;
182 } 181 }
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
@@ -23,15 +23,13 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { @@ -23,15 +23,13 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
23 STATE_OFFLINE("thing.state.offline", true), 23 STATE_OFFLINE("thing.state.offline", true),
24 24
25 // ========== 设备属性 ========== 25 // ========== 设备属性 ==========
26 - // 可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services  
27 - // TODO @haohao:使用 report 哈; 26 + // 可参考
  27 + // https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services
28 PROPERTY_POST("thing.property.post", true), 28 PROPERTY_POST("thing.property.post", true),
29 - PROPERTY_REPORT("thing.property.report", true),  
30 -  
31 PROPERTY_SET("thing.property.set", false), 29 PROPERTY_SET("thing.property.set", false),
32 - PROPERTY_GET("thing.property.get", false),  
33 30
34 - // 31 + // ========== 设备事件 ==========
  32 + EVENT_POST("thing.event.post", true),
35 33
36 ; 34 ;
37 35
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
@@ -25,9 +25,9 @@ public class IotDeviceMessage { @@ -25,9 +25,9 @@ public class IotDeviceMessage {
25 public static final String MESSAGE_BUS_DEVICE_MESSAGE_TOPIC = "iot_device_message"; 25 public static final String MESSAGE_BUS_DEVICE_MESSAGE_TOPIC = "iot_device_message";
26 26
27 /** 27 /**
28 - * 【消息总线】设备消息 Topic,由 iot-biz 发送给 iot-gateway 的某个 “server”(protocol) 进行消费 28 + * 【消息总线】设备消息 Topic,由 iot-biz 发送给 iot-gateway 的某个 "server"(protocol) 进行消费
29 * 29 *
30 - * 其中,%s 就是该“server”(protocol) 的标识 30 + * 其中,%s 就是该"server"(protocol) 的标识
31 */ 31 */
32 public static final String MESSAGE_BUS_GATEWAY_DEVICE_MESSAGE_TOPIC = MESSAGE_BUS_DEVICE_MESSAGE_TOPIC + "_%s"; 32 public static final String MESSAGE_BUS_GATEWAY_DEVICE_MESSAGE_TOPIC = MESSAGE_BUS_DEVICE_MESSAGE_TOPIC + "_%s";
33 33
@@ -92,7 +92,7 @@ public class IotDeviceMessage { @@ -92,7 +92,7 @@ public class IotDeviceMessage {
92 */ 92 */
93 private String msg; 93 private String msg;
94 94
95 - // ========== 基础方法:只传递“codec(编解码)字段” ========== 95 + // ========== 基础方法:只传递"codec(编解码)字段" ==========
96 96
97 public static IotDeviceMessage requestOf(String method) { 97 public static IotDeviceMessage requestOf(String method) {
98 return requestOf(null, method, null); 98 return requestOf(null, method, null);
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
@@ -45,7 +45,7 @@ public class IotGatewayConfiguration { @@ -45,7 +45,7 @@ public class IotGatewayConfiguration {
45 public static class MqttProtocolConfiguration { 45 public static class MqttProtocolConfiguration {
46 46
47 @Bean 47 @Bean
48 - public IotMqttUpstreamProtocol iotMqttUnifiedProtocol(IotGatewayProperties gatewayProperties) { 48 + public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties) {
49 return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getEmqx()); 49 return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getEmqx());
50 } 50 }
51 51
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
@@ -134,7 +134,7 @@ public class IotGatewayProperties { @@ -134,7 +134,7 @@ public class IotGatewayProperties {
134 private String mqttPassword; 134 private String mqttPassword;
135 135
136 /** 136 /**
137 - * MQTT 是否开启 SSL(默认:false) 137 + * MQTT 客户端的 SSL 开关
138 */ 138 */
139 @NotNull(message = "MQTT 是否开启 SSL 不能为空") 139 @NotNull(message = "MQTT 是否开启 SSL 不能为空")
140 private Boolean mqttSsl = false; 140 private Boolean mqttSsl = false;
@@ -145,12 +145,31 @@ public class IotGatewayProperties { @@ -145,12 +145,31 @@ public class IotGatewayProperties {
145 private String mqttClientId; 145 private String mqttClientId;
146 146
147 /** 147 /**
148 - * MQTT 主题列表 148 + * MQTT 订阅的主题
149 */ 149 */
150 @NotEmpty(message = "MQTT 主题不能为空") 150 @NotEmpty(message = "MQTT 主题不能为空")
151 private List<String> mqttTopics; 151 private List<String> mqttTopics;
152 152
153 /** 153 /**
  154 + * 默认 QoS 级别
  155 + * <p>
  156 + * 0 - 最多一次
  157 + * 1 - 至少一次
  158 + * 2 - 刚好一次
  159 + */
  160 + private Integer mqttQos = 1;
  161 +
  162 + /**
  163 + * 连接超时时间(秒)
  164 + */
  165 + private Integer connectTimeoutSeconds = 10;
  166 +
  167 + /**
  168 + * 重连延迟时间(毫秒)
  169 + */
  170 + private Long reconnectDelayMs = 5000L;
  171 +
  172 + /**
154 * 获取 MQTT 客户端 ID,如果未配置则自动生成 173 * 获取 MQTT 客户端 ID,如果未配置则自动生成
155 * 174 *
156 * @return MQTT 客户端 ID 175 * @return MQTT 客户端 ID
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/enums/IotDeviceTopicEnum.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.enums;  
2 -  
3 -import lombok.Getter;  
4 -import lombok.RequiredArgsConstructor;  
5 -  
6 -// TODO @haohao:这个类,我们是不是可以删除了哈?  
7 -/**  
8 - * IoT 设备主题枚举  
9 - * <p>  
10 - * 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范  
11 - *  
12 - * @author haohao  
13 - */  
14 -@RequiredArgsConstructor  
15 -@Getter  
16 -public enum IotDeviceTopicEnum {  
17 -  
18 - /**  
19 - * 设备属性设置主题  
20 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/property/set  
21 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/property/set_reply  
22 - */  
23 - PROPERTY_SET_TOPIC("/thing/property/set", "设备属性设置主题"),  
24 -  
25 - /**  
26 - * 设备属性获取主题  
27 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/property/get  
28 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/property/get_reply  
29 - */  
30 - PROPERTY_GET_TOPIC("/thing/property/get", "设备属性获取主题"),  
31 -  
32 - /**  
33 - * 设备配置设置主题  
34 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/config/set  
35 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/config/set_reply  
36 - */  
37 - CONFIG_SET_TOPIC("/thing/config/set", "设备配置设置主题"),  
38 -  
39 - /**  
40 - * 设备 OTA 升级主题  
41 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/ota/upgrade  
42 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/ota/upgrade_reply  
43 - */  
44 - OTA_UPGRADE_TOPIC("/thing/ota/upgrade", "设备 OTA 升级主题"),  
45 -  
46 - /**  
47 - * 设备属性上报主题  
48 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post  
49 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply  
50 - */  
51 - PROPERTY_POST_TOPIC("/thing/property/post", "设备属性上报主题"),  
52 -  
53 - /**  
54 - * 设备事件上报主题前缀  
55 - */  
56 - EVENT_POST_TOPIC_PREFIX("/thing/event/", "设备事件上报主题前缀"),  
57 -  
58 - /**  
59 - * 设备事件上报主题后缀  
60 - */  
61 - EVENT_POST_TOPIC_SUFFIX("/post", "设备事件上报主题后缀");  
62 -  
63 - // ========== 静态常量 ==========  
64 -  
65 - /**  
66 - * 系统主题前缀  
67 - */  
68 - public static final String SYS_TOPIC_PREFIX = "/sys/";  
69 -  
70 - /**  
71 - * 服务调用主题前缀  
72 - */  
73 - public static final String SERVICE_TOPIC_PREFIX = "/thing/";  
74 -  
75 - /**  
76 - * 响应主题后缀  
77 - */  
78 - public static final String REPLY_SUFFIX = "_reply";  
79 -  
80 - // ========== 方法常量 ==========  
81 -  
82 - /**  
83 - * 服务方法前缀  
84 - */  
85 - public static final String SERVICE_METHOD_PREFIX = "thing.";  
86 -  
87 - /**  
88 - * 属性服务方法前缀  
89 - */  
90 - public static final String PROPERTY_SERVICE_METHOD_PREFIX = "thing.property.";  
91 -  
92 - /**  
93 - * 配置服务方法前缀  
94 - */  
95 - public static final String CONFIG_SERVICE_METHOD_PREFIX = "thing.config.";  
96 -  
97 - /**  
98 - * OTA 服务方法前缀  
99 - */  
100 - public static final String OTA_SERVICE_METHOD_PREFIX = "thing.ota.";  
101 -  
102 - /**  
103 - * 属性设置方法  
104 - */  
105 - public static final String PROPERTY_SET_METHOD = "thing.property.set";  
106 -  
107 - /**  
108 - * 属性获取方法  
109 - */  
110 - public static final String PROPERTY_GET_METHOD = "thing.property.get";  
111 -  
112 - // ========== 主题匹配常量 ==========  
113 -  
114 - /**  
115 - * 事件上报主题模式  
116 - */  
117 - public static final String EVENT_POST_TOPIC_PATTERN = "/thing/event/";  
118 -  
119 - /**  
120 - * 主题后缀:post  
121 - */  
122 - public static final String POST_SUFFIX = "/post";  
123 -  
124 - /**  
125 - * 属性上报主题后缀  
126 - */  
127 - public static final String PROPERTY_POST_SUFFIX = "/thing/property/post";  
128 -  
129 - /**  
130 - * 属性设置响应主题包含  
131 - */  
132 - public static final String PROPERTY_SET_TOPIC_CONTAINS = "/thing/property/set";  
133 -  
134 - /**  
135 - * 属性获取响应主题包含  
136 - */  
137 - public static final String PROPERTY_GET_TOPIC_CONTAINS = "/thing/property/get";  
138 -  
139 - // ========== MQTT 认证路径常量 ==========  
140 -  
141 - /**  
142 - * MQTT 认证路径  
143 - */  
144 - public static final String MQTT_AUTH_AUTHENTICATE_PATH = "/mqtt/auth/authenticate";  
145 -  
146 - /**  
147 - * MQTT 连接事件路径  
148 - */  
149 - public static final String MQTT_AUTH_CONNECTED_PATH = "/mqtt/auth/connected";  
150 -  
151 - /**  
152 - * MQTT 断开事件路径  
153 - */  
154 - public static final String MQTT_AUTH_DISCONNECTED_PATH = "/mqtt/auth/disconnected";  
155 -  
156 - private final String topic;  
157 - private final String description;  
158 -  
159 - // ========== 工具方法 ==========  
160 -  
161 - /**  
162 - * 构建设备主题前缀  
163 - *  
164 - * @param productKey 产品 Key  
165 - * @param deviceName 设备名称  
166 - * @return 设备主题前缀:/sys/{productKey}/{deviceName}  
167 - */  
168 - private static String buildDeviceTopicPrefix(String productKey, String deviceName) {  
169 - return SYS_TOPIC_PREFIX + productKey + "/" + deviceName;  
170 - }  
171 -  
172 - /**  
173 - * 构建设备服务调用主题  
174 - *  
175 - * @param productKey 产品 Key  
176 - * @param deviceName 设备名称  
177 - * @param serviceIdentifier 服务标识符  
178 - * @return 完整的主题路径  
179 - */  
180 - public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {  
181 - return buildDeviceTopicPrefix(productKey, deviceName) + SERVICE_TOPIC_PREFIX + serviceIdentifier;  
182 - }  
183 -  
184 - /**  
185 - * 构建设备属性设置主题  
186 - *  
187 - * @param productKey 产品 Key  
188 - * @param deviceName 设备名称  
189 - * @return 完整的主题路径  
190 - */  
191 - public static String buildPropertySetTopic(String productKey, String deviceName) {  
192 - return buildDeviceTopicPrefix(productKey, deviceName) + PROPERTY_SET_TOPIC.getTopic();  
193 - }  
194 -  
195 - /**  
196 - * 构建设备属性获取主题  
197 - *  
198 - * @param productKey 产品 Key  
199 - * @param deviceName 设备名称  
200 - * @return 完整的主题路径  
201 - */  
202 - public static String buildPropertyGetTopic(String productKey, String deviceName) {  
203 - return buildDeviceTopicPrefix(productKey, deviceName) + PROPERTY_GET_TOPIC.getTopic();  
204 - }  
205 -  
206 - /**  
207 - * 构建设备配置设置主题  
208 - *  
209 - * @param productKey 产品 Key  
210 - * @param deviceName 设备名称  
211 - * @return 完整的主题路径  
212 - */  
213 - public static String buildConfigSetTopic(String productKey, String deviceName) {  
214 - return buildDeviceTopicPrefix(productKey, deviceName) + CONFIG_SET_TOPIC.getTopic();  
215 - }  
216 -  
217 - /**  
218 - * 构建设备 OTA 升级主题  
219 - *  
220 - * @param productKey 产品 Key  
221 - * @param deviceName 设备名称  
222 - * @return 完整的主题路径  
223 - */  
224 - public static String buildOtaUpgradeTopic(String productKey, String deviceName) {  
225 - return buildDeviceTopicPrefix(productKey, deviceName) + OTA_UPGRADE_TOPIC.getTopic();  
226 - }  
227 -  
228 - /**  
229 - * 构建设备属性上报主题  
230 - *  
231 - * @param productKey 产品 Key  
232 - * @param deviceName 设备名称  
233 - * @return 完整的主题路径  
234 - */  
235 - public static String buildPropertyPostTopic(String productKey, String deviceName) {  
236 - return buildDeviceTopicPrefix(productKey, deviceName) + PROPERTY_POST_TOPIC.getTopic();  
237 - }  
238 -  
239 - /**  
240 - * 构建设备事件上报主题  
241 - *  
242 - * @param productKey 产品 Key  
243 - * @param deviceName 设备名称  
244 - * @param eventIdentifier 事件标识符  
245 - * @return 完整的主题路径  
246 - */  
247 - public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) {  
248 - return buildDeviceTopicPrefix(productKey, deviceName) +  
249 - EVENT_POST_TOPIC_PREFIX.getTopic() + eventIdentifier + EVENT_POST_TOPIC_SUFFIX.getTopic();  
250 - }  
251 -  
252 - /**  
253 - * 获取响应主题  
254 - *  
255 - * @param requestTopic 请求主题  
256 - * @return 响应主题  
257 - */  
258 - public static String getReplyTopic(String requestTopic) {  
259 - return requestTopic + REPLY_SUFFIX;  
260 - }  
261 -  
262 -}  
263 \ No newline at end of file 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/IotMqttDownstreamSubscriber.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2 2
3 -import cn.hutool.core.util.StrUtil;  
4 -import cn.hutool.json.JSONObject;  
5 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;  
6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 3 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
7 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
8 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; 5 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
11 -import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;  
12 -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; 8 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler;
13 import jakarta.annotation.PostConstruct; 9 import jakarta.annotation.PostConstruct;
14 -import jakarta.annotation.Resource;  
15 -import lombok.RequiredArgsConstructor;  
16 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
17 11
18 /** 12 /**
@@ -20,15 +14,18 @@ import lombok.extern.slf4j.Slf4j; @@ -20,15 +14,18 @@ import lombok.extern.slf4j.Slf4j;
20 * 14 *
21 * @author 芋道源码 15 * @author 芋道源码
22 */ 16 */
23 -@RequiredArgsConstructor  
24 @Slf4j 17 @Slf4j
25 public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> { 18 public class IotMqttDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
26 19
27 - private final IotMqttUpstreamProtocol protocol; 20 + private final IotMqttDownstreamHandler downstreamHandler;
28 private final IotMessageBus messageBus; 21 private final IotMessageBus messageBus;
  22 + private final IotMqttUpstreamProtocol protocol;
29 23
30 - @Resource  
31 - private IotDeviceService deviceService; 24 + public IotMqttDownstreamSubscriber(IotMqttUpstreamProtocol protocol, IotMessageBus messageBus) {
  25 + this.protocol = protocol;
  26 + this.messageBus = messageBus;
  27 + this.downstreamHandler = new IotMqttDownstreamHandler(protocol);
  28 + }
32 29
33 @PostConstruct 30 @PostConstruct
34 public void init() { 31 public void init() {
@@ -51,7 +48,7 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi @@ -51,7 +48,7 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
51 log.info("[onMessage][接收到下行消息][messageId: {}][method: {}][deviceId: {}]", 48 log.info("[onMessage][接收到下行消息][messageId: {}][method: {}][deviceId: {}]",
52 message.getId(), message.getMethod(), message.getDeviceId()); 49 message.getId(), message.getMethod(), message.getDeviceId());
53 try { 50 try {
54 - // 根据消息方法处理不同的下行消息 51 + // 1. 校验
55 String method = message.getMethod(); 52 String method = message.getMethod();
56 if (method == null) { 53 if (method == null) {
57 log.warn("[onMessage][消息方法为空][messageId: {}][deviceId: {}]", 54 log.warn("[onMessage][消息方法为空][messageId: {}][deviceId: {}]",
@@ -59,15 +56,8 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi @@ -59,15 +56,8 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
59 return; 56 return;
60 } 57 }
61 58
62 - // 过滤上行消息:下行订阅者只处理下行消息  
63 - if (isUpstreamMessage(method)) {  
64 - // TODO @haohao:打个 erroor log,按道理不会发生;  
65 - log.debug("[onMessage][忽略上行消息][method: {}][messageId: {}]", method, message.getId());  
66 - return;  
67 - }  
68 -  
69 - // 处理下行消息  
70 - handleDownstreamMessage(message); 59 + // 2. 处理下行消息
  60 + downstreamHandler.handle(message);
71 } catch (Exception e) { 61 } catch (Exception e) {
72 log.error("[onMessage][处理下行消息失败][messageId: {}][method: {}][deviceId: {}]", 62 log.error("[onMessage][处理下行消息失败][messageId: {}][method: {}][deviceId: {}]",
73 message.getId(), message.getMethod(), message.getDeviceId(), e); 63 message.getId(), message.getMethod(), message.getDeviceId(), e);
@@ -75,99 +65,6 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi @@ -75,99 +65,6 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
75 } 65 }
76 66
77 /** 67 /**
78 - * 处理下行消息  
79 - *  
80 - * @param message 设备消息  
81 - */  
82 - private void handleDownstreamMessage(IotDeviceMessage message) {  
83 - // 1. 获取设备信息(使用缓存)  
84 - IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());  
85 - if (deviceInfo == null) {  
86 - log.warn("[handleDownstreamMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());  
87 - return;  
88 - }  
89 -  
90 - // 2. 根据方法构建主题  
91 - String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName());  
92 - if (StrUtil.isBlank(topic)) {  
93 - log.warn("[handleDownstreamMessage][未知的消息方法:{}]", message.getMethod());  
94 - return;  
95 - }  
96 -  
97 - // 3. 构建载荷  
98 - JSONObject payload = buildDownstreamPayload(message, message.getMethod());  
99 -  
100 - // 4. 发送消息  
101 - protocol.publishMessage(topic, payload.toString());  
102 - log.info("[handleDownstreamMessage][发送下行消息成功][method: {}][topic: {}]", message.getMethod(), topic);  
103 - }  
104 -  
105 - /**  
106 - * 根据方法构建主题  
107 - *  
108 - * @param method 消息方法  
109 - * @param productKey 产品标识  
110 - * @param deviceName 设备名称  
111 - * @return 构建的主题,如果方法不支持返回 null  
112 - */  
113 - private String buildTopicByMethod(String method, String productKey, String deviceName) {  
114 - if (StrUtil.isBlank(method)) {  
115 - return null;  
116 - }  
117 -  
118 - // 属性相关操作  
119 - if (method.startsWith(IotDeviceTopicEnum.PROPERTY_SERVICE_METHOD_PREFIX)) {  
120 - if (IotDeviceTopicEnum.PROPERTY_SET_METHOD.equals(method)) {  
121 - return IotDeviceTopicEnum.buildPropertySetTopic(productKey, deviceName);  
122 - } else if (IotDeviceTopicEnum.PROPERTY_GET_METHOD.equals(method)) {  
123 - return IotDeviceTopicEnum.buildPropertyGetTopic(productKey, deviceName);  
124 - }  
125 - return null;  
126 - }  
127 -  
128 - // 配置设置操作  
129 - if (method.startsWith(IotDeviceTopicEnum.CONFIG_SERVICE_METHOD_PREFIX)) {  
130 - return IotDeviceTopicEnum.buildConfigSetTopic(productKey, deviceName);  
131 - }  
132 -  
133 - // OTA 升级操作  
134 - if (method.startsWith(IotDeviceTopicEnum.OTA_SERVICE_METHOD_PREFIX)) {  
135 - return IotDeviceTopicEnum.buildOtaUpgradeTopic(productKey, deviceName);  
136 - }  
137 -  
138 - // 一般服务调用操作  
139 - if (method.startsWith(IotDeviceTopicEnum.SERVICE_METHOD_PREFIX)) {  
140 - // 排除属性、配置、OTA 相关的服务调用  
141 - if (method.contains("property") || method.contains("config") || method.contains("ota")) {  
142 - return null; // 已在上面处理  
143 - }  
144 - // 从方法中提取服务标识符  
145 - String serviceIdentifier = method.substring(IotDeviceTopicEnum.SERVICE_METHOD_PREFIX.length());  
146 - return IotDeviceTopicEnum.buildServiceTopic(productKey, deviceName, serviceIdentifier);  
147 - }  
148 -  
149 - // 不支持的方法  
150 - return null;  
151 - }  
152 -  
153 - // TODO @haohao:按道理说,这里的应该是通过 encodeMessage;  
154 - /**  
155 - * 构建下行消息载荷  
156 - *  
157 - * @param message 设备消息  
158 - * @param method 方法名  
159 - * @return JSON 载荷  
160 - */  
161 - private JSONObject buildDownstreamPayload(IotDeviceMessage message, String method) {  
162 - JSONObject payload = new JSONObject();  
163 - payload.set("id", message.getId()); // 使用正确的消息ID字段  
164 - payload.set("version", "1.0");  
165 - payload.set("method", method);  
166 - payload.set("params", message.getData());  
167 - return payload;  
168 - }  
169 -  
170 - /**  
171 * 判断是否为上行消息 68 * 判断是否为上行消息
172 * 69 *
173 * @param method 消息方法 70 * @param method 消息方法
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java
@@ -4,9 +4,9 @@ import cn.hutool.core.collection.CollUtil; @@ -4,9 +4,9 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 5 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
6 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; 6 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
7 -import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;  
8 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttHttpAuthHandler; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttHttpAuthHandler;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamHandler; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamHandler;
  9 +import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
10 import io.netty.handler.codec.mqtt.MqttQoS; 10 import io.netty.handler.codec.mqtt.MqttQoS;
11 import io.vertx.core.Vertx; 11 import io.vertx.core.Vertx;
12 import io.vertx.core.buffer.Buffer; 12 import io.vertx.core.buffer.Buffer;
@@ -21,13 +21,10 @@ import lombok.Getter; @@ -21,13 +21,10 @@ import lombok.Getter;
21 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
22 22
23 import java.util.List; 23 import java.util.List;
24 -import java.util.concurrent.CompletableFuture;  
25 import java.util.concurrent.TimeUnit; 24 import java.util.concurrent.TimeUnit;
26 25
27 -// TODO @haohao:看看有没多余的 log,可以不打噢。  
28 -// TODO @haohao:有没多余的注释可以去掉,减少 ai 味,保持简洁;  
29 /** 26 /**
30 - * IoT 网关 MQTT 统一协议 27 + * IoT 网关 MQTT 协议:接收设备上行消息
31 * <p> 28 * <p>
32 * 1. MQTT 客户端:连接 EMQX,消费处理设备上行和下行消息 29 * 1. MQTT 客户端:连接 EMQX,消费处理设备上行和下行消息
33 * 2. HTTP 认证服务:为 EMQX 提供设备认证、连接、断开接口 30 * 2. HTTP 认证服务:为 EMQX 提供设备认证、连接、断开接口
@@ -37,24 +34,6 @@ import java.util.concurrent.TimeUnit; @@ -37,24 +34,6 @@ import java.util.concurrent.TimeUnit;
37 @Slf4j 34 @Slf4j
38 public class IotMqttUpstreamProtocol { 35 public class IotMqttUpstreamProtocol {
39 36
40 - // TODO @haohao:是不是也丢到配置里?  
41 - /**  
42 - * 默认 QoS 级别 - 至少一次  
43 - */  
44 - private static final MqttQoS DEFAULT_QOS = MqttQoS.AT_LEAST_ONCE;  
45 -  
46 - // TODO @haohao:这个也是;  
47 - /**  
48 - * 连接超时时间(秒)  
49 - */  
50 - private static final int CONNECT_TIMEOUT_SECONDS = 10;  
51 -  
52 - // TODO @haohao:重连也是;  
53 - /**  
54 - * 重连延迟时间(毫秒)  
55 - */  
56 - private static final long RECONNECT_DELAY_MS = 5000;  
57 -  
58 private final IotGatewayProperties.EmqxProperties emqxProperties; 37 private final IotGatewayProperties.EmqxProperties emqxProperties;
59 38
60 private Vertx vertx; 39 private Vertx vertx;
@@ -68,8 +47,6 @@ public class IotMqttUpstreamProtocol { @@ -68,8 +47,6 @@ public class IotMqttUpstreamProtocol {
68 47
69 // HTTP 认证服务相关 48 // HTTP 认证服务相关
70 private HttpServer httpAuthServer; 49 private HttpServer httpAuthServer;
71 - // TODO @haohao:authHandler 可以 local 哈;  
72 - private IotMqttHttpAuthHandler authHandler;  
73 50
74 /** 51 /**
75 * 服务运行状态标志 52 * 服务运行状态标志
@@ -92,7 +69,6 @@ public class IotMqttUpstreamProtocol { @@ -92,7 +69,6 @@ public class IotMqttUpstreamProtocol {
92 try { 69 try {
93 // 1. 创建共享的 Vertx 实例 70 // 1. 创建共享的 Vertx 实例
94 this.vertx = Vertx.vertx(); 71 this.vertx = Vertx.vertx();
95 - log.info("[start][共享 Vertx 实例创建成功]");  
96 72
97 // 2. 启动 HTTP 认证服务 73 // 2. 启动 HTTP 认证服务
98 startHttpAuthServer(); 74 startHttpAuthServer();
@@ -103,7 +79,6 @@ public class IotMqttUpstreamProtocol { @@ -103,7 +79,6 @@ public class IotMqttUpstreamProtocol {
103 isRunning = true; 79 isRunning = true;
104 log.info("[start][MQTT 统一协议服务启动完成]"); 80 log.info("[start][MQTT 统一协议服务启动完成]");
105 } catch (Exception e) { 81 } catch (Exception e) {
106 - // TODO @haohao:失败,是不是直接 System.exit 哈!  
107 log.error("[start][MQTT 统一协议服务启动失败]", e); 82 log.error("[start][MQTT 统一协议服务启动失败]", e);
108 // 启动失败时清理资源 83 // 启动失败时清理资源
109 stop(); 84 stop();
@@ -150,12 +125,10 @@ public class IotMqttUpstreamProtocol { @@ -150,12 +125,10 @@ public class IotMqttUpstreamProtocol {
150 router.route().handler(BodyHandler.create()); 125 router.route().handler(BodyHandler.create());
151 126
152 // 创建认证处理器 127 // 创建认证处理器
153 - this.authHandler = new IotMqttHttpAuthHandler();  
154 -  
155 - // 添加认证路由  
156 - router.post(IotDeviceTopicEnum.MQTT_AUTH_AUTHENTICATE_PATH).handler(authHandler::authenticate);  
157 - router.post(IotDeviceTopicEnum.MQTT_AUTH_CONNECTED_PATH).handler(authHandler::connected);  
158 - router.post(IotDeviceTopicEnum.MQTT_AUTH_DISCONNECTED_PATH).handler(authHandler::disconnected); 128 + IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler();
  129 + router.post(IotMqttTopicUtils.MQTT_AUTH_AUTHENTICATE_PATH).handler(authHandler::authenticate);
  130 + router.post(IotMqttTopicUtils.MQTT_AUTH_CONNECTED_PATH).handler(authHandler::connected);
  131 + router.post(IotMqttTopicUtils.MQTT_AUTH_DISCONNECTED_PATH).handler(authHandler::disconnected);
159 132
160 // 启动 HTTP 服务器 133 // 启动 HTTP 服务器
161 int authPort = emqxProperties.getHttpAuthPort(); 134 int authPort = emqxProperties.getHttpAuthPort();
@@ -175,14 +148,14 @@ public class IotMqttUpstreamProtocol { @@ -175,14 +148,14 @@ public class IotMqttUpstreamProtocol {
175 * 停止 HTTP 认证服务 148 * 停止 HTTP 认证服务
176 */ 149 */
177 private void stopHttpAuthServer() { 150 private void stopHttpAuthServer() {
178 - // TODO @haohao:一些 if return 最好搞下;  
179 - if (httpAuthServer != null) {  
180 - try {  
181 - httpAuthServer.close().result();  
182 - log.info("[stopHttpAuthServer][HTTP 认证服务已停止]");  
183 - } catch (Exception e) {  
184 - log.error("[stopHttpAuthServer][HTTP 认证服务停止失败]", e);  
185 - } 151 + if (httpAuthServer == null) {
  152 + return;
  153 + }
  154 + try {
  155 + httpAuthServer.close().result();
  156 + log.info("[stopHttpAuthServer][HTTP 认证服务已停止]");
  157 + } catch (Exception e) {
  158 + log.error("[stopHttpAuthServer][HTTP 认证服务停止失败]", e);
186 } 159 }
187 } 160 }
188 161
@@ -192,10 +165,10 @@ public class IotMqttUpstreamProtocol { @@ -192,10 +165,10 @@ public class IotMqttUpstreamProtocol {
192 private void startMqttClient() { 165 private void startMqttClient() {
193 log.info("[startMqttClient][开始启动 MQTT 客户端]"); 166 log.info("[startMqttClient][开始启动 MQTT 客户端]");
194 167
195 - // 初始化消息处理器 168 + // 1. 初始化消息处理器
196 this.upstreamHandler = new IotMqttUpstreamHandler(this); 169 this.upstreamHandler = new IotMqttUpstreamHandler(this);
197 170
198 - // 创建 MQTT 客户端 171 + // 2. 创建 MQTT 客户端
199 log.info("[startMqttClient][使用 MQTT 客户端 ID: {}]", emqxProperties.getMqttClientId()); 172 log.info("[startMqttClient][使用 MQTT 客户端 ID: {}]", emqxProperties.getMqttClientId());
200 173
201 MqttClientOptions options = new MqttClientOptions() 174 MqttClientOptions options = new MqttClientOptions()
@@ -205,7 +178,7 @@ public class IotMqttUpstreamProtocol { @@ -205,7 +178,7 @@ public class IotMqttUpstreamProtocol {
205 .setSsl(emqxProperties.getMqttSsl()); 178 .setSsl(emqxProperties.getMqttSsl());
206 this.mqttClient = MqttClient.create(vertx, options); 179 this.mqttClient = MqttClient.create(vertx, options);
207 180
208 - // 连接 MQTT Broker 181 + // 3. 连接 MQTT Broker
209 connectMqtt(); 182 connectMqtt();
210 } 183 }
211 184
@@ -229,13 +202,13 @@ public class IotMqttUpstreamProtocol { @@ -229,13 +202,13 @@ public class IotMqttUpstreamProtocol {
229 } 202 }
230 203
231 // 2. 关闭 MQTT 客户端 204 // 2. 关闭 MQTT 客户端
232 - try {  
233 - if (mqttClient != null && mqttClient.isConnected()) { 205 + if (mqttClient != null && mqttClient.isConnected()) {
  206 + try {
234 mqttClient.disconnect(); 207 mqttClient.disconnect();
235 log.info("[stopMqttClient][MQTT 客户端已断开]"); 208 log.info("[stopMqttClient][MQTT 客户端已断开]");
  209 + } catch (Exception e) {
  210 + log.warn("[stopMqttClient][关闭 MQTT 客户端异常]", e);
236 } 211 }
237 - } catch (Exception e) {  
238 - log.warn("[stopMqttClient][关闭 MQTT 客户端异常]", e);  
239 } 212 }
240 } 213 }
241 214
@@ -243,10 +216,9 @@ public class IotMqttUpstreamProtocol { @@ -243,10 +216,9 @@ public class IotMqttUpstreamProtocol {
243 * 连接 MQTT Broker 并订阅主题 216 * 连接 MQTT Broker 并订阅主题
244 */ 217 */
245 private void connectMqtt() { 218 private void connectMqtt() {
246 - // 参数校验 219 + // 1. 参数校验
247 String host = emqxProperties.getMqttHost(); 220 String host = emqxProperties.getMqttHost();
248 Integer port = emqxProperties.getMqttPort(); 221 Integer port = emqxProperties.getMqttPort();
249 -  
250 if (StrUtil.isBlank(host)) { 222 if (StrUtil.isBlank(host)) {
251 log.error("[connectMqtt][MQTT Host 为空,无法连接]"); 223 log.error("[connectMqtt][MQTT Host 为空,无法连接]");
252 throw new IllegalArgumentException("MQTT Host 不能为空"); 224 throw new IllegalArgumentException("MQTT Host 不能为空");
@@ -255,63 +227,47 @@ public class IotMqttUpstreamProtocol { @@ -255,63 +227,47 @@ public class IotMqttUpstreamProtocol {
255 log.error("[connectMqtt][MQTT Port 无效:{}]", port); 227 log.error("[connectMqtt][MQTT Port 无效:{}]", port);
256 throw new IllegalArgumentException("MQTT Port 必须为正整数"); 228 throw new IllegalArgumentException("MQTT Port 必须为正整数");
257 } 229 }
258 -  
259 log.info("[connectMqtt][开始连接 MQTT Broker][host: {}][port: {}]", host, port); 230 log.info("[connectMqtt][开始连接 MQTT Broker][host: {}][port: {}]", host, port);
260 231
261 - CompletableFuture<Void> connectFuture = mqttClient.connect(port, host)  
262 - .toCompletionStage()  
263 - .toCompletableFuture()  
264 - .thenAccept(connAck -> {  
265 - // TODO @haohao:是不是可以连接完,然后在执行里面;不用 通过 thenAccept 哈;  
266 - log.info("[connectMqtt][MQTT 客户端连接成功][host: {}][port: {}]", host, port);  
267 - // 设置断开重连监听器  
268 - mqttClient.closeHandler(closeEvent -> {  
269 - log.warn("[closeHandler][MQTT 连接已断开,准备重连]");  
270 - reconnectWithDelay();  
271 - });  
272 - // 设置消息处理器  
273 - setupMessageHandler();  
274 - // 订阅主题  
275 - subscribeToTopics();  
276 - })  
277 - .exceptionally(error -> {  
278 - // TODO @haohao:这里的异常,是不是不用重连哈?因为直接就退出了。然后有 closeHandler 监听重连了;  
279 - log.error("[connectMqtt][连接 MQTT Broker 失败][host: {}][port: {}]", host, port, error);  
280 - // 连接失败时也要尝试重连  
281 - reconnectWithDelay();  
282 - return null;  
283 - });  
284 -  
285 - // 等待连接完成 232 + // 2. 连接
286 try { 233 try {
287 - connectFuture.get(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);  
288 - log.info("[connectMqtt][MQTT 客户端启动完成]"); 234 + mqttClient.connect(port, host)
  235 + .toCompletionStage()
  236 + .toCompletableFuture()
  237 + .get(emqxProperties.getConnectTimeoutSeconds(), TimeUnit.SECONDS);
  238 + log.info("[connectMqtt][MQTT 客户端连接成功][host: {}][port: {}]", host, port);
  239 +
  240 + // 3. 设置处理器
  241 + // 3.1 设置断开重连监听器
  242 + mqttClient.closeHandler(closeEvent -> {
  243 + log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
  244 + reconnectWithDelay();
  245 + });
  246 + // 3.2 设置消息处理器
  247 + mqttClient.publishHandler(upstreamHandler::handle);
  248 + log.debug("[setupMessageHandler][MQTT 消息处理器设置完成]");
  249 +
  250 + // 4. 订阅主题
  251 + subscribeToTopics();
289 } catch (Exception e) { 252 } catch (Exception e) {
290 - log.error("[connectMqtt][MQTT 客户端启动失败]", e); 253 + log.error("[connectMqtt][连接 MQTT Broker 失败][host: {}][port: {}]", host, port, e);
  254 + reconnectWithDelay(); // 连接失败时,也要尝试重连
291 throw new RuntimeException("MQTT 客户端启动失败", e); 255 throw new RuntimeException("MQTT 客户端启动失败", e);
292 } 256 }
293 } 257 }
294 258
295 /** 259 /**
296 - * 设置 MQTT 消息处理器  
297 - */  
298 - private void setupMessageHandler() {  
299 - mqttClient.publishHandler(upstreamHandler::handle);  
300 - log.debug("[setupMessageHandler][MQTT 消息处理器设置完成]");  
301 - }  
302 -  
303 - /**  
304 * 订阅设备上行消息主题 260 * 订阅设备上行消息主题
305 */ 261 */
306 private void subscribeToTopics() { 262 private void subscribeToTopics() {
307 List<String> topicList = emqxProperties.getMqttTopics(); 263 List<String> topicList = emqxProperties.getMqttTopics();
  264 + int qos = emqxProperties.getMqttQos();
308 265
309 log.info("[subscribeToTopics][开始订阅主题,共 {} 个]", topicList.size()); 266 log.info("[subscribeToTopics][开始订阅主题,共 {} 个]", topicList.size());
310 -  
311 for (String topic : topicList) { 267 for (String topic : topicList) {
312 - mqttClient.subscribe(topic, DEFAULT_QOS.value(), subscribeResult -> { 268 + mqttClient.subscribe(topic, qos, subscribeResult -> {
313 if (subscribeResult.succeeded()) { 269 if (subscribeResult.succeeded()) {
314 - log.info("[subscribeToTopics][订阅主题成功: {}][QoS: {}]", topic, DEFAULT_QOS.value()); 270 + log.info("[subscribeToTopics][订阅主题成功: {}][QoS: {}]", topic, qos);
315 } else { 271 } else {
316 log.error("[subscribeToTopics][订阅主题失败: {}]", topic, subscribeResult.cause()); 272 log.error("[subscribeToTopics][订阅主题失败: {}]", topic, subscribeResult.cause());
317 } 273 }
@@ -323,17 +279,17 @@ public class IotMqttUpstreamProtocol { @@ -323,17 +279,17 @@ public class IotMqttUpstreamProtocol {
323 * 延迟重连 279 * 延迟重连
324 */ 280 */
325 private void reconnectWithDelay() { 281 private void reconnectWithDelay() {
326 - vertx.setTimer(RECONNECT_DELAY_MS, timerId -> {  
327 - // TODO @haohao:if return,括号少一些;  
328 - if (isRunning && (mqttClient == null || !mqttClient.isConnected())) {  
329 - log.info("[reconnectWithDelay][开始重连 MQTT Broker,延迟 {} 毫秒]", RECONNECT_DELAY_MS);  
330 - try {  
331 - connectMqtt();  
332 - } catch (Exception e) {  
333 - log.error("[reconnectWithDelay][重连失败,将继续尝试重连]", e);  
334 - // 重连失败时继续尝试重连  
335 - reconnectWithDelay();  
336 - } 282 + long delay = emqxProperties.getReconnectDelayMs();
  283 + vertx.setTimer(delay, timerId -> {
  284 + if (!isRunning || (mqttClient != null && mqttClient.isConnected())) {
  285 + return;
  286 + }
  287 + log.info("[reconnectWithDelay][开始重连 MQTT Broker,延迟 {} 毫秒]", delay);
  288 + try {
  289 + connectMqtt();
  290 + } catch (Exception e) {
  291 + log.error("[reconnectWithDelay][重连失败,将继续尝试重连]", e);
  292 + reconnectWithDelay(); // 失败后,继续尝试
337 } 293 }
338 }); 294 });
339 } 295 }
@@ -345,12 +301,12 @@ public class IotMqttUpstreamProtocol { @@ -345,12 +301,12 @@ public class IotMqttUpstreamProtocol {
345 * @param payload 消息内容 301 * @param payload 消息内容
346 */ 302 */
347 public void publishMessage(String topic, String payload) { 303 public void publishMessage(String topic, String payload) {
348 - if (mqttClient != null && mqttClient.isConnected()) {  
349 - mqttClient.publish(topic, Buffer.buffer(payload), DEFAULT_QOS, false, false);  
350 - log.debug("[publishMessage][发布消息成功][topic: {}]", topic);  
351 - } else { 304 + if (mqttClient == null || !mqttClient.isConnected()) {
352 log.warn("[publishMessage][MQTT 客户端未连接,无法发布消息][topic: {}]", topic); 305 log.warn("[publishMessage][MQTT 客户端未连接,无法发布消息][topic: {}]", topic);
  306 + return;
353 } 307 }
  308 + MqttQoS qos = MqttQoS.valueOf(emqxProperties.getMqttQos());
  309 + mqttClient.publish(topic, Buffer.buffer(payload), qos, false, false);
354 } 310 }
355 311
356 } 312 }
357 \ No newline at end of file 313 \ 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 cn.hutool.core.util.StrUtil;  
4 -import lombok.extern.slf4j.Slf4j;  
5 -  
6 -// TODO @haohao:是不是不用基类哈;  
7 -/**  
8 - * IoT 网关 MQTT 协议的处理器抽象基类  
9 - * <p>  
10 - * 提供通用的异常处理、参数校验等功能  
11 - *  
12 - * @author 芋道源码  
13 - */  
14 -@Slf4j  
15 -public abstract class IotMqttAbstractHandler {  
16 -  
17 - /**  
18 - * 处理 MQTT 消息的模板方法  
19 - *  
20 - * @param topic 主题  
21 - * @param payload 消息内容  
22 - */  
23 - public final void handle(String topic, String payload) {  
24 - try {  
25 - // 1. 前置校验  
26 - if (!validateInput(topic, payload)) {  
27 - return;  
28 - }  
29 -  
30 - // 2. 执行具体逻辑  
31 - doHandle(topic, payload);  
32 -  
33 - } catch (Exception e) {  
34 - log.error("[handle][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, payload, e);  
35 - handleException(topic, payload, e);  
36 - }  
37 - }  
38 -  
39 - /**  
40 - * 具体的处理逻辑,由子类实现  
41 - *  
42 - * @param topic 主题  
43 - * @param payload 消息内容  
44 - */  
45 - protected abstract void doHandle(String topic, String payload);  
46 -  
47 - /**  
48 - * 输入参数校验  
49 - *  
50 - * @param topic 主题  
51 - * @param payload 消息内容  
52 - * @return 校验是否通过  
53 - */  
54 - protected boolean validateInput(String topic, String payload) {  
55 - if (StrUtil.isBlank(topic)) {  
56 - log.warn("[validateInput][主题为空,忽略消息]");  
57 - return false;  
58 - }  
59 -  
60 - if (StrUtil.isBlank(payload)) {  
61 - log.warn("[validateInput][消息内容为空][topic: {}]", topic);  
62 - return false;  
63 - }  
64 -  
65 - return true;  
66 - }  
67 -  
68 - /**  
69 - * 异常处理  
70 - *  
71 - * @param topic 主题  
72 - * @param payload 消息内容  
73 - * @param e 异常  
74 - */  
75 - protected void handleException(String topic, String payload, Exception e) {  
76 - // 默认实现:记录错误日志  
77 - // 子类可以重写此方法,添加特定的异常处理逻辑  
78 - log.error("[handleException][MQTT 消息处理异常][topic: {}]", topic, e);  
79 - }  
80 -  
81 - /**  
82 - * 解析主题,获取主题各部分  
83 - *  
84 - * @param topic 主题  
85 - * @return 主题各部分数组,如果解析失败返回 null  
86 - */  
87 - protected String[] parseTopic(String topic) {  
88 - String[] topicParts = topic.split("/");  
89 - if (topicParts.length < 7) {  
90 - log.warn("[parseTopic][主题格式不正确][topic: {}]", topic);  
91 - return null;  
92 - }  
93 - return topicParts;  
94 - }  
95 -}  
96 \ No newline at end of file 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/IotMqttDownstreamHandler.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.hutool.json.JSONObject;
  6 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
  7 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
  10 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
  11 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  12 +import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +
  15 +/**
  16 + * IoT 网关 MQTT 下行消息处理器
  17 + * <p>
  18 + * 从消息总线接收到下行消息,然后发布到 MQTT Broker
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Slf4j
  23 +public class IotMqttDownstreamHandler {
  24 +
  25 + private final IotMqttUpstreamProtocol protocol;
  26 + private final IotDeviceService deviceService;
  27 + private final IotDeviceMessageService deviceMessageService;
  28 +
  29 + public IotMqttDownstreamHandler(IotMqttUpstreamProtocol protocol) {
  30 + this.protocol = protocol;
  31 + this.deviceService = SpringUtil.getBean(IotDeviceService.class);
  32 + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  33 + }
  34 +
  35 + /**
  36 + * 处理下行消息
  37 + *
  38 + * @param message 设备消息
  39 + */
  40 + public void handle(IotDeviceMessage message) {
  41 + // 1. 获取设备信息(使用缓存)
  42 + IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());
  43 + if (deviceInfo == null) {
  44 + log.warn("[handle][设备信息不存在][deviceId: {}]", message.getDeviceId());
  45 + return;
  46 + }
  47 +
  48 + // 2. 根据方法构建主题
  49 + String topic = buildTopicByMethod(message.getMethod(), deviceInfo.getProductKey(), deviceInfo.getDeviceName());
  50 + if (StrUtil.isBlank(topic)) {
  51 + log.warn("[handle][未知的消息方法:{}]", message.getMethod());
  52 + return;
  53 + }
  54 +
  55 + // 3. 构建载荷
  56 + JSONObject payload = buildDownstreamPayload(message);
  57 +
  58 + // 4. 发布消息
  59 + protocol.publishMessage(topic, payload.toString());
  60 + log.info("[handle][发布下行消息成功][method: {}][topic: {}]", message.getMethod(), topic);
  61 + }
  62 +
  63 + /**
  64 + * 根据方法构建主题
  65 + *
  66 + * @param method 消息方法
  67 + * @param productKey 产品标识
  68 + * @param deviceName 设备名称
  69 + * @return 构建的主题,如果方法不支持返回 null
  70 + */
  71 + private String buildTopicByMethod(String method, String productKey, String deviceName) {
  72 + IotDeviceMessageMethodEnum methodEnum = IotDeviceMessageMethodEnum.of(method);
  73 + if (methodEnum == null) {
  74 + return null;
  75 + }
  76 + return switch (methodEnum) {
  77 + case PROPERTY_POST -> IotMqttTopicUtils.buildPropertyPostReplyTopic(productKey, deviceName);
  78 + case PROPERTY_SET -> IotMqttTopicUtils.buildPropertySetTopic(productKey, deviceName);
  79 + default -> null;
  80 + };
  81 +
  82 + }
  83 +
  84 + /**
  85 + * 构建下行消息载荷
  86 + *
  87 + * @param message 设备消息
  88 + * @return JSON 载荷
  89 + */
  90 + private JSONObject buildDownstreamPayload(IotDeviceMessage message) {
  91 + // 使用 IotDeviceMessageService 进行消息编码
  92 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(message.getDeviceId());
  93 + byte[] encodedBytes = deviceMessageService.encodeDeviceMessage(message, device.getProductKey(),
  94 + device.getDeviceName());
  95 + return new JSONObject(new String(encodedBytes));
  96 + }
  97 +
  98 +}
0 \ No newline at end of file 99 \ 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
1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.hutool.extra.spring.SpringUtil; 4 import cn.hutool.extra.spring.SpringUtil;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 -import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;  
6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
8 import io.vertx.mqtt.messages.MqttPublishMessage; 8 import io.vertx.mqtt.messages.MqttPublishMessage;
9 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
10 -  
11 -import java.nio.charset.StandardCharsets; 10 +import org.springframework.util.Assert;
12 11
13 /** 12 /**
14 - * IoT 网关 MQTT 协议的【上行】处理器  
15 - * <p>  
16 - * 处理设备上行消息,包括事件上报、属性上报、服务调用响应等 13 + * IoT 网关 MQTT 上行消息处理器
17 * 14 *
18 * @author 芋道源码 15 * @author 芋道源码
19 */ 16 */
20 @Slf4j 17 @Slf4j
21 -public class IotMqttUpstreamHandler extends IotMqttAbstractHandler { 18 +public class IotMqttUpstreamHandler {
22 19
23 private final IotDeviceMessageService deviceMessageService; 20 private final IotDeviceMessageService deviceMessageService;
24 -  
25 private final String serverId; 21 private final String serverId;
26 22
27 public IotMqttUpstreamHandler(IotMqttUpstreamProtocol protocol) { 23 public IotMqttUpstreamHandler(IotMqttUpstreamProtocol protocol) {
@@ -34,167 +30,66 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler { @@ -34,167 +30,66 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler {
34 */ 30 */
35 public void handle(MqttPublishMessage message) { 31 public void handle(MqttPublishMessage message) {
36 String topic = message.topicName(); 32 String topic = message.topicName();
37 - // TODO @haohao: message.payload().getBytes();  
38 - String payload = message.payload().toString(StandardCharsets.UTF_8); 33 + byte[] payload = message.payload().getBytes();
39 34
40 log.debug("[handle][收到 MQTT 消息][topic: {}]", topic); 35 log.debug("[handle][收到 MQTT 消息][topic: {}]", topic);
41 - // 调用父类的 handle 方法,父类会进行参数校验  
42 - handle(topic, payload);  
43 - }  
44 36
45 - @Override  
46 - protected void doHandle(String topic, String payload) {  
47 - // 1. 识别并验证消息类型  
48 - String messageType = getMessageType(topic);  
49 - if (messageType == null) {  
50 - // TODO @haohao:log 是不是把 payload 也打印下哈  
51 - log.warn("[doHandle][未知的消息类型][topic: {}]", topic);  
52 - return; 37 + try {
  38 + // 1. 前置校验
  39 + if (StrUtil.isBlank(topic)) {
  40 + log.warn("[validateInput][主题为空,忽略消息]");
  41 + return;
  42 + }
  43 + // 注意:payload 可以为空
  44 +
  45 + // 2. 识别并验证消息类型
  46 + String messageType = getMessageType(topic);
  47 + Assert.notNull(messageType, String.format("未知的消息类型, topic(%s)", topic));
  48 + log.info("[handle][接收到{}][topic: {}]", messageType, topic);
  49 +
  50 + // 3. 解析主题,获取 productKey 和 deviceName
  51 + String[] topicParts = topic.split("/");
  52 + if (topicParts.length < 4) {
  53 + log.warn("[handle][主题格式不正确,无法解析 productKey 和 deviceName][topic: {}]", topic);
  54 + return;
  55 + }
  56 + String productKey = topicParts[2];
  57 + String deviceName = topicParts[3];
  58 + if (StrUtil.isAllBlank(productKey, deviceName)) {
  59 + log.warn("[handle][主题中 productKey 或 deviceName 为空][topic: {}]", topic);
  60 + return;
  61 + }
  62 +
  63 + // 4. 解码消息
  64 + IotDeviceMessage deviceMessage = deviceMessageService.decodeDeviceMessage(
  65 + payload, productKey, deviceName);
  66 + if (deviceMessage == null) {
  67 + log.warn("[handle][消息解码失败][topic: {}]", topic);
  68 + return;
  69 + }
  70 +
  71 + // 5. 发送消息到队列
  72 + deviceMessageService.sendDeviceMessage(deviceMessage, productKey, deviceName, serverId);
  73 +
  74 + // 6. 记录成功日志
  75 + log.info("[handle][处理{}成功,已转发到 MQ][topic: {}]", messageType, topic);
  76 + } catch (Exception e) {
  77 + log.error("[handle][处理 MQTT 消息失败][topic: {}][payload: {}]", topic, new String(payload), e);
53 } 78 }
54 -  
55 - // 2. 处理消息  
56 - processMessage(topic, payload, messageType);  
57 } 79 }
58 80
59 /** 81 /**
60 - * 处理消息的统一逻辑  
61 - */  
62 - private void processMessage(String topic, String payload, String messageType) {  
63 - // TODO @haohao:messageType 解析,是不是作用不大哈?  
64 - log.info("[processMessage][接收到{}][topic: {}]", messageType, topic);  
65 -  
66 - // 解析主题获取设备信息  
67 - // TODO @haohao:不一定是 7 个哈;阿里云 topic 有点差异的;可以考虑解析到 topicParts[2]、topicParts[3] 的 topic  
68 - String[] topicParts = parseTopic(topic);  
69 - if (topicParts == null) {  
70 - return;  
71 - }  
72 -  
73 - String productKey = topicParts[2];  
74 - String deviceName = topicParts[3];  
75 - // TODO @haohao:解析不到,可以打个 error log;  
76 -  
77 - // 解码消息  
78 - byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);  
79 - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(  
80 - messageBytes, productKey, deviceName);  
81 -  
82 - // 发送消息到队列  
83 - deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);  
84 -  
85 - // 记录成功日志  
86 - log.info("[processMessage][处理{}成功][topic: {}]", messageType, topic);  
87 - }  
88 -  
89 - // TODO @haohao:合并下处理;不搞成每个 topic 一个处理;  
90 - /**  
91 - * 识别消息类型 82 + * 从主题中,获得消息类型
92 * 83 *
93 * @param topic 主题 84 * @param topic 主题
94 - * @return 消息类型描述,如果不支持返回 null 85 + * @return 消息类型
95 */ 86 */
96 private String getMessageType(String topic) { 87 private String getMessageType(String topic) {
97 - // 此方法由 doHandle 调用,topic 已经在父类中校验过,无需重复校验  
98 -  
99 - // 按优先级匹配主题类型,避免误匹配  
100 -  
101 - // 1. 设备属性上报: /sys/{productKey}/{deviceName}/thing/property/post  
102 - if (isPropertyPostTopic(topic)) {  
103 - return IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getDescription();  
104 - }  
105 -  
106 - // 2. 设备事件上报: /sys/{productKey}/{deviceName}/thing/{eventIdentifier}/post  
107 - if (isEventPostTopic(topic)) {  
108 - return "设备事件上报";  
109 - }  
110 -  
111 - // 3. 设备属性设置响应: /sys/{productKey}/{deviceName}/thing/property/set_reply  
112 - if (isPropertySetReplyTopic(topic)) {  
113 - return "设备属性设置响应";  
114 - }  
115 -  
116 - // 4. 设备属性获取响应: /sys/{productKey}/{deviceName}/thing/property/get_reply  
117 - if (isPropertyGetReplyTopic(topic)) {  
118 - return "设备属性获取响应"; 88 + String[] topicParts = topic.split("/");
  89 + if (topicParts.length < 7) {
  90 + return null;
119 } 91 }
120 -  
121 - // 5. 设备配置设置响应: /sys/{productKey}/{deviceName}/thing/config/set_reply  
122 - if (isConfigSetReplyTopic(topic)) {  
123 - return IotDeviceTopicEnum.CONFIG_SET_TOPIC.getDescription() + "响应";  
124 - }  
125 -  
126 - // 6. 设备 OTA 升级响应:  
127 - // /sys/{productKey}/{deviceName}/thing/ota/upgrade_reply  
128 - if (isOtaUpgradeReplyTopic(topic)) {  
129 - return IotDeviceTopicEnum.OTA_UPGRADE_TOPIC.getDescription() + "响应";  
130 - }  
131 -  
132 - // 7. 其他服务调用响应: 通用服务调用响应  
133 - if (isServiceReplyTopic(topic)) {  
134 - return "设备服务调用响应";  
135 - }  
136 -  
137 - // 不支持的消息类型  
138 - return null;  
139 - }  
140 -  
141 - /**  
142 - * 判断是否为属性上报主题  
143 - */  
144 - private boolean isPropertyPostTopic(String topic) {  
145 - return topic.contains(IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic());  
146 - }  
147 -  
148 - /**  
149 - * 判断是否为事件上报主题  
150 - */  
151 - private boolean isEventPostTopic(String topic) {  
152 - return topic.contains(IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic())  
153 - && topic.endsWith(IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic())  
154 - && !topic.contains("property"); // 排除属性上报主题  
155 - }  
156 -  
157 - /**  
158 - * 判断是否为属性设置响应主题  
159 - */  
160 - private boolean isPropertySetReplyTopic(String topic) {  
161 - return topic.contains(IotDeviceTopicEnum.PROPERTY_SET_TOPIC.getTopic())  
162 - && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX);  
163 - }  
164 -  
165 - /**  
166 - * 判断是否为属性获取响应主题  
167 - */  
168 - private boolean isPropertyGetReplyTopic(String topic) {  
169 - return topic.contains(IotDeviceTopicEnum.PROPERTY_GET_TOPIC.getTopic())  
170 - && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX);  
171 - }  
172 -  
173 - /**  
174 - * 判断是否为配置设置响应主题  
175 - */  
176 - private boolean isConfigSetReplyTopic(String topic) {  
177 - return topic.contains(IotDeviceTopicEnum.CONFIG_SET_TOPIC.getTopic())  
178 - && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX);  
179 - }  
180 -  
181 - /**  
182 - * 判断是否为 OTA 升级响应主题  
183 - */  
184 - private boolean isOtaUpgradeReplyTopic(String topic) {  
185 - return topic.contains(IotDeviceTopicEnum.OTA_UPGRADE_TOPIC.getTopic())  
186 - && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX);  
187 - }  
188 -  
189 - /**  
190 - * 判断是否为服务调用响应主题(排除已处理的特殊服务)  
191 - */  
192 - private boolean isServiceReplyTopic(String topic) {  
193 - return topic.contains(IotDeviceTopicEnum.SERVICE_TOPIC_PREFIX)  
194 - && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX)  
195 - && !topic.contains("property")  
196 - && !topic.contains("config")  
197 - && !topic.contains("ota"); 92 + return topicParts[3];
198 } 93 }
199 94
200 } 95 }
201 \ No newline at end of file 96 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.util;
  2 +
  3 +/**
  4 + * IoT 网关 MQTT 主题工具类
  5 + * <p>
  6 + * 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范
  7 + *
  8 + * @author 芋道源码
  9 + */
  10 +public final class IotMqttTopicUtils {
  11 +
  12 + // ========== 静态常量 ==========
  13 +
  14 + /**
  15 + * 系统主题前缀
  16 + */
  17 + private static final String SYS_TOPIC_PREFIX = "/sys/";
  18 +
  19 + /**
  20 + * 服务调用主题前缀
  21 + */
  22 + private static final String SERVICE_TOPIC_PREFIX = "/thing/";
  23 +
  24 + // ========== MQTT 认证路径常量 ==========
  25 +
  26 + /**
  27 + * MQTT 认证路径
  28 + */
  29 + public static final String MQTT_AUTH_AUTHENTICATE_PATH = "/mqtt/auth/authenticate";
  30 +
  31 + /**
  32 + * MQTT 连接事件路径
  33 + */
  34 + public static final String MQTT_AUTH_CONNECTED_PATH = "/mqtt/auth/connected";
  35 +
  36 + /**
  37 + * MQTT 断开事件路径
  38 + */
  39 + public static final String MQTT_AUTH_DISCONNECTED_PATH = "/mqtt/auth/disconnected";
  40 +
  41 + // ========== 工具方法 ==========
  42 +
  43 + /**
  44 + * 构建设备主题前缀
  45 + *
  46 + * @param productKey 产品 Key
  47 + * @param deviceName 设备名称
  48 + * @return 设备主题前缀:/sys/{productKey}/{deviceName}
  49 + */
  50 + private static String buildDeviceTopicPrefix(String productKey, String deviceName) {
  51 + return SYS_TOPIC_PREFIX + productKey + "/" + deviceName;
  52 + }
  53 +
  54 + /**
  55 + * 构建设备属性设置主题
  56 + *
  57 + * @param productKey 产品 Key
  58 + * @param deviceName 设备名称
  59 + * @return 完整的主题路径
  60 + */
  61 + public static String buildPropertySetTopic(String productKey, String deviceName) {
  62 + return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/property/set";
  63 + }
  64 +
  65 + /**
  66 + * 构建设备属性上报回复主题
  67 + * <p>
  68 + * 当设备上报属性时,会收到该主题的回复
  69 + *
  70 + * @param productKey 产品 Key
  71 + * @param deviceName 设备名称
  72 + * @return 完整的主题路径
  73 + */
  74 + public static String buildPropertyPostReplyTopic(String productKey, String deviceName) {
  75 + return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/property/post_reply";
  76 + }
  77 +
  78 + /**
  79 + * 构建设备服务调用主题
  80 + *
  81 + * @param productKey 产品 Key
  82 + * @param deviceName 设备名称
  83 + * @param serviceIdentifier 服务标识符
  84 + * @return 完整的主题路径
  85 + */
  86 + public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
  87 + return buildDeviceTopicPrefix(productKey, deviceName) + SERVICE_TOPIC_PREFIX + serviceIdentifier;
  88 + }
  89 +
  90 +}
0 \ No newline at end of file 91 \ No newline at end of file