diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
index 4ec70e08fb..148dd071e5 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
@@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoChangeReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
+import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
@@ -819,8 +820,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
if (BooleanUtil.isFalse(product.getRegisterEnabled())) {
throw exception(DEVICE_REGISTER_DISABLED);
}
- // 1.3 验证 productSecret
- if (ObjUtil.notEqual(product.getProductSecret(), reqDTO.getProductSecret())) {
+ // 1.3 【重要!!!】验证签名
+ if (!IotProductAuthUtils.verifySign(reqDTO.getProductKey(), reqDTO.getDeviceName(),
+ product.getProductSecret(), reqDTO.getSign())) {
throw exception(DEVICE_REGISTER_SECRET_INVALID);
}
return TenantUtils.execute(product.getTenantId(), () -> {
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
index b8db15f188..a77cd428ad 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
@@ -27,9 +27,11 @@ public class IotDeviceRegisterReqDTO {
private String deviceName;
/**
- * 产品密钥
+ * 注册签名
+ *
+ * @see cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils#buildSign(String, String, String)
*/
- @NotEmpty(message = "产品密钥不能为空")
- private String productSecret;
+ @NotEmpty(message = "签名不能为空")
+ private String sign;
}
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java
new file mode 100644
index 0000000000..12d1229d10
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.iot.core.util;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import cn.hutool.crypto.digest.HmacAlgorithm;
+
+/**
+ * IoT 产品【动态注册】认证工具类
+ *
+ * 用于一型一密场景,使用 productSecret 生成签名
+ *
+ * @author 芋道源码
+ */
+public class IotProductAuthUtils {
+
+ /**
+ * 生成设备动态注册签名
+ *
+ * @param productKey 产品标识
+ * @param deviceName 设备名称
+ * @param productSecret 产品密钥
+ * @return 签名
+ */
+ public static String buildSign(String productKey, String deviceName, String productSecret) {
+ String content = buildContent(productKey, deviceName);
+ return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.utf8Bytes(productSecret))
+ .digestHex(content);
+ }
+
+ /**
+ * 验证设备动态注册签名
+ *
+ * @param productKey 产品标识
+ * @param deviceName 设备名称
+ * @param productSecret 产品密钥
+ * @param sign 待验证的签名
+ * @return 是否验证通过
+ */
+ public static boolean verifySign(String productKey, String deviceName, String productSecret, String sign) {
+ String expectedSign = buildSign(productKey, deviceName, productSecret);
+ return expectedSign.equals(sign);
+ }
+
+ /**
+ * 构建签名内容
+ *
+ * @param productKey 产品标识
+ * @param deviceName 设备名称
+ * @return 签名内容
+ */
+ private static String buildContent(String productKey, String deviceName) {
+ return "deviceName" + deviceName + "productKey" + productKey;
+ }
+
+}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java
index 28fa998807..ac348a2db5 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java
@@ -21,7 +21,7 @@ import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.config.CoapConfig;
import org.eclipse.californium.elements.config.Configuration;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
import java.util.concurrent.TimeUnit;
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapRegisterHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapRegisterHandler.java
index a00cce4971..12a70d91b4 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapRegisterHandler.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapRegisterHandler.java
@@ -33,7 +33,7 @@ public class IotCoapRegisterHandler extends IotCoapAbstractHandler {
Assert.notNull(request, "请求体不能为空");
Assert.notBlank(request.getProductKey(), "productKey 不能为空");
Assert.notBlank(request.getDeviceName(), "deviceName 不能为空");
- Assert.notBlank(request.getProductSecret(), "productSecret 不能为空");
+ Assert.notBlank(request.getSign(), "sign 不能为空");
// 2. 调用动态注册
CommonResult result = deviceApi.registerDevice(request);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpRegisterHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpRegisterHandler.java
index 08c60f3c9d..df010f988f 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpRegisterHandler.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpRegisterHandler.java
@@ -35,7 +35,7 @@ public class IotHttpRegisterHandler extends IotHttpAbstractHandler {
Assert.notNull(request, "请求参数不能为空");
Assert.notBlank(request.getProductKey(), "productKey 不能为空");
Assert.notBlank(request.getDeviceName(), "deviceName 不能为空");
- Assert.notBlank(request.getProductSecret(), "productSecret 不能为空");
+ Assert.notBlank(request.getSign(), "sign 不能为空");
// 2. 调用动态注册
CommonResult result = deviceApi.registerDevice(request);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttConfig.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttConfig.java
index 8fef367476..48060d7285 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttConfig.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttConfig.java
@@ -4,7 +4,6 @@ import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
-// done @AI:validator 参数校验。也看看其他几个配置类有没有类似问题
/**
* IoT 网关 MQTT 协议配置
*
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java
index 58c5fff10c..7a77f0bf32 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java
@@ -26,7 +26,8 @@ import io.vertx.mqtt.MqttTopicSubscription;
import io.vertx.mqtt.messages.MqttPublishMessage;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
import java.util.List;
@@ -40,6 +41,13 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
@Slf4j
public class IotMqttProtocol implements IotProtocol {
+ /**
+ * 注册连接的 clientId 标识
+ *
+ * @see #handleEndpoint(MqttEndpoint)
+ */
+ private static final String AUTH_TYPE_REGISTER = "|authType=register|";
+
/**
* 协议配置
*/
@@ -93,7 +101,7 @@ public class IotMqttProtocol implements IotProtocol {
this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
this.authHandler = new IotMqttAuthHandler(connectionManager, deviceMessageService, deviceApi, serverId);
- this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService, deviceApi);
+ this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService);
this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, deviceMessageService, serverId);
// 初始化下行消息订阅者
@@ -112,7 +120,6 @@ public class IotMqttProtocol implements IotProtocol {
return IotProtocolTypeEnum.MQTT;
}
- // done @AI:这个方法的整体注释风格,参考 IotTcpProtocol 的 start 方法。
@Override
public void start() {
if (running) {
@@ -209,13 +216,18 @@ public class IotMqttProtocol implements IotProtocol {
* @param endpoint MQTT 连接端点
*/
private void handleEndpoint(MqttEndpoint endpoint) {
+ // 1. 如果是注册请求,注册待认证连接;否则走正常认证流程
String clientId = endpoint.clientIdentifier();
-
- // 1. 委托 authHandler 处理连接认证
- // done @AI:register topic 不需要注册,需要判断下;当前逻辑已支持(设备可在未认证状态发送 register 消息,registerHandler 会处理)
- if (!authHandler.handleAuthenticationRequest(endpoint)) {
- endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
+ if (StrUtil.endWith(clientId, AUTH_TYPE_REGISTER)) {
+ // 情况一:设备注册请求
+ registerHandler.handleRegister(endpoint);
return;
+ } else {
+ // 情况二:普通认证请求
+ if (!authHandler.handleAuthenticationRequest(endpoint)) {
+ endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
+ return;
+ }
}
// 2.1 设置异常和关闭处理器
@@ -224,9 +236,8 @@ public class IotMqttProtocol implements IotProtocol {
clientId, connectionManager.getEndpointAddress(endpoint), ex.getMessage());
endpoint.close();
});
- // done @AI:closeHandler 处理底层连接关闭(网络中断、异常等),disconnectHandler 处理 MQTT DISCONNECT 报文
- endpoint.closeHandler(v -> cleanupConnection(endpoint));
- endpoint.disconnectHandler(v -> {
+ endpoint.closeHandler(v -> cleanupConnection(endpoint)); // 处理底层连接关闭(网络中断、异常等)
+ endpoint.disconnectHandler(v -> { // 处理 MQTT DISCONNECT 报文
log.debug("[handleEndpoint][设备断开连接,客户端 ID: {}]", clientId);
cleanupConnection(endpoint);
});
@@ -239,7 +250,6 @@ public class IotMqttProtocol implements IotProtocol {
endpoint.publishReleaseHandler(endpoint::publishComplete);
// 4.1 设置订阅处理器
- // done @AI:使用 CollectionUtils.convertList 简化
endpoint.subscribeHandler(subscribe -> {
List topicNames = convertList(subscribe.topicSubscriptions(), MqttTopicSubscription::topicName);
log.debug("[handleEndpoint][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames);
@@ -265,21 +275,16 @@ public class IotMqttProtocol implements IotProtocol {
private void processMessage(MqttEndpoint endpoint, MqttPublishMessage message) {
String clientId = endpoint.clientIdentifier();
try {
- // 根据 topic 分发到不同 handler
+ // 1. 处理业务消息
String topic = message.topicName();
byte[] payload = message.payload().getBytes();
- if (registerHandler.isRegisterMessage(topic)) {
- registerHandler.handleRegister(endpoint, topic, payload);
- } else {
- upstreamHandler.handleBusinessRequest(endpoint, topic, payload);
- }
+ upstreamHandler.handleBusinessRequest(endpoint, topic, payload);
- // 根据 QoS 级别发送相应的确认消息
+ // 2. 根据 QoS 级别发送相应的确认消息
handleQoSAck(endpoint, message);
} catch (Exception e) {
log.error("[processMessage][消息处理失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage());
- cleanupConnection(endpoint);
endpoint.close();
}
}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java
index 3663eeecd6..4acb037a34 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java
@@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
public abstract class IotMqttAbstractHandler {
- // done @AI:当前使用 Alink 序列化类型,后续可考虑支持更多序列化方式
+ // TODO @AI:当前使用 Alink 序列化类型,后续可考虑支持更多序列化方式
/**
* 默认编解码类型(MQTT 使用 Alink 协议)
*/
@@ -31,7 +31,6 @@ public abstract class IotMqttAbstractHandler {
protected final IotDeviceMessageService deviceMessageService;
// done @AI:基于 method 通过 IotMqttTopicUtils.buildTopicByMethod 计算 reply topic
- // done @AI:当前通过 deviceMessageService.encodeDeviceMessage 编码,保持简洁
/**
* 发送成功响应到设备
*
@@ -89,6 +88,6 @@ public abstract class IotMqttAbstractHandler {
}
}
- // done @AI:当前 sendSuccessResponse/sendErrorResponse 已足够清晰,暂不抽取 writeResponse
+ // TODO @AI:当前 sendSuccessResponse/sendErrorResponse 已足够清晰,暂不抽取 writeResponse(必须抽取!!!)
}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAuthHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAuthHandler.java
index f5b1a552cb..b2155a3a66 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAuthHandler.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAuthHandler.java
@@ -14,7 +14,7 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
import io.vertx.mqtt.MqttEndpoint;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -43,13 +43,13 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler {
this.serverId = serverId;
}
- // (暂时不改)TODO @AI:【动态注册】在 clientId 包含 |authType=register 时,进行动态注册设备;校验是 clientId、username、password 三者组合;它是拼接 productSecret 的哈希值;所以 IotDeviceAuthUtils 里面的 buildContent 要改造;
/**
* 处理 MQTT 连接(认证)请求
*
* @param endpoint MQTT 连接端点
* @return 认证是否成功
*/
+ @SuppressWarnings("DataFlowIssue")
public boolean handleAuthenticationRequest(MqttEndpoint endpoint) {
String clientId = endpoint.clientIdentifier();
String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null;
@@ -59,9 +59,9 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler {
try {
// 1.1 解析认证参数
- Assert.hasText(clientId, "clientId 不能为空");
- Assert.hasText(username, "username 不能为空");
- Assert.hasText(password, "password 不能为空");
+ Assert.notBlank(clientId, "clientId 不能为空");
+ Assert.notBlank(username, "username 不能为空");
+ Assert.notBlank(password, "password 不能为空");
// 1.2 构建认证参数
IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO()
.setClientId(clientId)
@@ -102,8 +102,6 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler {
.setDeviceId(device.getId())
.setProductKey(device.getProductKey())
.setDeviceName(device.getDeviceName())
- .setClientId(clientId)
- .setAuthenticated(true)
.setRemoteAddress(connectionManager.getEndpointAddress(endpoint));
connectionManager.registerConnection(endpoint, device.getId(), connectionInfo);
}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java
index 0ba0dfb49d..77fda8ea0a 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java
@@ -1,23 +1,18 @@
package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream;
-import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
-import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
+import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
+import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
import io.vertx.mqtt.MqttEndpoint;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
-
-import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
-import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
/**
* IoT 网关 MQTT 设备注册处理器:处理设备动态注册消息(一型一密)
@@ -27,114 +22,62 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
@Slf4j
public class IotMqttRegisterHandler extends IotMqttAbstractHandler {
- // done @AI:IotDeviceMessageMethodEnum.DEVICE_REGISTER 计算出来?IotMqttTopicUtils?已使用常量,保持简洁
- /**
- * register 请求的 topic 后缀
- */
- public static final String REGISTER_TOPIC_SUFFIX = "/thing/auth/register";
-
private final IotDeviceCommonApi deviceApi;
- // done @AI:通过 springutil 处理;构造函数注入更清晰,保持原样
public IotMqttRegisterHandler(IotMqttConnectionManager connectionManager,
- IotDeviceMessageService deviceMessageService,
- IotDeviceCommonApi deviceApi) {
+ IotDeviceMessageService deviceMessageService) {
super(connectionManager, deviceMessageService);
- this.deviceApi = deviceApi;
+ this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
}
/**
- * 判断是否为注册消息
- *
- * @param topic 主题
- * @return 是否为注册消息
- */
- // done @AI:是不是搞到 IotMqttTopicUtils 里?当前实现简洁,保持原样
- public boolean isRegisterMessage(String topic) {
- return topic != null && topic.endsWith(REGISTER_TOPIC_SUFFIX);
- }
-
- /**
- * 处理注册消息
+ * 处理注册连接
+ *
+ * 通过 MQTT 连接的 username 解析设备信息,password 作为签名,直接处理设备注册
*
* @param endpoint MQTT 连接端点
- * @param topic 主题
- * @param payload 消息内容
+ * @see 阿里云 - 一型一密
*/
- public void handleRegister(MqttEndpoint endpoint, String topic, byte[] payload) {
+ @SuppressWarnings("DataFlowIssue")
+ public void handleRegister(MqttEndpoint endpoint) {
String clientId = endpoint.clientIdentifier();
- IotDeviceMessage message = null;
+ String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null;
+ String password = endpoint.auth() != null ? endpoint.auth().getPassword() : null;
+ String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod();
String productKey = null;
String deviceName = null;
- String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod();
try {
- // 1.1 基础检查
- if (ArrayUtil.isEmpty(payload)) {
- return;
- }
- // 1.2 解析主题,获取 productKey 和 deviceName
- String[] topicParts = topic.split("/");
- Assert.isTrue(topicParts.length >= 4 && !StrUtil.hasBlank(topicParts[2], topicParts[3]),
- "topic 格式不正确,无法解析 productKey 和 deviceName");
- productKey = topicParts[2];
- deviceName = topicParts[3];
+ // 1.1 校验参数
+ Assert.notBlank(clientId, "clientId 不能为空");
+ Assert.notBlank(username, "username 不能为空");
+ Assert.notBlank(password, "password 不能为空");
+ IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username);
+ Assert.notNull(deviceInfo, "解析设备信息失败");
+ productKey = deviceInfo.getProductKey();
+ deviceName = deviceInfo.getDeviceName();
+ log.info("[handleRegister][设备注册连接,客户端 ID: {},设备: {}.{}]",
+ clientId, productKey, deviceName);
+ // 1.2 构建注册参数
+ IotDeviceRegisterReqDTO params = new IotDeviceRegisterReqDTO()
+ .setProductKey(productKey)
+ .setDeviceName(deviceName)
+ .setSign(password);
- // 2. 使用默认编解码器解码消息(设备可能未注册,无法获取 codecType)
- message = deviceMessageService.decodeDeviceMessage(payload, DEFAULT_CODEC_TYPE);
- Assert.notNull(message, "消息解码失败");
+ // 2. 调用动态注册 API
+ CommonResult result = deviceApi.registerDevice(params);
+ result.checkError();
- // 3. 处理设备动态注册请求
- log.info("[handleRegister][收到设备注册消息,设备: {}.{}, 方法: {}]",
- productKey, deviceName, message.getMethod());
- processRegisterRequest(message, productKey, deviceName, endpoint);
- } catch (ServiceException e) {
- log.warn("[handleRegister][业务异常,客户端 ID: {},主题: {},错误: {}]",
- clientId, topic, e.getMessage());
- String requestId = message != null ? message.getRequestId() : null;
- sendErrorResponse(endpoint, productKey, deviceName, requestId, method, e.getCode(), e.getMessage());
- } catch (IllegalArgumentException e) {
- log.warn("[handleRegister][参数校验失败,客户端 ID: {},主题: {},错误: {}]",
- clientId, topic, e.getMessage());
- String requestId = message != null ? message.getRequestId() : null;
- sendErrorResponse(endpoint, productKey, deviceName, requestId, method,
- BAD_REQUEST.getCode(), e.getMessage());
+ // 3. 接受连接,并发送成功响应
+ endpoint.accept(false);
+ sendSuccessResponse(endpoint, productKey, deviceName, null, method, result.getData());
+ log.info("[handleRegister][注册成功,设备: {}.{},客户端 ID: {}]", productKey, deviceName, clientId);
} catch (Exception e) {
- log.error("[handleRegister][消息处理异常,客户端 ID: {},主题: {},错误: {}]",
- clientId, topic, e.getMessage(), e);
- String requestId = message != null ? message.getRequestId() : null;
- sendErrorResponse(endpoint, productKey, deviceName, requestId, method,
- INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
+ log.warn("[handleRegister][注册失败,客户端 ID: {},错误: {}]", clientId, e.getMessage());
+ // 接受连接,并发送错误响应
+ endpoint.accept(false);
+ sendErrorResponse(endpoint, productKey, deviceName, null, method, 500, e.getMessage());
}
}
- /**
- * 处理设备动态注册请求(一型一密,不需要 deviceSecret)
- *
- * @param message 消息信息
- * @param productKey 产品 Key
- * @param deviceName 设备名称
- * @param endpoint MQTT 连接端点
- * @see 阿里云 - 一型一密
- */
- @SuppressWarnings("DuplicatedCode")
- private void processRegisterRequest(IotDeviceMessage message, String productKey, String deviceName,
- MqttEndpoint endpoint) {
- // 1. 解析注册参数
- IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class);
- Assert.notNull(params, "注册参数不能为空");
- Assert.hasText(params.getProductKey(), "productKey 不能为空");
- Assert.hasText(params.getDeviceName(), "deviceName 不能为空");
-
- // 2. 调用动态注册 API
- CommonResult result = deviceApi.registerDevice(params);
- result.checkError();
-
- // 3. 发送成功响应(包含 deviceSecret)
- String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod();
- sendSuccessResponse(endpoint, productKey, deviceName, message.getRequestId(), method, result.getData());
- log.info("[processRegisterRequest][注册成功,设备名: {},客户端 ID: {}]",
- params.getDeviceName(), endpoint.clientIdentifier());
- }
-
}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
index 4580205747..6ebc123054 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
@@ -76,6 +76,7 @@ public class IotMqttConnectionManager {
* @param deviceId 设备 ID
* @param connectionInfo 连接信息
*/
+ // TODO @AI:移除掉 deviceId ???参考别的 tcp 等模块协议
public void registerConnection(MqttEndpoint endpoint, Long deviceId, ConnectionInfo connectionInfo) {
// 如果设备已有其他连接,先清理旧连接
MqttEndpoint oldEndpoint = deviceEndpointMap.get(deviceId);
@@ -176,28 +177,15 @@ public class IotMqttConnectionManager {
* 设备 ID
*/
private Long deviceId;
-
/**
* 产品 Key
*/
private String productKey;
-
/**
* 设备名称
*/
private String deviceName;
- /**
- * 客户端 ID
- */
- private String clientId;
-
- // done @AI:保留 authenticated 字段,用于区分已认证连接和待认证连接(如动态注册场景)
- /**
- * 是否已认证
- */
- private boolean authenticated;
-
/**
* 连接地址
*/
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpProtocol.java
index 3a31f505b5..24660389b7 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpProtocol.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpProtocol.java
@@ -21,7 +21,7 @@ import io.vertx.core.net.NetServerOptions;
import io.vertx.core.net.PemKeyCertOptions;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
/**
* IoT TCP 协议实现
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/delimiter/IotTcpDelimiterFrameCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/delimiter/IotTcpDelimiterFrameCodec.java
index 6e15e95a21..269d6b1b0b 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/delimiter/IotTcpDelimiterFrameCodec.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/delimiter/IotTcpDelimiterFrameCodec.java
@@ -8,7 +8,7 @@ import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.parsetools.RecordParser;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
/**
* IoT TCP 分隔符帧编解码器
@@ -39,7 +39,7 @@ public class IotTcpDelimiterFrameCodec implements IotTcpFrameCodec {
private final byte[] delimiterBytes;
public IotTcpDelimiterFrameCodec(IotTcpConfig.CodecConfig config) {
- Assert.hasText(config.getDelimiter(), "delimiter 不能为空");
+ Assert.notBlank(config.getDelimiter(), "delimiter 不能为空");
this.delimiterBytes = parseDelimiter(config.getDelimiter());
}
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpFixedLengthFrameCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpFixedLengthFrameCodec.java
index eda77c4d59..4bd454914d 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpFixedLengthFrameCodec.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpFixedLengthFrameCodec.java
@@ -7,7 +7,7 @@ import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.parsetools.RecordParser;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
/**
* IoT TCP 定长帧编解码器
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpLengthFieldFrameCodec.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpLengthFieldFrameCodec.java
index 4200b6b1fb..08b7c23efd 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpLengthFieldFrameCodec.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpLengthFieldFrameCodec.java
@@ -7,7 +7,7 @@ import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.parsetools.RecordParser;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
import java.util.concurrent.atomic.AtomicReference;
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/upstream/IotTcpUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/upstream/IotTcpUpstreamHandler.java
index 93fadd8bbe..5d54758f94 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/upstream/IotTcpUpstreamHandler.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/upstream/IotTcpUpstreamHandler.java
@@ -24,7 +24,7 @@ import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetSocket;
import io.vertx.core.parsetools.RecordParser;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -167,8 +167,8 @@ public class IotTcpUpstreamHandler implements Handler {
// 1. 解析认证参数
IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class);
Assert.notNull(authParams, "认证参数不能为空");
- Assert.hasText(authParams.getUsername(), "username 不能为空");
- Assert.hasText(authParams.getPassword(), "password 不能为空");
+ Assert.notBlank(authParams.getUsername(), "username 不能为空");
+ Assert.notBlank(authParams.getPassword(), "password 不能为空");
// 2.1 执行认证
CommonResult authResult = deviceApi.authDevice(authParams);
@@ -204,8 +204,9 @@ public class IotTcpUpstreamHandler implements Handler {
// 1. 解析注册参数
IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class);
Assert.notNull(params, "注册参数不能为空");
- Assert.hasText(params.getProductKey(), "productKey 不能为空");
- Assert.hasText(params.getDeviceName(), "deviceName 不能为空");
+ Assert.notBlank(params.getProductKey(), "productKey 不能为空");
+ Assert.notBlank(params.getDeviceName(), "deviceName 不能为空");
+ Assert.notBlank(params.getSign(), "sign 不能为空");
// 2. 调用动态注册
CommonResult result = deviceApi.registerDevice(params);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpProtocol.java
index 647a713b55..13cd85b0ed 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpProtocol.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpProtocol.java
@@ -18,7 +18,7 @@ import io.vertx.core.datagram.DatagramSocket;
import io.vertx.core.datagram.DatagramSocketOptions;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
/**
* IoT UDP 协议实现
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/handler/upstream/IotUdpUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/handler/upstream/IotUdpUpstreamHandler.java
index dd41a52527..7b248ab7c9 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/handler/upstream/IotUdpUpstreamHandler.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/handler/upstream/IotUdpUpstreamHandler.java
@@ -27,7 +27,7 @@ import io.vertx.core.buffer.Buffer;
import io.vertx.core.datagram.DatagramPacket;
import io.vertx.core.datagram.DatagramSocket;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
import java.net.InetSocketAddress;
import java.util.Map;
@@ -173,8 +173,8 @@ public class IotUdpUpstreamHandler {
// 1. 解析认证参数
IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class);
Assert.notNull(authParams, "认证参数不能为空");
- Assert.hasText(authParams.getUsername(), "username 不能为空");
- Assert.hasText(authParams.getPassword(), "password 不能为空");
+ Assert.notBlank(authParams.getUsername(), "username 不能为空");
+ Assert.notBlank(authParams.getPassword(), "password 不能为空");
// 2.1 执行认证
CommonResult authResult = deviceApi.authDevice(authParams);
@@ -218,8 +218,9 @@ public class IotUdpUpstreamHandler {
// 1. 解析注册参数
IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class);
Assert.notNull(params, "注册参数不能为空");
- Assert.hasText(params.getProductKey(), "productKey 不能为空");
- Assert.hasText(params.getDeviceName(), "deviceName 不能为空");
+ Assert.notBlank(params.getProductKey(), "productKey 不能为空");
+ Assert.notBlank(params.getDeviceName(), "deviceName 不能为空");
+ Assert.notBlank(params.getSign(), "sign 不能为空");
// 2. 调用动态注册
CommonResult result = deviceApi.registerDevice(params);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketProtocol.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketProtocol.java
index 67d5608936..10a57f9b99 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketProtocol.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketProtocol.java
@@ -20,7 +20,7 @@ import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.net.PemKeyCertOptions;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
/**
* IoT WebSocket 协议实现
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/handler/upstream/IotWebSocketUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/handler/upstream/IotWebSocketUpstreamHandler.java
index c838198115..48de7097bc 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/handler/upstream/IotWebSocketUpstreamHandler.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/handler/upstream/IotWebSocketUpstreamHandler.java
@@ -23,7 +23,7 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessa
import io.vertx.core.Handler;
import io.vertx.core.http.ServerWebSocket;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.util.Assert;
+import cn.hutool.core.lang.Assert;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -109,7 +109,7 @@ public class IotWebSocketUpstreamHandler implements Handler {
// 1.2 解码消息
message = serializer.deserialize(payload);
Assert.notNull(message, "消息反序列化失败");
- Assert.hasText(message.getMethod(), "method 不能为空");
+ Assert.notBlank(message.getMethod(), "method 不能为空");
// 2. 根据消息类型路由处理
if (AUTH_METHOD.equals(message.getMethod())) {
@@ -150,8 +150,8 @@ public class IotWebSocketUpstreamHandler implements Handler {
// 1. 解析认证参数
IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class);
Assert.notNull(authParams, "认证参数不能为空");
- Assert.hasText(authParams.getUsername(), "username 不能为空");
- Assert.hasText(authParams.getPassword(), "password 不能为空");
+ Assert.notBlank(authParams.getUsername(), "username 不能为空");
+ Assert.notBlank(authParams.getPassword(), "password 不能为空");
// 2.1 执行认证
CommonResult authResult = deviceApi.authDevice(authParams);
@@ -187,8 +187,9 @@ public class IotWebSocketUpstreamHandler implements Handler {
// 1. 解析注册参数
IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class);
Assert.notNull(params, "注册参数不能为空");
- Assert.hasText(params.getProductKey(), "productKey 不能为空");
- Assert.hasText(params.getDeviceName(), "deviceName 不能为空");
+ Assert.notBlank(params.getProductKey(), "productKey 不能为空");
+ Assert.notBlank(params.getDeviceName(), "deviceName 不能为空");
+ Assert.notBlank(params.getSign(), "sign 不能为空");
// 2. 调用动态注册
CommonResult result = deviceApi.registerDevice(params);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotDirectDeviceCoapProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotDirectDeviceCoapProtocolIntegrationTest.java
index 6c852affca..8fc49901f7 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotDirectDeviceCoapProtocolIntegrationTest.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotDirectDeviceCoapProtocolIntegrationTest.java
@@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
+import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
@@ -203,10 +204,13 @@ public class IotDirectDeviceCoapProtocolIntegrationTest {
// 1.1 构建请求
String uri = String.format("coap://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT);
// 1.2 构建请求参数
- IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO();
- reqDTO.setProductKey(PRODUCT_KEY);
- reqDTO.setDeviceName("test-" + System.currentTimeMillis());
- reqDTO.setProductSecret("test-product-secret");
+ String deviceName = "test-" + System.currentTimeMillis();
+ String productSecret = "test-product-secret"; // 替换为实际的 productSecret
+ String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret);
+ IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO()
+ .setProductKey(PRODUCT_KEY)
+ .setDeviceName(deviceName)
+ .setSign(sign);
String payload = JsonUtils.toJsonString(reqDTO);
// 1.3 输出请求
log.info("[testDeviceRegister][请求 URI: {}]", uri);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotDirectDeviceHttpProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotDirectDeviceHttpProtocolIntegrationTest.java
index ea412a2079..1759000b05 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotDirectDeviceHttpProtocolIntegrationTest.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotDirectDeviceHttpProtocolIntegrationTest.java
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
+import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -158,10 +159,13 @@ public class IotDirectDeviceHttpProtocolIntegrationTest {
// 1.1 构建请求
String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT);
// 1.2 构建请求参数
+ String deviceName = "test-" + System.currentTimeMillis();
+ String productSecret = "test-product-secret"; // 替换为实际的 productSecret
+ String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret);
IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO()
.setProductKey(PRODUCT_KEY)
- .setDeviceName("test-" + System.currentTimeMillis())
- .setProductSecret("test-product-secret");
+ .setDeviceName(deviceName)
+ .setSign(sign);
String payload = JsonUtils.toJsonString(reqDTO);
// 1.3 输出请求
log.info("[testDeviceRegister][请求 URL: {}]", url);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotDirectDeviceMqttProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotDirectDeviceMqttProtocolIntegrationTest.java
index 5f59e01ae1..415d15a4de 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotDirectDeviceMqttProtocolIntegrationTest.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotDirectDeviceMqttProtocolIntegrationTest.java
@@ -4,10 +4,10 @@ import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
-import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
+import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
import io.netty.handler.codec.mqtt.MqttQoS;
@@ -173,36 +173,51 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
/**
* 直连设备动态注册测试(一型一密)
*
- * 使用产品密钥(productSecret)验证身份,成功后返回设备密钥(deviceSecret)
+ * 认证方式:
+ * - clientId: 任意值 + "|authType=register|" 后缀
+ * - username: {deviceName}&{productKey}(与普通认证相同)
+ * - password: 签名(使用 productSecret 对 "deviceName" + deviceName + "productKey" + productKey 进行 HMAC-SHA256)
*
- * 注意:此接口不需要认证
+ * 成功后返回设备密钥(deviceSecret),可用于后续一机一密认证
*/
@Test
public void testDeviceRegister() throws Exception {
- // 1. 连接并认证(使用已有设备连接)
- MqttClient client = connectAndAuth();
- log.info("[testDeviceRegister][连接认证成功]");
+ // 1.1 构建注册参数
+ String deviceName = "test-mqtt-" + System.currentTimeMillis();
+ String productSecret = "test-product-secret"; // 替换为实际的 productSecret
+ String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret);
+ // 1.2 构建 MQTT 连接参数(clientId 需要添加 |authType=register| 后缀)
+ String clientId = IotDeviceAuthUtils.buildClientId(PRODUCT_KEY, deviceName) + "|authType=register|";
+ String username = IotDeviceAuthUtils.buildUsername(PRODUCT_KEY, deviceName);
+ log.info("[testDeviceRegister][注册参数: clientId={}, username={}, sign={}]",
+ clientId, username, sign);
+ // 1.3 创建客户端并连接(连接时服务端自动处理注册)
+ MqttClientOptions options = new MqttClientOptions()
+ .setClientId(clientId)
+ .setUsername(username)
+ .setPassword(sign)
+ .setCleanSession(true)
+ .setKeepAliveInterval(60);
+ MqttClient client = MqttClient.create(vertx, options);
try {
- // 2.1 构建注册消息
- IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO()
- .setProductKey(PRODUCT_KEY)
- .setDeviceName("test-mqtt-" + System.currentTimeMillis())
- .setProductSecret("test-product-secret");
- IotDeviceMessage request = IotDeviceMessage.requestOf(
- IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(),
- registerReqDTO);
+ // 2. 设置消息处理器,接收注册响应
+ CompletableFuture responseFuture = new CompletableFuture<>();
+ client.publishHandler(message -> {
+ log.info("[testDeviceRegister][收到响应: topic={}, payload={}]",
+ message.topicName(), message.payload().toString());
+ IotDeviceMessage response = CODEC.decode(message.payload().getBytes());
+ responseFuture.complete(response);
+ });
- // 2.2 订阅 _reply 主题
- String replyTopic = String.format("/sys/%s/%s/thing/auth/register_reply",
- registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
- subscribe(client, replyTopic);
+ // 3. 连接服务器(连接成功后服务端会自动处理注册并发送响应)
+ client.connect(SERVER_PORT, SERVER_HOST)
+ .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ log.info("[testDeviceRegister][连接成功,等待注册响应...]");
- // 3. 发布消息并等待响应
- String topic = String.format("/sys/%s/%s/thing/auth/register",
- registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
- IotDeviceMessage response = publishAndWaitReply(client, topic, request);
- log.info("[testDeviceRegister][响应消息: {}]", response);
+ // 4. 等待注册响应
+ IotDeviceMessage response = responseFuture.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ log.info("[testDeviceRegister][注册响应: {}]", response);
log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
} finally {
disconnect(client);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java
index 192dce359c..778c72fd66 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
+import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpCodecTypeEnum;
import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodecFactory;
@@ -146,10 +147,13 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
@Test
public void testDeviceRegister() throws Exception {
// 1. 构建注册消息
+ String deviceName = "test-tcp-" + System.currentTimeMillis();
+ String productSecret = "test-product-secret"; // 替换为实际的 productSecret
+ String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret);
IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO()
.setProductKey(PRODUCT_KEY)
- .setDeviceName("test-tcp-" + System.currentTimeMillis())
- .setProductSecret("test-product-secret");
+ .setDeviceName(deviceName)
+ .setSign(sign);
IotDeviceMessage request = IotDeviceMessage.requestOf(
IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotDirectDeviceUdpProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotDirectDeviceUdpProtocolIntegrationTest.java
index 74169b2f12..ef7f2ff308 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotDirectDeviceUdpProtocolIntegrationTest.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotDirectDeviceUdpProtocolIntegrationTest.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
+import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
import lombok.extern.slf4j.Slf4j;
@@ -100,10 +101,13 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
@Test
public void testDeviceRegister() throws Exception {
// 1. 构建注册消息
+ String deviceName = "test-udp-" + System.currentTimeMillis();
+ String productSecret = "test-product-secret"; // 替换为实际的 productSecret
+ String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret);
IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO()
.setProductKey(PRODUCT_KEY)
- .setDeviceName("test-udp-" + System.currentTimeMillis())
- .setProductSecret("test-product-secret");
+ .setDeviceName(deviceName)
+ .setSign(sign);
IotDeviceMessage request = IotDeviceMessage.requestOf(
IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO);
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotDirectDeviceWebSocketProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotDirectDeviceWebSocketProtocolIntegrationTest.java
index 15eed61e2a..ba80ed1ed6 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotDirectDeviceWebSocketProtocolIntegrationTest.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotDirectDeviceWebSocketProtocolIntegrationTest.java
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
+import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
import io.vertx.core.Vertx;
@@ -131,10 +132,13 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
@Test
public void testDeviceRegister() throws Exception {
// 1.1 构建注册消息
- IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO();
- registerReqDTO.setProductKey(PRODUCT_KEY);
- registerReqDTO.setDeviceName("test-ws-" + System.currentTimeMillis());
- registerReqDTO.setProductSecret("test-product-secret");
+ String deviceName = "test-ws-" + System.currentTimeMillis();
+ String productSecret = "test-product-secret"; // 替换为实际的 productSecret
+ String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret);
+ IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO()
+ .setProductKey(PRODUCT_KEY)
+ .setDeviceName(deviceName)
+ .setSign(sign);
IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(),
IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null);
// 1.2 序列化