pluginConfigList = TenantUtils.executeIgnore(
- () -> pluginConfigService.getPluginConfigListByStatusAndDeployType(
- IotPluginStatusEnum.RUNNING.getStatus(), IotPluginDeployTypeEnum.JAR.getDeployType()));
- if (CollUtil.isEmpty(pluginConfigList)) {
- log.info("[run][没有需要启动的插件]");
- return;
- }
-
- // 遍历插件列表,逐个启动
- pluginConfigList.forEach(pluginConfig -> {
- try {
- log.info("[run][插件({}) 启动开始]", pluginConfig.getPluginKey());
- springPluginManager.startPlugin(pluginConfig.getPluginKey());
- log.info("[run][插件({}) 启动完成]", pluginConfig.getPluginKey());
- } catch (Exception e) {
- log.error("[run][插件({}) 启动异常]", pluginConfig.getPluginKey(), e);
- }
- });
- }
-
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStateListener.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStateListener.java
deleted file mode 100644
index bbc73c619e..0000000000
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStateListener.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.iot.framework.plugin.core;
-
-import lombok.extern.slf4j.Slf4j;
-import org.pf4j.PluginStateEvent;
-import org.pf4j.PluginStateListener;
-
-/**
- * IoT 插件状态监听器,用于 log 插件的状态变化
- *
- * @author haohao
- */
-@Slf4j
-public class IotPluginStateListener implements PluginStateListener {
-
- @Override
- public void pluginStateChanged(PluginStateEvent event) {
- log.info("[pluginStateChanged][插件({}) 状态变化,从 {} 变为 {}]", event.getPlugin().getPluginId(),
- event.getOldState().toString(), event.getPluginState().toString());
- }
-
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginConfigServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginConfigServiceImpl.java
index 18376bc578..3b5e9e2e01 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginConfigServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginConfigServiceImpl.java
@@ -9,8 +9,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginConfigMapper;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
-import org.pf4j.PluginWrapper;
-import org.pf4j.spring.SpringPluginManager;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
@@ -35,8 +33,8 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
@Resource
private IotPluginInstanceService pluginInstanceService;
- @Resource
- private SpringPluginManager springPluginManager;
+// @Resource
+// private SpringPluginManager springPluginManager;
@Override
public Long createPluginConfig(PluginConfigSaveReqVO createReqVO) {
@@ -130,16 +128,16 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
validatePluginConfigFile(pluginKeyNew);
// 4. 更新插件配置
- IotPluginConfigDO updatedPluginConfig = new IotPluginConfigDO()
- .setId(pluginConfigDO.getId())
- .setPluginKey(pluginKeyNew)
- .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) // TODO @haohao:这个状态,是不是非 stop 哈?
- .setFileName(file.getOriginalFilename())
- .setScript("") // TODO @haohao:这个设置为 "" 会不会覆盖数据里的哈?应该从插件里读取?未来?
- .setConfigSchema(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription())
- .setVersion(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion())
- .setDescription(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription());
- pluginConfigMapper.updateById(updatedPluginConfig);
+// IotPluginConfigDO updatedPluginConfig = new IotPluginConfigDO()
+// .setId(pluginConfigDO.getId())
+// .setPluginKey(pluginKeyNew)
+// .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) // TODO @haohao:这个状态,是不是非 stop 哈?
+// .setFileName(file.getOriginalFilename())
+// .setScript("") // TODO @haohao:这个设置为 "" 会不会覆盖数据里的哈?应该从插件里读取?未来?
+// .setConfigSchema(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription())
+// .setVersion(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion())
+// .setDescription(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription());
+// pluginConfigMapper.updateById(updatedPluginConfig);
}
/**
@@ -149,13 +147,13 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
*/
private void validatePluginConfigFile(String pluginKeyNew) {
// TODO @haohao:校验 file 相关参数,是否完整,类似:version 之类是不是可以解析到
- PluginWrapper plugin = springPluginManager.getPlugin(pluginKeyNew);
- if (plugin == null) {
- throw exception(PLUGIN_INSTALL_FAILED);
- }
- if (plugin.getDescriptor().getVersion() == null) {
- throw exception(PLUGIN_INSTALL_FAILED);
- }
+// PluginWrapper plugin = springPluginManager.getPlugin(pluginKeyNew);
+// if (plugin == null) {
+// throw exception(PLUGIN_INSTALL_FAILED);
+// }
+// if (plugin.getDescriptor().getVersion() == null) {
+// throw exception(PLUGIN_INSTALL_FAILED);
+// }
}
@Override
diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java
index 3c15ff774b..14912edff7 100644
--- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.iot.service.plugin;
import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
@@ -9,13 +8,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginConfigDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInstanceMapper;
import cn.iocoder.yudao.module.iot.dal.redis.plugin.DevicePluginProcessIdRedisDAO;
-import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants;
-import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
-import org.pf4j.PluginState;
-import org.pf4j.PluginWrapper;
-import org.pf4j.spring.SpringPluginManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -23,17 +17,10 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-
/**
* IoT 插件实例 Service 实现类
*
@@ -54,8 +41,8 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
@Resource
private DevicePluginProcessIdRedisDAO devicePluginProcessIdRedisDAO;
- @Resource
- private SpringPluginManager pluginManager;
+// @Resource
+// private SpringPluginManager pluginManager;
@Value("${pf4j.pluginsDir}")
private String pluginsDir;
@@ -120,17 +107,17 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
@Override
public void stopAndUnloadPlugin(String pluginKey) {
- PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
- if (plugin == null) {
- log.warn("插件不存在或已卸载: {}", pluginKey);
- return;
- }
- if (plugin.getPluginState().equals(PluginState.STARTED)) {
- pluginManager.stopPlugin(pluginKey); // 停止插件
- log.info("已停止插件: {}", pluginKey);
- }
- pluginManager.unloadPlugin(pluginKey); // 卸载插件
- log.info("已卸载插件: {}", pluginKey);
+// PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
+// if (plugin == null) {
+// log.warn("插件不存在或已卸载: {}", pluginKey);
+// return;
+// }
+// if (plugin.getPluginState().equals(PluginState.STARTED)) {
+// pluginManager.stopPlugin(pluginKey); // 停止插件
+// log.info("已停止插件: {}", pluginKey);
+// }
+// pluginManager.unloadPlugin(pluginKey); // 卸载插件
+// log.info("已卸载插件: {}", pluginKey);
}
@Override
@@ -151,65 +138,66 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
@Override
public String uploadAndLoadNewPlugin(MultipartFile file) {
- String pluginKeyNew;
- // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载
- Path pluginsPath = Paths.get(pluginsDir);
- try {
- FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录
- String filename = file.getOriginalFilename();
- if (filename != null) {
- Path jarPath = pluginsPath.resolve(filename);
- Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
- pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
- log.info("已加载插件: {}", pluginKeyNew);
- } else {
- throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED);
- }
- } catch (IOException e) {
- log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e);
- throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
- } catch (Exception e) {
- log.error("[uploadAndLoadNewPlugin][加载插件失败]", e);
- throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
- }
- return pluginKeyNew;
+// String pluginKeyNew;
+// // TODO @haohao:多节点,是不是要上传 s3 之类的存储器;然后定时去加载
+// Path pluginsPath = Paths.get(pluginsDir);
+// try {
+// FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录
+// String filename = file.getOriginalFilename();
+// if (filename != null) {
+// Path jarPath = pluginsPath.resolve(filename);
+// Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
+//// pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
+//// log.info("已加载插件: {}", pluginKeyNew);
+// } else {
+// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED);
+// }
+// } catch (IOException e) {
+// log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e);
+// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
+// } catch (Exception e) {
+// log.error("[uploadAndLoadNewPlugin][加载插件失败]", e);
+// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
+// }
+// return pluginKeyNew;
+ return null;
}
@Override
public void updatePluginStatus(IotPluginConfigDO pluginConfigDO, Integer status) {
- String pluginKey = pluginConfigDO.getPluginKey();
- PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
-
- if (plugin == null) {
- // 插件不存在且状态为停止,抛出异常
- if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginConfigDO.getStatus())) {
- throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
- }
- return;
- }
-
- // 启动插件
- if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
- && plugin.getPluginState() != PluginState.STARTED) {
- try {
- pluginManager.startPlugin(pluginKey);
- } catch (Exception e) {
- log.error("[updatePluginStatus][启动插件({}) 失败]", pluginKey, e);
- throw exception(ErrorCodeConstants.PLUGIN_START_FAILED, e);
- }
- log.info("已启动插件: {}", pluginKey);
- }
- // 停止插件
- else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
- && plugin.getPluginState() == PluginState.STARTED) {
- try {
- pluginManager.stopPlugin(pluginKey);
- } catch (Exception e) {
- log.error("[updatePluginStatus][停止插件({}) 失败]", pluginKey, e);
- throw exception(ErrorCodeConstants.PLUGIN_STOP_FAILED, e);
- }
- log.info("已停止插件: {}", pluginKey);
- }
+// String pluginKey = pluginConfigDO.getPluginKey();
+// PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
+//
+// if (plugin == null) {
+// // 插件不存在且状态为停止,抛出异常
+// if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginConfigDO.getStatus())) {
+// throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
+// }
+// return;
+// }
+//
+// // 启动插件
+// if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
+// && plugin.getPluginState() != PluginState.STARTED) {
+// try {
+// pluginManager.startPlugin(pluginKey);
+// } catch (Exception e) {
+// log.error("[updatePluginStatus][启动插件({}) 失败]", pluginKey, e);
+// throw exception(ErrorCodeConstants.PLUGIN_START_FAILED, e);
+// }
+// log.info("已启动插件: {}", pluginKey);
+// }
+// // 停止插件
+// else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
+// && plugin.getPluginState() == PluginState.STARTED) {
+// try {
+// pluginManager.stopPlugin(pluginKey);
+// } catch (Exception e) {
+// log.error("[updatePluginStatus][停止插件({}) 失败]", pluginKey, e);
+// throw exception(ErrorCodeConstants.PLUGIN_STOP_FAILED, e);
+// }
+// log.info("已停止插件: {}", pluginKey);
+// }
}
// ========== 设备与插件的映射操作 ==========
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/config/IotComponentCommonAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/config/IotComponentCommonAutoConfiguration.java
deleted file mode 100644
index 0d6adc2aed..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/config/IotComponentCommonAutoConfiguration.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package cn.iocoder.yudao.module.iot.component.core.config;
-
-import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
-import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler;
-import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamServer;
-import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentInstanceHeartbeatJob;
-import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;
-import cn.iocoder.yudao.module.iot.component.core.upstream.IotDeviceUpstreamClient;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.scheduling.annotation.EnableScheduling;
-
-/**
- * IoT 组件的通用自动配置类
- *
- * @author haohao
- */
-@AutoConfiguration
-@EnableConfigurationProperties(IotComponentCommonProperties.class)
-@EnableScheduling // 开启定时任务,因为 IotComponentInstanceHeartbeatJob 是一个定时任务
-public class IotComponentCommonAutoConfiguration {
-
- /**
- * 创建 EMQX 设备下行服务器
- *
- * 当 yudao.iot.component.emqx.enabled = true 时,优先使用 emqxDeviceDownstreamHandler
- */
- @Bean
- @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true")
- public IotDeviceDownstreamServer emqxDeviceDownstreamServer(
- IotComponentCommonProperties properties,
- @Qualifier("emqxDeviceDownstreamHandler") IotDeviceDownstreamHandler deviceDownstreamHandler) {
- return new IotDeviceDownstreamServer(properties, deviceDownstreamHandler);
- }
-
- @Bean(initMethod = "init", destroyMethod = "stop")
- public IotComponentInstanceHeartbeatJob pluginInstanceHeartbeatJob(IotDeviceUpstreamApi deviceUpstreamApi,
- IotDeviceDownstreamServer deviceDownstreamServer,
- IotComponentCommonProperties commonProperties,
- IotComponentRegistry componentRegistry) {
- return new IotComponentInstanceHeartbeatJob(deviceUpstreamApi, deviceDownstreamServer, commonProperties,
- componentRegistry);
- }
-
- @Bean
- public IotDeviceUpstreamClient deviceUpstreamClient() {
- return new IotDeviceUpstreamClient();
- }
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/config/IotComponentCommonProperties.java b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/config/IotComponentCommonProperties.java
deleted file mode 100644
index 43eec749e4..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/config/IotComponentCommonProperties.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package cn.iocoder.yudao.module.iot.component.core.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.validation.annotation.Validated;
-
-/**
- * IoT 组件通用配置属性
- *
- * @author haohao
- */
-@ConfigurationProperties(prefix = "yudao.iot.component.core")
-@Validated
-@Data
-public class IotComponentCommonProperties {
-
- /**
- * 组件的唯一标识
- *
- * 注意:该值将在运行时由各组件设置,不再从配置读取
- */
- private String pluginKey;
-
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring.factories b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring.factories
deleted file mode 100644
index 7f075529e5..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring.factories
+++ /dev/null
@@ -1,2 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- cn.iocoder.yudao.module.iot.component.core.config.IotPluginCommonAutoConfiguration
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
deleted file mode 100644
index e7b9b8ba6e..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ /dev/null
@@ -1 +0,0 @@
-cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonAutoConfiguration
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/config/IotComponentEmqxAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/config/IotComponentEmqxAutoConfiguration.java
deleted file mode 100644
index 22ba00587c..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/config/IotComponentEmqxAutoConfiguration.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package cn.iocoder.yudao.module.iot.component.emqx.config;
-
-import cn.hutool.core.util.IdUtil;
-import cn.hutool.system.SystemUtil;
-import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
-import cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonProperties;
-import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler;
-import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;
-import cn.iocoder.yudao.module.iot.component.emqx.downstream.IotDeviceDownstreamHandlerImpl;
-import cn.iocoder.yudao.module.iot.component.emqx.upstream.IotDeviceUpstreamServer;
-import io.vertx.core.Vertx;
-import io.vertx.mqtt.MqttClient;
-import io.vertx.mqtt.MqttClientOptions;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.event.ApplicationStartedEvent;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.event.EventListener;
-
-import java.lang.management.ManagementFactory;
-
-/**
- * IoT 组件 EMQX 的自动配置类
- *
- * @author haohao
- */
-@Slf4j
-@AutoConfiguration
-@EnableConfigurationProperties(IotComponentEmqxProperties.class)
-@ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true", matchIfMissing = false)
-// TODO @haohao:是不是不用扫 cn.iocoder.yudao.module.iot.component.core 拉,它尽量靠自动配置
-@ComponentScan(basePackages = {
- "cn.iocoder.yudao.module.iot.component.core", // 核心包
- "cn.iocoder.yudao.module.iot.component.emqx" // EMQX 组件包
-})
-public class IotComponentEmqxAutoConfiguration {
-
- /**
- * 组件 key
- */
- private static final String PLUGIN_KEY = "emqx";
-
- public IotComponentEmqxAutoConfiguration() {
- // TODO @haohao:这个日志,融合到 initialize ?
- log.info("[IotComponentEmqxAutoConfiguration][已启动]");
- }
-
- @EventListener(ApplicationStartedEvent.class)
- public void initialize(ApplicationStartedEvent event) {
- // 从应用上下文中获取需要的 Bean
- IotComponentRegistry componentRegistry = event.getApplicationContext().getBean(IotComponentRegistry.class);
- IotComponentCommonProperties commonProperties = event.getApplicationContext().getBean(IotComponentCommonProperties.class);
-
- // 设置当前组件的核心标识
- // TODO @haohao:如果多个组件,都去设置,会不会冲突哈?
- commonProperties.setPluginKey(PLUGIN_KEY);
-
- // 将 EMQX 组件注册到组件注册表
- componentRegistry.registerComponent(
- PLUGIN_KEY,
- SystemUtil.getHostInfo().getAddress(),
- 0, // 内嵌模式固定为 0
- getProcessId()
- );
-
- log.info("[initialize][IoT EMQX 组件初始化完成]");
- }
-
- // TODO @haohao:这个可能要注意,可能会有多个?冲突?
- @Bean
- public Vertx vertx() {
- return Vertx.vertx();
- }
-
- @Bean
- public MqttClient mqttClient(Vertx vertx, IotComponentEmqxProperties emqxProperties) {
- // TODO @haohao:这个日志,要不要去掉,避免过多哈
- log.info("MQTT配置: host={}, port={}, username={}, ssl={}",
- emqxProperties.getMqttHost(), emqxProperties.getMqttPort(),
- emqxProperties.getMqttUsername(), emqxProperties.getMqttSsl());
-
- MqttClientOptions options = new MqttClientOptions()
- .setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID())
- .setUsername(emqxProperties.getMqttUsername())
- .setPassword(emqxProperties.getMqttPassword());
- // TODO @haohao:可以用 ObjUtil.default
- if (emqxProperties.getMqttSsl() != null) {
- options.setSsl(emqxProperties.getMqttSsl());
- } else {
- options.setSsl(false);
- }
- return MqttClient.create(vertx, options);
- }
-
- @Bean(name = "emqxDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop")
- public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,
- IotComponentEmqxProperties emqxProperties,
- Vertx vertx,
- MqttClient mqttClient,
- IotComponentRegistry componentRegistry) {
- return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi, vertx, mqttClient, componentRegistry);
- }
-
- @Bean(name = "emqxDeviceDownstreamHandler")
- public IotDeviceDownstreamHandler deviceDownstreamHandler(MqttClient mqttClient) {
- return new IotDeviceDownstreamHandlerImpl(mqttClient);
- }
-
- // TODO @haohao:这个通用下一下哈。
- /**
- * 获取当前进程ID
- *
- * @return 进程ID
- */
- private String getProcessId() {
- // 获取进程的 name
- String name = ManagementFactory.getRuntimeMXBean().getName();
- // 分割名称,格式为 pid@hostname
- String pid = name.split("@")[0];
- return pid;
- }
-
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
deleted file mode 100644
index bf8624f153..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ /dev/null
@@ -1 +0,0 @@
-cn.iocoder.yudao.module.iot.component.emqx.config.IotComponentEmqxAutoConfiguration
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/config/IotComponentHttpAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/config/IotComponentHttpAutoConfiguration.java
deleted file mode 100644
index 805a13b9f8..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/config/IotComponentHttpAutoConfiguration.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package cn.iocoder.yudao.module.iot.component.http.config;
-
-import cn.hutool.system.SystemUtil;
-import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
-import cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonProperties;
-import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler;
-import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;
-import cn.iocoder.yudao.module.iot.component.http.downstream.IotDeviceDownstreamHandlerImpl;
-import cn.iocoder.yudao.module.iot.component.http.upstream.IotDeviceUpstreamServer;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.event.ApplicationStartedEvent;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.event.EventListener;
-
-import java.lang.management.ManagementFactory;
-
-// TODO @haohao:类似 IotComponentEmqxAutoConfiguration 的建议
-/**
- * IoT 组件 HTTP 的自动配置类
- *
- * @author haohao
- */
-@Slf4j
-@AutoConfiguration
-@EnableConfigurationProperties(IotComponentHttpProperties.class)
-@ConditionalOnProperty(prefix = "yudao.iot.component.http", name = "enabled", havingValue = "true", matchIfMissing = false)
-@ComponentScan(basePackages = {
- "cn.iocoder.yudao.module.iot.component.core", // 核心包
- "cn.iocoder.yudao.module.iot.component.http" // HTTP组件包
-})
-public class IotComponentHttpAutoConfiguration {
-
- /**
- * 组件key
- */
- private static final String PLUGIN_KEY = "http";
-
- public IotComponentHttpAutoConfiguration() {
- log.info("[IotComponentHttpAutoConfiguration][已启动]");
- }
-
- @EventListener(ApplicationStartedEvent.class)
- public void initialize(ApplicationStartedEvent event) {
- // 从应用上下文中获取需要的Bean
- IotComponentRegistry componentRegistry = event.getApplicationContext().getBean(IotComponentRegistry.class);
- IotComponentCommonProperties commonProperties = event.getApplicationContext()
- .getBean(IotComponentCommonProperties.class);
-
- // 设置当前组件的核心标识
- commonProperties.setPluginKey(PLUGIN_KEY);
-
- // 将HTTP组件注册到组件注册表
- componentRegistry.registerComponent(
- PLUGIN_KEY,
- SystemUtil.getHostInfo().getAddress(),
- 0, // 内嵌模式固定为0
- getProcessId());
-
- log.info("[initialize][IoT HTTP 组件初始化完成]");
- }
-
- @Bean(name = "httpDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop")
- public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,
- IotComponentHttpProperties properties,
- ApplicationContext applicationContext,
- IotComponentRegistry componentRegistry) {
- return new IotDeviceUpstreamServer(properties, deviceUpstreamApi, applicationContext, componentRegistry);
- }
-
- @Bean(name = "httpDeviceDownstreamHandler")
- public IotDeviceDownstreamHandler deviceDownstreamHandler() {
- return new IotDeviceDownstreamHandlerImpl();
- }
-
- /**
- * 获取当前进程ID
- *
- * @return 进程ID
- */
- private String getProcessId() {
- // 获取进程的 name
- String name = ManagementFactory.getRuntimeMXBean().getName();
- // 分割名称,格式为 pid@hostname
- String pid = name.split("@")[0];
- return pid;
- }
-}
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/upstream/IotDeviceUpstreamServer.java b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/upstream/IotDeviceUpstreamServer.java
deleted file mode 100644
index ff570f1867..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/upstream/IotDeviceUpstreamServer.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package cn.iocoder.yudao.module.iot.component.http.upstream;
-
-import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
-import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;
-import cn.iocoder.yudao.module.iot.component.http.config.IotComponentHttpProperties;
-import cn.iocoder.yudao.module.iot.component.http.upstream.router.IotDeviceUpstreamVertxHandler;
-import io.vertx.core.Vertx;
-import io.vertx.core.http.HttpServer;
-import io.vertx.ext.web.Router;
-import io.vertx.ext.web.handler.BodyHandler;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.ApplicationContext;
-
-/**
- * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
- *
- * 协议:HTTP
- *
- * @author haohao
- */
-@Slf4j
-public class IotDeviceUpstreamServer {
-
- private final Vertx vertx;
- private final HttpServer server;
- private final IotComponentHttpProperties properties;
- private final IotComponentRegistry componentRegistry;
-
- public IotDeviceUpstreamServer(IotComponentHttpProperties properties,
- IotDeviceUpstreamApi deviceUpstreamApi,
- ApplicationContext applicationContext,
- IotComponentRegistry componentRegistry) {
- this.properties = properties;
- this.componentRegistry = componentRegistry;
-
- // 创建 Vertx 实例
- this.vertx = Vertx.vertx();
- // 创建 Router 实例
- Router router = Router.router(vertx);
- router.route().handler(BodyHandler.create()); // 处理 Body
-
- // 使用统一的 Handler 处理所有上行请求
- IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(deviceUpstreamApi,
- applicationContext);
- router.post(IotDeviceUpstreamVertxHandler.PROPERTY_PATH).handler(upstreamHandler);
- router.post(IotDeviceUpstreamVertxHandler.EVENT_PATH).handler(upstreamHandler);
-
- // 创建 HttpServer 实例
- this.server = vertx.createHttpServer().requestHandler(router);
- }
-
- /**
- * 启动 HTTP 服务器
- */
- public void start() {
- log.info("[start][开始启动]");
- server.listen(properties.getServerPort())
- .toCompletionStage()
- .toCompletableFuture()
- .join();
- log.info("[start][启动完成,端口({})]", this.server.actualPort());
- }
-
- /**
- * 停止所有
- */
- public void stop() {
- log.info("[stop][开始关闭]");
- try {
- // 关闭 HTTP 服务器
- if (server != null) {
- server.close()
- .toCompletionStage()
- .toCompletableFuture()
- .join();
- }
-
- // 关闭 Vertx 实例
- if (vertx != null) {
- vertx.close()
- .toCompletionStage()
- .toCompletableFuture()
- .join();
- }
- log.info("[stop][关闭完成]");
- } catch (Exception e) {
- log.error("[stop][关闭异常]", e);
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/upstream/router/IotDeviceUpstreamVertxHandler.java b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/upstream/router/IotDeviceUpstreamVertxHandler.java
deleted file mode 100644
index d1d30575a7..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/upstream/router/IotDeviceUpstreamVertxHandler.java
+++ /dev/null
@@ -1,212 +0,0 @@
-package cn.iocoder.yudao.module.iot.component.http.upstream.router;
-
-import cn.hutool.core.util.IdUtil;
-import cn.hutool.core.util.ObjUtil;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
-import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
-import cn.iocoder.yudao.module.iot.component.core.pojo.IotStandardResponse;
-import cn.iocoder.yudao.module.iot.component.core.util.IotPluginCommonUtils;
-import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
-import io.vertx.core.Handler;
-import io.vertx.core.json.JsonObject;
-import io.vertx.ext.web.RoutingContext;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.ApplicationContext;
-
-import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.Map;
-
-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 设备上行统一处理的 Vert.x Handler
- *
- * 统一处理设备属性上报和事件上报的请求
- *
- * @author haohao
- */
-@Slf4j
-public class IotDeviceUpstreamVertxHandler implements Handler {
-
- /**
- * 属性上报路径
- */
- public static final String PROPERTY_PATH = "/sys/:productKey/:deviceName/thing/event/property/post";
- /**
- * 事件上报路径
- */
- public static final String EVENT_PATH = "/sys/:productKey/:deviceName/thing/event/:identifier/post";
-
- private static final String PROPERTY_METHOD = "thing.event.property.post";
- private static final String EVENT_METHOD_PREFIX = "thing.event.";
- private static final String EVENT_METHOD_SUFFIX = ".post";
-
- private final IotDeviceUpstreamApi deviceUpstreamApi;
-// private final HttpScriptService scriptService;
-
- public IotDeviceUpstreamVertxHandler(IotDeviceUpstreamApi deviceUpstreamApi,
- ApplicationContext applicationContext) {
- this.deviceUpstreamApi = deviceUpstreamApi;
-// this.scriptService = applicationContext.getBean(HttpScriptService.class);
- }
-
- @Override
- public void handle(RoutingContext routingContext) {
- String path = routingContext.request().path();
- String requestId = IdUtil.fastSimpleUUID();
-
- try {
- // 1. 解析通用参数
- String productKey = routingContext.pathParam("productKey");
- String deviceName = routingContext.pathParam("deviceName");
- JsonObject body = routingContext.body().asJsonObject();
- requestId = ObjUtil.defaultIfBlank(body.getString("id"), requestId);
-
- // 2. 根据路径模式处理不同类型的请求
- CommonResult result;
- String method;
- if (path.matches(".*/thing/event/property/post")) {
- // 处理属性上报
- IotDevicePropertyReportReqDTO reportReqDTO = parsePropertyReportRequest(productKey, deviceName,
- requestId, body);
-
- // 设备上线
- updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
-
- // 属性上报
- result = deviceUpstreamApi.reportDeviceProperty(reportReqDTO);
- method = PROPERTY_METHOD;
- } else if (path.matches(".*/thing/event/.+/post")) {
- // 处理事件上报
- String identifier = routingContext.pathParam("identifier");
- IotDeviceEventReportReqDTO reportReqDTO = parseEventReportRequest(productKey, deviceName, identifier,
- requestId, body);
-
- // 设备上线
- updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
-
- // 事件上报
- result = deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
- method = EVENT_METHOD_PREFIX + identifier + EVENT_METHOD_SUFFIX;
- } else {
- // 不支持的请求路径
- IotStandardResponse errorResponse = IotStandardResponse.error(requestId, "unknown",
- BAD_REQUEST.getCode(), "不支持的请求路径");
- IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
- return;
- }
-
- // 3. 返回标准响应
- IotStandardResponse response;
- if (result.isSuccess()) {
- response = IotStandardResponse.success(requestId, method, result.getData());
- } else {
- response = IotStandardResponse.error(requestId, method, result.getCode(), result.getMsg());
- }
- IotPluginCommonUtils.writeJsonResponse(routingContext, response);
- } catch (Exception e) {
- log.error("[handle][处理上行请求异常] path={}", path, e);
- String method = path.contains("/property/") ? PROPERTY_METHOD
- : EVENT_METHOD_PREFIX + (routingContext.pathParams().containsKey("identifier")
- ? routingContext.pathParam("identifier")
- : "unknown") + EVENT_METHOD_SUFFIX;
- IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method,
- INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
- IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
- }
- }
-
- /**
- * 更新设备状态
- *
- * @param productKey 产品 Key
- * @param deviceName 设备名称
- */
- private void updateDeviceState(String productKey, String deviceName) {
- deviceUpstreamApi.updateDeviceState(((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO()
- .setRequestId(IdUtil.fastSimpleUUID()).setProcessId(IotPluginCommonUtils.getProcessId())
- .setReportTime(LocalDateTime.now())
- .setProductKey(productKey).setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState()));
- }
-
- /**
- * 解析属性上报请求
- *
- * @param productKey 产品 Key
- * @param deviceName 设备名称
- * @param requestId 请求 ID
- * @param body 请求体
- * @return 属性上报请求 DTO
- */
- private IotDevicePropertyReportReqDTO parsePropertyReportRequest(String productKey, String deviceName,
- String requestId, JsonObject body) {
- // 使用脚本解析数据
-// Map properties = scriptService.parsePropertyData(productKey, deviceName, body);
-
-
- // 如果脚本解析结果为空,使用默认解析逻辑
- // TODO @芋艿:注释说明一下,为什么要这么处理?
-// if (CollUtil.isNotEmpty(properties)) {
- Map properties = new HashMap<>();
- Map params = body.getJsonObject("params") != null ?
- body.getJsonObject("params").getMap() : null;
- if (params != null) {
- // 将标准格式的 params 转换为平台需要的 properties 格式
- for (Map.Entry entry : params.entrySet()) {
- String key = entry.getKey();
- Object valueObj = entry.getValue();
- // 如果是复杂结构(包含 value 和 time)
- if (valueObj instanceof Map) {
- @SuppressWarnings("unchecked")
- Map valueMap = (Map) valueObj;
- properties.put(key, valueMap.getOrDefault("value", valueObj));
- } else {
- properties.put(key, valueObj);
- }
- }
- }
-// }
-
- // 构建属性上报请求 DTO
- return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO().setRequestId(requestId)
- .setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
- .setProductKey(productKey).setDeviceName(deviceName)).setProperties(properties);
- }
-
- /**
- * 解析事件上报请求
- *
- * @param productKey 产品K ey
- * @param deviceName 设备名称
- * @param identifier 事件标识符
- * @param requestId 请求 ID
- * @param body 请求体
- * @return 事件上报请求 DTO
- */
- private IotDeviceEventReportReqDTO parseEventReportRequest(String productKey, String deviceName, String identifier,
- String requestId, JsonObject body) {
- // 使用脚本解析事件数据
-// Map params = scriptService.parseEventData(productKey, deviceName, identifier, body);
- Map params = null;
-
- // 如果脚本解析结果为空,使用默认解析逻辑
-// if (CollUtil.isNotEmpty(params)) {
- if (body.containsKey("params")) {
- params = body.getJsonObject("params").getMap();
- } else {
- // 兼容旧格式
- params = new HashMap<>();
- }
-// }
-
- // 构建事件上报请求 DTO
- return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO().setRequestId(requestId)
- .setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
- .setProductKey(productKey).setDeviceName(deviceName)).setIdentifier(identifier).setParams(params);
- }
-}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
deleted file mode 100644
index f735566c97..0000000000
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ /dev/null
@@ -1 +0,0 @@
-cn.iocoder.yudao.module.iot.component.http.config.IotComponentHttpAutoConfiguration
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/README.md b/yudao-module-iot/yudao-module-iot-net-components/README.md
similarity index 95%
rename from yudao-module-iot/yudao-module-iot-components/README.md
rename to yudao-module-iot/yudao-module-iot-net-components/README.md
index 08c4b66609..d60c0dd93d 100644
--- a/yudao-module-iot/yudao-module-iot-components/README.md
+++ b/yudao-module-iot/yudao-module-iot-net-components/README.md
@@ -4,9 +4,9 @@
该模块包含多个 IoT 设备连接组件,提供不同的通信协议支持:
-- `yudao-module-iot-component-core`: 核心接口和通用类
-- `yudao-module-iot-component-http`: 基于 HTTP 协议的设备通信组件
-- `yudao-module-iot-component-emqx`: 基于 MQTT/EMQX 的设备通信组件
+- `yudao-module-iot-net-component-core`: 核心接口和通用类
+- `yudao-module-iot-net-component-http`: 基于 HTTP 协议的设备通信组件
+- `yudao-module-iot-net-component-emqx`: 基于 MQTT/EMQX 的设备通信组件
## 组件架构
diff --git a/yudao-module-iot/yudao-module-iot-components/pom.xml b/yudao-module-iot/yudao-module-iot-net-components/pom.xml
similarity index 64%
rename from yudao-module-iot/yudao-module-iot-components/pom.xml
rename to yudao-module-iot/yudao-module-iot-net-components/pom.xml
index 297761f9c3..cd8f39a966 100644
--- a/yudao-module-iot/yudao-module-iot-components/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-net-components/pom.xml
@@ -9,18 +9,18 @@
4.0.0
- yudao-module-iot-components
+ yudao-module-iot-net-components
pom
${project.artifactId}
- 物联网组件模块,提供与物联网设备通讯、管理的组件实现
+ 物联网网络组件模块,提供与物联网设备通讯、管理的网络组件实现
- yudao-module-iot-component-core
- yudao-module-iot-component-http
- yudao-module-iot-component-emqx
+ yudao-module-iot-net-component-core
+ yudao-module-iot-net-component-http
+ yudao-module-iot-net-component-emqx
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/pom.xml b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/pom.xml
similarity index 87%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/pom.xml
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/pom.xml
index 9fb9ca936f..b7d6d861f6 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/pom.xml
@@ -3,19 +3,18 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- yudao-module-iot-components
+ yudao-module-iot-net-components
cn.iocoder.boot
${revision}
4.0.0
- yudao-module-iot-component-core
+ yudao-module-iot-net-component-core
jar
${project.artifactId}
-
- 物联网组件核心模块
+ 物联网网络组件核心模块
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonAutoConfiguration.java
new file mode 100644
index 0000000000..d880df5cfb
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonAutoConfiguration.java
@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.iot.net.component.core.config;
+
+import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
+import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
+import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamServer;
+import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentInstanceHeartbeatJob;
+import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry;
+import cn.iocoder.yudao.module.iot.net.component.core.upstream.IotDeviceUpstreamClient;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * IoT 网络组件的通用自动配置类
+ *
+ * @author haohao
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(IotNetComponentCommonProperties.class)
+@EnableScheduling // 开启定时任务,因为 IotNetComponentInstanceHeartbeatJob 是一个定时任务
+public class IotNetComponentCommonAutoConfiguration {
+
+ /**
+ * 创建 EMQX 设备下行服务器
+ *
+ * 当 yudao.iot.component.emqx.enabled = true 时,优先使用 emqxDeviceDownstreamHandler
+ */
+ @Bean
+ @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true")
+ public IotDeviceDownstreamServer emqxDeviceDownstreamServer(
+ IotNetComponentCommonProperties properties,
+ @Qualifier("emqxDeviceDownstreamHandler") IotDeviceDownstreamHandler deviceDownstreamHandler) {
+ return new IotDeviceDownstreamServer(properties, deviceDownstreamHandler);
+ }
+
+ /**
+ * 创建网络组件实例心跳任务
+ */
+ @Bean(initMethod = "init", destroyMethod = "stop")
+ public IotNetComponentInstanceHeartbeatJob pluginInstanceHeartbeatJob(
+ IotDeviceUpstreamApi deviceUpstreamApi,
+ IotNetComponentCommonProperties commonProperties,
+ IotNetComponentRegistry componentRegistry) {
+ return new IotNetComponentInstanceHeartbeatJob(
+ deviceUpstreamApi,
+ commonProperties,
+ componentRegistry);
+ }
+
+ /**
+ * 创建设备上行客户端
+ */
+ @Bean
+ public IotDeviceUpstreamClient deviceUpstreamClient() {
+ return new IotDeviceUpstreamClient();
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonProperties.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonProperties.java
new file mode 100644
index 0000000000..6f1df82a1b
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/config/IotNetComponentCommonProperties.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.iot.net.component.core.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * IoT 网络组件通用配置属性
+ *
+ * @author haohao
+ */
+@ConfigurationProperties(prefix = "yudao.iot.component")
+@Validated
+@Data
+public class IotNetComponentCommonProperties {
+
+ /**
+ * 组件的唯一标识
+ *
+ * 注意:该值将在运行时由各组件设置,不再从配置读取
+ */
+ private String pluginKey;
+
+ /**
+ * 组件实例心跳超时时间,单位:毫秒
+ *
+ * 默认值:30 秒
+ */
+ private Long instanceHeartbeatTimeout = 30000L;
+
+ /**
+ * 网络组件消息转发配置
+ */
+ private ForwardMessage forwardMessage = new ForwardMessage();
+
+ /**
+ * 消息转发配置
+ */
+ @Data
+ public static class ForwardMessage {
+
+ /**
+ * 是否转发所有设备消息到 EMQX
+ *
+ * 默认为 true 开启
+ */
+ private boolean forwardAllDeviceMessageToEmqx = true;
+
+ /**
+ * 是否转发所有设备消息到 HTTP
+ *
+ * 默认为 false 关闭
+ */
+ private boolean forwardAllDeviceMessageToHttp = false;
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/constants/IotDeviceTopicEnum.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/constants/IotDeviceTopicEnum.java
new file mode 100644
index 0000000000..00e1142458
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/constants/IotDeviceTopicEnum.java
@@ -0,0 +1,173 @@
+package cn.iocoder.yudao.module.iot.net.component.core.constants;
+
+import lombok.Getter;
+
+/**
+ * IoT 设备主题枚举
+ *
+ * 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范
+ *
+ * @author haohao
+ */
+@Getter
+public enum IotDeviceTopicEnum {
+
+ /**
+ * 系统主题前缀
+ */
+ SYS_TOPIC_PREFIX("/sys/", "系统主题前缀"),
+
+ /**
+ * 服务调用主题前缀
+ */
+ SERVICE_TOPIC_PREFIX("/thing/service/", "服务调用主题前缀"),
+
+ /**
+ * 设备属性设置主题
+ * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set
+ * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply
+ */
+ PROPERTY_SET_TOPIC("/thing/service/property/set", "设备属性设置主题"),
+
+ /**
+ * 设备属性获取主题
+ * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/get
+ * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/get_reply
+ */
+ PROPERTY_GET_TOPIC("/thing/service/property/get", "设备属性获取主题"),
+
+ /**
+ * 设备配置设置主题
+ * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/config/set
+ * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/config/set_reply
+ */
+ CONFIG_SET_TOPIC("/thing/service/config/set", "设备配置设置主题"),
+
+ /**
+ * 设备OTA升级主题
+ * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade
+ * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade_reply
+ */
+ OTA_UPGRADE_TOPIC("/thing/service/ota/upgrade", "设备OTA升级主题"),
+
+ /**
+ * 设备属性上报主题
+ * 请求Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
+ * 响应Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
+ */
+ PROPERTY_POST_TOPIC("/thing/event/property/post", "设备属性上报主题"),
+
+ /**
+ * 设备事件上报主题前缀
+ */
+ EVENT_POST_TOPIC_PREFIX("/thing/event/", "设备事件上报主题前缀"),
+
+ /**
+ * 设备事件上报主题后缀
+ */
+ EVENT_POST_TOPIC_SUFFIX("/post", "设备事件上报主题后缀"),
+
+ /**
+ * 响应主题后缀
+ */
+ REPLY_SUFFIX("_reply", "响应主题后缀");
+
+ private final String topic;
+ private final String description;
+
+ IotDeviceTopicEnum(String topic, String description) {
+ this.topic = topic;
+ this.description = description;
+ }
+
+ /**
+ * 构建设备服务调用主题
+ *
+ * @param productKey 产品Key
+ * @param deviceName 设备名称
+ * @param serviceIdentifier 服务标识符
+ * @return 完整的主题路径
+ */
+ public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
+ return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName +
+ SERVICE_TOPIC_PREFIX.getTopic() + serviceIdentifier;
+ }
+
+ /**
+ * 构建设备属性设置主题
+ *
+ * @param productKey 产品Key
+ * @param deviceName 设备名称
+ * @return 完整的主题路径
+ */
+ public static String buildPropertySetTopic(String productKey, String deviceName) {
+ return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_SET_TOPIC.getTopic();
+ }
+
+ /**
+ * 构建设备属性获取主题
+ *
+ * @param productKey 产品Key
+ * @param deviceName 设备名称
+ * @return 完整的主题路径
+ */
+ public static String buildPropertyGetTopic(String productKey, String deviceName) {
+ return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_GET_TOPIC.getTopic();
+ }
+
+ /**
+ * 构建设备配置设置主题
+ *
+ * @param productKey 产品Key
+ * @param deviceName 设备名称
+ * @return 完整的主题路径
+ */
+ public static String buildConfigSetTopic(String productKey, String deviceName) {
+ return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + CONFIG_SET_TOPIC.getTopic();
+ }
+
+ /**
+ * 构建设备OTA升级主题
+ *
+ * @param productKey 产品Key
+ * @param deviceName 设备名称
+ * @return 完整的主题路径
+ */
+ public static String buildOtaUpgradeTopic(String productKey, String deviceName) {
+ return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + OTA_UPGRADE_TOPIC.getTopic();
+ }
+
+ /**
+ * 构建设备属性上报主题
+ *
+ * @param productKey 产品Key
+ * @param deviceName 设备名称
+ * @return 完整的主题路径
+ */
+ public static String buildPropertyPostTopic(String productKey, String deviceName) {
+ return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + PROPERTY_POST_TOPIC.getTopic();
+ }
+
+ /**
+ * 构建设备事件上报主题
+ *
+ * @param productKey 产品Key
+ * @param deviceName 设备名称
+ * @param eventIdentifier 事件标识符
+ * @return 完整的主题路径
+ */
+ public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) {
+ return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName +
+ EVENT_POST_TOPIC_PREFIX.getTopic() + eventIdentifier + EVENT_POST_TOPIC_SUFFIX.getTopic();
+ }
+
+ /**
+ * 获取响应主题
+ *
+ * @param requestTopic 请求主题
+ * @return 响应主题
+ */
+ public static String getReplyTopic(String requestTopic) {
+ return requestTopic + REPLY_SUFFIX.getTopic();
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/downstream/IotDeviceDownstreamHandler.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/downstream/IotDeviceDownstreamHandler.java
similarity index 95%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/downstream/IotDeviceDownstreamHandler.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/downstream/IotDeviceDownstreamHandler.java
index d3fefde970..e69e4c41d4 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/downstream/IotDeviceDownstreamHandler.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/downstream/IotDeviceDownstreamHandler.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.component.core.downstream;
+package cn.iocoder.yudao.module.iot.net.component.core.downstream;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/downstream/IotDeviceDownstreamServer.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/downstream/IotDeviceDownstreamServer.java
similarity index 90%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/downstream/IotDeviceDownstreamServer.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/downstream/IotDeviceDownstreamServer.java
index dfff2b1b3e..1f58eb2ed2 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/downstream/IotDeviceDownstreamServer.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/downstream/IotDeviceDownstreamServer.java
@@ -1,8 +1,8 @@
-package cn.iocoder.yudao.module.iot.component.core.downstream;
+package cn.iocoder.yudao.module.iot.net.component.core.downstream;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
-import cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonProperties;
+import cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -15,7 +15,7 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
public class IotDeviceDownstreamServer {
- private final IotComponentCommonProperties properties;
+ private final IotNetComponentCommonProperties properties;
private final IotDeviceDownstreamHandler deviceDownstreamHandler;
/**
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/heartbeat/IotComponentInstanceHeartbeatJob.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/heartbeat/IotNetComponentInstanceHeartbeatJob.java
similarity index 56%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/heartbeat/IotComponentInstanceHeartbeatJob.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/heartbeat/IotNetComponentInstanceHeartbeatJob.java
index f41b538681..395b765b0f 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/heartbeat/IotComponentInstanceHeartbeatJob.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/heartbeat/IotNetComponentInstanceHeartbeatJob.java
@@ -1,59 +1,49 @@
-package cn.iocoder.yudao.module.iot.component.core.heartbeat;
+package cn.iocoder.yudao.module.iot.net.component.core.heartbeat;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.system.SystemUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
-import cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonProperties;
-import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamServer;
-import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry.IotComponentInfo;
+import cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonProperties;
+import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry.IotNetComponentInfo;
+import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
-import java.lang.management.ManagementFactory;
+import java.util.Collection;
import java.util.concurrent.TimeUnit;
/**
- * IoT 组件实例心跳定时任务
+ * IoT 网络组件实例心跳定时任务
*
* 将组件的状态,定时上报给 server 服务器
+ *
+ * @author haohao
*/
@RequiredArgsConstructor
@Slf4j
-public class IotComponentInstanceHeartbeatJob {
-
- /**
- * 内嵌模式的端口值(固定为 0)
- */
- private static final Integer EMBEDDED_PORT = 0;
+public class IotNetComponentInstanceHeartbeatJob {
private final IotDeviceUpstreamApi deviceUpstreamApi;
- private final IotDeviceDownstreamServer deviceDownstreamServer; // TODO @haohao:这个变量还需要哇?
- private final IotComponentCommonProperties commonProperties;
- private final IotComponentRegistry componentRegistry;
+ private final IotNetComponentCommonProperties commonProperties;
+ private final IotNetComponentRegistry componentRegistry;
/**
- * 初始化方法,由 Spring调 用:注册当前组件并发送上线心跳
+ * 初始化方法,由 Spring 调用:注册当前组件并发送上线心跳
*/
public void init() {
- // 将当前组件注册到注册表
- String processId = getProcessId();
- String hostIp = SystemUtil.getHostInfo().getAddress();
-
- // 注册当前组件
- componentRegistry.registerComponent(
- commonProperties.getPluginKey(),
- hostIp,
- EMBEDDED_PORT,
- processId);
-
// 发送所有组件的上线心跳
- for (IotComponentInfo component : componentRegistry.getAllComponents()) {
+ Collection components = componentRegistry.getAllComponents();
+ if (CollUtil.isEmpty(components)) {
+ return;
+ }
+ for (IotNetComponentInfo component : components) {
try {
CommonResult result = deviceUpstreamApi.heartbeatPluginInstance(
buildPluginInstanceHeartbeatReqDTO(component, true));
- log.info("[init][组件({})上线结果:{})]", component.getPluginKey(), result);
+ log.info("[init][组件({})上线结果:{}]", component.getPluginKey(), result);
} catch (Exception e) {
log.error("[init][组件({})上线发送异常]", component.getPluginKey(), e);
}
@@ -65,11 +55,15 @@ public class IotComponentInstanceHeartbeatJob {
*/
public void stop() {
// 发送所有组件的下线心跳
- for (IotComponentInfo component : componentRegistry.getAllComponents()) {
+ Collection components = componentRegistry.getAllComponents();
+ if (CollUtil.isEmpty(components)) {
+ return;
+ }
+ for (IotNetComponentInfo component : components) {
try {
CommonResult result = deviceUpstreamApi.heartbeatPluginInstance(
buildPluginInstanceHeartbeatReqDTO(component, false));
- log.info("[stop][组件({})下线结果:{})]", component.getPluginKey(), result);
+ log.info("[stop][组件({})下线结果:{}]", component.getPluginKey(), result);
} catch (Exception e) {
log.error("[stop][组件({})下线发送异常]", component.getPluginKey(), e);
}
@@ -85,11 +79,15 @@ public class IotComponentInstanceHeartbeatJob {
@Scheduled(initialDelay = 1, fixedRate = 1, timeUnit = TimeUnit.MINUTES) // 1 分钟执行一次
public void execute() {
// 发送所有组件的心跳
- for (IotComponentInfo component : componentRegistry.getAllComponents()) {
+ Collection components = componentRegistry.getAllComponents();
+ if (CollUtil.isEmpty(components)) {
+ return;
+ }
+ for (IotNetComponentInfo component : components) {
try {
CommonResult result = deviceUpstreamApi.heartbeatPluginInstance(
buildPluginInstanceHeartbeatReqDTO(component, true));
- log.info("[execute][组件({})心跳结果:{})]", component.getPluginKey(), result);
+ log.info("[execute][组件({})心跳结果:{}]", component.getPluginKey(), result);
} catch (Exception e) {
log.error("[execute][组件({})心跳发送异常]", component.getPluginKey(), e);
}
@@ -103,23 +101,11 @@ public class IotComponentInstanceHeartbeatJob {
* @param online 是否在线
* @return 心跳 DTO
*/
- private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(IotComponentInfo component,
+ private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(IotNetComponentInfo component,
Boolean online) {
return new IotPluginInstanceHeartbeatReqDTO()
.setPluginKey(component.getPluginKey()).setProcessId(component.getProcessId())
.setHostIp(component.getHostIp()).setDownstreamPort(component.getDownstreamPort())
.setOnline(online);
}
-
- // TODO @haohao:要和 IotPluginCommonUtils 保持一致么?
- /**
- * 获取当前进程 ID
- *
- * @return 进程 ID
- */
- private String getProcessId() {
- String name = ManagementFactory.getRuntimeMXBean().getName();
- // TODO @haohao:是不是 SystemUtil.getCurrentPID(); 直接获取 pid 哈?
- return name.split("@")[0];
- }
-}
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/heartbeat/IotComponentRegistry.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/heartbeat/IotNetComponentRegistry.java
similarity index 53%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/heartbeat/IotComponentRegistry.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/heartbeat/IotNetComponentRegistry.java
index 3b3cc2870b..ce8f4de66e 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/heartbeat/IotComponentRegistry.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/heartbeat/IotNetComponentRegistry.java
@@ -1,5 +1,7 @@
-package cn.iocoder.yudao.module.iot.component.core.heartbeat;
+package cn.iocoder.yudao.module.iot.net.component.core.heartbeat;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -8,49 +10,51 @@ import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-// TODO @haohao:组件相关的注释,要不把 组件 => 网络组件?可能更容易理解?
-// TODO @haohao:yudao-module-iot-components => yudao-module-iot-net-components 增加一个 net 如何?虽然会长一点,但是意思更精准?
/**
- * IoT 组件注册表
+ * IoT 网络组件注册表
*
- * 用于管理多个组件的注册信息,解决多组件心跳问题
+ * 用于管理多个网络组件的注册信息,解决多组件心跳问题
+ *
+ * @author haohao
*/
@Component
@Slf4j
-public class IotComponentRegistry {
+public class IotNetComponentRegistry {
/**
- * 组件信息
+ * 网络组件信息
*/
@Data
- public static class IotComponentInfo {
+ public static class IotNetComponentInfo {
/**
* 组件 Key
*/
private final String pluginKey;
+
/**
* 主机 IP
*/
private final String hostIp;
+
/**
* 下游端口
*/
private final Integer downstreamPort;
+
/**
* 进程 ID
*/
private final String processId;
-
}
/**
* 组件映射表:key 为组件 Key
*/
- private final Map components = new ConcurrentHashMap<>();
+ private final Map components = new ConcurrentHashMap<>();
/**
- * 注册组件
+ * 注册网络组件
*
* @param pluginKey 组件 Key
* @param hostIp 主机 IP
@@ -58,38 +62,37 @@ public class IotComponentRegistry {
* @param processId 进程 ID
*/
public void registerComponent(String pluginKey, String hostIp, Integer downstreamPort, String processId) {
- log.info("[registerComponent][注册组件, pluginKey={}, hostIp={}, downstreamPort={}, processId={}]",
+ log.info("[registerComponent][注册网络组件, pluginKey={}, hostIp={}, downstreamPort={}, processId={}]",
pluginKey, hostIp, downstreamPort, processId);
- components.put(pluginKey, new IotComponentInfo(pluginKey, hostIp, downstreamPort, processId));
+ components.put(pluginKey, new IotNetComponentInfo(pluginKey, hostIp, downstreamPort, processId));
}
/**
- * 注销组件
+ * 注销网络组件
*
* @param pluginKey 组件 Key
*/
public void unregisterComponent(String pluginKey) {
- log.info("[unregisterComponent][注销组件, pluginKey={}]", pluginKey);
+ log.info("[unregisterComponent][注销网络组件, pluginKey={}]", pluginKey);
components.remove(pluginKey);
}
/**
- * 获取所有组件
+ * 获取所有网络组件
*
* @return 所有组件集合
*/
- public Collection getAllComponents() {
- return components.values();
+ public Collection getAllComponents() {
+ return CollUtil.isEmpty(components) ? CollUtil.newArrayList() : components.values();
}
/**
- * 获取指定组件
+ * 获取指定网络组件
*
* @param pluginKey 组件 Key
* @return 组件信息
*/
- public IotComponentInfo getComponent(String pluginKey) {
- return components.get(pluginKey);
+ public IotNetComponentInfo getComponent(String pluginKey) {
+ return MapUtil.isEmpty(components) ? null : components.get(pluginKey);
}
-
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/message/IotAlinkMessage.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/message/IotAlinkMessage.java
new file mode 100644
index 0000000000..f997f91f58
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/message/IotAlinkMessage.java
@@ -0,0 +1,153 @@
+package cn.iocoder.yudao.module.iot.net.component.core.message;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONObject;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * IoT Alink 消息模型
+ *
+ * 基于阿里云 Alink 协议规范实现的标准消息格式
+ *
+ * @author haohao
+ */
+@Data
+@Builder
+public class IotAlinkMessage {
+
+ /**
+ * 消息 ID
+ */
+ private String id;
+
+ /**
+ * 协议版本
+ */
+ @Builder.Default
+ private String version = "1.0";
+
+ /**
+ * 消息方法
+ */
+ private String method;
+
+ /**
+ * 消息参数
+ */
+ private Map params;
+
+ /**
+ * 转换为 JSONObject
+ *
+ * @return JSONObject 对象
+ */
+ public JSONObject toJsonObject() {
+ JSONObject json = new JSONObject();
+ json.set("id", id);
+ json.set("version", version);
+ json.set("method", method);
+ json.set("params", params != null ? params : new JSONObject());
+ return json;
+ }
+
+ /**
+ * 转换为 JSON 字符串
+ *
+ * @return JSON 字符串
+ */
+ public String toJsonString() {
+ return toJsonObject().toString();
+ }
+
+ /**
+ * 创建设备服务调用消息
+ *
+ * @param requestId 请求 ID,为空时自动生成
+ * @param serviceIdentifier 服务标识符
+ * @param params 服务参数
+ * @return Alink 消息对象
+ */
+ public static IotAlinkMessage createServiceInvokeMessage(String requestId, String serviceIdentifier,
+ Map params) {
+ return IotAlinkMessage.builder()
+ .id(requestId != null ? requestId : generateRequestId())
+ .method("thing.service." + serviceIdentifier)
+ .params(params)
+ .build();
+ }
+
+ /**
+ * 创建设备属性设置消息
+ *
+ * @param requestId 请求 ID,为空时自动生成
+ * @param properties 设备属性
+ * @return Alink 消息对象
+ */
+ public static IotAlinkMessage createPropertySetMessage(String requestId, Map properties) {
+ return IotAlinkMessage.builder()
+ .id(requestId != null ? requestId : generateRequestId())
+ .method("thing.service.property.set")
+ .params(properties)
+ .build();
+ }
+
+ /**
+ * 创建设备属性获取消息
+ *
+ * @param requestId 请求 ID,为空时自动生成
+ * @param identifiers 要获取的属性标识符列表
+ * @return Alink 消息对象
+ */
+ public static IotAlinkMessage createPropertyGetMessage(String requestId, String[] identifiers) {
+ JSONObject params = new JSONObject();
+ params.set("identifiers", identifiers);
+
+ return IotAlinkMessage.builder()
+ .id(requestId != null ? requestId : generateRequestId())
+ .method("thing.service.property.get")
+ .params(params)
+ .build();
+ }
+
+ /**
+ * 创建设备配置设置消息
+ *
+ * @param requestId 请求 ID,为空时自动生成
+ * @param configs 设备配置
+ * @return Alink 消息对象
+ */
+ public static IotAlinkMessage createConfigSetMessage(String requestId, Map configs) {
+ return IotAlinkMessage.builder()
+ .id(requestId != null ? requestId : generateRequestId())
+ .method("thing.service.config.set")
+ .params(configs)
+ .build();
+ }
+
+ /**
+ * 创建设备 OTA 升级消息
+ *
+ * @param requestId 请求 ID,为空时自动生成
+ * @param otaInfo OTA 升级信息
+ * @return Alink 消息对象
+ */
+ public static IotAlinkMessage createOtaUpgradeMessage(String requestId, Map otaInfo) {
+ return IotAlinkMessage.builder()
+ .id(requestId != null ? requestId : generateRequestId())
+ .method("thing.service.ota.upgrade")
+ .params(otaInfo)
+ .build();
+ }
+
+ /**
+ * 生成请求 ID
+ *
+ * @return 请求 ID
+ */
+ public static String generateRequestId() {
+ return IdUtil.fastSimpleUUID();
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/pojo/IotStandardResponse.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/pojo/IotStandardResponse.java
similarity index 62%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/pojo/IotStandardResponse.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/pojo/IotStandardResponse.java
index 4b7058b1dc..1e14c37ca0 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/pojo/IotStandardResponse.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/pojo/IotStandardResponse.java
@@ -1,6 +1,8 @@
-package cn.iocoder.yudao.module.iot.component.core.pojo;
+package cn.iocoder.yudao.module.iot.net.component.core.pojo;
+import cn.hutool.core.util.StrUtil;
import lombok.Data;
+import lombok.experimental.Accessors;
/**
* IoT 标准协议响应实体类
@@ -10,10 +12,11 @@ import lombok.Data;
* @author haohao
*/
@Data
+@Accessors(chain = true)
public class IotStandardResponse {
/**
- * 消息ID
+ * 消息 ID
*/
private String id;
@@ -45,7 +48,7 @@ public class IotStandardResponse {
/**
* 创建成功响应
*
- * @param id 消息ID
+ * @param id 消息 ID
* @param method 方法名
* @return 成功响应
*/
@@ -56,28 +59,37 @@ public class IotStandardResponse {
/**
* 创建成功响应
*
- * @param id 消息ID
+ * @param id 消息 ID
* @param method 方法名
* @param data 响应数据
* @return 成功响应
*/
public static IotStandardResponse success(String id, String method, Object data) {
- return new IotStandardResponse().setId(id).setCode(200).setData(data).setMessage("success")
- .setMethod(method).setVersion("1.0");
+ return new IotStandardResponse()
+ .setId(id)
+ .setCode(200)
+ .setData(data)
+ .setMessage("success")
+ .setMethod(method)
+ .setVersion("1.0");
}
/**
* 创建错误响应
*
- * @param id 消息ID
+ * @param id 消息 ID
* @param method 方法名
* @param code 错误码
* @param message 错误消息
* @return 错误响应
*/
public static IotStandardResponse error(String id, String method, Integer code, String message) {
- return new IotStandardResponse().setId(id).setCode(code).setData(null).setMessage(message)
- .setMethod(method).setVersion("1.0");
+ return new IotStandardResponse()
+ .setId(id)
+ .setCode(code)
+ .setData(null)
+ .setMessage(StrUtil.blankToDefault(message, "error"))
+ .setMethod(method)
+ .setVersion("1.0");
}
-
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/upstream/IotDeviceUpstreamClient.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/upstream/IotDeviceUpstreamClient.java
similarity index 96%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/upstream/IotDeviceUpstreamClient.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/upstream/IotDeviceUpstreamClient.java
index 1cec3ee0f1..efd6cc0943 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/upstream/IotDeviceUpstreamClient.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/upstream/IotDeviceUpstreamClient.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.component.core.upstream;
+package cn.iocoder.yudao.module.iot.net.component.core.upstream;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/util/IotPluginCommonUtils.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/util/IotNetComponentCommonUtils.java
similarity index 73%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/util/IotPluginCommonUtils.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/util/IotNetComponentCommonUtils.java
index 7f84c1305c..9e432af320 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/java/cn/iocoder/yudao/module/iot/component/core/util/IotPluginCommonUtils.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/java/cn/iocoder/yudao/module/iot/net/component/core/util/IotNetComponentCommonUtils.java
@@ -1,27 +1,31 @@
-package cn.iocoder.yudao.module.iot.component.core.util;
+package cn.iocoder.yudao.module.iot.net.component.core.util;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.module.iot.component.core.pojo.IotStandardResponse;
+import cn.iocoder.yudao.module.iot.net.component.core.pojo.IotStandardResponse;
import io.vertx.core.http.HttpHeaders;
import io.vertx.ext.web.RoutingContext;
import org.springframework.http.MediaType;
-// TODO @haohao:名字要改下哈。
/**
- * IoT 插件的通用工具类
+ * IoT 网络组件的通用工具类
*
* @author 芋道源码
*/
-public class IotPluginCommonUtils {
+public class IotNetComponentCommonUtils {
/**
* 流程实例的进程编号
*/
private static String processId;
+ /**
+ * 获取进程ID
+ *
+ * @return 进程ID
+ */
public static String getProcessId() {
if (StrUtil.isEmpty(processId)) {
initProcessId();
@@ -29,11 +33,23 @@ public class IotPluginCommonUtils {
return processId;
}
+ /**
+ * 初始化进程ID
+ */
private synchronized static void initProcessId() {
processId = String.format("%s@%d@%s", // IP@PID@${uuid}
SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID(), IdUtil.fastSimpleUUID());
}
+ /**
+ * 生成请求ID
+ *
+ * @return 生成的唯一请求ID
+ */
+ public static String generateRequestId() {
+ return IdUtil.fastSimpleUUID();
+ }
+
/**
* 将对象转换为JSON字符串后写入HTTP响应
*
@@ -51,20 +67,20 @@ public class IotPluginCommonUtils {
/**
* 生成标准JSON格式的响应并写入HTTP响应(基于IotStandardResponse)
*
- * 推荐使用此方法,统一MQTT和HTTP的响应格式。使用方式:
+ * 推荐使用此方法,统一 MQTT 和 HTTP 的响应格式。使用方式:
*
*
* // 成功响应
* IotStandardResponse response = IotStandardResponse.success(requestId, method, data);
- * IotPluginCommonUtils.writeJsonResponse(routingContext, response);
+ * IotNetComponentCommonUtils.writeJsonResponse(routingContext, response);
*
* // 错误响应
* IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
- * IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
+ * IotNetComponentCommonUtils.writeJsonResponse(routingContext, errorResponse);
*
*
* @param routingContext 路由上下文
- * @param response IotStandardResponse响应对象
+ * @param response IotStandardResponse 响应对象
*/
@SuppressWarnings("deprecation")
public static void writeJsonResponse(RoutingContext routingContext, IotStandardResponse response) {
@@ -73,5 +89,4 @@ public class IotPluginCommonUtils {
.putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.end(JsonUtils.toJsonString(response));
}
-
-}
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring.factories b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000..1fb7cb13a7
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonAutoConfiguration
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000..57f1b43109
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonAutoConfiguration
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/pom.xml b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/pom.xml
similarity index 83%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/pom.xml
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/pom.xml
index 977fcc5014..7bb896e229 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/pom.xml
@@ -3,23 +3,23 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- yudao-module-iot-components
+ yudao-module-iot-net-components
cn.iocoder.boot
${revision}
4.0.0
- yudao-module-iot-component-emqx
+ yudao-module-iot-net-component-emqx
jar
${project.artifactId}
- 物联网组件 EMQX 模块
+ 物联网网络组件 EMQX 模块
cn.iocoder.boot
- yudao-module-iot-component-core
+ yudao-module-iot-net-component-core
${revision}
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/config/IotNetComponentEmqxAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/config/IotNetComponentEmqxAutoConfiguration.java
new file mode 100644
index 0000000000..bd6f88df3d
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/config/IotNetComponentEmqxAutoConfiguration.java
@@ -0,0 +1,129 @@
+package cn.iocoder.yudao.module.iot.net.component.emqx.config;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.system.SystemUtil;
+import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
+import cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonProperties;
+import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
+import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry;
+import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
+import cn.iocoder.yudao.module.iot.net.component.emqx.downstream.IotDeviceDownstreamHandlerImpl;
+import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.IotDeviceUpstreamServer;
+import io.vertx.core.Vertx;
+import io.vertx.mqtt.MqttClient;
+import io.vertx.mqtt.MqttClientOptions;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.event.EventListener;
+
+/**
+ * IoT 网络组件 EMQX 的自动配置类
+ *
+ * @author haohao
+ */
+@Slf4j
+@AutoConfiguration
+@EnableConfigurationProperties(IotNetComponentEmqxProperties.class)
+@ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true", matchIfMissing = false)
+@ComponentScan(basePackages = {
+ "cn.iocoder.yudao.module.iot.net.component.emqx" // 只扫描 EMQX 组件包
+})
+public class IotNetComponentEmqxAutoConfiguration {
+
+ /**
+ * 组件 key
+ */
+ private static final String PLUGIN_KEY = "emqx";
+
+ public IotNetComponentEmqxAutoConfiguration() {
+ // 构造函数中不输出日志,移到 initialize 方法中
+ }
+
+ /**
+ * 初始化 EMQX 组件
+ *
+ * @param event 应用启动事件
+ */
+ @EventListener(ApplicationStartedEvent.class)
+ public void initialize(ApplicationStartedEvent event) {
+ log.info("[IotNetComponentEmqxAutoConfiguration][开始初始化]");
+
+ // 从应用上下文中获取需要的 Bean
+ IotNetComponentRegistry componentRegistry = event.getApplicationContext()
+ .getBean(IotNetComponentRegistry.class);
+ IotNetComponentCommonProperties commonProperties = event.getApplicationContext()
+ .getBean(IotNetComponentCommonProperties.class);
+
+ // 设置当前组件的核心标识
+ // 注意:这里只为当前 EMQX 组件设置 pluginKey,不影响其他组件
+ commonProperties.setPluginKey(PLUGIN_KEY);
+
+ // 将 EMQX 组件注册到组件注册表
+ componentRegistry.registerComponent(
+ PLUGIN_KEY,
+ SystemUtil.getHostInfo().getAddress(),
+ 0, // 内嵌模式固定为 0
+ IotNetComponentCommonUtils.getProcessId());
+
+ log.info("[initialize][IoT EMQX 组件初始化完成]");
+ }
+
+ /**
+ * 创建 Vert.x 实例
+ */
+ @Bean(name = "emqxVertx")
+ public Vertx vertx() {
+ return Vertx.vertx();
+ }
+
+ /**
+ * 创建 MQTT 客户端
+ */
+ @Bean
+ public MqttClient mqttClient(@Qualifier("emqxVertx") Vertx vertx, IotNetComponentEmqxProperties emqxProperties) {
+ // 使用 debug 级别记录详细配置,减少生产环境日志
+ if (log.isDebugEnabled()) {
+ log.debug("MQTT 配置: host={}, port={}, username={}, ssl={}",
+ emqxProperties.getMqttHost(), emqxProperties.getMqttPort(),
+ emqxProperties.getMqttUsername(), emqxProperties.getMqttSsl());
+ } else {
+ log.info("MQTT 连接至: {}:{}", emqxProperties.getMqttHost(), emqxProperties.getMqttPort());
+ }
+
+ MqttClientOptions options = new MqttClientOptions()
+ .setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID())
+ .setUsername(emqxProperties.getMqttUsername())
+ .setPassword(emqxProperties.getMqttPassword());
+ // 设置 SSL 选项
+ options.setSsl(ObjUtil.defaultIfNull(emqxProperties.getMqttSsl(), false));
+ return MqttClient.create(vertx, options);
+ }
+
+ /**
+ * 创建设备上行服务器
+ */
+ @Bean(name = "emqxDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop")
+ public IotDeviceUpstreamServer deviceUpstreamServer(
+ IotDeviceUpstreamApi deviceUpstreamApi,
+ IotNetComponentEmqxProperties emqxProperties,
+ @Qualifier("emqxVertx") Vertx vertx,
+ MqttClient mqttClient,
+ IotNetComponentRegistry componentRegistry) {
+ return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi, vertx, mqttClient, componentRegistry);
+ }
+
+ /**
+ * 创建设备下行处理器
+ */
+ @Bean(name = "emqxDeviceDownstreamHandler")
+ public IotDeviceDownstreamHandler deviceDownstreamHandler(MqttClient mqttClient) {
+ return new IotDeviceDownstreamHandlerImpl(mqttClient);
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/config/IotComponentEmqxProperties.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/config/IotNetComponentEmqxProperties.java
similarity index 65%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/config/IotComponentEmqxProperties.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/config/IotNetComponentEmqxProperties.java
index 576ed5cded..d300bb70d3 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/config/IotComponentEmqxProperties.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/config/IotNetComponentEmqxProperties.java
@@ -1,44 +1,51 @@
-package cn.iocoder.yudao.module.iot.component.emqx.config;
+package cn.iocoder.yudao.module.iot.net.component.emqx.config;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
/**
- * IoT EMQX 组件配置属性
+ * IoT EMQX 网络组件配置属性
+ *
+ * @author haohao
*/
@ConfigurationProperties(prefix = "yudao.iot.component.emqx")
@Data
-public class IotComponentEmqxProperties {
+@Validated
+public class IotNetComponentEmqxProperties {
/**
* 是否启用 EMQX 组件
*/
private Boolean enabled;
- // TODO @haohao:一般中英文之间,加个空格哈,写作(注释)习惯。类似 MQTT 密码;
/**
- * 服务主机
+ * MQTT 服务主机
*/
@NotBlank(message = "MQTT 服务器主机不能为空")
private String mqttHost;
+
/**
- * 服务端口
+ * MQTT 服务端口
*/
@NotNull(message = "MQTT 服务器端口不能为空")
private Integer mqttPort;
+
/**
- * 服务用户名
+ * MQTT 服务用户名
*/
@NotBlank(message = "MQTT 服务器用户名不能为空")
private String mqttUsername;
+
/**
- * 服务密码
+ * MQTT 服务密码
*/
@NotBlank(message = "MQTT 服务器密码不能为空")
private String mqttPassword;
+
/**
* 是否启用 SSL
*/
@@ -57,4 +64,17 @@ public class IotComponentEmqxProperties {
@NotNull(message = "认证端口不能为空")
private Integer authPort;
+ /**
+ * 重连延迟时间(毫秒)
+ *
+ * 默认值:5000 毫秒
+ */
+ private Integer reconnectDelayMs = 5000;
+
+ /**
+ * 连接超时时间(毫秒)
+ *
+ * 默认值:10000 毫秒
+ */
+ private Integer connectionTimeoutMs = 10000;
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
similarity index 51%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
index 1a800d79ad..771ad42973 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/downstream/IotDeviceDownstreamHandlerImpl.java
@@ -1,48 +1,38 @@
-package cn.iocoder.yudao.module.iot.component.emqx.downstream;
+package cn.iocoder.yudao.module.iot.net.component.emqx.downstream;
-import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
-import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler;
+import cn.iocoder.yudao.module.iot.net.component.core.constants.IotDeviceTopicEnum;
+import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
+import cn.iocoder.yudao.module.iot.net.component.core.message.IotAlinkMessage;
+import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.vertx.core.buffer.Buffer;
import io.vertx.mqtt.MqttClient;
import lombok.extern.slf4j.Slf4j;
-import java.util.Map;
-
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.MQTT_TOPIC_ILLEGAL;
/**
- * EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类
+ * EMQX 网络组件的 {@link IotDeviceDownstreamHandler} 实现类
*
* @author 芋道源码
*/
@Slf4j
public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
- private static final String SYS_TOPIC_PREFIX = "/sys/";
-
- // TODO @haohao:是不是可以类似 IotDeviceConfigSetVertxHandler 的建议,抽到统一的枚举类
- // TODO @haohao:讨论,感觉 mqtt 和 http,可以做个相对统一的格式哈。;回复 都使用 Alink 格式,方便后续扩展。
- // 设备服务调用 标准 JSON
- // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}
- // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}_reply
- private static final String SERVICE_TOPIC_PREFIX = "/thing/service/";
-
- // 设置设备属性 标准 JSON
- // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set
- // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply
- private static final String PROPERTY_SET_TOPIC = "/thing/service/property/set";
-
+ /**
+ * MQTT 客户端
+ */
private final MqttClient mqttClient;
/**
* 构造函数
*
- * @param mqttClient MQTT客户端
+ * @param mqttClient MQTT 客户端
*/
public IotDeviceDownstreamHandlerImpl(MqttClient mqttClient) {
this.mqttClient = mqttClient;
@@ -60,12 +50,17 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
try {
// 构建请求主题
- String topic = buildServiceTopic(reqDTO.getProductKey(), reqDTO.getDeviceName(), reqDTO.getIdentifier());
+ String topic = IotDeviceTopicEnum.buildServiceTopic(reqDTO.getProductKey(), reqDTO.getDeviceName(),
+ reqDTO.getIdentifier());
+
// 构建请求消息
- String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId();
- JSONObject request = buildServiceRequest(requestId, reqDTO.getIdentifier(), reqDTO.getParams());
+ String requestId = StrUtil.isNotEmpty(reqDTO.getRequestId()) ? reqDTO.getRequestId()
+ : IotNetComponentCommonUtils.generateRequestId();
+ IotAlinkMessage message = IotAlinkMessage.createServiceInvokeMessage(
+ requestId, reqDTO.getIdentifier(), reqDTO.getParams());
+
// 发送消息
- publishMessage(topic, request);
+ publishMessage(topic, message.toJsonObject());
log.info("[invokeService][调用设备服务成功][requestId: {}][topic: {}]", requestId, topic);
return CommonResult.success(true);
@@ -77,13 +72,15 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
@Override
public CommonResult getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
+ // 暂未实现,返回成功
return CommonResult.success(true);
}
@Override
public CommonResult setDeviceProperty(IotDevicePropertySetReqDTO reqDTO) {
- // 验证参数
log.info("[setProperty][开始设置设备属性][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
+
+ // 验证参数
if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null) {
log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg());
@@ -91,12 +88,15 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
try {
// 构建请求主题
- String topic = buildPropertySetTopic(reqDTO.getProductKey(), reqDTO.getDeviceName());
+ String topic = IotDeviceTopicEnum.buildPropertySetTopic(reqDTO.getProductKey(), reqDTO.getDeviceName());
+
// 构建请求消息
- String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId();
- JSONObject request = buildPropertySetRequest(requestId, reqDTO.getProperties());
+ String requestId = StrUtil.isNotEmpty(reqDTO.getRequestId()) ? reqDTO.getRequestId()
+ : IotNetComponentCommonUtils.generateRequestId();
+ IotAlinkMessage message = IotAlinkMessage.createPropertySetMessage(requestId, reqDTO.getProperties());
+
// 发送消息
- publishMessage(topic, request);
+ publishMessage(topic, message.toJsonObject());
log.info("[setProperty][设置设备属性成功][requestId: {}][topic: {}]", requestId, topic);
return CommonResult.success(true);
@@ -108,54 +108,21 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
@Override
public CommonResult setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
+ // 暂未实现,返回成功
return CommonResult.success(true);
}
@Override
public CommonResult upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
+ // 暂未实现,返回成功
return CommonResult.success(true);
}
- /**
- * 构建服务调用主题
- */
- private String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
- return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + SERVICE_TOPIC_PREFIX + serviceIdentifier;
- }
-
- /**
- * 构建属性设置主题
- */
- private String buildPropertySetTopic(String productKey, String deviceName) {
- return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + PROPERTY_SET_TOPIC;
- }
-
- // TODO @haohao:这个,后面搞个对象,会不会好点哈?
-
- /**
- * 构建服务调用请求
- */
- private JSONObject buildServiceRequest(String requestId, String serviceIdentifier, Map params) {
- return new JSONObject()
- .set("id", requestId)
- .set("version", "1.0")
- .set("method", "thing.service." + serviceIdentifier)
- .set("params", params != null ? params : new JSONObject());
- }
-
- /**
- * 构建属性设置请求
- */
- private JSONObject buildPropertySetRequest(String requestId, Map properties) {
- return new JSONObject()
- .set("id", requestId)
- .set("version", "1.0")
- .set("method", "thing.service.property.set")
- .set("params", properties);
- }
-
/**
* 发布 MQTT 消息
+ *
+ * @param topic 主题
+ * @param payload 消息内容
*/
private void publishMessage(String topic, JSONObject payload) {
mqttClient.publish(
@@ -166,13 +133,4 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
false);
log.info("[publishMessage][发送消息成功][topic: {}][payload: {}]", topic, payload);
}
-
- // TODO @haohao:这个要不抽到 IotPluginCommonUtils 里?
- /**
- * 生成请求 ID
- */
- private String generateRequestId() {
- return IdUtil.fastSimpleUUID();
- }
-
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/IotDeviceUpstreamServer.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/IotDeviceUpstreamServer.java
similarity index 59%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/IotDeviceUpstreamServer.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/IotDeviceUpstreamServer.java
index 4078b0c323..76d8f9e7eb 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/IotDeviceUpstreamServer.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/IotDeviceUpstreamServer.java
@@ -1,13 +1,13 @@
-package cn.iocoder.yudao.module.iot.component.emqx.upstream;
+package cn.iocoder.yudao.module.iot.net.component.emqx.upstream;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
-import cn.iocoder.yudao.module.iot.component.core.heartbeat.IotComponentRegistry;
-import cn.iocoder.yudao.module.iot.component.emqx.config.IotComponentEmqxProperties;
-import cn.iocoder.yudao.module.iot.component.emqx.upstream.router.IotDeviceAuthVertxHandler;
-import cn.iocoder.yudao.module.iot.component.emqx.upstream.router.IotDeviceMqttMessageHandler;
-import cn.iocoder.yudao.module.iot.component.emqx.upstream.router.IotDeviceWebhookVertxHandler;
+import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry;
+import cn.iocoder.yudao.module.iot.net.component.emqx.config.IotNetComponentEmqxProperties;
+import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceAuthVertxHandler;
+import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceMqttMessageHandler;
+import cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router.IotDeviceWebhookVertxHandler;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
@@ -21,7 +21,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
- * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
+ * IoT 设备上行服务端,接收来自 device 设备的请求,转发给 server 服务器
*
* 协议:HTTP、MQTT
*
@@ -30,15 +30,6 @@ import java.util.concurrent.TimeUnit;
@Slf4j
public class IotDeviceUpstreamServer {
- // TODO @haohao:抽到 IotComponentEmqxProperties 里?
- /**
- * 重连延迟时间(毫秒)
- */
- private static final int RECONNECT_DELAY_MS = 5000;
- /**
- * 连接超时时间(毫秒)
- */
- private static final int CONNECTION_TIMEOUT_MS = 10000;
/**
* 默认 QoS 级别
*/
@@ -47,20 +38,20 @@ public class IotDeviceUpstreamServer {
private final Vertx vertx;
private final HttpServer server;
private final MqttClient client;
- private final IotComponentEmqxProperties emqxProperties;
+ private final IotNetComponentEmqxProperties emqxProperties;
private final IotDeviceMqttMessageHandler mqttMessageHandler;
- private final IotComponentRegistry componentRegistry;
+ private final IotNetComponentRegistry componentRegistry;
/**
* 服务运行状态标志
*/
private volatile boolean isRunning = false;
- public IotDeviceUpstreamServer(IotComponentEmqxProperties emqxProperties,
+ public IotDeviceUpstreamServer(IotNetComponentEmqxProperties emqxProperties,
IotDeviceUpstreamApi deviceUpstreamApi,
Vertx vertx,
MqttClient client,
- IotComponentRegistry componentRegistry) {
+ IotNetComponentRegistry componentRegistry) {
this.vertx = vertx;
this.emqxProperties = emqxProperties;
this.client = client;
@@ -70,8 +61,7 @@ public class IotDeviceUpstreamServer {
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create()); // 处理 Body
router.post(IotDeviceAuthVertxHandler.PATH)
- // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀?
- // 回复:MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式
+ // MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式
.handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
// 添加 Webhook 处理器,用于处理设备连接和断开连接事件
router.post(IotDeviceWebhookVertxHandler.PATH)
@@ -91,15 +81,20 @@ public class IotDeviceUpstreamServer {
}
log.info("[start][开始启动服务]");
- // 检查authPort是否为null
+ // 检查 authPort 是否为 null
Integer authPort = emqxProperties.getAuthPort();
if (authPort == null) {
- log.warn("[start][authPort为null,使用默认端口8080]");
+ log.warn("[start][authPort 为 null,使用默认端口 8080]");
authPort = 8080; // 默认端口
}
+ // 获取连接超时时间
+ int connectionTimeoutMs = emqxProperties.getConnectionTimeoutMs() != null
+ ? emqxProperties.getConnectionTimeoutMs()
+ : 10000;
+
// 1. 启动 HTTP 服务器
- final Integer finalAuthPort = authPort; // 为lambda表达式创建final变量
+ final Integer finalAuthPort = authPort; // 为 lambda 表达式创建 final 变量
CompletableFuture httpFuture = server.listen(finalAuthPort)
.toCompletionStage()
.toCompletableFuture()
@@ -115,13 +110,13 @@ public class IotDeviceUpstreamServer {
log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
reconnectWithDelay();
});
- // 2. 设置 MQTT 消息处理器
+ // 2.2 设置 MQTT 消息处理器
setupMessageHandler();
});
// 3. 等待所有服务启动完成
CompletableFuture.allOf(httpFuture, mqttFuture)
- .orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ .orTimeout(connectionTimeoutMs, TimeUnit.MILLISECONDS)
.whenComplete((result, error) -> {
if (error != null) {
log.error("[start][服务启动失败]", error);
@@ -149,7 +144,12 @@ public class IotDeviceUpstreamServer {
return;
}
- vertx.setTimer(RECONNECT_DELAY_MS, id -> {
+ // 获取重连延迟时间
+ int reconnectDelayMs = emqxProperties.getReconnectDelayMs() != null
+ ? emqxProperties.getReconnectDelayMs()
+ : 5000;
+
+ vertx.setTimer(reconnectDelayMs, id -> {
log.info("[reconnectWithDelay][开始重新连接 MQTT]");
connectMqtt();
});
@@ -158,14 +158,14 @@ public class IotDeviceUpstreamServer {
/**
* 连接 MQTT Broker 并订阅主题
*
- * @return 连接结果的Future
+ * @return 连接结果的 Future
*/
private Future connectMqtt() {
// 检查必要的 MQTT 配置
String host = emqxProperties.getMqttHost();
Integer port = emqxProperties.getMqttPort();
- if (host == null) {
- String msg = "[connectMqtt][MQTT Host 为 null,无法连接]";
+ if (StrUtil.isBlank(host)) {
+ String msg = "[connectMqtt][MQTT Host 为空,无法连接]";
log.error(msg);
return Future.failedFuture(new IllegalStateException(msg));
}
@@ -177,11 +177,11 @@ public class IotDeviceUpstreamServer {
final Integer finalPort = port;
return client.connect(finalPort, host)
.compose(connAck -> {
- log.info("[connectMqtt][MQTT客户端连接成功]");
+ log.info("[connectMqtt][MQTT 客户端连接成功]");
return subscribeToTopics();
})
.recover(error -> {
- log.error("[connectMqtt][连接MQTT Broker失败:]", error);
+ log.error("[connectMqtt][连接 MQTT Broker 失败:]", error);
reconnectWithDelay();
return Future.failedFuture(error);
});
@@ -198,62 +198,67 @@ public class IotDeviceUpstreamServer {
log.warn("[subscribeToTopics][未配置 MQTT 主题或为 null,使用默认主题]");
topics = new String[]{"/device/#"}; // 默认订阅所有设备上下行主题
}
- log.info("[subscribeToTopics][开始订阅设备上行消息主题]");
- Future compositeFuture = Future.succeededFuture();
+ // 使用协调器追踪多个 Future 的完成状态
+ Future result = Future.succeededFuture();
for (String topic : topics) {
- String trimmedTopic = StrUtil.trim(topic);
- if (StrUtil.isBlank(trimmedTopic)) {
+ if (StrUtil.isBlank(topic)) {
+ log.warn("[subscribeToTopics][跳过空主题]");
continue;
}
- compositeFuture = compositeFuture.compose(v -> client.subscribe(trimmedTopic, DEFAULT_QOS.value())
+
+ result = result.compose(v -> client.subscribe(topic, DEFAULT_QOS.value())
.map(ack -> {
- log.info("[subscribeToTopics][成功订阅主题: {}]", trimmedTopic);
+ log.info("[subscribeToTopics][订阅主题成功: {}]", topic);
return null;
})
- .recover(error -> {
- log.error("[subscribeToTopics][订阅主题失败: {}]", trimmedTopic, error);
- return Future.succeededFuture(); // 继续订阅其他主题
+ .recover(err -> {
+ log.error("[subscribeToTopics][订阅主题失败: {}]", topic, err);
+ return Future.failedFuture(err);
}));
}
- return compositeFuture;
+ return result;
}
/**
- * 停止所有服务
+ * 停止服务
*/
public void stop() {
if (!isRunning) {
- log.warn("[stop][服务未运行,无需停止]");
+ log.warn("[stop][服务已经停止,无需再次停止]");
return;
}
- log.info("[stop][开始关闭服务]");
- isRunning = false;
+ log.info("[stop][开始停止服务]");
- try {
- CompletableFuture serverFuture = server != null
- ? server.close().toCompletionStage().toCompletableFuture()
- : CompletableFuture.completedFuture(null);
- CompletableFuture clientFuture = client != null
- ? client.disconnect().toCompletionStage().toCompletableFuture()
- : CompletableFuture.completedFuture(null);
- CompletableFuture vertxFuture = vertx != null
- ? vertx.close().toCompletionStage().toCompletableFuture()
- : CompletableFuture.completedFuture(null);
-
- // 等待所有资源关闭
- CompletableFuture.allOf(serverFuture, clientFuture, vertxFuture)
- .orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
- .whenComplete((result, error) -> {
- if (error != null) {
- log.error("[stop][服务关闭过程中发生异常]", error);
- } else {
- log.info("[stop][所有服务关闭完成]");
- }
- });
- } catch (Exception e) {
- log.error("[stop][关闭服务异常]", e);
- throw new RuntimeException("关闭 IoT 设备上行服务失败", e);
+ // 1. 取消 MQTT 主题订阅
+ if (client.isConnected()) {
+ for (String topic : emqxProperties.getMqttTopics()) {
+ try {
+ client.unsubscribe(topic);
+ } catch (Exception e) {
+ log.warn("[stop][取消订阅主题异常: {}]", topic, e);
+ }
+ }
}
+
+ // 2. 关闭 MQTT 客户端
+ try {
+ if (client.isConnected()) {
+ client.disconnect();
+ }
+ } catch (Exception e) {
+ log.warn("[stop][关闭 MQTT 客户端异常]", e);
+ }
+
+ // 3. 关闭 HTTP 服务器
+ try {
+ server.close();
+ } catch (Exception e) {
+ log.warn("[stop][关闭 HTTP 服务器异常]", e);
+ }
+
+ // 4. 更新状态
+ isRunning = false;
+ log.info("[stop][服务已停止]");
}
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceAuthVertxHandler.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceAuthVertxHandler.java
similarity index 81%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceAuthVertxHandler.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceAuthVertxHandler.java
index 5b7f92845d..7ca1592e81 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceAuthVertxHandler.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceAuthVertxHandler.java
@@ -1,9 +1,9 @@
-package cn.iocoder.yudao.module.iot.component.emqx.upstream.router;
+package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEmqxAuthReqDTO;
-import cn.iocoder.yudao.module.iot.component.core.util.IotPluginCommonUtils;
+import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
@@ -47,17 +47,17 @@ public class IotDeviceAuthVertxHandler implements Handler {
CommonResult authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);
if (authResult.getCode() != 0 || !authResult.getData()) {
// 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求
- IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
+ IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
return;
}
// 响应结果
// 注意:这里必须返回 {"result": "allow"} 格式,以符合 EMQX 认证插件的要求
- IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "allow"));
+ IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "allow"));
} catch (Exception e) {
log.error("[handle][EMQX 认证异常]", e);
// 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求
- IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
+ IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
}
}
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceMqttMessageHandler.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceMqttMessageHandler.java
similarity index 85%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceMqttMessageHandler.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceMqttMessageHandler.java
index 19463d6a13..66c38dfe15 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceMqttMessageHandler.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceMqttMessageHandler.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.iot.component.emqx.upstream.router;
+package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
@@ -7,8 +7,9 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
-import cn.iocoder.yudao.module.iot.component.core.pojo.IotStandardResponse;
-import cn.iocoder.yudao.module.iot.component.core.util.IotPluginCommonUtils;
+import cn.iocoder.yudao.module.iot.net.component.core.constants.IotDeviceTopicEnum;
+import cn.iocoder.yudao.module.iot.net.component.core.pojo.IotStandardResponse;
+import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.vertx.core.buffer.Buffer;
import io.vertx.mqtt.MqttClient;
@@ -23,25 +24,12 @@ import java.util.Map;
/**
* IoT 设备 MQTT 消息处理器
*
- * 参考:设备属性、事件、服务
+ * 参考:设备属性、事件、服务
*/
@Slf4j
public class IotDeviceMqttMessageHandler {
- // TODO @haohao:讨论,感觉 mqtt 和 http,可以做个相对统一的格式哈;回复 都使用 Alink 格式,方便后续扩展。
- // 设备上报属性 标准 JSON
- // 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
- // 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
-
- // 设备上报事件 标准 JSON
- // 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post
- // 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply
-
- private static final String SYS_TOPIC_PREFIX = "/sys/";
- private static final String PROPERTY_POST_TOPIC = "/thing/event/property/post";
- private static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/";
- private static final String EVENT_POST_TOPIC_SUFFIX = "/post";
- private static final String REPLY_SUFFIX = "_reply";
private static final String PROPERTY_METHOD = "thing.event.property.post";
private static final String EVENT_METHOD_PREFIX = "thing.event.";
private static final String EVENT_METHOD_SUFFIX = ".post";
@@ -83,20 +71,21 @@ public class IotDeviceMqttMessageHandler {
*/
private void handleMessage(String topic, String payload) {
// 校验前缀
- if (!topic.startsWith(SYS_TOPIC_PREFIX)) {
+ if (!topic.startsWith(IotDeviceTopicEnum.SYS_TOPIC_PREFIX.getTopic())) {
log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
return;
}
// 处理设备属性上报消息
- if (topic.endsWith(PROPERTY_POST_TOPIC)) {
+ if (topic.endsWith(IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic())) {
log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic);
handlePropertyPost(topic, payload);
return;
}
// 处理设备事件上报消息
- if (topic.contains(EVENT_POST_TOPIC_PREFIX) && topic.endsWith(EVENT_POST_TOPIC_SUFFIX)) {
+ if (topic.contains(IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic()) &&
+ topic.endsWith(IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic())) {
log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic);
handleEventPost(topic, payload);
return;
@@ -212,7 +201,7 @@ public class IotDeviceMqttMessageHandler {
* @param customData 自定义数据,可为 null
*/
private void sendResponse(String topic, JSONObject jsonObject, String method, Object customData) {
- String replyTopic = topic + REPLY_SUFFIX;
+ String replyTopic = IotDeviceTopicEnum.getReplyTopic(topic);
// 响应结果
IotStandardResponse response = IotStandardResponse.success(
@@ -236,7 +225,7 @@ public class IotDeviceMqttMessageHandler {
private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, String[] topicParts) {
IotDevicePropertyReportReqDTO reportReqDTO = new IotDevicePropertyReportReqDTO();
reportReqDTO.setRequestId(jsonObject.getStr("id"));
- reportReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
+ reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
reportReqDTO.setReportTime(LocalDateTime.now());
reportReqDTO.setProductKey(topicParts[2]);
reportReqDTO.setDeviceName(topicParts[3]);
@@ -276,7 +265,7 @@ public class IotDeviceMqttMessageHandler {
private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) {
IotDeviceEventReportReqDTO reportReqDTO = new IotDeviceEventReportReqDTO();
reportReqDTO.setRequestId(jsonObject.getStr("id"));
- reportReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
+ reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
reportReqDTO.setReportTime(LocalDateTime.now());
reportReqDTO.setProductKey(topicParts[2]);
reportReqDTO.setDeviceName(topicParts[3]);
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceWebhookVertxHandler.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceWebhookVertxHandler.java
similarity index 91%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceWebhookVertxHandler.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceWebhookVertxHandler.java
index 7efd0b9343..7c5ebefe4e 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/component/emqx/upstream/router/IotDeviceWebhookVertxHandler.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/java/cn/iocoder/yudao/module/iot/net/component/emqx/upstream/router/IotDeviceWebhookVertxHandler.java
@@ -1,11 +1,11 @@
-package cn.iocoder.yudao.module.iot.component.emqx.upstream.router;
+package cn.iocoder.yudao.module.iot.net.component.emqx.upstream.router;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
-import cn.iocoder.yudao.module.iot.component.core.util.IotPluginCommonUtils;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
import io.vertx.core.Handler;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
@@ -57,11 +57,11 @@ public class IotDeviceWebhookVertxHandler implements Handler {
// 返回成功响应
// 注意:这里必须返回 {"result": "success"} 格式,以符合 EMQX Webhook 插件的要求
- IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "success"));
+ IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "success"));
} catch (Exception e) {
log.error("[handle][处理 Webhook 事件异常]", e);
// 注意:这里必须返回 {"result": "error"} 格式,以符合 EMQX Webhook 插件的要求
- IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "error"));
+ IotNetComponentCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "error"));
}
}
@@ -87,7 +87,7 @@ public class IotDeviceWebhookVertxHandler implements Handler {
updateReqDTO.setProductKey(parts[1]);
updateReqDTO.setDeviceName(parts[0]);
updateReqDTO.setState(IotDeviceStateEnum.ONLINE.getState());
- updateReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
+ updateReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
updateReqDTO.setReportTime(LocalDateTime.now());
CommonResult result = deviceUpstreamApi.updateDeviceState(updateReqDTO);
if (result.getCode() != 0 || !result.getData()) {
@@ -120,7 +120,7 @@ public class IotDeviceWebhookVertxHandler implements Handler {
offlineReqDTO.setProductKey(parts[1]);
offlineReqDTO.setDeviceName(parts[0]);
offlineReqDTO.setState(IotDeviceStateEnum.OFFLINE.getState());
- offlineReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
+ offlineReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
offlineReqDTO.setReportTime(LocalDateTime.now());
CommonResult offlineResult = deviceUpstreamApi.updateDeviceState(offlineReqDTO);
if (offlineResult.getCode() != 0 || !offlineResult.getData()) {
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000..c5597d25af
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+cn.iocoder.yudao.module.iot.net.component.emqx.config.IotNetComponentEmqxAutoConfiguration
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/application.yml b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/application.yml
similarity index 100%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/application.yml
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/application.yml
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/pom.xml b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/pom.xml
similarity index 85%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/pom.xml
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/pom.xml
index cd40c99bcc..cb71977f43 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/pom.xml
@@ -3,23 +3,23 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- yudao-module-iot-components
+ yudao-module-iot-net-components
cn.iocoder.boot
${revision}
4.0.0
- yudao-module-iot-component-http
+ yudao-module-iot-net-component-http
jar
${project.artifactId}
- 物联网组件 HTTP 模块
+ 物联网网络组件 HTTP 模块
cn.iocoder.boot
- yudao-module-iot-component-core
+ yudao-module-iot-net-component-core
${revision}
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/config/IotNetComponentHttpAutoConfiguration.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/config/IotNetComponentHttpAutoConfiguration.java
new file mode 100644
index 0000000000..2b3150c8be
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/config/IotNetComponentHttpAutoConfiguration.java
@@ -0,0 +1,118 @@
+package cn.iocoder.yudao.module.iot.net.component.http.config;
+
+import cn.hutool.system.SystemUtil;
+import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
+import cn.iocoder.yudao.module.iot.net.component.http.downstream.IotDeviceDownstreamHandlerImpl;
+import cn.iocoder.yudao.module.iot.net.component.http.upstream.IotDeviceUpstreamServer;
+import cn.iocoder.yudao.module.iot.net.component.core.config.IotNetComponentCommonProperties;
+import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
+import cn.iocoder.yudao.module.iot.net.component.core.heartbeat.IotNetComponentRegistry;
+import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
+import io.vertx.core.Vertx;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.context.event.EventListener;
+
+/**
+ * IoT 网络组件 HTTP 的自动配置类
+ *
+ * @author haohao
+ */
+@Slf4j
+@AutoConfiguration
+@EnableConfigurationProperties(IotNetComponentHttpProperties.class)
+@ConditionalOnProperty(prefix = "yudao.iot.component.http", name = "enabled", havingValue = "true", matchIfMissing = false)
+@ComponentScan(basePackages = {
+ "cn.iocoder.yudao.module.iot.net.component.http" // 只扫描 HTTP 组件包
+})
+public class IotNetComponentHttpAutoConfiguration {
+
+ /**
+ * 组件 key
+ */
+ private static final String PLUGIN_KEY = "http";
+
+ public IotNetComponentHttpAutoConfiguration() {
+ // 构造函数中不输出日志,移到 initialize 方法中
+ }
+
+ /**
+ * 初始化 HTTP 组件
+ *
+ * @param event 应用启动事件
+ */
+ @EventListener(ApplicationStartedEvent.class)
+ public void initialize(ApplicationStartedEvent event) {
+ log.info("[IotNetComponentHttpAutoConfiguration][开始初始化]");
+
+ // 从应用上下文中获取需要的 Bean
+ IotNetComponentRegistry componentRegistry = event.getApplicationContext()
+ .getBean(IotNetComponentRegistry.class);
+ IotNetComponentCommonProperties commonProperties = event.getApplicationContext()
+ .getBean(IotNetComponentCommonProperties.class);
+
+ // 设置当前组件的核心标识
+ // 注意:这里只为当前 HTTP 组件设置 pluginKey,不影响其他组件
+ commonProperties.setPluginKey(PLUGIN_KEY);
+
+ // 将 HTTP 组件注册到组件注册表
+ componentRegistry.registerComponent(
+ PLUGIN_KEY,
+ SystemUtil.getHostInfo().getAddress(),
+ 0, // 内嵌模式固定为 0
+ IotNetComponentCommonUtils.getProcessId());
+
+ log.info("[initialize][IoT HTTP 组件初始化完成]");
+ }
+
+ /**
+ * 创建 Vert.x 实例
+ *
+ * @return Vert.x 实例
+ */
+ @Bean(name = "httpVertx")
+ public Vertx vertx() {
+ return Vertx.vertx();
+ }
+
+ /**
+ * 创建设备上行服务器
+ *
+ * @param vertx Vert.x 实例
+ * @param deviceUpstreamApi 设备上行 API
+ * @param properties HTTP 组件配置属性
+ * @param applicationContext 应用上下文
+ * @return 设备上行服务器
+ */
+ @Bean(name = "httpDeviceUpstreamServer", initMethod = "start", destroyMethod = "stop")
+ public IotDeviceUpstreamServer deviceUpstreamServer(
+ @Lazy @Qualifier("httpVertx") Vertx vertx,
+ IotDeviceUpstreamApi deviceUpstreamApi,
+ IotNetComponentHttpProperties properties,
+ ApplicationContext applicationContext) {
+ if (log.isDebugEnabled()) {
+ log.debug("HTTP 服务器配置: port={}", properties.getServerPort());
+ } else {
+ log.info("HTTP 服务器将监听端口: {}", properties.getServerPort());
+ }
+ return new IotDeviceUpstreamServer(vertx, properties, deviceUpstreamApi, applicationContext);
+ }
+
+ /**
+ * 创建设备下行处理器
+ *
+ * @return 设备下行处理器
+ */
+ @Bean(name = "httpDeviceDownstreamHandler")
+ public IotDeviceDownstreamHandler deviceDownstreamHandler() {
+ return new IotDeviceDownstreamHandlerImpl();
+ }
+}
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/config/IotComponentHttpProperties.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/config/IotNetComponentHttpProperties.java
similarity index 51%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/config/IotComponentHttpProperties.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/config/IotNetComponentHttpProperties.java
index 160705be4a..02bbca2d2e 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/config/IotComponentHttpProperties.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/config/IotNetComponentHttpProperties.java
@@ -1,19 +1,21 @@
-package cn.iocoder.yudao.module.iot.component.http.config;
+package cn.iocoder.yudao.module.iot.net.component.http.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
/**
- * IoT HTTP 组件配置属性
+ * IoT HTTP 网络组件配置属性
+ *
+ * @author haohao
*/
@ConfigurationProperties(prefix = "yudao.iot.component.http")
@Validated
@Data
-public class IotComponentHttpProperties {
+public class IotNetComponentHttpProperties {
/**
- * 是否启用
+ * 是否启用 HTTP 组件
*/
private Boolean enabled;
@@ -22,4 +24,10 @@ public class IotComponentHttpProperties {
*/
private Integer serverPort;
+ /**
+ * 连接超时时间(毫秒)
+ *
+ * 默认值:10000 毫秒
+ */
+ private Integer connectionTimeoutMs = 10000;
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/downstream/IotDeviceDownstreamHandlerImpl.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/downstream/IotDeviceDownstreamHandlerImpl.java
similarity index 54%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/downstream/IotDeviceDownstreamHandlerImpl.java
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/downstream/IotDeviceDownstreamHandlerImpl.java
index 4519bda1bf..ed26bc02fa 100644
--- a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/java/cn/iocoder/yudao/module/iot/component/http/downstream/IotDeviceDownstreamHandlerImpl.java
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/downstream/IotDeviceDownstreamHandlerImpl.java
@@ -1,44 +1,50 @@
-package cn.iocoder.yudao.module.iot.component.http.downstream;
+package cn.iocoder.yudao.module.iot.net.component.http.downstream;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
-import cn.iocoder.yudao.module.iot.component.core.downstream.IotDeviceDownstreamHandler;
+import cn.iocoder.yudao.module.iot.net.component.core.downstream.IotDeviceDownstreamHandler;
+import lombok.extern.slf4j.Slf4j;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
/**
- * HTTP 插件的 {@link IotDeviceDownstreamHandler} 实现类
+ * HTTP 网络组件的 {@link IotDeviceDownstreamHandler} 实现类
*
* 但是:由于设备通过 HTTP 短链接接入,导致其实无法下行指导给 device 设备,所以基本都是直接返回失败!!!
- * 类似 MQTT、WebSocket、TCP 插件,是可以实现下行指令的。
+ * 类似 MQTT、WebSocket、TCP 网络组件,是可以实现下行指令的。
*
* @author 芋道源码
*/
+@Slf4j
public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
+ /**
+ * 不支持的错误消息
+ */
+ private static final String NOT_SUPPORTED_MSG = "HTTP 不支持设备下行通信";
+
@Override
public CommonResult invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
- return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持调用设备服务");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
}
@Override
public CommonResult getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
- return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持获取设备属性");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
}
@Override
public CommonResult setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
- return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
}
@Override
public CommonResult setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
- return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
}
@Override
public CommonResult upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
- return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
+ return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
}
-
}
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/IotDeviceUpstreamServer.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/IotDeviceUpstreamServer.java
new file mode 100644
index 0000000000..05af7bf2d8
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/IotDeviceUpstreamServer.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.iot.net.component.http.upstream;
+
+import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
+import cn.iocoder.yudao.module.iot.net.component.http.config.IotNetComponentHttpProperties;
+import cn.iocoder.yudao.module.iot.net.component.http.upstream.router.IotDeviceUpstreamVertxHandler;
+import io.vertx.core.AbstractVerticle;
+import io.vertx.core.Promise;
+import io.vertx.core.Vertx;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.handler.BodyHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Lazy;
+
+/**
+ * IoT 设备上行服务器
+ *
+ * 处理设备通过 HTTP 方式接入的上行消息
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class IotDeviceUpstreamServer extends AbstractVerticle {
+
+ /**
+ * Vert.x 实例
+ */
+ private final Vertx vertx;
+
+ /**
+ * HTTP 组件配置属性
+ */
+ private final IotNetComponentHttpProperties httpProperties;
+
+ /**
+ * 设备上行 API
+ */
+ private final IotDeviceUpstreamApi deviceUpstreamApi;
+
+ /**
+ * Spring 应用上下文
+ */
+ private final ApplicationContext applicationContext;
+
+ /**
+ * 构造函数
+ *
+ * @param vertx Vert.x 实例
+ * @param httpProperties HTTP 组件配置属性
+ * @param deviceUpstreamApi 设备上行 API
+ * @param applicationContext Spring 应用上下文
+ */
+ public IotDeviceUpstreamServer(
+ @Lazy Vertx vertx,
+ IotNetComponentHttpProperties httpProperties,
+ IotDeviceUpstreamApi deviceUpstreamApi,
+ ApplicationContext applicationContext) {
+ this.vertx = vertx;
+ this.httpProperties = httpProperties;
+ this.deviceUpstreamApi = deviceUpstreamApi;
+ this.applicationContext = applicationContext;
+ }
+
+ @Override
+ public void start(Promise startPromise) {
+ // 创建路由
+ Router router = Router.router(vertx);
+ router.route().handler(BodyHandler.create());
+
+ // 创建处理器
+ IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(
+ deviceUpstreamApi, applicationContext);
+
+ // 添加路由处理器
+ router.post(IotDeviceUpstreamVertxHandler.PROPERTY_PATH).handler(upstreamHandler::handle);
+ router.post(IotDeviceUpstreamVertxHandler.EVENT_PATH).handler(upstreamHandler::handle);
+
+ // 启动 HTTP 服务器
+ vertx.createHttpServer()
+ .requestHandler(router)
+ .listen(httpProperties.getServerPort(), result -> {
+ if (result.succeeded()) {
+ log.info("[start][IoT 设备上行服务器启动成功,端口:{}]", httpProperties.getServerPort());
+ startPromise.complete();
+ } else {
+ log.error("[start][IoT 设备上行服务器启动失败]", result.cause());
+ startPromise.fail(result.cause());
+ }
+ });
+ }
+
+ @Override
+ public void stop(Promise stopPromise) {
+ log.info("[stop][IoT 设备上行服务器已停止]");
+ stopPromise.complete();
+ }
+}
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/auth/IotDeviceAuthProvider.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/auth/IotDeviceAuthProvider.java
new file mode 100644
index 0000000000..13977da7d1
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/auth/IotDeviceAuthProvider.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.iot.net.component.http.upstream.auth;
+
+import io.vertx.core.Future;
+import io.vertx.ext.web.RoutingContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * IoT 设备认证提供者
+ *
+ * 用于 HTTP 设备接入时的身份认证
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class IotDeviceAuthProvider {
+
+ private final ApplicationContext applicationContext;
+
+ /**
+ * 构造函数
+ *
+ * @param applicationContext Spring 应用上下文
+ */
+ public IotDeviceAuthProvider(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * 认证设备
+ *
+ * @param context 路由上下文
+ * @param clientId 设备唯一标识
+ * @return 认证结果 Future 对象
+ */
+ public Future authenticate(RoutingContext context, String clientId) {
+ if (clientId == null || clientId.isEmpty()) {
+ return Future.failedFuture("clientId 不能为空");
+ }
+
+ try {
+ log.info("[authenticate][设备认证成功,clientId={}]", clientId);
+ return Future.succeededFuture();
+ } catch (Exception e) {
+ log.error("[authenticate][设备认证异常,clientId={}]", clientId, e);
+ return Future.failedFuture(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/router/IotDeviceUpstreamVertxHandler.java b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/router/IotDeviceUpstreamVertxHandler.java
new file mode 100644
index 0000000000..86c2e9dc13
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/java/cn/iocoder/yudao/module/iot/net/component/http/upstream/router/IotDeviceUpstreamVertxHandler.java
@@ -0,0 +1,378 @@
+package cn.iocoder.yudao.module.iot.net.component.http.upstream.router;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
+import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
+import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
+import cn.iocoder.yudao.module.iot.net.component.core.constants.IotDeviceTopicEnum;
+import cn.iocoder.yudao.module.iot.net.component.core.pojo.IotStandardResponse;
+import cn.iocoder.yudao.module.iot.net.component.core.util.IotNetComponentCommonUtils;
+import io.vertx.core.Handler;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.RoutingContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+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 设备上行统一处理的 Vert.x Handler
+ *
+ * 统一处理设备属性上报和事件上报的请求。
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class IotDeviceUpstreamVertxHandler implements Handler {
+
+ /**
+ * 属性上报路径
+ */
+ public static final String PROPERTY_PATH = "/sys/:productKey/:deviceName"
+ + IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic();
+
+ /**
+ * 事件上报路径
+ */
+ public static final String EVENT_PATH = "/sys/:productKey/:deviceName"
+ + IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic() + ":identifier"
+ + IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic();
+
+ /**
+ * 属性上报方法标识
+ */
+ private static final String PROPERTY_METHOD = "thing.event.property.post";
+
+ /**
+ * 事件上报方法前缀
+ */
+ private static final String EVENT_METHOD_PREFIX = "thing.event.";
+
+ /**
+ * 事件上报方法后缀
+ */
+ private static final String EVENT_METHOD_SUFFIX = ".post";
+
+ /**
+ * 设备上行 API
+ */
+ private final IotDeviceUpstreamApi deviceUpstreamApi;
+
+ /**
+ * 构造函数
+ *
+ * @param deviceUpstreamApi 设备上行 API
+ * @param applicationContext 应用上下文
+ */
+ public IotDeviceUpstreamVertxHandler(IotDeviceUpstreamApi deviceUpstreamApi,
+ ApplicationContext applicationContext) {
+ this.deviceUpstreamApi = deviceUpstreamApi;
+ }
+
+ @Override
+ public void handle(RoutingContext routingContext) {
+ String path = routingContext.request().path();
+ String requestId = IdUtil.fastSimpleUUID();
+
+ try {
+ // 1. 解析通用参数
+ Map params = parseCommonParams(routingContext, requestId);
+ String productKey = params.get("productKey");
+ String deviceName = params.get("deviceName");
+ JsonObject body = routingContext.body().asJsonObject();
+ requestId = params.get("requestId");
+
+ // 2. 根据路径模式处理不同类型的请求
+ if (isPropertyPostPath(path)) {
+ // 处理属性上报
+ handlePropertyPost(routingContext, productKey, deviceName, requestId, body);
+ return;
+ }
+
+ if (isEventPostPath(path)) {
+ // 处理事件上报
+ String identifier = routingContext.pathParam("identifier");
+ handleEventPost(routingContext, productKey, deviceName, identifier, requestId, body);
+ return;
+ }
+
+ // 不支持的请求路径
+ sendErrorResponse(routingContext, requestId, "unknown", BAD_REQUEST.getCode(), "不支持的请求路径");
+ } catch (Exception e) {
+ log.error("[handle][处理上行请求异常] path={}", path, e);
+ String method = determineMethodFromPath(path, routingContext);
+ sendErrorResponse(routingContext, requestId, method, INTERNAL_SERVER_ERROR.getCode(),
+ INTERNAL_SERVER_ERROR.getMsg());
+ }
+ }
+
+ /**
+ * 解析通用参数
+ *
+ * @param routingContext 路由上下文
+ * @param defaultRequestId 默认请求 ID
+ * @return 参数映射
+ */
+ private Map parseCommonParams(RoutingContext routingContext, String defaultRequestId) {
+ Map params = MapUtil.newHashMap();
+ params.put("productKey", routingContext.pathParam("productKey"));
+ params.put("deviceName", routingContext.pathParam("deviceName"));
+
+ JsonObject body = routingContext.body().asJsonObject();
+ String requestId = ObjUtil.defaultIfNull(body.getString("id"), defaultRequestId);
+ params.put("requestId", requestId);
+
+ return params;
+ }
+
+ /**
+ * 判断是否是属性上报路径
+ *
+ * @param path 路径
+ * @return 是否是属性上报路径
+ */
+ private boolean isPropertyPostPath(String path) {
+ return StrUtil.endWith(path, IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic());
+ }
+
+ /**
+ * 判断是否是事件上报路径
+ *
+ * @param path 路径
+ * @return 是否是事件上报路径
+ */
+ private boolean isEventPostPath(String path) {
+ return StrUtil.contains(path, IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic())
+ && StrUtil.endWith(path, IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic());
+ }
+
+ /**
+ * 处理属性上报请求
+ *
+ * @param routingContext 路由上下文
+ * @param productKey 产品 Key
+ * @param deviceName 设备名称
+ * @param requestId 请求 ID
+ * @param body 请求体
+ */
+ private void handlePropertyPost(RoutingContext routingContext, String productKey, String deviceName,
+ String requestId, JsonObject body) {
+ // 处理属性上报
+ IotDevicePropertyReportReqDTO reportReqDTO = parsePropertyReportRequest(productKey, deviceName,
+ requestId, body);
+
+ // 设备上线
+ updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
+
+ // 属性上报
+ CommonResult result = deviceUpstreamApi.reportDeviceProperty(reportReqDTO);
+
+ // 返回响应
+ sendResponse(routingContext, requestId, PROPERTY_METHOD, result);
+ }
+
+ /**
+ * 处理事件上报请求
+ *
+ * @param routingContext 路由上下文
+ * @param productKey 产品 Key
+ * @param deviceName 设备名称
+ * @param identifier 事件标识符
+ * @param requestId 请求 ID
+ * @param body 请求体
+ */
+ private void handleEventPost(RoutingContext routingContext, String productKey, String deviceName,
+ String identifier, String requestId, JsonObject body) {
+ // 处理事件上报
+ IotDeviceEventReportReqDTO reportReqDTO = parseEventReportRequest(productKey, deviceName, identifier,
+ requestId, body);
+
+ // 设备上线
+ updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
+
+ // 事件上报
+ CommonResult result = deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
+ String method = EVENT_METHOD_PREFIX + identifier + EVENT_METHOD_SUFFIX;
+
+ // 返回响应
+ sendResponse(routingContext, requestId, method, result);
+ }
+
+ /**
+ * 发送响应
+ *
+ * @param routingContext 路由上下文
+ * @param requestId 请求 ID
+ * @param method 方法名
+ * @param result 结果
+ */
+ private void sendResponse(RoutingContext routingContext, String requestId, String method,
+ CommonResult result) {
+ IotStandardResponse response;
+ if (result.isSuccess()) {
+ response = IotStandardResponse.success(requestId, method, result.getData());
+ } else {
+ response = IotStandardResponse.error(requestId, method, result.getCode(), result.getMsg());
+ }
+ IotNetComponentCommonUtils.writeJsonResponse(routingContext, response);
+ }
+
+ /**
+ * 发送错误响应
+ *
+ * @param routingContext 路由上下文
+ * @param requestId 请求 ID
+ * @param method 方法名
+ * @param code 错误代码
+ * @param message 错误消息
+ */
+ private void sendErrorResponse(RoutingContext routingContext, String requestId, String method, Integer code,
+ String message) {
+ IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
+ IotNetComponentCommonUtils.writeJsonResponse(routingContext, errorResponse);
+ }
+
+ /**
+ * 从路径确定方法名
+ *
+ * @param path 路径
+ * @param routingContext 路由上下文
+ * @return 方法名
+ */
+ private String determineMethodFromPath(String path, RoutingContext routingContext) {
+ if (StrUtil.contains(path, "/property/")) {
+ return PROPERTY_METHOD;
+ }
+
+ return EVENT_METHOD_PREFIX +
+ (routingContext.pathParams().containsKey("identifier")
+ ? routingContext.pathParam("identifier")
+ : "unknown")
+ +
+ EVENT_METHOD_SUFFIX;
+ }
+
+ /**
+ * 更新设备状态
+ *
+ * @param productKey 产品 Key
+ * @param deviceName 设备名称
+ */
+ private void updateDeviceState(String productKey, String deviceName) {
+ IotDeviceStateUpdateReqDTO reqDTO = ((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO()
+ .setRequestId(IdUtil.fastSimpleUUID())
+ .setProcessId(IotNetComponentCommonUtils.getProcessId())
+ .setReportTime(LocalDateTime.now())
+ .setProductKey(productKey)
+ .setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState());
+
+ deviceUpstreamApi.updateDeviceState(reqDTO);
+ }
+
+ /**
+ * 解析属性上报请求
+ *
+ * @param productKey 产品 Key
+ * @param deviceName 设备名称
+ * @param requestId 请求 ID
+ * @param body 请求体
+ * @return 属性上报请求 DTO
+ */
+ private IotDevicePropertyReportReqDTO parsePropertyReportRequest(String productKey, String deviceName,
+ String requestId, JsonObject body) {
+ // 解析属性
+ Map properties = parsePropertiesFromBody(body);
+
+ // 构建属性上报请求 DTO
+ return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
+ .setRequestId(requestId)
+ .setProcessId(IotNetComponentCommonUtils.getProcessId())
+ .setReportTime(LocalDateTime.now())
+ .setProductKey(productKey)
+ .setDeviceName(deviceName)).setProperties(properties);
+ }
+
+ /**
+ * 从请求体解析属性
+ *
+ * @param body 请求体
+ * @return 属性映射
+ */
+ private Map parsePropertiesFromBody(JsonObject body) {
+ Map properties = MapUtil.newHashMap();
+ JsonObject params = body.getJsonObject("params");
+
+ if (params == null) {
+ return properties;
+ }
+
+ // 将标准格式的 params 转换为平台需要的 properties 格式
+ for (String key : params.fieldNames()) {
+ Object valueObj = params.getValue(key);
+ // 如果是复杂结构(包含 value 和 time)
+ if (valueObj instanceof JsonObject) {
+ JsonObject valueJson = (JsonObject) valueObj;
+ properties.put(key, valueJson.containsKey("value") ? valueJson.getValue("value") : valueObj);
+ } else {
+ properties.put(key, valueObj);
+ }
+ }
+
+ return properties;
+ }
+
+ /**
+ * 解析事件上报请求
+ *
+ * @param productKey 产品 Key
+ * @param deviceName 设备名称
+ * @param identifier 事件标识符
+ * @param requestId 请求 ID
+ * @param body 请求体
+ * @return 事件上报请求 DTO
+ */
+ private IotDeviceEventReportReqDTO parseEventReportRequest(String productKey, String deviceName, String identifier,
+ String requestId, JsonObject body) {
+ // 解析参数
+ Map params = parseParamsFromBody(body);
+
+ // 构建事件上报请求 DTO
+ return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO()
+ .setRequestId(requestId)
+ .setProcessId(IotNetComponentCommonUtils.getProcessId())
+ .setReportTime(LocalDateTime.now())
+ .setProductKey(productKey)
+ .setDeviceName(deviceName)).setIdentifier(identifier).setParams(params);
+ }
+
+ /**
+ * 从请求体解析参数
+ *
+ * @param body 请求体
+ * @return 参数映射
+ */
+ private Map parseParamsFromBody(JsonObject body) {
+ Map params = MapUtil.newHashMap();
+ JsonObject paramsJson = body.getJsonObject("params");
+
+ if (paramsJson == null) {
+ return params;
+ }
+
+ for (String key : paramsJson.fieldNames()) {
+ params.put(key, paramsJson.getValue(key));
+ }
+
+ return params;
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000..9d3b4057c0
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+cn.iocoder.yudao.module.iot.net.component.http.config.IotNetComponentHttpAutoConfiguration
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/application.yml b/yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/application.yml
similarity index 100%
rename from yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/application.yml
rename to yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/application.yml