Commit a4e80d45fed7fe541e207510aea2ce7581b4c29d

Authored by haohao
1 parent c658ac69

fix:【IoT 物联网】修复设备属性上报和配置设置主题,优化 MQTT 消息处理逻辑,更新相关配置文件

yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java
@@ -144,12 +144,13 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -144,12 +144,13 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
144 144
145 // 3. 回复消息。前提:非 _reply 消息,并且非禁用回复的消息 145 // 3. 回复消息。前提:非 _reply 消息,并且非禁用回复的消息
146 if (IotDeviceMessageUtils.isReplyMessage(message) 146 if (IotDeviceMessageUtils.isReplyMessage(message)
147 - || IotDeviceMessageMethodEnum.isReplyDisabled(message.getMethod())  
148 - || StrUtil.isEmpty(message.getServerId())) { 147 + || IotDeviceMessageMethodEnum.isReplyDisabled(message.getMethod())
  148 + || StrUtil.isEmpty(message.getServerId())) {
149 return; 149 return;
150 } 150 }
151 try { 151 try {
152 - IotDeviceMessage replyMessage = IotDeviceMessage.replyOf(message.getRequestId(), message.getMethod(), replyData, 152 + IotDeviceMessage replyMessage = IotDeviceMessage.replyOf(message.getRequestId(), message.getMethod(),
  153 + replyData,
153 serviceException != null ? serviceException.getCode() : null, 154 serviceException != null ? serviceException.getCode() : null,
154 serviceException != null ? serviceException.getMessage() : null); 155 serviceException != null ? serviceException.getMessage() : null);
155 sendDeviceMessage(replyMessage, device, message.getServerId()); 156 sendDeviceMessage(replyMessage, device, message.getServerId());
@@ -175,7 +176,8 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -175,7 +176,8 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
175 } 176 }
176 177
177 // 属性上报 178 // 属性上报
178 - if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.PROPERTY_REPORT.getMethod())) { 179 + if (Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.PROPERTY_REPORT.getMethod()) ||
  180 + Objects.equal(message.getMethod(), IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())) {
179 devicePropertyService.saveDeviceProperty(device, message); 181 devicePropertyService.saveDeviceProperty(device, message);
180 return null; 182 return null;
181 } 183 }
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
@@ -24,6 +24,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { @@ -24,6 +24,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
24 24
25 // ========== 设备属性 ========== 25 // ========== 设备属性 ==========
26 // 可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 26 // 可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services
  27 + PROPERTY_POST("thing.property.post", true),
27 PROPERTY_REPORT("thing.property.report", true), 28 PROPERTY_REPORT("thing.property.report", true),
28 29
29 PROPERTY_SET("thing.property.set", false), 30 PROPERTY_SET("thing.property.set", false),
@@ -33,7 +34,8 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> { @@ -33,7 +34,8 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable<String> {
33 34
34 ; 35 ;
35 36
36 - public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageMethodEnum::getMethod).toArray(String[]::new); 37 + public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageMethodEnum::getMethod)
  38 + .toArray(String[]::new);
37 39
38 /** 40 /**
39 * 不进行 reply 回复的方法集合 41 * 不进行 reply 回复的方法集合
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
@@ -128,6 +128,10 @@ public class IotGatewayProperties { @@ -128,6 +128,10 @@ public class IotGatewayProperties {
128 */ 128 */
129 private Boolean mqttSsl; 129 private Boolean mqttSsl;
130 /** 130 /**
  131 + * MQTT客户端 ID
  132 + */
  133 + private String mqttClientId;
  134 + /**
131 * MQTT 主题 135 * MQTT 主题
132 */ 136 */
133 private List<String> mqttTopics; 137 private List<String> mqttTopics;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/enums/IotDeviceTopicEnum.java
@@ -14,52 +14,40 @@ import lombok.RequiredArgsConstructor; @@ -14,52 +14,40 @@ import lombok.RequiredArgsConstructor;
14 @Getter 14 @Getter
15 public enum IotDeviceTopicEnum { 15 public enum IotDeviceTopicEnum {
16 16
17 - // TODO @haohao:SYS_TOPIC_PREFIX、SERVICE_TOPIC_PREFIX、REPLY_SUFFIX 类似这种,要不搞成这个里面的静态变量?不是枚举值  
18 - /**  
19 - * 系统主题前缀  
20 - */  
21 - SYS_TOPIC_PREFIX("/sys/", "系统主题前缀"),  
22 -  
23 - /**  
24 - * 服务调用主题前缀  
25 - */  
26 - SERVICE_TOPIC_PREFIX("/thing/service/", "服务调用主题前缀"),  
27 -  
28 - // TODO @haohao:注释时,中英文之间,有个空格;  
29 /** 17 /**
30 * 设备属性设置主题 18 * 设备属性设置主题
31 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/service/property/set  
32 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply 19 + * 请求 Topic:/sys/${productKey}/${deviceName}/thing/property/set
  20 + * 响应 Topic:/sys/${productKey}/${deviceName}/thing/property/set_reply
33 */ 21 */
34 - PROPERTY_SET_TOPIC("/thing/service/property/set", "设备属性设置主题"), 22 + PROPERTY_SET_TOPIC("/thing/property/set", "设备属性设置主题"),
35 23
36 /** 24 /**
37 * 设备属性获取主题 25 * 设备属性获取主题
38 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/service/property/get  
39 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/service/property/get_reply 26 + * 请求 Topic:/sys/${productKey}/${deviceName}/thing/property/get
  27 + * 响应 Topic:/sys/${productKey}/${deviceName}/thing/property/get_reply
40 */ 28 */
41 - PROPERTY_GET_TOPIC("/thing/service/property/get", "设备属性获取主题"), 29 + PROPERTY_GET_TOPIC("/thing/property/get", "设备属性获取主题"),
42 30
43 /** 31 /**
44 * 设备配置设置主题 32 * 设备配置设置主题
45 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/service/config/set  
46 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/service/config/set_reply 33 + * 请求 Topic:/sys/${productKey}/${deviceName}/thing/config/set
  34 + * 响应 Topic:/sys/${productKey}/${deviceName}/thing/config/set_reply
47 */ 35 */
48 - CONFIG_SET_TOPIC("/thing/service/config/set", "设备配置设置主题"), 36 + CONFIG_SET_TOPIC("/thing/config/set", "设备配置设置主题"),
49 37
50 /** 38 /**
51 - * 设备OTA升级主题  
52 - * 请求 Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade  
53 - * 响应 Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade_reply 39 + * 设备 OTA 升级主题
  40 + * 请求 Topic:/sys/${productKey}/${deviceName}/thing/ota/upgrade
  41 + * 响应 Topic:/sys/${productKey}/${deviceName}/thing/ota/upgrade_reply
54 */ 42 */
55 - OTA_UPGRADE_TOPIC("/thing/service/ota/upgrade", "设备OTA升级主题"), 43 + OTA_UPGRADE_TOPIC("/thing/ota/upgrade", "设备 OTA 升级主题"),
56 44
57 /** 45 /**
58 * 设备属性上报主题 46 * 设备属性上报主题
59 * 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post 47 * 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
60 * 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply 48 * 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
61 */ 49 */
62 - PROPERTY_POST_TOPIC("/thing/event/property/post", "设备属性上报主题"), 50 + PROPERTY_POST_TOPIC("/thing/property/post", "设备属性上报主题"),
63 51
64 /** 52 /**
65 * 设备事件上报主题前缀 53 * 设备事件上报主题前缀
@@ -69,95 +57,194 @@ public enum IotDeviceTopicEnum { @@ -69,95 +57,194 @@ public enum IotDeviceTopicEnum {
69 /** 57 /**
70 * 设备事件上报主题后缀 58 * 设备事件上报主题后缀
71 */ 59 */
72 - EVENT_POST_TOPIC_SUFFIX("/post", "设备事件上报主题后缀"), 60 + EVENT_POST_TOPIC_SUFFIX("/post", "设备事件上报主题后缀");
  61 +
  62 + // ========== 静态常量 ==========
  63 +
  64 + /**
  65 + * 系统主题前缀
  66 + */
  67 + public static final String SYS_TOPIC_PREFIX = "/sys/";
  68 +
  69 + /**
  70 + * 服务调用主题前缀
  71 + */
  72 + public static final String SERVICE_TOPIC_PREFIX = "/thing/";
73 73
74 /** 74 /**
75 * 响应主题后缀 75 * 响应主题后缀
76 */ 76 */
77 - REPLY_SUFFIX("_reply", "响应主题后缀"); 77 + public static final String REPLY_SUFFIX = "_reply";
  78 +
  79 + // ========== 方法常量 ==========
  80 +
  81 + /**
  82 + * 服务方法前缀
  83 + */
  84 + public static final String SERVICE_METHOD_PREFIX = "thing.";
  85 +
  86 + /**
  87 + * 属性服务方法前缀
  88 + */
  89 + public static final String PROPERTY_SERVICE_METHOD_PREFIX = "thing.property.";
  90 +
  91 + /**
  92 + * 配置服务方法前缀
  93 + */
  94 + public static final String CONFIG_SERVICE_METHOD_PREFIX = "thing.config.";
  95 +
  96 + /**
  97 + * OTA 服务方法前缀
  98 + */
  99 + public static final String OTA_SERVICE_METHOD_PREFIX = "thing.ota.";
  100 +
  101 + /**
  102 + * 属性设置方法
  103 + */
  104 + public static final String PROPERTY_SET_METHOD = "thing.property.set";
  105 +
  106 + /**
  107 + * 属性获取方法
  108 + */
  109 + public static final String PROPERTY_GET_METHOD = "thing.property.get";
  110 +
  111 + // ========== 主题匹配常量 ==========
  112 +
  113 + /**
  114 + * 事件上报主题模式
  115 + */
  116 + public static final String EVENT_POST_TOPIC_PATTERN = "/thing/event/";
  117 +
  118 + /**
  119 + * 主题后缀:post
  120 + */
  121 + public static final String POST_SUFFIX = "/post";
  122 +
  123 + /**
  124 + * 属性上报主题后缀
  125 + */
  126 + public static final String PROPERTY_POST_SUFFIX = "/thing/property/post";
  127 +
  128 + /**
  129 + * 属性设置响应主题包含
  130 + */
  131 + public static final String PROPERTY_SET_TOPIC_CONTAINS = "/thing/property/set";
  132 +
  133 + /**
  134 + * 属性获取响应主题包含
  135 + */
  136 + public static final String PROPERTY_GET_TOPIC_CONTAINS = "/thing/property/get";
  137 +
  138 + // ========== MQTT 认证路径常量 ==========
  139 +
  140 + /**
  141 + * MQTT 认证路径
  142 + */
  143 + public static final String MQTT_AUTH_AUTHENTICATE_PATH = "/mqtt/auth/authenticate";
  144 +
  145 + /**
  146 + * MQTT 连接事件路径
  147 + */
  148 + public static final String MQTT_AUTH_CONNECTED_PATH = "/mqtt/auth/connected";
  149 +
  150 + /**
  151 + * MQTT 断开事件路径
  152 + */
  153 + public static final String MQTT_AUTH_DISCONNECTED_PATH = "/mqtt/auth/disconnected";
78 154
79 private final String topic; 155 private final String topic;
80 private final String description; 156 private final String description;
81 157
  158 + // ========== 工具方法 ==========
  159 +
  160 + /**
  161 + * 构建设备主题前缀
  162 + *
  163 + * @param productKey 产品 Key
  164 + * @param deviceName 设备名称
  165 + * @return 设备主题前缀:/sys/{productKey}/{deviceName}
  166 + */
  167 + private static String buildDeviceTopicPrefix(String productKey, String deviceName) {
  168 + return SYS_TOPIC_PREFIX + productKey + "/" + deviceName;
  169 + }
  170 +
82 /** 171 /**
83 * 构建设备服务调用主题 172 * 构建设备服务调用主题
84 * 173 *
85 - * @param productKey 产品Key 174 + * @param productKey 产品 Key
86 * @param deviceName 设备名称 175 * @param deviceName 设备名称
87 * @param serviceIdentifier 服务标识符 176 * @param serviceIdentifier 服务标识符
88 * @return 完整的主题路径 177 * @return 完整的主题路径
89 */ 178 */
90 public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) { 179 public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
91 - // TODO @haohao:貌似 SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName 是统一的;  
92 - return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName +  
93 - SERVICE_TOPIC_PREFIX.getTopic() + serviceIdentifier; 180 + return buildDeviceTopicPrefix(productKey, deviceName) + SERVICE_TOPIC_PREFIX + serviceIdentifier;
94 } 181 }
95 182
96 /** 183 /**
97 * 构建设备属性设置主题 184 * 构建设备属性设置主题
98 * 185 *
99 - * @param productKey 产品Key 186 + * @param productKey 产品 Key
100 * @param deviceName 设备名称 187 * @param deviceName 设备名称
101 * @return 完整的主题路径 188 * @return 完整的主题路径
102 */ 189 */
103 public static String buildPropertySetTopic(String productKey, String deviceName) { 190 public static String buildPropertySetTopic(String productKey, String deviceName) {
104 - return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_SET_TOPIC.getTopic(); 191 + return buildDeviceTopicPrefix(productKey, deviceName) + PROPERTY_SET_TOPIC.getTopic();
105 } 192 }
106 193
107 /** 194 /**
108 * 构建设备属性获取主题 195 * 构建设备属性获取主题
109 * 196 *
110 - * @param productKey 产品Key 197 + * @param productKey 产品 Key
111 * @param deviceName 设备名称 198 * @param deviceName 设备名称
112 * @return 完整的主题路径 199 * @return 完整的主题路径
113 */ 200 */
114 public static String buildPropertyGetTopic(String productKey, String deviceName) { 201 public static String buildPropertyGetTopic(String productKey, String deviceName) {
115 - return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_GET_TOPIC.getTopic(); 202 + return buildDeviceTopicPrefix(productKey, deviceName) + PROPERTY_GET_TOPIC.getTopic();
116 } 203 }
117 204
118 /** 205 /**
119 * 构建设备配置设置主题 206 * 构建设备配置设置主题
120 * 207 *
121 - * @param productKey 产品Key 208 + * @param productKey 产品 Key
122 * @param deviceName 设备名称 209 * @param deviceName 设备名称
123 * @return 完整的主题路径 210 * @return 完整的主题路径
124 */ 211 */
125 public static String buildConfigSetTopic(String productKey, String deviceName) { 212 public static String buildConfigSetTopic(String productKey, String deviceName) {
126 - return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + CONFIG_SET_TOPIC.getTopic(); 213 + return buildDeviceTopicPrefix(productKey, deviceName) + CONFIG_SET_TOPIC.getTopic();
127 } 214 }
128 215
129 /** 216 /**
130 * 构建设备 OTA 升级主题 217 * 构建设备 OTA 升级主题
131 * 218 *
132 - * @param productKey 产品Key 219 + * @param productKey 产品 Key
133 * @param deviceName 设备名称 220 * @param deviceName 设备名称
134 * @return 完整的主题路径 221 * @return 完整的主题路径
135 */ 222 */
136 public static String buildOtaUpgradeTopic(String productKey, String deviceName) { 223 public static String buildOtaUpgradeTopic(String productKey, String deviceName) {
137 - return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + OTA_UPGRADE_TOPIC.getTopic(); 224 + return buildDeviceTopicPrefix(productKey, deviceName) + OTA_UPGRADE_TOPIC.getTopic();
138 } 225 }
139 226
140 /** 227 /**
141 * 构建设备属性上报主题 228 * 构建设备属性上报主题
142 * 229 *
143 - * @param productKey 产品Key 230 + * @param productKey 产品 Key
144 * @param deviceName 设备名称 231 * @param deviceName 设备名称
145 * @return 完整的主题路径 232 * @return 完整的主题路径
146 */ 233 */
147 public static String buildPropertyPostTopic(String productKey, String deviceName) { 234 public static String buildPropertyPostTopic(String productKey, String deviceName) {
148 - return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_POST_TOPIC.getTopic(); 235 + return buildDeviceTopicPrefix(productKey, deviceName) + PROPERTY_POST_TOPIC.getTopic();
149 } 236 }
150 237
151 /** 238 /**
152 * 构建设备事件上报主题 239 * 构建设备事件上报主题
153 * 240 *
154 - * @param productKey 产品Key 241 + * @param productKey 产品 Key
155 * @param deviceName 设备名称 242 * @param deviceName 设备名称
156 * @param eventIdentifier 事件标识符 243 * @param eventIdentifier 事件标识符
157 * @return 完整的主题路径 244 * @return 完整的主题路径
158 */ 245 */
159 public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) { 246 public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) {
160 - return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + 247 + return buildDeviceTopicPrefix(productKey, deviceName) +
161 EVENT_POST_TOPIC_PREFIX.getTopic() + eventIdentifier + EVENT_POST_TOPIC_SUFFIX.getTopic(); 248 EVENT_POST_TOPIC_PREFIX.getTopic() + eventIdentifier + EVENT_POST_TOPIC_SUFFIX.getTopic();
162 } 249 }
163 250
@@ -168,7 +255,7 @@ public enum IotDeviceTopicEnum { @@ -168,7 +255,7 @@ public enum IotDeviceTopicEnum {
168 * @return 响应主题 255 * @return 响应主题
169 */ 256 */
170 public static String getReplyTopic(String requestTopic) { 257 public static String getReplyTopic(String requestTopic) {
171 - return requestTopic + REPLY_SUFFIX.getTopic(); 258 + return requestTopic + REPLY_SUFFIX;
172 } 259 }
173 260
174 } 261 }
175 \ No newline at end of file 262 \ 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
@@ -105,33 +105,33 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi @@ -105,33 +105,33 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
105 } 105 }
106 106
107 // 属性相关操作 107 // 属性相关操作
108 - if (method.startsWith("thing.service.property.")) {  
109 - if ("thing.service.property.set".equals(method)) { 108 + if (method.startsWith(IotDeviceTopicEnum.PROPERTY_SERVICE_METHOD_PREFIX)) {
  109 + if (IotDeviceTopicEnum.PROPERTY_SET_METHOD.equals(method)) {
110 return IotDeviceTopicEnum.buildPropertySetTopic(productKey, deviceName); 110 return IotDeviceTopicEnum.buildPropertySetTopic(productKey, deviceName);
111 - } else if ("thing.service.property.get".equals(method)) { 111 + } else if (IotDeviceTopicEnum.PROPERTY_GET_METHOD.equals(method)) {
112 return IotDeviceTopicEnum.buildPropertyGetTopic(productKey, deviceName); 112 return IotDeviceTopicEnum.buildPropertyGetTopic(productKey, deviceName);
113 } 113 }
114 return null; 114 return null;
115 } 115 }
116 116
117 // 配置设置操作 117 // 配置设置操作
118 - if (method.startsWith("thing.service.config.")) { 118 + if (method.startsWith(IotDeviceTopicEnum.CONFIG_SERVICE_METHOD_PREFIX)) {
119 return IotDeviceTopicEnum.buildConfigSetTopic(productKey, deviceName); 119 return IotDeviceTopicEnum.buildConfigSetTopic(productKey, deviceName);
120 } 120 }
121 121
122 // OTA 升级操作 122 // OTA 升级操作
123 - if (method.startsWith("thing.service.ota.")) { 123 + if (method.startsWith(IotDeviceTopicEnum.OTA_SERVICE_METHOD_PREFIX)) {
124 return IotDeviceTopicEnum.buildOtaUpgradeTopic(productKey, deviceName); 124 return IotDeviceTopicEnum.buildOtaUpgradeTopic(productKey, deviceName);
125 } 125 }
126 126
127 // 一般服务调用操作 127 // 一般服务调用操作
128 - if (method.startsWith("thing.service.")) {  
129 - // 排除属性、配置、OTA相关的服务调用 128 + if (method.startsWith(IotDeviceTopicEnum.SERVICE_METHOD_PREFIX)) {
  129 + // 排除属性、配置、OTA 相关的服务调用
130 if (method.contains("property") || method.contains("config") || method.contains("ota")) { 130 if (method.contains("property") || method.contains("config") || method.contains("ota")) {
131 return null; // 已在上面处理 131 return null; // 已在上面处理
132 } 132 }
133 // 从方法中提取服务标识符 133 // 从方法中提取服务标识符
134 - String serviceIdentifier = method.substring("thing.service.".length()); 134 + String serviceIdentifier = method.substring(IotDeviceTopicEnum.SERVICE_METHOD_PREFIX.length());
135 return IotDeviceTopicEnum.buildServiceTopic(productKey, deviceName, serviceIdentifier); 135 return IotDeviceTopicEnum.buildServiceTopic(productKey, deviceName, serviceIdentifier);
136 } 136 }
137 137
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.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.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.hutool.core.util.IdUtil;  
5 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
6 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; 7 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  8 +import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttHttpAuthHandler; 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 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamHandler;
11 import io.netty.handler.codec.mqtt.MqttQoS; 11 import io.netty.handler.codec.mqtt.MqttQoS;
@@ -137,9 +137,9 @@ public class IotMqttUpstreamProtocol { @@ -137,9 +137,9 @@ public class IotMqttUpstreamProtocol {
137 this.authHandler = new IotMqttHttpAuthHandler(); 137 this.authHandler = new IotMqttHttpAuthHandler();
138 138
139 // 添加认证路由 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); 140 + router.post(IotDeviceTopicEnum.MQTT_AUTH_AUTHENTICATE_PATH).handler(authHandler::authenticate);
  141 + router.post(IotDeviceTopicEnum.MQTT_AUTH_CONNECTED_PATH).handler(authHandler::connected);
  142 + router.post(IotDeviceTopicEnum.MQTT_AUTH_DISCONNECTED_PATH).handler(authHandler::disconnected);
143 143
144 // 启动 HTTP 服务器 144 // 启动 HTTP 服务器
145 int authPort = emqxProperties.getHttpAuthPort(); 145 int authPort = emqxProperties.getHttpAuthPort();
@@ -176,11 +176,13 @@ public class IotMqttUpstreamProtocol { @@ -176,11 +176,13 @@ public class IotMqttUpstreamProtocol {
176 log.info("[startMqttClient][开始启动 MQTT 客户端]"); 176 log.info("[startMqttClient][开始启动 MQTT 客户端]");
177 177
178 // 初始化消息处理器 178 // 初始化消息处理器
179 - this.upstreamHandler = new IotMqttUpstreamHandler(); 179 + this.upstreamHandler = new IotMqttUpstreamHandler(this);
180 180
181 // 创建 MQTT 客户端 181 // 创建 MQTT 客户端
  182 + log.info("[startMqttClient][使用 MQTT 客户端 ID: {}]", emqxProperties.getMqttClientId());
  183 +
182 MqttClientOptions options = new MqttClientOptions() 184 MqttClientOptions options = new MqttClientOptions()
183 - .setClientId("yudao-iot-gateway-" + IdUtil.fastSimpleUUID()) 185 + .setClientId(emqxProperties.getMqttClientId())
184 .setUsername(emqxProperties.getMqttUsername()) 186 .setUsername(emqxProperties.getMqttUsername())
185 .setPassword(emqxProperties.getMqttPassword()) 187 .setPassword(emqxProperties.getMqttPassword())
186 .setSsl(ObjUtil.defaultIfNull(emqxProperties.getMqttSsl(), false)); 188 .setSsl(ObjUtil.defaultIfNull(emqxProperties.getMqttSsl(), false));
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamHandler.java
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil; @@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
4 import cn.hutool.extra.spring.SpringUtil; 4 import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; 6 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
  7 +import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 8 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
8 import io.vertx.mqtt.messages.MqttPublishMessage; 9 import io.vertx.mqtt.messages.MqttPublishMessage;
9 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
@@ -22,10 +23,12 @@ public class IotMqttUpstreamHandler { @@ -22,10 +23,12 @@ public class IotMqttUpstreamHandler {
22 23
23 private final IotDeviceMessageProducer deviceMessageProducer; 24 private final IotDeviceMessageProducer deviceMessageProducer;
24 private final IotDeviceMessageService deviceMessageService; 25 private final IotDeviceMessageService deviceMessageService;
  26 + private final String serverId;
25 27
26 - public IotMqttUpstreamHandler() { 28 + public IotMqttUpstreamHandler(cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol protocol) {
27 this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class); 29 this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
28 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); 30 this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  31 + this.serverId = protocol.getServerId();
29 } 32 }
30 33
31 /** 34 /**
@@ -92,8 +95,8 @@ public class IotMqttUpstreamHandler { @@ -92,8 +95,8 @@ public class IotMqttUpstreamHandler {
92 IotDeviceMessage message = deviceMessageService.decodeDeviceMessage( 95 IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
93 messageBytes, productKey, deviceName); 96 messageBytes, productKey, deviceName);
94 97
95 - // 发送消息到队列  
96 - deviceMessageProducer.sendDeviceMessage(message); 98 + // 发送消息到队列(需要补充设备信息)
  99 + deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);
97 100
98 // 记录成功日志 101 // 记录成功日志
99 log.info("[processMessage][处理{}成功][topic: {}]", messageType, topic); 102 log.info("[processMessage][处理{}成功][topic: {}]", messageType, topic);
@@ -106,22 +109,46 @@ public class IotMqttUpstreamHandler { @@ -106,22 +109,46 @@ public class IotMqttUpstreamHandler {
106 * @return 消息类型描述,如果不支持返回 null 109 * @return 消息类型描述,如果不支持返回 null
107 */ 110 */
108 private String getMessageType(String topic) { 111 private String getMessageType(String topic) {
109 - // 设备事件上报: /sys/{productKey}/{deviceName}/thing/event/{eventIdentifier}/post  
110 - if (topic.contains("/thing/event/") && topic.endsWith("/post")) { 112 + if (StrUtil.isBlank(topic)) {
  113 + return null;
  114 + }
  115 +
  116 + // 按优先级匹配主题类型,避免误匹配
  117 +
  118 + // 1. 设备属性上报: /sys/{productKey}/{deviceName}/thing/event/property/post
  119 + if (isPropertyPostTopic(topic)) {
  120 + return IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getDescription();
  121 + }
  122 +
  123 + // 2. 设备事件上报: /sys/{productKey}/{deviceName}/thing/event/{eventIdentifier}/post
  124 + if (isEventPostTopic(topic)) {
111 return "设备事件上报"; 125 return "设备事件上报";
112 } 126 }
113 127
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 "设备属性操作"; 128 + // 3. 设备属性设置响应: /sys/{productKey}/{deviceName}/thing/service/property/set_reply
  129 + if (isPropertySetReplyTopic(topic)) {
  130 + return "设备属性设置响应";
  131 + }
  132 +
  133 + // 4. 设备属性获取响应: /sys/{productKey}/{deviceName}/thing/service/property/get_reply
  134 + if (isPropertyGetReplyTopic(topic)) {
  135 + return "设备属性获取响应";
  136 + }
  137 +
  138 + // 5. 设备配置设置响应: /sys/{productKey}/{deviceName}/thing/service/config/set_reply
  139 + if (isConfigSetReplyTopic(topic)) {
  140 + return IotDeviceTopicEnum.CONFIG_SET_TOPIC.getDescription() + "响应";
120 } 141 }
121 142
122 - // 设备服务调用: /sys/{productKey}/{deviceName}/thing/service/{serviceIdentifier}  
123 - if (topic.contains("/thing/service/") && !topic.contains("/property/")) {  
124 - return "设备服务调用"; 143 + // 6. 设备 OTA 升级响应:
  144 + // /sys/{productKey}/{deviceName}/thing/service/ota/upgrade_reply
  145 + if (isOtaUpgradeReplyTopic(topic)) {
  146 + return IotDeviceTopicEnum.OTA_UPGRADE_TOPIC.getDescription() + "响应";
  147 + }
  148 +
  149 + // 7. 其他服务调用响应: 通用服务调用响应
  150 + if (isServiceReplyTopic(topic)) {
  151 + return "设备服务调用响应";
125 } 152 }
126 153
127 // 不支持的消息类型 154 // 不支持的消息类型
@@ -129,6 +156,65 @@ public class IotMqttUpstreamHandler { @@ -129,6 +156,65 @@ public class IotMqttUpstreamHandler {
129 } 156 }
130 157
131 /** 158 /**
  159 + * 判断是否为属性上报主题
  160 + */
  161 + private boolean isPropertyPostTopic(String topic) {
  162 + return topic.contains(IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic());
  163 + }
  164 +
  165 + /**
  166 + * 判断是否为事件上报主题
  167 + */
  168 + private boolean isEventPostTopic(String topic) {
  169 + return topic.contains(IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic())
  170 + && topic.endsWith(IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic())
  171 + && !topic.contains("property"); // 排除属性上报主题
  172 + }
  173 +
  174 + /**
  175 + * 判断是否为属性设置响应主题
  176 + */
  177 + private boolean isPropertySetReplyTopic(String topic) {
  178 + return topic.contains(IotDeviceTopicEnum.PROPERTY_SET_TOPIC.getTopic())
  179 + && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX);
  180 + }
  181 +
  182 + /**
  183 + * 判断是否为属性获取响应主题
  184 + */
  185 + private boolean isPropertyGetReplyTopic(String topic) {
  186 + return topic.contains(IotDeviceTopicEnum.PROPERTY_GET_TOPIC.getTopic())
  187 + && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX);
  188 + }
  189 +
  190 + /**
  191 + * 判断是否为配置设置响应主题
  192 + */
  193 + private boolean isConfigSetReplyTopic(String topic) {
  194 + return topic.contains(IotDeviceTopicEnum.CONFIG_SET_TOPIC.getTopic())
  195 + && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX);
  196 + }
  197 +
  198 + /**
  199 + * 判断是否为 OTA 升级响应主题
  200 + */
  201 + private boolean isOtaUpgradeReplyTopic(String topic) {
  202 + return topic.contains(IotDeviceTopicEnum.OTA_UPGRADE_TOPIC.getTopic())
  203 + && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX);
  204 + }
  205 +
  206 + /**
  207 + * 判断是否为服务调用响应主题(排除已处理的特殊服务)
  208 + */
  209 + private boolean isServiceReplyTopic(String topic) {
  210 + return topic.contains(IotDeviceTopicEnum.SERVICE_TOPIC_PREFIX)
  211 + && topic.endsWith(IotDeviceTopicEnum.REPLY_SUFFIX)
  212 + && !topic.contains("property")
  213 + && !topic.contains("config")
  214 + && !topic.contains("ota");
  215 + }
  216 +
  217 + /**
132 * 解析主题,获取主题各部分 218 * 解析主题,获取主题各部分
133 * 219 *
134 * @param topic 主题 220 * @param topic 主题
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application-local.yaml
@@ -39,9 +39,7 @@ yudao: @@ -39,9 +39,7 @@ yudao:
39 mqtt-password: public # MQTT 密码 39 mqtt-password: public # MQTT 密码
40 mqtt-ssl: false # 是否开启 SSL 40 mqtt-ssl: false # 是否开启 SSL
41 mqtt-topics: 41 mqtt-topics:
42 - - "/sys/#" # 系统主题(设备上报)  
43 - - "/ota/#" # OTA 升级主题  
44 - - "/config/#" # 配置主题 42 + - "/sys/#" # 系统主题
45 43
46 # 消息总线配置 44 # 消息总线配置
47 message-bus: 45 message-bus:
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
@@ -40,9 +40,7 @@ yudao: @@ -40,9 +40,7 @@ yudao:
40 enabled: true 40 enabled: true
41 mqtt-ssl: false 41 mqtt-ssl: false
42 mqtt-topics: 42 mqtt-topics:
43 - - "/sys/#" # 系统主题(设备上报)  
44 - - "/ota/#" # OTA 升级主题  
45 - - "/config/#" # 配置主题 43 + - "/sys/#" # 系统主题
46 44
47 # 消息总线配置 45 # 消息总线配置
48 message-bus: 46 message-bus: