From 83990086fa8b630276d7847fef73048cc03e1858 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 2 Feb 2026 08:34:52 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=88iot=EF=BC=89=EF=BC=9A=E3=80=90?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E6=94=B9=E9=80=A0=E3=80=91=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=EF=BC=8C=E8=B7=9F=E9=98=BF=E9=87=8C=E4=BA=91?= =?UTF-8?q?=20iot=20=E8=BF=9B=E4=B8=80=E6=AD=A5=E5=AF=B9=E9=BD=90=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20sign=20=E6=9B=BF=E4=BB=A3=20password=20?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/device/IotDeviceServiceImpl.java | 6 +- .../topic/auth/IotDeviceRegisterReqDTO.java | 8 +- .../iot/core/util/IotProductAuthUtils.java | 55 +++++++ .../protocol/coap/IotCoapProtocol.java | 2 +- .../upstream/IotCoapRegisterHandler.java | 2 +- .../upstream/IotHttpRegisterHandler.java | 2 +- .../gateway/protocol/mqtt/IotMqttConfig.java | 1 - .../protocol/mqtt/IotMqttProtocol.java | 45 +++--- .../upstream/IotMqttAbstractHandler.java | 5 +- .../handler/upstream/IotMqttAuthHandler.java | 12 +- .../upstream/IotMqttRegisterHandler.java | 139 ++++++------------ .../manager/IotMqttConnectionManager.java | 14 +- .../gateway/protocol/tcp/IotTcpProtocol.java | 2 +- .../delimiter/IotTcpDelimiterFrameCodec.java | 4 +- .../length/IotTcpFixedLengthFrameCodec.java | 2 +- .../length/IotTcpLengthFieldFrameCodec.java | 2 +- .../upstream/IotTcpUpstreamHandler.java | 11 +- .../gateway/protocol/udp/IotUdpProtocol.java | 2 +- .../upstream/IotUdpUpstreamHandler.java | 11 +- .../websocket/IotWebSocketProtocol.java | 2 +- .../upstream/IotWebSocketUpstreamHandler.java | 13 +- ...rectDeviceCoapProtocolIntegrationTest.java | 12 +- ...rectDeviceHttpProtocolIntegrationTest.java | 8 +- ...rectDeviceMqttProtocolIntegrationTest.java | 61 +++++--- ...irectDeviceTcpProtocolIntegrationTest.java | 8 +- ...irectDeviceUdpProtocolIntegrationTest.java | 8 +- ...eviceWebSocketProtocolIntegrationTest.java | 12 +- 27 files changed, 239 insertions(+), 210 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java 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 序列化