Commit 83990086fa8b630276d7847fef73048cc03e1858
1 parent
9156aef4
feat(iot):【协议改造】设备注册,跟阿里云 iot 进一步对齐,使用 sign 替代 password 参数
Showing
27 changed files
with
240 additions
and
211 deletions
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; | @@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoChangeReqDTO; | ||
| 29 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; | 29 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; |
| 30 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO; | 30 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO; |
| 31 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 31 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 32 | +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils; | ||
| 32 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; | 33 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 33 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; | 34 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; |
| 34 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; | 35 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| @@ -819,8 +820,9 @@ public class IotDeviceServiceImpl implements IotDeviceService { | @@ -819,8 +820,9 @@ public class IotDeviceServiceImpl implements IotDeviceService { | ||
| 819 | if (BooleanUtil.isFalse(product.getRegisterEnabled())) { | 820 | if (BooleanUtil.isFalse(product.getRegisterEnabled())) { |
| 820 | throw exception(DEVICE_REGISTER_DISABLED); | 821 | throw exception(DEVICE_REGISTER_DISABLED); |
| 821 | } | 822 | } |
| 822 | - // 1.3 验证 productSecret | ||
| 823 | - if (ObjUtil.notEqual(product.getProductSecret(), reqDTO.getProductSecret())) { | 823 | + // 1.3 【重要!!!】验证签名 |
| 824 | + if (!IotProductAuthUtils.verifySign(reqDTO.getProductKey(), reqDTO.getDeviceName(), | ||
| 825 | + product.getProductSecret(), reqDTO.getSign())) { | ||
| 824 | throw exception(DEVICE_REGISTER_SECRET_INVALID); | 826 | throw exception(DEVICE_REGISTER_SECRET_INVALID); |
| 825 | } | 827 | } |
| 826 | return TenantUtils.execute(product.getTenantId(), () -> { | 828 | return TenantUtils.execute(product.getTenantId(), () -> { |
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 { | @@ -27,9 +27,11 @@ public class IotDeviceRegisterReqDTO { | ||
| 27 | private String deviceName; | 27 | private String deviceName; |
| 28 | 28 | ||
| 29 | /** | 29 | /** |
| 30 | - * 产品密钥 | 30 | + * 注册签名 |
| 31 | + * | ||
| 32 | + * @see cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils#buildSign(String, String, String) | ||
| 31 | */ | 33 | */ |
| 32 | - @NotEmpty(message = "产品密钥不能为空") | ||
| 33 | - private String productSecret; | 34 | + @NotEmpty(message = "签名不能为空") |
| 35 | + private String sign; | ||
| 34 | 36 | ||
| 35 | } | 37 | } |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.util; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.hutool.crypto.digest.DigestUtil; | ||
| 5 | +import cn.hutool.crypto.digest.HmacAlgorithm; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * IoT 产品【动态注册】认证工具类 | ||
| 9 | + * <p> | ||
| 10 | + * 用于一型一密场景,使用 productSecret 生成签名 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +public class IotProductAuthUtils { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 生成设备动态注册签名 | ||
| 18 | + * | ||
| 19 | + * @param productKey 产品标识 | ||
| 20 | + * @param deviceName 设备名称 | ||
| 21 | + * @param productSecret 产品密钥 | ||
| 22 | + * @return 签名 | ||
| 23 | + */ | ||
| 24 | + public static String buildSign(String productKey, String deviceName, String productSecret) { | ||
| 25 | + String content = buildContent(productKey, deviceName); | ||
| 26 | + return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.utf8Bytes(productSecret)) | ||
| 27 | + .digestHex(content); | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 验证设备动态注册签名 | ||
| 32 | + * | ||
| 33 | + * @param productKey 产品标识 | ||
| 34 | + * @param deviceName 设备名称 | ||
| 35 | + * @param productSecret 产品密钥 | ||
| 36 | + * @param sign 待验证的签名 | ||
| 37 | + * @return 是否验证通过 | ||
| 38 | + */ | ||
| 39 | + public static boolean verifySign(String productKey, String deviceName, String productSecret, String sign) { | ||
| 40 | + String expectedSign = buildSign(productKey, deviceName, productSecret); | ||
| 41 | + return expectedSign.equals(sign); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * 构建签名内容 | ||
| 46 | + * | ||
| 47 | + * @param productKey 产品标识 | ||
| 48 | + * @param deviceName 设备名称 | ||
| 49 | + * @return 签名内容 | ||
| 50 | + */ | ||
| 51 | + private static String buildContent(String productKey, String deviceName) { | ||
| 52 | + return "deviceName" + deviceName + "productKey" + productKey; | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | +} |
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; | @@ -21,7 +21,7 @@ import org.eclipse.californium.core.CoapResource; | ||
| 21 | import org.eclipse.californium.core.CoapServer; | 21 | import org.eclipse.californium.core.CoapServer; |
| 22 | import org.eclipse.californium.core.config.CoapConfig; | 22 | import org.eclipse.californium.core.config.CoapConfig; |
| 23 | import org.eclipse.californium.elements.config.Configuration; | 23 | import org.eclipse.californium.elements.config.Configuration; |
| 24 | -import org.springframework.util.Assert; | 24 | +import cn.hutool.core.lang.Assert; |
| 25 | 25 | ||
| 26 | import java.util.concurrent.TimeUnit; | 26 | import java.util.concurrent.TimeUnit; |
| 27 | 27 |
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 { | @@ -33,7 +33,7 @@ public class IotCoapRegisterHandler extends IotCoapAbstractHandler { | ||
| 33 | Assert.notNull(request, "请求体不能为空"); | 33 | Assert.notNull(request, "请求体不能为空"); |
| 34 | Assert.notBlank(request.getProductKey(), "productKey 不能为空"); | 34 | Assert.notBlank(request.getProductKey(), "productKey 不能为空"); |
| 35 | Assert.notBlank(request.getDeviceName(), "deviceName 不能为空"); | 35 | Assert.notBlank(request.getDeviceName(), "deviceName 不能为空"); |
| 36 | - Assert.notBlank(request.getProductSecret(), "productSecret 不能为空"); | 36 | + Assert.notBlank(request.getSign(), "sign 不能为空"); |
| 37 | 37 | ||
| 38 | // 2. 调用动态注册 | 38 | // 2. 调用动态注册 |
| 39 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(request); | 39 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(request); |
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 { | @@ -35,7 +35,7 @@ public class IotHttpRegisterHandler extends IotHttpAbstractHandler { | ||
| 35 | Assert.notNull(request, "请求参数不能为空"); | 35 | Assert.notNull(request, "请求参数不能为空"); |
| 36 | Assert.notBlank(request.getProductKey(), "productKey 不能为空"); | 36 | Assert.notBlank(request.getProductKey(), "productKey 不能为空"); |
| 37 | Assert.notBlank(request.getDeviceName(), "deviceName 不能为空"); | 37 | Assert.notBlank(request.getDeviceName(), "deviceName 不能为空"); |
| 38 | - Assert.notBlank(request.getProductSecret(), "productSecret 不能为空"); | 38 | + Assert.notBlank(request.getSign(), "sign 不能为空"); |
| 39 | 39 | ||
| 40 | // 2. 调用动态注册 | 40 | // 2. 调用动态注册 |
| 41 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(request); | 41 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(request); |
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; | @@ -4,7 +4,6 @@ import jakarta.validation.constraints.Min; | ||
| 4 | import jakarta.validation.constraints.NotNull; | 4 | import jakarta.validation.constraints.NotNull; |
| 5 | import lombok.Data; | 5 | import lombok.Data; |
| 6 | 6 | ||
| 7 | -// done @AI:validator 参数校验。也看看其他几个配置类有没有类似问题 | ||
| 8 | /** | 7 | /** |
| 9 | * IoT 网关 MQTT 协议配置 | 8 | * IoT 网关 MQTT 协议配置 |
| 10 | * | 9 | * |
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; | @@ -26,7 +26,8 @@ import io.vertx.mqtt.MqttTopicSubscription; | ||
| 26 | import io.vertx.mqtt.messages.MqttPublishMessage; | 26 | import io.vertx.mqtt.messages.MqttPublishMessage; |
| 27 | import lombok.Getter; | 27 | import lombok.Getter; |
| 28 | import lombok.extern.slf4j.Slf4j; | 28 | import lombok.extern.slf4j.Slf4j; |
| 29 | -import org.springframework.util.Assert; | 29 | +import cn.hutool.core.lang.Assert; |
| 30 | +import cn.hutool.core.util.StrUtil; | ||
| 30 | 31 | ||
| 31 | import java.util.List; | 32 | import java.util.List; |
| 32 | 33 | ||
| @@ -41,6 +42,13 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. | @@ -41,6 +42,13 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. | ||
| 41 | public class IotMqttProtocol implements IotProtocol { | 42 | public class IotMqttProtocol implements IotProtocol { |
| 42 | 43 | ||
| 43 | /** | 44 | /** |
| 45 | + * 注册连接的 clientId 标识 | ||
| 46 | + * | ||
| 47 | + * @see #handleEndpoint(MqttEndpoint) | ||
| 48 | + */ | ||
| 49 | + private static final String AUTH_TYPE_REGISTER = "|authType=register|"; | ||
| 50 | + | ||
| 51 | + /** | ||
| 44 | * 协议配置 | 52 | * 协议配置 |
| 45 | */ | 53 | */ |
| 46 | private final ProtocolInstanceProperties properties; | 54 | private final ProtocolInstanceProperties properties; |
| @@ -93,7 +101,7 @@ public class IotMqttProtocol implements IotProtocol { | @@ -93,7 +101,7 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 93 | this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); | 101 | this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); |
| 94 | IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); | 102 | IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); |
| 95 | this.authHandler = new IotMqttAuthHandler(connectionManager, deviceMessageService, deviceApi, serverId); | 103 | this.authHandler = new IotMqttAuthHandler(connectionManager, deviceMessageService, deviceApi, serverId); |
| 96 | - this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService, deviceApi); | 104 | + this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService); |
| 97 | this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, deviceMessageService, serverId); | 105 | this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, deviceMessageService, serverId); |
| 98 | 106 | ||
| 99 | // 初始化下行消息订阅者 | 107 | // 初始化下行消息订阅者 |
| @@ -112,7 +120,6 @@ public class IotMqttProtocol implements IotProtocol { | @@ -112,7 +120,6 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 112 | return IotProtocolTypeEnum.MQTT; | 120 | return IotProtocolTypeEnum.MQTT; |
| 113 | } | 121 | } |
| 114 | 122 | ||
| 115 | - // done @AI:这个方法的整体注释风格,参考 IotTcpProtocol 的 start 方法。 | ||
| 116 | @Override | 123 | @Override |
| 117 | public void start() { | 124 | public void start() { |
| 118 | if (running) { | 125 | if (running) { |
| @@ -209,13 +216,18 @@ public class IotMqttProtocol implements IotProtocol { | @@ -209,13 +216,18 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 209 | * @param endpoint MQTT 连接端点 | 216 | * @param endpoint MQTT 连接端点 |
| 210 | */ | 217 | */ |
| 211 | private void handleEndpoint(MqttEndpoint endpoint) { | 218 | private void handleEndpoint(MqttEndpoint endpoint) { |
| 219 | + // 1. 如果是注册请求,注册待认证连接;否则走正常认证流程 | ||
| 212 | String clientId = endpoint.clientIdentifier(); | 220 | String clientId = endpoint.clientIdentifier(); |
| 213 | - | ||
| 214 | - // 1. 委托 authHandler 处理连接认证 | ||
| 215 | - // done @AI:register topic 不需要注册,需要判断下;当前逻辑已支持(设备可在未认证状态发送 register 消息,registerHandler 会处理) | ||
| 216 | - if (!authHandler.handleAuthenticationRequest(endpoint)) { | ||
| 217 | - endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD); | 221 | + if (StrUtil.endWith(clientId, AUTH_TYPE_REGISTER)) { |
| 222 | + // 情况一:设备注册请求 | ||
| 223 | + registerHandler.handleRegister(endpoint); | ||
| 218 | return; | 224 | return; |
| 225 | + } else { | ||
| 226 | + // 情况二:普通认证请求 | ||
| 227 | + if (!authHandler.handleAuthenticationRequest(endpoint)) { | ||
| 228 | + endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD); | ||
| 229 | + return; | ||
| 230 | + } | ||
| 219 | } | 231 | } |
| 220 | 232 | ||
| 221 | // 2.1 设置异常和关闭处理器 | 233 | // 2.1 设置异常和关闭处理器 |
| @@ -224,9 +236,8 @@ public class IotMqttProtocol implements IotProtocol { | @@ -224,9 +236,8 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 224 | clientId, connectionManager.getEndpointAddress(endpoint), ex.getMessage()); | 236 | clientId, connectionManager.getEndpointAddress(endpoint), ex.getMessage()); |
| 225 | endpoint.close(); | 237 | endpoint.close(); |
| 226 | }); | 238 | }); |
| 227 | - // done @AI:closeHandler 处理底层连接关闭(网络中断、异常等),disconnectHandler 处理 MQTT DISCONNECT 报文 | ||
| 228 | - endpoint.closeHandler(v -> cleanupConnection(endpoint)); | ||
| 229 | - endpoint.disconnectHandler(v -> { | 239 | + endpoint.closeHandler(v -> cleanupConnection(endpoint)); // 处理底层连接关闭(网络中断、异常等) |
| 240 | + endpoint.disconnectHandler(v -> { // 处理 MQTT DISCONNECT 报文 | ||
| 230 | log.debug("[handleEndpoint][设备断开连接,客户端 ID: {}]", clientId); | 241 | log.debug("[handleEndpoint][设备断开连接,客户端 ID: {}]", clientId); |
| 231 | cleanupConnection(endpoint); | 242 | cleanupConnection(endpoint); |
| 232 | }); | 243 | }); |
| @@ -239,7 +250,6 @@ public class IotMqttProtocol implements IotProtocol { | @@ -239,7 +250,6 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 239 | endpoint.publishReleaseHandler(endpoint::publishComplete); | 250 | endpoint.publishReleaseHandler(endpoint::publishComplete); |
| 240 | 251 | ||
| 241 | // 4.1 设置订阅处理器 | 252 | // 4.1 设置订阅处理器 |
| 242 | - // done @AI:使用 CollectionUtils.convertList 简化 | ||
| 243 | endpoint.subscribeHandler(subscribe -> { | 253 | endpoint.subscribeHandler(subscribe -> { |
| 244 | List<String> topicNames = convertList(subscribe.topicSubscriptions(), MqttTopicSubscription::topicName); | 254 | List<String> topicNames = convertList(subscribe.topicSubscriptions(), MqttTopicSubscription::topicName); |
| 245 | log.debug("[handleEndpoint][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames); | 255 | log.debug("[handleEndpoint][设备订阅,客户端 ID: {},主题: {}]", clientId, topicNames); |
| @@ -265,21 +275,16 @@ public class IotMqttProtocol implements IotProtocol { | @@ -265,21 +275,16 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 265 | private void processMessage(MqttEndpoint endpoint, MqttPublishMessage message) { | 275 | private void processMessage(MqttEndpoint endpoint, MqttPublishMessage message) { |
| 266 | String clientId = endpoint.clientIdentifier(); | 276 | String clientId = endpoint.clientIdentifier(); |
| 267 | try { | 277 | try { |
| 268 | - // 根据 topic 分发到不同 handler | 278 | + // 1. 处理业务消息 |
| 269 | String topic = message.topicName(); | 279 | String topic = message.topicName(); |
| 270 | byte[] payload = message.payload().getBytes(); | 280 | byte[] payload = message.payload().getBytes(); |
| 271 | - if (registerHandler.isRegisterMessage(topic)) { | ||
| 272 | - registerHandler.handleRegister(endpoint, topic, payload); | ||
| 273 | - } else { | ||
| 274 | - upstreamHandler.handleBusinessRequest(endpoint, topic, payload); | ||
| 275 | - } | 281 | + upstreamHandler.handleBusinessRequest(endpoint, topic, payload); |
| 276 | 282 | ||
| 277 | - // 根据 QoS 级别发送相应的确认消息 | 283 | + // 2. 根据 QoS 级别发送相应的确认消息 |
| 278 | handleQoSAck(endpoint, message); | 284 | handleQoSAck(endpoint, message); |
| 279 | } catch (Exception e) { | 285 | } catch (Exception e) { |
| 280 | log.error("[processMessage][消息处理失败,断开连接,客户端 ID: {},地址: {},错误: {}]", | 286 | log.error("[processMessage][消息处理失败,断开连接,客户端 ID: {},地址: {},错误: {}]", |
| 281 | clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage()); | 287 | clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage()); |
| 282 | - cleanupConnection(endpoint); | ||
| 283 | endpoint.close(); | 288 | endpoint.close(); |
| 284 | } | 289 | } |
| 285 | } | 290 | } |
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; | @@ -21,7 +21,7 @@ import lombok.extern.slf4j.Slf4j; | ||
| 21 | @RequiredArgsConstructor | 21 | @RequiredArgsConstructor |
| 22 | public abstract class IotMqttAbstractHandler { | 22 | public abstract class IotMqttAbstractHandler { |
| 23 | 23 | ||
| 24 | - // done @AI:当前使用 Alink 序列化类型,后续可考虑支持更多序列化方式 | 24 | + // TODO @AI:当前使用 Alink 序列化类型,后续可考虑支持更多序列化方式 |
| 25 | /** | 25 | /** |
| 26 | * 默认编解码类型(MQTT 使用 Alink 协议) | 26 | * 默认编解码类型(MQTT 使用 Alink 协议) |
| 27 | */ | 27 | */ |
| @@ -31,7 +31,6 @@ public abstract class IotMqttAbstractHandler { | @@ -31,7 +31,6 @@ public abstract class IotMqttAbstractHandler { | ||
| 31 | protected final IotDeviceMessageService deviceMessageService; | 31 | protected final IotDeviceMessageService deviceMessageService; |
| 32 | 32 | ||
| 33 | // done @AI:基于 method 通过 IotMqttTopicUtils.buildTopicByMethod 计算 reply topic | 33 | // done @AI:基于 method 通过 IotMqttTopicUtils.buildTopicByMethod 计算 reply topic |
| 34 | - // done @AI:当前通过 deviceMessageService.encodeDeviceMessage 编码,保持简洁 | ||
| 35 | /** | 34 | /** |
| 36 | * 发送成功响应到设备 | 35 | * 发送成功响应到设备 |
| 37 | * | 36 | * |
| @@ -89,6 +88,6 @@ public abstract class IotMqttAbstractHandler { | @@ -89,6 +88,6 @@ public abstract class IotMqttAbstractHandler { | ||
| 89 | } | 88 | } |
| 90 | } | 89 | } |
| 91 | 90 | ||
| 92 | - // done @AI:当前 sendSuccessResponse/sendErrorResponse 已足够清晰,暂不抽取 writeResponse | 91 | + // TODO @AI:当前 sendSuccessResponse/sendErrorResponse 已足够清晰,暂不抽取 writeResponse(必须抽取!!!) |
| 93 | 92 | ||
| 94 | } | 93 | } |
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; | @@ -14,7 +14,7 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; | ||
| 14 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | 14 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| 15 | import io.vertx.mqtt.MqttEndpoint; | 15 | import io.vertx.mqtt.MqttEndpoint; |
| 16 | import lombok.extern.slf4j.Slf4j; | 16 | import lombok.extern.slf4j.Slf4j; |
| 17 | -import org.springframework.util.Assert; | 17 | +import cn.hutool.core.lang.Assert; |
| 18 | 18 | ||
| 19 | import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL; | 19 | import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL; |
| 20 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 20 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| @@ -43,13 +43,13 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler { | @@ -43,13 +43,13 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler { | ||
| 43 | this.serverId = serverId; | 43 | this.serverId = serverId; |
| 44 | } | 44 | } |
| 45 | 45 | ||
| 46 | - // (暂时不改)TODO @AI:【动态注册】在 clientId 包含 |authType=register 时,进行动态注册设备;校验是 clientId、username、password 三者组合;它是拼接 productSecret 的哈希值;所以 IotDeviceAuthUtils 里面的 buildContent 要改造; | ||
| 47 | /** | 46 | /** |
| 48 | * 处理 MQTT 连接(认证)请求 | 47 | * 处理 MQTT 连接(认证)请求 |
| 49 | * | 48 | * |
| 50 | * @param endpoint MQTT 连接端点 | 49 | * @param endpoint MQTT 连接端点 |
| 51 | * @return 认证是否成功 | 50 | * @return 认证是否成功 |
| 52 | */ | 51 | */ |
| 52 | + @SuppressWarnings("DataFlowIssue") | ||
| 53 | public boolean handleAuthenticationRequest(MqttEndpoint endpoint) { | 53 | public boolean handleAuthenticationRequest(MqttEndpoint endpoint) { |
| 54 | String clientId = endpoint.clientIdentifier(); | 54 | String clientId = endpoint.clientIdentifier(); |
| 55 | String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null; | 55 | String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null; |
| @@ -59,9 +59,9 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler { | @@ -59,9 +59,9 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler { | ||
| 59 | 59 | ||
| 60 | try { | 60 | try { |
| 61 | // 1.1 解析认证参数 | 61 | // 1.1 解析认证参数 |
| 62 | - Assert.hasText(clientId, "clientId 不能为空"); | ||
| 63 | - Assert.hasText(username, "username 不能为空"); | ||
| 64 | - Assert.hasText(password, "password 不能为空"); | 62 | + Assert.notBlank(clientId, "clientId 不能为空"); |
| 63 | + Assert.notBlank(username, "username 不能为空"); | ||
| 64 | + Assert.notBlank(password, "password 不能为空"); | ||
| 65 | // 1.2 构建认证参数 | 65 | // 1.2 构建认证参数 |
| 66 | IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO() | 66 | IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO() |
| 67 | .setClientId(clientId) | 67 | .setClientId(clientId) |
| @@ -102,8 +102,6 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler { | @@ -102,8 +102,6 @@ public class IotMqttAuthHandler extends IotMqttAbstractHandler { | ||
| 102 | .setDeviceId(device.getId()) | 102 | .setDeviceId(device.getId()) |
| 103 | .setProductKey(device.getProductKey()) | 103 | .setProductKey(device.getProductKey()) |
| 104 | .setDeviceName(device.getDeviceName()) | 104 | .setDeviceName(device.getDeviceName()) |
| 105 | - .setClientId(clientId) | ||
| 106 | - .setAuthenticated(true) | ||
| 107 | .setRemoteAddress(connectionManager.getEndpointAddress(endpoint)); | 105 | .setRemoteAddress(connectionManager.getEndpointAddress(endpoint)); |
| 108 | connectionManager.registerConnection(endpoint, device.getId(), connectionInfo); | 106 | connectionManager.registerConnection(endpoint, device.getId(), connectionInfo); |
| 109 | } | 107 | } |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream; |
| 2 | 2 | ||
| 3 | -import cn.hutool.core.util.ArrayUtil; | ||
| 4 | -import cn.hutool.core.util.StrUtil; | ||
| 5 | -import cn.iocoder.yudao.framework.common.exception.ServiceException; | 3 | +import cn.hutool.core.lang.Assert; |
| 4 | +import cn.hutool.extra.spring.SpringUtil; | ||
| 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | 6 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 9 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 7 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 10 | -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 8 | +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; |
| 11 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | 9 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; |
| 12 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; | 10 | import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; |
| 11 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | ||
| 13 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; | 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; |
| 14 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | 13 | import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; |
| 15 | import io.vertx.mqtt.MqttEndpoint; | 14 | import io.vertx.mqtt.MqttEndpoint; |
| 16 | import lombok.extern.slf4j.Slf4j; | 15 | import lombok.extern.slf4j.Slf4j; |
| 17 | -import org.springframework.util.Assert; | ||
| 18 | - | ||
| 19 | -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; | ||
| 20 | -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; | ||
| 21 | 16 | ||
| 22 | /** | 17 | /** |
| 23 | * IoT 网关 MQTT 设备注册处理器:处理设备动态注册消息(一型一密) | 18 | * IoT 网关 MQTT 设备注册处理器:处理设备动态注册消息(一型一密) |
| @@ -27,114 +22,62 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC | @@ -27,114 +22,62 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC | ||
| 27 | @Slf4j | 22 | @Slf4j |
| 28 | public class IotMqttRegisterHandler extends IotMqttAbstractHandler { | 23 | public class IotMqttRegisterHandler extends IotMqttAbstractHandler { |
| 29 | 24 | ||
| 30 | - // done @AI:IotDeviceMessageMethodEnum.DEVICE_REGISTER 计算出来?IotMqttTopicUtils?已使用常量,保持简洁 | ||
| 31 | - /** | ||
| 32 | - * register 请求的 topic 后缀 | ||
| 33 | - */ | ||
| 34 | - public static final String REGISTER_TOPIC_SUFFIX = "/thing/auth/register"; | ||
| 35 | - | ||
| 36 | private final IotDeviceCommonApi deviceApi; | 25 | private final IotDeviceCommonApi deviceApi; |
| 37 | 26 | ||
| 38 | - // done @AI:通过 springutil 处理;构造函数注入更清晰,保持原样 | ||
| 39 | public IotMqttRegisterHandler(IotMqttConnectionManager connectionManager, | 27 | public IotMqttRegisterHandler(IotMqttConnectionManager connectionManager, |
| 40 | - IotDeviceMessageService deviceMessageService, | ||
| 41 | - IotDeviceCommonApi deviceApi) { | 28 | + IotDeviceMessageService deviceMessageService) { |
| 42 | super(connectionManager, deviceMessageService); | 29 | super(connectionManager, deviceMessageService); |
| 43 | - this.deviceApi = deviceApi; | 30 | + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class); |
| 44 | } | 31 | } |
| 45 | 32 | ||
| 46 | /** | 33 | /** |
| 47 | - * 判断是否为注册消息 | ||
| 48 | - * | ||
| 49 | - * @param topic 主题 | ||
| 50 | - * @return 是否为注册消息 | ||
| 51 | - */ | ||
| 52 | - // done @AI:是不是搞到 IotMqttTopicUtils 里?当前实现简洁,保持原样 | ||
| 53 | - public boolean isRegisterMessage(String topic) { | ||
| 54 | - return topic != null && topic.endsWith(REGISTER_TOPIC_SUFFIX); | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - /** | ||
| 58 | - * 处理注册消息 | 34 | + * 处理注册连接 |
| 35 | + * <p> | ||
| 36 | + * 通过 MQTT 连接的 username 解析设备信息,password 作为签名,直接处理设备注册 | ||
| 59 | * | 37 | * |
| 60 | * @param endpoint MQTT 连接端点 | 38 | * @param endpoint MQTT 连接端点 |
| 61 | - * @param topic 主题 | ||
| 62 | - * @param payload 消息内容 | 39 | + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> |
| 63 | */ | 40 | */ |
| 64 | - public void handleRegister(MqttEndpoint endpoint, String topic, byte[] payload) { | 41 | + @SuppressWarnings("DataFlowIssue") |
| 42 | + public void handleRegister(MqttEndpoint endpoint) { | ||
| 65 | String clientId = endpoint.clientIdentifier(); | 43 | String clientId = endpoint.clientIdentifier(); |
| 66 | - IotDeviceMessage message = null; | 44 | + String username = endpoint.auth() != null ? endpoint.auth().getUsername() : null; |
| 45 | + String password = endpoint.auth() != null ? endpoint.auth().getPassword() : null; | ||
| 46 | + String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(); | ||
| 67 | String productKey = null; | 47 | String productKey = null; |
| 68 | String deviceName = null; | 48 | String deviceName = null; |
| 69 | - String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(); | ||
| 70 | 49 | ||
| 71 | try { | 50 | try { |
| 72 | - // 1.1 基础检查 | ||
| 73 | - if (ArrayUtil.isEmpty(payload)) { | ||
| 74 | - return; | ||
| 75 | - } | ||
| 76 | - // 1.2 解析主题,获取 productKey 和 deviceName | ||
| 77 | - String[] topicParts = topic.split("/"); | ||
| 78 | - Assert.isTrue(topicParts.length >= 4 && !StrUtil.hasBlank(topicParts[2], topicParts[3]), | ||
| 79 | - "topic 格式不正确,无法解析 productKey 和 deviceName"); | ||
| 80 | - productKey = topicParts[2]; | ||
| 81 | - deviceName = topicParts[3]; | 51 | + // 1.1 校验参数 |
| 52 | + Assert.notBlank(clientId, "clientId 不能为空"); | ||
| 53 | + Assert.notBlank(username, "username 不能为空"); | ||
| 54 | + Assert.notBlank(password, "password 不能为空"); | ||
| 55 | + IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username); | ||
| 56 | + Assert.notNull(deviceInfo, "解析设备信息失败"); | ||
| 57 | + productKey = deviceInfo.getProductKey(); | ||
| 58 | + deviceName = deviceInfo.getDeviceName(); | ||
| 59 | + log.info("[handleRegister][设备注册连接,客户端 ID: {},设备: {}.{}]", | ||
| 60 | + clientId, productKey, deviceName); | ||
| 61 | + // 1.2 构建注册参数 | ||
| 62 | + IotDeviceRegisterReqDTO params = new IotDeviceRegisterReqDTO() | ||
| 63 | + .setProductKey(productKey) | ||
| 64 | + .setDeviceName(deviceName) | ||
| 65 | + .setSign(password); | ||
| 82 | 66 | ||
| 83 | - // 2. 使用默认编解码器解码消息(设备可能未注册,无法获取 codecType) | ||
| 84 | - message = deviceMessageService.decodeDeviceMessage(payload, DEFAULT_CODEC_TYPE); | ||
| 85 | - Assert.notNull(message, "消息解码失败"); | 67 | + // 2. 调用动态注册 API |
| 68 | + CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); | ||
| 69 | + result.checkError(); | ||
| 86 | 70 | ||
| 87 | - // 3. 处理设备动态注册请求 | ||
| 88 | - log.info("[handleRegister][收到设备注册消息,设备: {}.{}, 方法: {}]", | ||
| 89 | - productKey, deviceName, message.getMethod()); | ||
| 90 | - processRegisterRequest(message, productKey, deviceName, endpoint); | ||
| 91 | - } catch (ServiceException e) { | ||
| 92 | - log.warn("[handleRegister][业务异常,客户端 ID: {},主题: {},错误: {}]", | ||
| 93 | - clientId, topic, e.getMessage()); | ||
| 94 | - String requestId = message != null ? message.getRequestId() : null; | ||
| 95 | - sendErrorResponse(endpoint, productKey, deviceName, requestId, method, e.getCode(), e.getMessage()); | ||
| 96 | - } catch (IllegalArgumentException e) { | ||
| 97 | - log.warn("[handleRegister][参数校验失败,客户端 ID: {},主题: {},错误: {}]", | ||
| 98 | - clientId, topic, e.getMessage()); | ||
| 99 | - String requestId = message != null ? message.getRequestId() : null; | ||
| 100 | - sendErrorResponse(endpoint, productKey, deviceName, requestId, method, | ||
| 101 | - BAD_REQUEST.getCode(), e.getMessage()); | 71 | + // 3. 接受连接,并发送成功响应 |
| 72 | + endpoint.accept(false); | ||
| 73 | + sendSuccessResponse(endpoint, productKey, deviceName, null, method, result.getData()); | ||
| 74 | + log.info("[handleRegister][注册成功,设备: {}.{},客户端 ID: {}]", productKey, deviceName, clientId); | ||
| 102 | } catch (Exception e) { | 75 | } catch (Exception e) { |
| 103 | - log.error("[handleRegister][消息处理异常,客户端 ID: {},主题: {},错误: {}]", | ||
| 104 | - clientId, topic, e.getMessage(), e); | ||
| 105 | - String requestId = message != null ? message.getRequestId() : null; | ||
| 106 | - sendErrorResponse(endpoint, productKey, deviceName, requestId, method, | ||
| 107 | - INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg()); | 76 | + log.warn("[handleRegister][注册失败,客户端 ID: {},错误: {}]", clientId, e.getMessage()); |
| 77 | + // 接受连接,并发送错误响应 | ||
| 78 | + endpoint.accept(false); | ||
| 79 | + sendErrorResponse(endpoint, productKey, deviceName, null, method, 500, e.getMessage()); | ||
| 108 | } | 80 | } |
| 109 | } | 81 | } |
| 110 | 82 | ||
| 111 | - /** | ||
| 112 | - * 处理设备动态注册请求(一型一密,不需要 deviceSecret) | ||
| 113 | - * | ||
| 114 | - * @param message 消息信息 | ||
| 115 | - * @param productKey 产品 Key | ||
| 116 | - * @param deviceName 设备名称 | ||
| 117 | - * @param endpoint MQTT 连接端点 | ||
| 118 | - * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> | ||
| 119 | - */ | ||
| 120 | - @SuppressWarnings("DuplicatedCode") | ||
| 121 | - private void processRegisterRequest(IotDeviceMessage message, String productKey, String deviceName, | ||
| 122 | - MqttEndpoint endpoint) { | ||
| 123 | - // 1. 解析注册参数 | ||
| 124 | - IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class); | ||
| 125 | - Assert.notNull(params, "注册参数不能为空"); | ||
| 126 | - Assert.hasText(params.getProductKey(), "productKey 不能为空"); | ||
| 127 | - Assert.hasText(params.getDeviceName(), "deviceName 不能为空"); | ||
| 128 | - | ||
| 129 | - // 2. 调用动态注册 API | ||
| 130 | - CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); | ||
| 131 | - result.checkError(); | ||
| 132 | - | ||
| 133 | - // 3. 发送成功响应(包含 deviceSecret) | ||
| 134 | - String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(); | ||
| 135 | - sendSuccessResponse(endpoint, productKey, deviceName, message.getRequestId(), method, result.getData()); | ||
| 136 | - log.info("[processRegisterRequest][注册成功,设备名: {},客户端 ID: {}]", | ||
| 137 | - params.getDeviceName(), endpoint.clientIdentifier()); | ||
| 138 | - } | ||
| 139 | - | ||
| 140 | } | 83 | } |
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 { | @@ -76,6 +76,7 @@ public class IotMqttConnectionManager { | ||
| 76 | * @param deviceId 设备 ID | 76 | * @param deviceId 设备 ID |
| 77 | * @param connectionInfo 连接信息 | 77 | * @param connectionInfo 连接信息 |
| 78 | */ | 78 | */ |
| 79 | + // TODO @AI:移除掉 deviceId ???参考别的 tcp 等模块协议 | ||
| 79 | public void registerConnection(MqttEndpoint endpoint, Long deviceId, ConnectionInfo connectionInfo) { | 80 | public void registerConnection(MqttEndpoint endpoint, Long deviceId, ConnectionInfo connectionInfo) { |
| 80 | // 如果设备已有其他连接,先清理旧连接 | 81 | // 如果设备已有其他连接,先清理旧连接 |
| 81 | MqttEndpoint oldEndpoint = deviceEndpointMap.get(deviceId); | 82 | MqttEndpoint oldEndpoint = deviceEndpointMap.get(deviceId); |
| @@ -176,29 +177,16 @@ public class IotMqttConnectionManager { | @@ -176,29 +177,16 @@ public class IotMqttConnectionManager { | ||
| 176 | * 设备 ID | 177 | * 设备 ID |
| 177 | */ | 178 | */ |
| 178 | private Long deviceId; | 179 | private Long deviceId; |
| 179 | - | ||
| 180 | /** | 180 | /** |
| 181 | * 产品 Key | 181 | * 产品 Key |
| 182 | */ | 182 | */ |
| 183 | private String productKey; | 183 | private String productKey; |
| 184 | - | ||
| 185 | /** | 184 | /** |
| 186 | * 设备名称 | 185 | * 设备名称 |
| 187 | */ | 186 | */ |
| 188 | private String deviceName; | 187 | private String deviceName; |
| 189 | 188 | ||
| 190 | /** | 189 | /** |
| 191 | - * 客户端 ID | ||
| 192 | - */ | ||
| 193 | - private String clientId; | ||
| 194 | - | ||
| 195 | - // done @AI:保留 authenticated 字段,用于区分已认证连接和待认证连接(如动态注册场景) | ||
| 196 | - /** | ||
| 197 | - * 是否已认证 | ||
| 198 | - */ | ||
| 199 | - private boolean authenticated; | ||
| 200 | - | ||
| 201 | - /** | ||
| 202 | * 连接地址 | 190 | * 连接地址 |
| 203 | */ | 191 | */ |
| 204 | private String remoteAddress; | 192 | private String remoteAddress; |
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; | @@ -21,7 +21,7 @@ import io.vertx.core.net.NetServerOptions; | ||
| 21 | import io.vertx.core.net.PemKeyCertOptions; | 21 | import io.vertx.core.net.PemKeyCertOptions; |
| 22 | import lombok.Getter; | 22 | import lombok.Getter; |
| 23 | import lombok.extern.slf4j.Slf4j; | 23 | import lombok.extern.slf4j.Slf4j; |
| 24 | -import org.springframework.util.Assert; | 24 | +import cn.hutool.core.lang.Assert; |
| 25 | 25 | ||
| 26 | /** | 26 | /** |
| 27 | * IoT TCP 协议实现 | 27 | * IoT TCP 协议实现 |
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; | @@ -8,7 +8,7 @@ import io.vertx.core.Handler; | ||
| 8 | import io.vertx.core.buffer.Buffer; | 8 | import io.vertx.core.buffer.Buffer; |
| 9 | import io.vertx.core.parsetools.RecordParser; | 9 | import io.vertx.core.parsetools.RecordParser; |
| 10 | import lombok.extern.slf4j.Slf4j; | 10 | import lombok.extern.slf4j.Slf4j; |
| 11 | -import org.springframework.util.Assert; | 11 | +import cn.hutool.core.lang.Assert; |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| 14 | * IoT TCP 分隔符帧编解码器 | 14 | * IoT TCP 分隔符帧编解码器 |
| @@ -39,7 +39,7 @@ public class IotTcpDelimiterFrameCodec implements IotTcpFrameCodec { | @@ -39,7 +39,7 @@ public class IotTcpDelimiterFrameCodec implements IotTcpFrameCodec { | ||
| 39 | private final byte[] delimiterBytes; | 39 | private final byte[] delimiterBytes; |
| 40 | 40 | ||
| 41 | public IotTcpDelimiterFrameCodec(IotTcpConfig.CodecConfig config) { | 41 | public IotTcpDelimiterFrameCodec(IotTcpConfig.CodecConfig config) { |
| 42 | - Assert.hasText(config.getDelimiter(), "delimiter 不能为空"); | 42 | + Assert.notBlank(config.getDelimiter(), "delimiter 不能为空"); |
| 43 | this.delimiterBytes = parseDelimiter(config.getDelimiter()); | 43 | this.delimiterBytes = parseDelimiter(config.getDelimiter()); |
| 44 | } | 44 | } |
| 45 | 45 |
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; | @@ -7,7 +7,7 @@ import io.vertx.core.Handler; | ||
| 7 | import io.vertx.core.buffer.Buffer; | 7 | import io.vertx.core.buffer.Buffer; |
| 8 | import io.vertx.core.parsetools.RecordParser; | 8 | import io.vertx.core.parsetools.RecordParser; |
| 9 | import lombok.extern.slf4j.Slf4j; | 9 | import lombok.extern.slf4j.Slf4j; |
| 10 | -import org.springframework.util.Assert; | 10 | +import cn.hutool.core.lang.Assert; |
| 11 | 11 | ||
| 12 | /** | 12 | /** |
| 13 | * IoT TCP 定长帧编解码器 | 13 | * IoT TCP 定长帧编解码器 |
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; | @@ -7,7 +7,7 @@ import io.vertx.core.Handler; | ||
| 7 | import io.vertx.core.buffer.Buffer; | 7 | import io.vertx.core.buffer.Buffer; |
| 8 | import io.vertx.core.parsetools.RecordParser; | 8 | import io.vertx.core.parsetools.RecordParser; |
| 9 | import lombok.extern.slf4j.Slf4j; | 9 | import lombok.extern.slf4j.Slf4j; |
| 10 | -import org.springframework.util.Assert; | 10 | +import cn.hutool.core.lang.Assert; |
| 11 | 11 | ||
| 12 | import java.util.concurrent.atomic.AtomicReference; | 12 | import java.util.concurrent.atomic.AtomicReference; |
| 13 | 13 |
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; | @@ -24,7 +24,7 @@ import io.vertx.core.buffer.Buffer; | ||
| 24 | import io.vertx.core.net.NetSocket; | 24 | import io.vertx.core.net.NetSocket; |
| 25 | import io.vertx.core.parsetools.RecordParser; | 25 | import io.vertx.core.parsetools.RecordParser; |
| 26 | import lombok.extern.slf4j.Slf4j; | 26 | import lombok.extern.slf4j.Slf4j; |
| 27 | -import org.springframework.util.Assert; | 27 | +import cn.hutool.core.lang.Assert; |
| 28 | 28 | ||
| 29 | import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*; | 29 | import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*; |
| 30 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 30 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| @@ -167,8 +167,8 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | @@ -167,8 +167,8 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 167 | // 1. 解析认证参数 | 167 | // 1. 解析认证参数 |
| 168 | IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class); | 168 | IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class); |
| 169 | Assert.notNull(authParams, "认证参数不能为空"); | 169 | Assert.notNull(authParams, "认证参数不能为空"); |
| 170 | - Assert.hasText(authParams.getUsername(), "username 不能为空"); | ||
| 171 | - Assert.hasText(authParams.getPassword(), "password 不能为空"); | 170 | + Assert.notBlank(authParams.getUsername(), "username 不能为空"); |
| 171 | + Assert.notBlank(authParams.getPassword(), "password 不能为空"); | ||
| 172 | 172 | ||
| 173 | // 2.1 执行认证 | 173 | // 2.1 执行认证 |
| 174 | CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); | 174 | CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); |
| @@ -204,8 +204,9 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | @@ -204,8 +204,9 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 204 | // 1. 解析注册参数 | 204 | // 1. 解析注册参数 |
| 205 | IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class); | 205 | IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class); |
| 206 | Assert.notNull(params, "注册参数不能为空"); | 206 | Assert.notNull(params, "注册参数不能为空"); |
| 207 | - Assert.hasText(params.getProductKey(), "productKey 不能为空"); | ||
| 208 | - Assert.hasText(params.getDeviceName(), "deviceName 不能为空"); | 207 | + Assert.notBlank(params.getProductKey(), "productKey 不能为空"); |
| 208 | + Assert.notBlank(params.getDeviceName(), "deviceName 不能为空"); | ||
| 209 | + Assert.notBlank(params.getSign(), "sign 不能为空"); | ||
| 209 | 210 | ||
| 210 | // 2. 调用动态注册 | 211 | // 2. 调用动态注册 |
| 211 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); | 212 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); |
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; | @@ -18,7 +18,7 @@ import io.vertx.core.datagram.DatagramSocket; | ||
| 18 | import io.vertx.core.datagram.DatagramSocketOptions; | 18 | import io.vertx.core.datagram.DatagramSocketOptions; |
| 19 | import lombok.Getter; | 19 | import lombok.Getter; |
| 20 | import lombok.extern.slf4j.Slf4j; | 20 | import lombok.extern.slf4j.Slf4j; |
| 21 | -import org.springframework.util.Assert; | 21 | +import cn.hutool.core.lang.Assert; |
| 22 | 22 | ||
| 23 | /** | 23 | /** |
| 24 | * IoT UDP 协议实现 | 24 | * IoT UDP 协议实现 |
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; | @@ -27,7 +27,7 @@ import io.vertx.core.buffer.Buffer; | ||
| 27 | import io.vertx.core.datagram.DatagramPacket; | 27 | import io.vertx.core.datagram.DatagramPacket; |
| 28 | import io.vertx.core.datagram.DatagramSocket; | 28 | import io.vertx.core.datagram.DatagramSocket; |
| 29 | import lombok.extern.slf4j.Slf4j; | 29 | import lombok.extern.slf4j.Slf4j; |
| 30 | -import org.springframework.util.Assert; | 30 | +import cn.hutool.core.lang.Assert; |
| 31 | 31 | ||
| 32 | import java.net.InetSocketAddress; | 32 | import java.net.InetSocketAddress; |
| 33 | import java.util.Map; | 33 | import java.util.Map; |
| @@ -173,8 +173,8 @@ public class IotUdpUpstreamHandler { | @@ -173,8 +173,8 @@ public class IotUdpUpstreamHandler { | ||
| 173 | // 1. 解析认证参数 | 173 | // 1. 解析认证参数 |
| 174 | IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class); | 174 | IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class); |
| 175 | Assert.notNull(authParams, "认证参数不能为空"); | 175 | Assert.notNull(authParams, "认证参数不能为空"); |
| 176 | - Assert.hasText(authParams.getUsername(), "username 不能为空"); | ||
| 177 | - Assert.hasText(authParams.getPassword(), "password 不能为空"); | 176 | + Assert.notBlank(authParams.getUsername(), "username 不能为空"); |
| 177 | + Assert.notBlank(authParams.getPassword(), "password 不能为空"); | ||
| 178 | 178 | ||
| 179 | // 2.1 执行认证 | 179 | // 2.1 执行认证 |
| 180 | CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); | 180 | CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); |
| @@ -218,8 +218,9 @@ public class IotUdpUpstreamHandler { | @@ -218,8 +218,9 @@ public class IotUdpUpstreamHandler { | ||
| 218 | // 1. 解析注册参数 | 218 | // 1. 解析注册参数 |
| 219 | IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class); | 219 | IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class); |
| 220 | Assert.notNull(params, "注册参数不能为空"); | 220 | Assert.notNull(params, "注册参数不能为空"); |
| 221 | - Assert.hasText(params.getProductKey(), "productKey 不能为空"); | ||
| 222 | - Assert.hasText(params.getDeviceName(), "deviceName 不能为空"); | 221 | + Assert.notBlank(params.getProductKey(), "productKey 不能为空"); |
| 222 | + Assert.notBlank(params.getDeviceName(), "deviceName 不能为空"); | ||
| 223 | + Assert.notBlank(params.getSign(), "sign 不能为空"); | ||
| 223 | 224 | ||
| 224 | // 2. 调用动态注册 | 225 | // 2. 调用动态注册 |
| 225 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); | 226 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); |
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; | @@ -20,7 +20,7 @@ import io.vertx.core.http.HttpServerOptions; | ||
| 20 | import io.vertx.core.net.PemKeyCertOptions; | 20 | import io.vertx.core.net.PemKeyCertOptions; |
| 21 | import lombok.Getter; | 21 | import lombok.Getter; |
| 22 | import lombok.extern.slf4j.Slf4j; | 22 | import lombok.extern.slf4j.Slf4j; |
| 23 | -import org.springframework.util.Assert; | 23 | +import cn.hutool.core.lang.Assert; |
| 24 | 24 | ||
| 25 | /** | 25 | /** |
| 26 | * IoT WebSocket 协议实现 | 26 | * IoT WebSocket 协议实现 |
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 | @@ -23,7 +23,7 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessa | ||
| 23 | import io.vertx.core.Handler; | 23 | import io.vertx.core.Handler; |
| 24 | import io.vertx.core.http.ServerWebSocket; | 24 | import io.vertx.core.http.ServerWebSocket; |
| 25 | import lombok.extern.slf4j.Slf4j; | 25 | import lombok.extern.slf4j.Slf4j; |
| 26 | -import org.springframework.util.Assert; | 26 | +import cn.hutool.core.lang.Assert; |
| 27 | 27 | ||
| 28 | import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*; | 28 | import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*; |
| 29 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 29 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| @@ -109,7 +109,7 @@ public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> { | @@ -109,7 +109,7 @@ public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> { | ||
| 109 | // 1.2 解码消息 | 109 | // 1.2 解码消息 |
| 110 | message = serializer.deserialize(payload); | 110 | message = serializer.deserialize(payload); |
| 111 | Assert.notNull(message, "消息反序列化失败"); | 111 | Assert.notNull(message, "消息反序列化失败"); |
| 112 | - Assert.hasText(message.getMethod(), "method 不能为空"); | 112 | + Assert.notBlank(message.getMethod(), "method 不能为空"); |
| 113 | 113 | ||
| 114 | // 2. 根据消息类型路由处理 | 114 | // 2. 根据消息类型路由处理 |
| 115 | if (AUTH_METHOD.equals(message.getMethod())) { | 115 | if (AUTH_METHOD.equals(message.getMethod())) { |
| @@ -150,8 +150,8 @@ public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> { | @@ -150,8 +150,8 @@ public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> { | ||
| 150 | // 1. 解析认证参数 | 150 | // 1. 解析认证参数 |
| 151 | IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class); | 151 | IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class); |
| 152 | Assert.notNull(authParams, "认证参数不能为空"); | 152 | Assert.notNull(authParams, "认证参数不能为空"); |
| 153 | - Assert.hasText(authParams.getUsername(), "username 不能为空"); | ||
| 154 | - Assert.hasText(authParams.getPassword(), "password 不能为空"); | 153 | + Assert.notBlank(authParams.getUsername(), "username 不能为空"); |
| 154 | + Assert.notBlank(authParams.getPassword(), "password 不能为空"); | ||
| 155 | 155 | ||
| 156 | // 2.1 执行认证 | 156 | // 2.1 执行认证 |
| 157 | CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); | 157 | CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); |
| @@ -187,8 +187,9 @@ public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> { | @@ -187,8 +187,9 @@ public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> { | ||
| 187 | // 1. 解析注册参数 | 187 | // 1. 解析注册参数 |
| 188 | IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class); | 188 | IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class); |
| 189 | Assert.notNull(params, "注册参数不能为空"); | 189 | Assert.notNull(params, "注册参数不能为空"); |
| 190 | - Assert.hasText(params.getProductKey(), "productKey 不能为空"); | ||
| 191 | - Assert.hasText(params.getDeviceName(), "deviceName 不能为空"); | 190 | + Assert.notBlank(params.getProductKey(), "productKey 不能为空"); |
| 191 | + Assert.notBlank(params.getDeviceName(), "deviceName 不能为空"); | ||
| 192 | + Assert.notBlank(params.getSign(), "sign 不能为空"); | ||
| 192 | 193 | ||
| 193 | // 2. 调用动态注册 | 194 | // 2. 调用动态注册 |
| 194 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); | 195 | CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); |
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; | @@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 9 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | 9 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; | 10 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 11 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 11 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 12 | +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils; | ||
| 12 | import lombok.extern.slf4j.Slf4j; | 13 | import lombok.extern.slf4j.Slf4j; |
| 13 | import org.eclipse.californium.core.CoapClient; | 14 | import org.eclipse.californium.core.CoapClient; |
| 14 | import org.eclipse.californium.core.CoapResponse; | 15 | import org.eclipse.californium.core.CoapResponse; |
| @@ -203,10 +204,13 @@ public class IotDirectDeviceCoapProtocolIntegrationTest { | @@ -203,10 +204,13 @@ public class IotDirectDeviceCoapProtocolIntegrationTest { | ||
| 203 | // 1.1 构建请求 | 204 | // 1.1 构建请求 |
| 204 | String uri = String.format("coap://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT); | 205 | String uri = String.format("coap://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT); |
| 205 | // 1.2 构建请求参数 | 206 | // 1.2 构建请求参数 |
| 206 | - IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO(); | ||
| 207 | - reqDTO.setProductKey(PRODUCT_KEY); | ||
| 208 | - reqDTO.setDeviceName("test-" + System.currentTimeMillis()); | ||
| 209 | - reqDTO.setProductSecret("test-product-secret"); | 207 | + String deviceName = "test-" + System.currentTimeMillis(); |
| 208 | + String productSecret = "test-product-secret"; // 替换为实际的 productSecret | ||
| 209 | + String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret); | ||
| 210 | + IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO() | ||
| 211 | + .setProductKey(PRODUCT_KEY) | ||
| 212 | + .setDeviceName(deviceName) | ||
| 213 | + .setSign(sign); | ||
| 210 | String payload = JsonUtils.toJsonString(reqDTO); | 214 | String payload = JsonUtils.toJsonString(reqDTO); |
| 211 | // 1.3 输出请求 | 215 | // 1.3 输出请求 |
| 212 | log.info("[testDeviceRegister][请求 URI: {}]", uri); | 216 | log.info("[testDeviceRegister][请求 URI: {}]", uri); |
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; | @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 10 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | 10 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 11 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; | 11 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 12 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 12 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 13 | +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils; | ||
| 13 | import lombok.extern.slf4j.Slf4j; | 14 | import lombok.extern.slf4j.Slf4j; |
| 14 | import org.junit.jupiter.api.Disabled; | 15 | import org.junit.jupiter.api.Disabled; |
| 15 | import org.junit.jupiter.api.Test; | 16 | import org.junit.jupiter.api.Test; |
| @@ -158,10 +159,13 @@ public class IotDirectDeviceHttpProtocolIntegrationTest { | @@ -158,10 +159,13 @@ public class IotDirectDeviceHttpProtocolIntegrationTest { | ||
| 158 | // 1.1 构建请求 | 159 | // 1.1 构建请求 |
| 159 | String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT); | 160 | String url = String.format("http://%s:%d/auth/register/device", SERVER_HOST, SERVER_PORT); |
| 160 | // 1.2 构建请求参数 | 161 | // 1.2 构建请求参数 |
| 162 | + String deviceName = "test-" + System.currentTimeMillis(); | ||
| 163 | + String productSecret = "test-product-secret"; // 替换为实际的 productSecret | ||
| 164 | + String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret); | ||
| 161 | IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO() | 165 | IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO() |
| 162 | .setProductKey(PRODUCT_KEY) | 166 | .setProductKey(PRODUCT_KEY) |
| 163 | - .setDeviceName("test-" + System.currentTimeMillis()) | ||
| 164 | - .setProductSecret("test-product-secret"); | 167 | + .setDeviceName(deviceName) |
| 168 | + .setSign(sign); | ||
| 165 | String payload = JsonUtils.toJsonString(reqDTO); | 169 | String payload = JsonUtils.toJsonString(reqDTO); |
| 166 | // 1.3 输出请求 | 170 | // 1.3 输出请求 |
| 167 | log.info("[testDeviceRegister][请求 URL: {}]", url); | 171 | log.info("[testDeviceRegister][请求 URL: {}]", url); |
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; | @@ -4,10 +4,10 @@ import cn.hutool.core.map.MapUtil; | ||
| 4 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 4 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 5 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | 5 | import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | -import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | 7 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 9 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; | 8 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 9 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 10 | +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils; | ||
| 11 | import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | 11 | import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; |
| 12 | import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec; | 12 | import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec; |
| 13 | import io.netty.handler.codec.mqtt.MqttQoS; | 13 | import io.netty.handler.codec.mqtt.MqttQoS; |
| @@ -173,36 +173,51 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { | @@ -173,36 +173,51 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { | ||
| 173 | /** | 173 | /** |
| 174 | * 直连设备动态注册测试(一型一密) | 174 | * 直连设备动态注册测试(一型一密) |
| 175 | * <p> | 175 | * <p> |
| 176 | - * 使用产品密钥(productSecret)验证身份,成功后返回设备密钥(deviceSecret) | 176 | + * 认证方式: |
| 177 | + * - clientId: 任意值 + "|authType=register|" 后缀 | ||
| 178 | + * - username: {deviceName}&{productKey}(与普通认证相同) | ||
| 179 | + * - password: 签名(使用 productSecret 对 "deviceName" + deviceName + "productKey" + productKey 进行 HMAC-SHA256) | ||
| 177 | * <p> | 180 | * <p> |
| 178 | - * 注意:此接口不需要认证 | 181 | + * 成功后返回设备密钥(deviceSecret),可用于后续一机一密认证 |
| 179 | */ | 182 | */ |
| 180 | @Test | 183 | @Test |
| 181 | public void testDeviceRegister() throws Exception { | 184 | public void testDeviceRegister() throws Exception { |
| 182 | - // 1. 连接并认证(使用已有设备连接) | ||
| 183 | - MqttClient client = connectAndAuth(); | ||
| 184 | - log.info("[testDeviceRegister][连接认证成功]"); | 185 | + // 1.1 构建注册参数 |
| 186 | + String deviceName = "test-mqtt-" + System.currentTimeMillis(); | ||
| 187 | + String productSecret = "test-product-secret"; // 替换为实际的 productSecret | ||
| 188 | + String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret); | ||
| 189 | + // 1.2 构建 MQTT 连接参数(clientId 需要添加 |authType=register| 后缀) | ||
| 190 | + String clientId = IotDeviceAuthUtils.buildClientId(PRODUCT_KEY, deviceName) + "|authType=register|"; | ||
| 191 | + String username = IotDeviceAuthUtils.buildUsername(PRODUCT_KEY, deviceName); | ||
| 192 | + log.info("[testDeviceRegister][注册参数: clientId={}, username={}, sign={}]", | ||
| 193 | + clientId, username, sign); | ||
| 194 | + // 1.3 创建客户端并连接(连接时服务端自动处理注册) | ||
| 195 | + MqttClientOptions options = new MqttClientOptions() | ||
| 196 | + .setClientId(clientId) | ||
| 197 | + .setUsername(username) | ||
| 198 | + .setPassword(sign) | ||
| 199 | + .setCleanSession(true) | ||
| 200 | + .setKeepAliveInterval(60); | ||
| 201 | + MqttClient client = MqttClient.create(vertx, options); | ||
| 185 | 202 | ||
| 186 | try { | 203 | try { |
| 187 | - // 2.1 构建注册消息 | ||
| 188 | - IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO() | ||
| 189 | - .setProductKey(PRODUCT_KEY) | ||
| 190 | - .setDeviceName("test-mqtt-" + System.currentTimeMillis()) | ||
| 191 | - .setProductSecret("test-product-secret"); | ||
| 192 | - IotDeviceMessage request = IotDeviceMessage.requestOf( | ||
| 193 | - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), | ||
| 194 | - registerReqDTO); | ||
| 195 | - | ||
| 196 | - // 2.2 订阅 _reply 主题 | ||
| 197 | - String replyTopic = String.format("/sys/%s/%s/thing/auth/register_reply", | ||
| 198 | - registerReqDTO.getProductKey(), registerReqDTO.getDeviceName()); | ||
| 199 | - subscribe(client, replyTopic); | 204 | + // 2. 设置消息处理器,接收注册响应 |
| 205 | + CompletableFuture<IotDeviceMessage> responseFuture = new CompletableFuture<>(); | ||
| 206 | + client.publishHandler(message -> { | ||
| 207 | + log.info("[testDeviceRegister][收到响应: topic={}, payload={}]", | ||
| 208 | + message.topicName(), message.payload().toString()); | ||
| 209 | + IotDeviceMessage response = CODEC.decode(message.payload().getBytes()); | ||
| 210 | + responseFuture.complete(response); | ||
| 211 | + }); | ||
| 212 | + | ||
| 213 | + // 3. 连接服务器(连接成功后服务端会自动处理注册并发送响应) | ||
| 214 | + client.connect(SERVER_PORT, SERVER_HOST) | ||
| 215 | + .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); | ||
| 216 | + log.info("[testDeviceRegister][连接成功,等待注册响应...]"); | ||
| 200 | 217 | ||
| 201 | - // 3. 发布消息并等待响应 | ||
| 202 | - String topic = String.format("/sys/%s/%s/thing/auth/register", | ||
| 203 | - registerReqDTO.getProductKey(), registerReqDTO.getDeviceName()); | ||
| 204 | - IotDeviceMessage response = publishAndWaitReply(client, topic, request); | ||
| 205 | - log.info("[testDeviceRegister][响应消息: {}]", response); | 218 | + // 4. 等待注册响应 |
| 219 | + IotDeviceMessage response = responseFuture.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); | ||
| 220 | + log.info("[testDeviceRegister][注册响应: {}]", response); | ||
| 206 | log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]"); | 221 | log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]"); |
| 207 | } finally { | 222 | } finally { |
| 208 | disconnect(client); | 223 | disconnect(client); |
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; | @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | 8 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 9 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; | 9 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 11 | +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils; | ||
| 11 | import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpCodecTypeEnum; | 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpCodecTypeEnum; |
| 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec; | 13 | import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec; |
| 13 | import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodecFactory; | 14 | import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodecFactory; |
| @@ -146,10 +147,13 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { | @@ -146,10 +147,13 @@ public class IotDirectDeviceTcpProtocolIntegrationTest { | ||
| 146 | @Test | 147 | @Test |
| 147 | public void testDeviceRegister() throws Exception { | 148 | public void testDeviceRegister() throws Exception { |
| 148 | // 1. 构建注册消息 | 149 | // 1. 构建注册消息 |
| 150 | + String deviceName = "test-tcp-" + System.currentTimeMillis(); | ||
| 151 | + String productSecret = "test-product-secret"; // 替换为实际的 productSecret | ||
| 152 | + String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret); | ||
| 149 | IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO() | 153 | IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO() |
| 150 | .setProductKey(PRODUCT_KEY) | 154 | .setProductKey(PRODUCT_KEY) |
| 151 | - .setDeviceName("test-tcp-" + System.currentTimeMillis()) | ||
| 152 | - .setProductSecret("test-product-secret"); | 155 | + .setDeviceName(deviceName) |
| 156 | + .setSign(sign); | ||
| 153 | IotDeviceMessage request = IotDeviceMessage.requestOf( | 157 | IotDeviceMessage request = IotDeviceMessage.requestOf( |
| 154 | IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO); | 158 | IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO); |
| 155 | 159 |
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; | @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 8 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | 8 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 9 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; | 9 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 10 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 11 | +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils; | ||
| 11 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; | 12 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; |
| 12 | import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; | 13 | import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; |
| 13 | import lombok.extern.slf4j.Slf4j; | 14 | import lombok.extern.slf4j.Slf4j; |
| @@ -100,10 +101,13 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | @@ -100,10 +101,13 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { | ||
| 100 | @Test | 101 | @Test |
| 101 | public void testDeviceRegister() throws Exception { | 102 | public void testDeviceRegister() throws Exception { |
| 102 | // 1. 构建注册消息 | 103 | // 1. 构建注册消息 |
| 104 | + String deviceName = "test-udp-" + System.currentTimeMillis(); | ||
| 105 | + String productSecret = "test-product-secret"; // 替换为实际的 productSecret | ||
| 106 | + String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret); | ||
| 103 | IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO() | 107 | IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO() |
| 104 | .setProductKey(PRODUCT_KEY) | 108 | .setProductKey(PRODUCT_KEY) |
| 105 | - .setDeviceName("test-udp-" + System.currentTimeMillis()) | ||
| 106 | - .setProductSecret("test-product-secret"); | 109 | + .setDeviceName(deviceName) |
| 110 | + .setSign(sign); | ||
| 107 | IotDeviceMessage request = IotDeviceMessage.requestOf( | 111 | IotDeviceMessage request = IotDeviceMessage.requestOf( |
| 108 | IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO); | 112 | IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO); |
| 109 | 113 |
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; | @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; | ||
| 10 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; | 10 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 11 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; | 11 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 12 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; | 12 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 13 | +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils; | ||
| 13 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; | 14 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; |
| 14 | import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; | 15 | import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; |
| 15 | import io.vertx.core.Vertx; | 16 | import io.vertx.core.Vertx; |
| @@ -131,10 +132,13 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest { | @@ -131,10 +132,13 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest { | ||
| 131 | @Test | 132 | @Test |
| 132 | public void testDeviceRegister() throws Exception { | 133 | public void testDeviceRegister() throws Exception { |
| 133 | // 1.1 构建注册消息 | 134 | // 1.1 构建注册消息 |
| 134 | - IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO(); | ||
| 135 | - registerReqDTO.setProductKey(PRODUCT_KEY); | ||
| 136 | - registerReqDTO.setDeviceName("test-ws-" + System.currentTimeMillis()); | ||
| 137 | - registerReqDTO.setProductSecret("test-product-secret"); | 135 | + String deviceName = "test-ws-" + System.currentTimeMillis(); |
| 136 | + String productSecret = "test-product-secret"; // 替换为实际的 productSecret | ||
| 137 | + String sign = IotProductAuthUtils.buildSign(PRODUCT_KEY, deviceName, productSecret); | ||
| 138 | + IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO() | ||
| 139 | + .setProductKey(PRODUCT_KEY) | ||
| 140 | + .setDeviceName(deviceName) | ||
| 141 | + .setSign(sign); | ||
| 138 | IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), | 142 | IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), |
| 139 | IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null); | 143 | IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null); |
| 140 | // 1.2 序列化 | 144 | // 1.2 序列化 |