【功能完善】IoT: 更新 EMQX 插件配置,添加 MQTT 连接参数,重构相关逻辑
This commit is contained in:
parent
8cf8af1f6d
commit
53697b55c2
|
@ -67,7 +67,7 @@
|
||||||
<netty.version>4.1.116.Final</netty.version>
|
<netty.version>4.1.116.Final</netty.version>
|
||||||
<mqtt.version>1.2.5</mqtt.version>
|
<mqtt.version>1.2.5</mqtt.version>
|
||||||
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
||||||
<vertx.version>4.5.11</vertx.version>
|
<vertx.version>4.5.13</vertx.version>
|
||||||
<!-- 三方云服务相关 -->
|
<!-- 三方云服务相关 -->
|
||||||
<commons-io.version>2.17.0</commons-io.version>
|
<commons-io.version>2.17.0</commons-io.version>
|
||||||
<commons-compress.version>1.27.1</commons-compress.version>
|
<commons-compress.version>1.27.1</commons-compress.version>
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
|
public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
|
||||||
Boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
|
boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
|
||||||
return success(result);
|
return success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,18 +79,6 @@ public class IotDeviceRespVO {
|
||||||
@ExcelProperty("设备密钥")
|
@ExcelProperty("设备密钥")
|
||||||
private String deviceSecret;
|
private String deviceSecret;
|
||||||
|
|
||||||
@Schema(description = "MQTT 客户端 ID", example = "24602")
|
|
||||||
@ExcelProperty("MQTT 客户端 ID")
|
|
||||||
private String mqttClientId;
|
|
||||||
|
|
||||||
@Schema(description = "MQTT 用户名", example = "芋艿")
|
|
||||||
@ExcelProperty("MQTT 用户名")
|
|
||||||
private String mqttUsername;
|
|
||||||
|
|
||||||
@Schema(description = "MQTT 密码")
|
|
||||||
@ExcelProperty("MQTT 密码")
|
|
||||||
private String mqttPassword;
|
|
||||||
|
|
||||||
@Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
|
@Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
|
||||||
@ExcelProperty("认证类型(如一机一密、动态注册)")
|
@ExcelProperty("认证类型(如一机一密、动态注册)")
|
||||||
private String authType;
|
private String authType;
|
||||||
|
|
|
@ -67,6 +67,6 @@ public interface IotDeviceUpstreamService {
|
||||||
*
|
*
|
||||||
* @param authReqDTO Emqx 连接认证 DTO
|
* @param authReqDTO Emqx 连接认证 DTO
|
||||||
*/
|
*/
|
||||||
Boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO);
|
boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -280,16 +280,15 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
|
||||||
sendDeviceMessage(message, device);
|
sendDeviceMessage(message, device);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @haohao:建议返回 boolean;
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
|
public boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
|
||||||
log.info("[authenticateEmqxConnection][认证 Emqx 连接: {}]", authReqDTO);
|
log.info("[authenticateEmqxConnection][认证 Emqx 连接: {}]", authReqDTO);
|
||||||
// 1. 校验设备是否存在
|
// 1. 校验设备是否存在
|
||||||
// username 格式:${DeviceName}&${ProductKey}
|
// username 格式:${DeviceName}&${ProductKey}
|
||||||
String[] usernameParts = authReqDTO.getUsername().split("&");
|
String[] usernameParts = authReqDTO.getUsername().split("&");
|
||||||
if (usernameParts.length != 2) {
|
if (usernameParts.length != 2) {
|
||||||
log.error("[authenticateEmqxConnection][认证失败,username 格式不正确]");
|
log.error("[authenticateEmqxConnection][认证失败,username 格式不正确]");
|
||||||
return Boolean.FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
String deviceName = usernameParts[0];
|
String deviceName = usernameParts[0];
|
||||||
String productKey = usernameParts[1];
|
String productKey = usernameParts[1];
|
||||||
|
@ -298,19 +297,18 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
log.error("[authenticateEmqxConnection][设备({}/{}) 不存在]",
|
log.error("[authenticateEmqxConnection][设备({}/{}) 不存在]",
|
||||||
productKey, deviceName);
|
productKey, deviceName);
|
||||||
return Boolean.FALSE;
|
return false;
|
||||||
}
|
}
|
||||||
// 2. 校验密码
|
// 2. 校验密码
|
||||||
String deviceSecret = device.getDeviceSecret();
|
String deviceSecret = device.getDeviceSecret();
|
||||||
String clientId = authReqDTO.getClientId();
|
String clientId = authReqDTO.getClientId();
|
||||||
MqttSignResult sign = MqttSignUtils.calculate(productKey, deviceName, deviceSecret, clientId);
|
MqttSignResult sign = MqttSignUtils.calculate(productKey, deviceName, deviceSecret, clientId);
|
||||||
// TODO @haohao:notEquals,尽量不走取反逻辑哈
|
if (StrUtil.equals(sign.getPassword(), authReqDTO.getPassword())) {
|
||||||
if (!StrUtil.equals(sign.getPassword(), authReqDTO.getPassword())) {
|
|
||||||
log.error("[authenticateEmqxConnection][认证失败,密码不正确]");
|
|
||||||
return Boolean.FALSE;
|
|
||||||
}
|
|
||||||
log.info("[authenticateEmqxConnection][认证成功]");
|
log.info("[authenticateEmqxConnection][认证成功]");
|
||||||
return Boolean.TRUE;
|
return true;
|
||||||
|
}
|
||||||
|
log.error("[authenticateEmqxConnection][认证失败,密码不正确]");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
|
private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package cn.iocoder.yudao.module.iot.util;
|
package cn.iocoder.yudao.module.iot.util;
|
||||||
|
|
||||||
|
import cn.hutool.crypto.digest.HMac;
|
||||||
|
import cn.hutool.crypto.digest.HmacAlgorithm;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,10 +14,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
*/
|
*/
|
||||||
public class MqttSignUtils {
|
public class MqttSignUtils {
|
||||||
|
|
||||||
private static final String SIGN_METHOD = "hmacsha256";
|
|
||||||
|
|
||||||
// TODO @haohao:calculate 方法,可以融合么?
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算 MQTT 连接参数
|
* 计算 MQTT 连接参数
|
||||||
*
|
*
|
||||||
|
@ -26,14 +23,7 @@ public class MqttSignUtils {
|
||||||
* @return 包含 clientId, username, password 的结果对象
|
* @return 包含 clientId, username, password 的结果对象
|
||||||
*/
|
*/
|
||||||
public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret) {
|
public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret) {
|
||||||
String clientId = productKey + "." + deviceName;
|
return calculate(productKey, deviceName, deviceSecret, productKey + "." + deviceName);
|
||||||
String username = deviceName + "&" + productKey;
|
|
||||||
// 生成 password
|
|
||||||
// TODO @haohao:signContent 和 signContentBuilder 风格保持统一的实现哈
|
|
||||||
String signContent = String.format("clientId%sdeviceName%sdeviceSecret%sproductKey%s",
|
|
||||||
clientId, deviceName, deviceSecret, productKey);
|
|
||||||
String password = sign(signContent, deviceSecret);
|
|
||||||
return new MqttSignResult(clientId, username, password);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,56 +37,31 @@ public class MqttSignUtils {
|
||||||
*/
|
*/
|
||||||
public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret, String clientId) {
|
public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret, String clientId) {
|
||||||
String username = deviceName + "&" + productKey;
|
String username = deviceName + "&" + productKey;
|
||||||
String signContentBuilder = "clientId" + clientId +
|
// 构建签名内容
|
||||||
"deviceName" + deviceName +
|
StringBuilder signContentBuilder = new StringBuilder()
|
||||||
"deviceSecret" + deviceSecret +
|
.append("clientId").append(clientId)
|
||||||
"productKey" + productKey;
|
.append("deviceName").append(deviceName)
|
||||||
|
.append("deviceSecret").append(deviceSecret)
|
||||||
|
.append("productKey").append(productKey);
|
||||||
|
|
||||||
String password = sign(signContentBuilder, deviceSecret);
|
// 使用 HMac 计算签名
|
||||||
|
byte[] key = deviceSecret.getBytes(StandardCharsets.UTF_8);
|
||||||
|
String signContent = signContentBuilder.toString();
|
||||||
|
HMac mac = new HMac(HmacAlgorithm.HmacSHA256, key);
|
||||||
|
String password = mac.digestHex(signContent);
|
||||||
|
|
||||||
return new MqttSignResult(clientId, username, password);
|
return new MqttSignResult(clientId, username, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @haohao:hutool 貌似有工具类可以用哈。
|
|
||||||
private static String sign(String content, String key) {
|
|
||||||
try {
|
|
||||||
Mac mac = Mac.getInstance(SIGN_METHOD);
|
|
||||||
mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), SIGN_METHOD));
|
|
||||||
byte[] signData = mac.doFinal(content.getBytes(StandardCharsets.UTF_8));
|
|
||||||
return bytesToHex(signData);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to sign content with HmacSHA256", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String bytesToHex(byte[] bytes) {
|
|
||||||
StringBuilder hexString = new StringBuilder(bytes.length * 2);
|
|
||||||
for (byte b : bytes) {
|
|
||||||
String hex = Integer.toHexString(0xFF & b);
|
|
||||||
if (hex.length() == 1) {
|
|
||||||
hexString.append('0');
|
|
||||||
}
|
|
||||||
hexString.append(hex);
|
|
||||||
}
|
|
||||||
return hexString.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MQTT 签名结果类
|
* MQTT 签名结果类
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
// TODO @haohao:可以用 lombok 哈
|
@AllArgsConstructor
|
||||||
public static class MqttSignResult {
|
public static class MqttSignResult {
|
||||||
|
|
||||||
private final String clientId;
|
private final String clientId;
|
||||||
private final String username;
|
private final String username;
|
||||||
private final String password;
|
private final String password;
|
||||||
|
|
||||||
public MqttSignResult(String clientId, String username, String password) {
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.username = username;
|
|
||||||
this.password = password;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -160,5 +160,9 @@
|
||||||
<groupId>io.vertx</groupId>
|
<groupId>io.vertx</groupId>
|
||||||
<artifactId>vertx-web</artifactId>
|
<artifactId>vertx-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.vertx</groupId>
|
||||||
|
<artifactId>vertx-mqtt</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
|
@ -17,22 +17,29 @@ public class IotPluginEmqxProperties {
|
||||||
/**
|
/**
|
||||||
* 服务主机
|
* 服务主机
|
||||||
*/
|
*/
|
||||||
private String host;
|
private String mqttHost;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务端口
|
* 服务端口
|
||||||
*/
|
*/
|
||||||
private int port;
|
private int mqttPort;
|
||||||
|
/**
|
||||||
|
* 服务用户名
|
||||||
|
*/
|
||||||
|
private String mqttUsername;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务密码
|
||||||
|
*/
|
||||||
|
private String mqttPassword;
|
||||||
/**
|
/**
|
||||||
* 是否启用 SSL
|
* 是否启用 SSL
|
||||||
*/
|
*/
|
||||||
private boolean ssl;
|
private boolean mqttSsl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅的主题
|
* 订阅的主题
|
||||||
*/
|
*/
|
||||||
private String topics;
|
private String mqttTopics;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证端口
|
* 认证端口
|
||||||
|
|
|
@ -7,8 +7,6 @@ import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamH
|
||||||
/**
|
/**
|
||||||
* EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类
|
* EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类
|
||||||
* <p>
|
* <p>
|
||||||
* 但是:由于设备通过 HTTP 短链接接入,导致其实无法下行指导给 device 设备,所以基本都是直接返回失败!!!
|
|
||||||
* 类似 MQTT、WebSocket、TCP 插件,是可以实现下行指令的。
|
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,20 +1,30 @@
|
||||||
package cn.iocoder.yudao.module.iot.plugin.emqx.upstream;
|
package cn.iocoder.yudao.module.iot.plugin.emqx.upstream;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
|
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
|
||||||
|
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
|
||||||
import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
|
import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
|
||||||
import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
|
import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
|
||||||
|
import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
|
||||||
import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties;
|
import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties;
|
||||||
import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler;
|
import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler;
|
||||||
import io.vertx.core.Vertx;
|
import io.vertx.core.Vertx;
|
||||||
import io.vertx.core.http.HttpServer;
|
import io.vertx.core.http.HttpServer;
|
||||||
import io.vertx.ext.web.Router;
|
import io.vertx.ext.web.Router;
|
||||||
import io.vertx.ext.web.handler.BodyHandler;
|
import io.vertx.ext.web.handler.BodyHandler;
|
||||||
|
import io.vertx.mqtt.MqttClient;
|
||||||
|
import io.vertx.mqtt.MqttClientOptions;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
|
* IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
|
||||||
* <p>
|
* <p>
|
||||||
* 协议:HTTP
|
* 协议:HTTP、MQTT
|
||||||
*
|
*
|
||||||
* @author haohao
|
* @author haohao
|
||||||
*/
|
*/
|
||||||
|
@ -23,13 +33,16 @@ public class IotDeviceUpstreamServer {
|
||||||
|
|
||||||
private final Vertx vertx;
|
private final Vertx vertx;
|
||||||
private final HttpServer server;
|
private final HttpServer server;
|
||||||
|
private final MqttClient client;
|
||||||
private final IotPluginEmqxProperties emqxProperties;
|
private final IotPluginEmqxProperties emqxProperties;
|
||||||
|
private final IotDeviceUpstreamApi deviceUpstreamApi;
|
||||||
|
|
||||||
public IotDeviceUpstreamServer(IotPluginCommonProperties commonProperties,
|
public IotDeviceUpstreamServer(IotPluginCommonProperties commonProperties,
|
||||||
IotPluginEmqxProperties emqxProperties,
|
IotPluginEmqxProperties emqxProperties,
|
||||||
IotDeviceUpstreamApi deviceUpstreamApi,
|
IotDeviceUpstreamApi deviceUpstreamApi,
|
||||||
IotDeviceDownstreamServer deviceDownstreamServer) {
|
IotDeviceDownstreamServer deviceDownstreamServer) {
|
||||||
this.emqxProperties = emqxProperties;
|
this.emqxProperties = emqxProperties;
|
||||||
|
this.deviceUpstreamApi = deviceUpstreamApi;
|
||||||
// 创建 Vertx 实例
|
// 创建 Vertx 实例
|
||||||
this.vertx = Vertx.vertx();
|
this.vertx = Vertx.vertx();
|
||||||
// 创建 Router 实例
|
// 创建 Router 实例
|
||||||
|
@ -39,18 +52,104 @@ public class IotDeviceUpstreamServer {
|
||||||
.handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
|
.handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
|
||||||
// 创建 HttpServer 实例
|
// 创建 HttpServer 实例
|
||||||
this.server = vertx.createHttpServer().requestHandler(router);
|
this.server = vertx.createHttpServer().requestHandler(router);
|
||||||
|
|
||||||
|
// 创建 MQTT 客户端
|
||||||
|
MqttClientOptions options = new MqttClientOptions()
|
||||||
|
.setClientId("yudao-iot-server-" + UUID.randomUUID())
|
||||||
|
.setUsername(emqxProperties.getMqttUsername())
|
||||||
|
.setPassword(emqxProperties.getMqttPassword())
|
||||||
|
.setSsl(emqxProperties.isMqttSsl());
|
||||||
|
client = MqttClient.create(vertx, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动 HTTP 服务器
|
* 启动 HTTP 服务器、MQTT 客户端
|
||||||
*/
|
*/
|
||||||
public void start() {
|
public void start() {
|
||||||
|
// 1. 启动 HTTP 服务器
|
||||||
log.info("[start][开始启动]");
|
log.info("[start][开始启动]");
|
||||||
server.listen(emqxProperties.getAuthPort())
|
server.listen(emqxProperties.getAuthPort())
|
||||||
.toCompletionStage()
|
.toCompletionStage()
|
||||||
.toCompletableFuture()
|
.toCompletableFuture()
|
||||||
.join();
|
.join();
|
||||||
log.info("[start][启动完成,端口({})]", this.server.actualPort());
|
log.info("[start][HTTP服务器启动完成,端口({})]", this.server.actualPort());
|
||||||
|
|
||||||
|
// 2. 连接 MQTT Broker
|
||||||
|
connectMqtt();
|
||||||
|
|
||||||
|
// 3. 添加 MQTT 断开重连监听器
|
||||||
|
client.closeHandler(v -> {
|
||||||
|
log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
|
||||||
|
// 等待 5 秒后重连,避免频繁重连
|
||||||
|
vertx.setTimer(5000, id -> {
|
||||||
|
log.info("[closeHandler][开始重新连接 MQTT]");
|
||||||
|
connectMqtt();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 设置 MQTT 消息处理器
|
||||||
|
client.publishHandler(message -> {
|
||||||
|
String topic = message.topicName();
|
||||||
|
String payload = message.payload().toString();
|
||||||
|
log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 4.1 处理设备属性上报消息: /{productKey}/{deviceName}/event/property/post
|
||||||
|
if (topic.contains("/event/property/post")) {
|
||||||
|
// 4.2 解析消息内容
|
||||||
|
JSONObject jsonObject = JSONUtil.parseObj(payload);
|
||||||
|
String requestId = jsonObject.getStr("id");
|
||||||
|
Long timestamp = jsonObject.getLong("timestamp");
|
||||||
|
|
||||||
|
// 4.3 从 topic 中解析设备标识
|
||||||
|
String[] topicParts = topic.split("/");
|
||||||
|
String productKey = topicParts[1];
|
||||||
|
String deviceName = topicParts[2];
|
||||||
|
|
||||||
|
// 4.4 构建设备属性上报请求对象
|
||||||
|
IotDevicePropertyReportReqDTO devicePropertyReportReqDTO = ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
|
||||||
|
.setRequestId(requestId)
|
||||||
|
.setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
|
||||||
|
.setProductKey(productKey).setDeviceName(deviceName))
|
||||||
|
.setProperties(jsonObject.getJSONObject("params"));
|
||||||
|
|
||||||
|
// 4.5 调用上游 API 处理设备上报数据
|
||||||
|
deviceUpstreamApi.reportDeviceProperty(devicePropertyReportReqDTO);
|
||||||
|
log.info("[messageHandler][处理设备上行消息成功][topic: {}][devicePropertyReportReqDTO: {}]",
|
||||||
|
topic, JSONUtil.toJsonStr(devicePropertyReportReqDTO));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接 MQTT Broker 并订阅主题
|
||||||
|
*/
|
||||||
|
private void connectMqtt() {
|
||||||
|
// 连接 MQTT Broker
|
||||||
|
client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost())
|
||||||
|
.onSuccess(connAck -> {
|
||||||
|
log.info("[connectMqtt][MQTT客户端连接成功]");
|
||||||
|
// 连接成功后订阅主题
|
||||||
|
String mqttTopics = emqxProperties.getMqttTopics();
|
||||||
|
String[] topics = mqttTopics.split(",");
|
||||||
|
for (String topic : topics) {
|
||||||
|
client.subscribe(topic, 1)
|
||||||
|
.onSuccess(v -> log.info("[connectMqtt][成功订阅主题: {}]", topic))
|
||||||
|
.onFailure(err -> log.error("[connectMqtt][订阅主题失败: {}]", topic, err));
|
||||||
|
}
|
||||||
|
log.info("[connectMqtt][开始订阅设备上行消息主题]");
|
||||||
|
})
|
||||||
|
.onFailure(err -> {
|
||||||
|
log.error("[connectMqtt][连接 MQTT Broker 失败]", err);
|
||||||
|
// 连接失败后,等待 5 秒重试
|
||||||
|
vertx.setTimer(5000, id -> {
|
||||||
|
log.info("[connectMqtt][准备重新连接 MQTT]");
|
||||||
|
connectMqtt();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,6 +166,14 @@ public class IotDeviceUpstreamServer {
|
||||||
.join();
|
.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭 MQTT 客户端
|
||||||
|
if (client != null) {
|
||||||
|
client.disconnect()
|
||||||
|
.toCompletionStage()
|
||||||
|
.toCompletableFuture()
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭 Vertx 实例
|
// 关闭 Vertx 实例
|
||||||
if (vertx != null) {
|
if (vertx != null) {
|
||||||
vertx.close()
|
vertx.close()
|
||||||
|
|
|
@ -10,8 +10,10 @@ yudao:
|
||||||
downstream-port: 8100
|
downstream-port: 8100
|
||||||
plugin-key: yudao-module-iot-plugin-emqx
|
plugin-key: yudao-module-iot-plugin-emqx
|
||||||
emqx:
|
emqx:
|
||||||
host: 127.0.0.1
|
mqtt-host: 127.0.0.1
|
||||||
port: 1883
|
mqtt-port: 1883
|
||||||
ssl: false
|
mqtt-ssl: false
|
||||||
topics: "/sys/#"
|
mqtt-username: yudao
|
||||||
|
mqtt-password: yudao
|
||||||
|
mqtt-topics: "/+/#"
|
||||||
auth-port: 8101
|
auth-port: 8101
|
||||||
|
|
Loading…
Reference in New Issue