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