Commit 83990086fa8b630276d7847fef73048cc03e1858

Authored by YunaiV
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&lt;NetSocket&gt; { @@ -167,8 +167,8 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
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&lt;NetSocket&gt; { @@ -204,8 +204,9 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
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&lt;ServerWebSocket&gt; { @@ -109,7 +109,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
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&lt;ServerWebSocket&gt; { @@ -150,8 +150,8 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
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&lt;ServerWebSocket&gt; { @@ -187,8 +187,9 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
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 序列化