Browse Source

【功能修改】IoT: 修改网络组件模块,包含 HTTP 和 EMQX 组件,重构相关配置和处理逻辑,更新文档说明。

安浩浩 7 tháng trước cách đây
mục cha
commit
ae96ff4a25
53 tập tin đã thay đổi với 1632 bổ sung1148 xóa
  1. 1 1
      yudao-module-iot/pom.xml
  2. 6 6
      yudao-module-iot/yudao-module-iot-biz/pom.xml
  3. 1 1
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
  4. 0 46
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/config/IotPluginConfiguration.java
  5. 0 52
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStartRunner.java
  6. 0 21
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStateListener.java
  7. 19 21
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginConfigServiceImpl.java
  8. 69 81
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java
  9. 0 52
      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
  10. 0 24
      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
  11. 0 2
      yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring.factories
  12. 0 1
      yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  13. 0 126
      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
  14. 0 1
      yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  15. 0 92
      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
  16. 0 91
      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
  17. 0 212
      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
  18. 0 1
      yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  19. 3 3
      yudao-module-iot/yudao-module-iot-net-components/README.md
  20. 5 5
      yudao-module-iot/yudao-module-iot-net-components/pom.xml
  21. 3 4
      yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/pom.xml
  22. 60 0
      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
  23. 56 0
      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
  24. 173 0
      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
  25. 1 1
      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
  26. 3 3
      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
  27. 33 47
      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
  28. 25 22
      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
  29. 153 0
      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
  30. 22 10
      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
  31. 1 1
      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
  32. 26 11
      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
  33. 2 0
      yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/src/main/resources/META-INF/spring.factories
  34. 1 0
      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
  35. 4 4
      yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/pom.xml
  36. 129 0
      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
  37. 28 8
      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
  38. 35 77
      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
  39. 73 68
      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
  40. 5 5
      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
  41. 13 24
      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
  42. 6 6
      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
  43. 1 0
      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
  44. 0 0
      yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/application.yml
  45. 4 4
      yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/pom.xml
  46. 118 0
      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
  47. 12 4
      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
  48. 16 10
      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
  49. 97 0
      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
  50. 49 0
      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
  51. 378 0
      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
  52. 1 0
      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
  53. 0 0
      yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/application.yml

+ 1 - 1
yudao-module-iot/pom.xml

@@ -10,7 +10,7 @@
     <modules>
         <module>yudao-module-iot-api</module>
         <module>yudao-module-iot-biz</module>
-        <module>yudao-module-iot-components</module>
+        <module>yudao-module-iot-net-components</module>
         <!--        <module>yudao-module-iot-plugins</module>-->
     </modules>
     <modelVersion>4.0.0</modelVersion>

+ 6 - 6
yudao-module-iot/yudao-module-iot-biz/pom.xml

@@ -93,10 +93,10 @@
             <optional>true</optional>
         </dependency>
 
-        <dependency>
-            <groupId>org.pf4j</groupId>            <!-- PF4J:内置插件机制 -->
-            <artifactId>pf4j-spring</artifactId>
-        </dependency>
+        <!--        <dependency>-->
+        <!--            <groupId>org.pf4j</groupId>            &lt;!&ndash; PF4J:内置插件机制 &ndash;&gt;-->
+        <!--            <artifactId>pf4j-spring</artifactId>-->
+        <!--        </dependency>-->
 
         <!-- TODO @芋艿:bom 管理 -->
         <dependency>
@@ -137,12 +137,12 @@
         <!-- IoT 网络组件:接收来自设备的上行数据 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-iot-component-http</artifactId>
+            <artifactId>yudao-module-iot-net-component-http</artifactId>
             <version>${revision}</version>
         </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-iot-component-emqx</artifactId>
+            <artifactId>yudao-module-iot-net-component-emqx</artifactId>
             <version>${revision}</version>
         </dependency>
     </dependencies>

+ 1 - 1
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java

@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  */
 @RestController
 @Validated
-@Primary // 保证优先匹配,因为 yudao-module-iot-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入
+@Primary // 保证优先匹配,因为 yudao-module-iot-net-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入
 public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
 
     @Resource

+ 0 - 46
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/config/IotPluginConfiguration.java

@@ -1,46 +0,0 @@
-package cn.iocoder.yudao.module.iot.framework.plugin.config;
-
-import cn.iocoder.yudao.module.iot.framework.plugin.core.IotPluginStartRunner;
-import cn.iocoder.yudao.module.iot.framework.plugin.core.IotPluginStateListener;
-import cn.iocoder.yudao.module.iot.service.plugin.IotPluginConfigService;
-import lombok.extern.slf4j.Slf4j;
-import org.pf4j.spring.SpringPluginManager;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import java.nio.file.Paths;
-
-/**
- * IoT 插件配置类
- *
- * @author haohao
- */
-@Configuration
-@Slf4j
-public class IotPluginConfiguration {
-
-    @Bean
-    public IotPluginStartRunner pluginStartRunner(SpringPluginManager pluginManager,
-                                                  IotPluginConfigService pluginConfigService) {
-        return new IotPluginStartRunner(pluginManager, pluginConfigService);
-    }
-
-    // TODO @芋艿:需要 review 下
-    @Bean
-    public SpringPluginManager pluginManager(@Value("${pf4j.pluginsDir:pluginsDir}") String pluginsDir) {
-        log.info("[init][实例化 SpringPluginManager]");
-        SpringPluginManager springPluginManager = new SpringPluginManager(Paths.get(pluginsDir)) {
-
-            @Override
-            public void startPlugins() {
-                // 禁用插件启动,避免插件启动时,启动所有插件
-                log.info("[init][禁用默认启动所有插件]");
-            }
-
-        };
-        springPluginManager.addPluginStateListener(new IotPluginStateListener());
-        return springPluginManager;
-    }
-
-}

+ 0 - 52
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStartRunner.java

@@ -1,52 +0,0 @@
-package cn.iocoder.yudao.module.iot.framework.plugin.core;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
-import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginConfigDO;
-import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum;
-import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
-import cn.iocoder.yudao.module.iot.service.plugin.IotPluginConfigService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.pf4j.spring.SpringPluginManager;
-import org.springframework.boot.ApplicationArguments;
-import org.springframework.boot.ApplicationRunner;
-
-import java.util.List;
-
-/**
- * IoT 插件启动 Runner
- *
- * 用于 Spring Boot 启动时,启动 {@link IotPluginDeployTypeEnum#JAR} 部署类型的插件
- */
-@RequiredArgsConstructor
-@Slf4j
-public class IotPluginStartRunner implements ApplicationRunner {
-
-    private final SpringPluginManager springPluginManager;
-
-    private final IotPluginConfigService pluginConfigService;
-
-    @Override
-    public void run(ApplicationArguments args) {
-        List<IotPluginConfigDO> 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);
-            }
-        });
-    }
-
-}

+ 0 - 21
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/core/IotPluginStateListener.java

@@ -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());
-    }
-
-}

+ 19 - 21
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

+ 69 - 81
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);
+//        }
     }
 
     // ========== 设备与插件的映射操作 ==========

+ 0 - 52
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

@@ -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();
-    }
-}

+ 0 - 24
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

@@ -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 {
-
-    /**
-     * 组件的唯一标识
-     * <p>
-     * 注意:该值将在运行时由各组件设置,不再从配置读取
-     */
-    private String pluginKey;
-
-}

+ 0 - 2
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring.factories

@@ -1,2 +0,0 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-  cn.iocoder.yudao.module.iot.component.core.config.IotPluginCommonAutoConfiguration

+ 0 - 1
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1 +0,0 @@
-cn.iocoder.yudao.module.iot.component.core.config.IotComponentCommonAutoConfiguration

+ 0 - 126
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

@@ -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;
-    }
-
-}

+ 0 - 1
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1 +0,0 @@
-cn.iocoder.yudao.module.iot.component.emqx.config.IotComponentEmqxAutoConfiguration

+ 0 - 92
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

@@ -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;
-    }
-}

+ 0 - 91
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

@@ -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 服务器
- * <p>
- * 协议: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);
-        }
-    }
-}

+ 0 - 212
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

@@ -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
- * <p>
- * 统一处理设备属性上报和事件上报的请求
- *
- * @author haohao
- */
-@Slf4j
-public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
-
-    /**
-     * 属性上报路径
-     */
-    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<Boolean> 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<String, Object> properties = scriptService.parsePropertyData(productKey, deviceName, body);
-
-
-        // 如果脚本解析结果为空,使用默认解析逻辑
-        // TODO @芋艿:注释说明一下,为什么要这么处理?
-//        if (CollUtil.isNotEmpty(properties)) {
-        Map<String, Object> properties = new HashMap<>();
-        Map<String, Object> params = body.getJsonObject("params") != null ?
-                body.getJsonObject("params").getMap() : null;
-        if (params != null) {
-            // 将标准格式的 params 转换为平台需要的 properties 格式
-            for (Map.Entry<String, Object> entry : params.entrySet()) {
-                String key = entry.getKey();
-                Object valueObj = entry.getValue();
-                // 如果是复杂结构(包含 value 和 time)
-                if (valueObj instanceof Map) {
-                    @SuppressWarnings("unchecked")
-                    Map<String, Object> valueMap = (Map<String, Object>) 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<String, Object> params = scriptService.parseEventData(productKey, deviceName, identifier, body);
-        Map<String, Object> 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);
-    }
-}

+ 0 - 1
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1 +0,0 @@
-cn.iocoder.yudao.module.iot.component.http.config.IotComponentHttpAutoConfiguration

+ 3 - 3
yudao-module-iot/yudao-module-iot-components/README.md → 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 的设备通信组件
 
 ## 组件架构
 

+ 5 - 5
yudao-module-iot/yudao-module-iot-components/pom.xml → yudao-module-iot/yudao-module-iot-net-components/pom.xml

@@ -9,18 +9,18 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>yudao-module-iot-components</artifactId>
+    <artifactId>yudao-module-iot-net-components</artifactId>
     <packaging>pom</packaging>
 
     <name>${project.artifactId}</name>
     <description>
-        物联网组件模块,提供与物联网设备通讯、管理的组件实现
+        物联网网络组件模块,提供与物联网设备通讯、管理的网络组件实现
     </description>
 
     <modules>
-        <module>yudao-module-iot-component-core</module>
-        <module>yudao-module-iot-component-http</module>
-        <module>yudao-module-iot-component-emqx</module>
+        <module>yudao-module-iot-net-component-core</module>
+        <module>yudao-module-iot-net-component-http</module>
+        <module>yudao-module-iot-net-component-emqx</module>
     </modules>
 
 </project> 

+ 3 - 4
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-core/pom.xml → 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">
     <parent>
-        <artifactId>yudao-module-iot-components</artifactId>
+        <artifactId>yudao-module-iot-net-components</artifactId>
         <groupId>cn.iocoder.boot</groupId>
         <version>${revision}</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>yudao-module-iot-component-core</artifactId>
+    <artifactId>yudao-module-iot-net-component-core</artifactId>
     <packaging>jar</packaging>
 
     <name>${project.artifactId}</name>
-    <!-- TODO @芋艿:description 后续统一优化一波 -->
     <description>
-        物联网组件核心模块
+        物联网网络组件核心模块
     </description>
 
     <dependencies>

+ 60 - 0
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 设备下行服务器
+     * <p>
+     * 当 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();
+    }
+}

+ 56 - 0
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 {
+
+    /**
+     * 组件的唯一标识
+     * <p>
+     * 注意:该值将在运行时由各组件设置,不再从配置读取
+     */
+    private String pluginKey;
+
+    /**
+     * 组件实例心跳超时时间,单位:毫秒
+     * <p>
+     * 默认值:30 秒
+     */
+    private Long instanceHeartbeatTimeout = 30000L;
+
+    /**
+     * 网络组件消息转发配置
+     */
+    private ForwardMessage forwardMessage = new ForwardMessage();
+
+    /**
+     * 消息转发配置
+     */
+    @Data
+    public static class ForwardMessage {
+
+        /**
+         * 是否转发所有设备消息到 EMQX
+         * <p>
+         * 默认为 true 开启
+         */
+        private boolean forwardAllDeviceMessageToEmqx = true;
+
+        /**
+         * 是否转发所有设备消息到 HTTP
+         * <p>
+         * 默认为 false 关闭
+         */
+        private boolean forwardAllDeviceMessageToHttp = false;
+    }
+}

+ 173 - 0
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 设备主题枚举
+ * <p>
+ * 用于统一管理 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();
+    }
+}

+ 1 - 1
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 → 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.*;

+ 3 - 3
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 → 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;
 
     /**

+ 33 - 47
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 → 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 网络组件实例心跳定时任务
  * <p>
  * 将组件的状态,定时上报给 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<IotNetComponentInfo> components = componentRegistry.getAllComponents();
+        if (CollUtil.isEmpty(components)) {
+            return;
+        }
+        for (IotNetComponentInfo component : components) {
             try {
                 CommonResult<Boolean> 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<IotNetComponentInfo> components = componentRegistry.getAllComponents();
+        if (CollUtil.isEmpty(components)) {
+            return;
+        }
+        for (IotNetComponentInfo component : components) {
             try {
                 CommonResult<Boolean> 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<IotNetComponentInfo> components = componentRegistry.getAllComponents();
+        if (CollUtil.isEmpty(components)) {
+            return;
+        }
+        for (IotNetComponentInfo component : components) {
             try {
                 CommonResult<Boolean> 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];
-    }
-}
+}

+ 25 - 22
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 → 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 网络组件注册表
  * <p>
- * 用于管理多个组件的注册信息,解决多组件心跳问题
+ * 用于管理多个网络组件的注册信息,解决多组件心跳问题
+ *
+ * @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<String, IotComponentInfo> components = new ConcurrentHashMap<>();
+    private final Map<String, IotNetComponentInfo> 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<IotComponentInfo> getAllComponents() {
-        return components.values();
+    public Collection<IotNetComponentInfo> 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);
     }
-
 }

+ 153 - 0
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 消息模型
+ * <p>
+ * 基于阿里云 Alink 协议规范实现的标准消息格式
+ *
+ * @author haohao
+ */
+@Data
+@Builder
+public class IotAlinkMessage {
+
+    /**
+     * 消息 ID
+     */
+    private String id;
+
+    /**
+     * 协议版本
+     */
+    @Builder.Default
+    private String version = "1.0";
+
+    /**
+     * 消息方法
+     */
+    private String method;
+
+    /**
+     * 消息参数
+     */
+    private Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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();
+    }
+}

+ 22 - 10
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 → 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");
     }
-
 }

+ 1 - 1
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 → 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;

+ 26 - 11
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 → 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)
      * <p>
-     * 推荐使用此方法,统一MQTT和HTTP的响应格式。使用方式:
+     * 推荐使用此方法,统一 MQTT  HTTP 的响应格式。使用方式:
      *
      * <pre>
      * // 成功响应
      * 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);
      * </pre>
      *
      * @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));
     }
-
-}
+}

+ 2 - 0
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

+ 1 - 0
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

+ 4 - 4
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/pom.xml → 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">
     <parent>
-        <artifactId>yudao-module-iot-components</artifactId>
+        <artifactId>yudao-module-iot-net-components</artifactId>
         <groupId>cn.iocoder.boot</groupId>
         <version>${revision}</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-    <artifactId>yudao-module-iot-component-emqx</artifactId>
+    <artifactId>yudao-module-iot-net-component-emqx</artifactId>
     <packaging>jar</packaging>
 
     <name>${project.artifactId}</name>
     <description>
-        物联网组件 EMQX 模块
+        物联网网络组件 EMQX 模块
     </description>
 
     <dependencies>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-iot-component-core</artifactId>
+            <artifactId>yudao-module-iot-net-component-core</artifactId>
             <version>${revision}</version>
         </dependency>
 

+ 129 - 0
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);
+    }
+}

+ 28 - 8
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 → 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;
 
+    /**
+     * 重连延迟时间(毫秒)
+     * <p>
+     * 默认值:5000 毫秒
+     */
+    private Integer reconnectDelayMs = 5000;
+
+    /**
+     * 连接超时时间(毫秒)
+     * <p>
+     * 默认值:10000 毫秒
+     */
+    private Integer connectionTimeoutMs = 10000;
 }

+ 35 - 77
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 → 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<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
+        // 暂未实现,返回成功
         return CommonResult.success(true);
     }
 
     @Override
     public CommonResult<Boolean> 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<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
+        // 暂未实现,返回成功
         return CommonResult.success(true);
     }
 
     @Override
     public CommonResult<Boolean> 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<String, Object> 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<String, Object> 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();
-    }
-
 }

+ 73 - 68
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 → 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 服务器
  * <p>
  * 协议: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<Void> 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<Void> 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<Void> compositeFuture = Future.succeededFuture();
+        // 使用协调器追踪多个 Future 的完成状态
+        Future<Void> 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())
                     .<Void>map(ack -> {
-                        log.info("[subscribeToTopics][成功订阅主题: {}]", trimmedTopic);
+                        log.info("[subscribeToTopics][订阅主题成功: {}]", topic);
                         return null;
                     })
-                    .recover(error -> {
-                        log.error("[subscribeToTopics][订阅主题失败: {}]", trimmedTopic, error);
-                        return Future.<Void>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][开始停止服务]");
+
+        // 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 {
-            CompletableFuture<Void> serverFuture = server != null
-                    ? server.close().toCompletionStage().toCompletableFuture()
-                    : CompletableFuture.completedFuture(null);
-            CompletableFuture<Void> clientFuture = client != null
-                    ? client.disconnect().toCompletionStage().toCompletableFuture()
-                    : CompletableFuture.completedFuture(null);
-            CompletableFuture<Void> vertxFuture = vertx != null
-                    ? vertx.close().toCompletionStage().toCompletableFuture()
-                    : CompletableFuture.completedFuture(null);
+            if (client.isConnected()) {
+                client.disconnect();
+            }
+        } catch (Exception e) {
+            log.warn("[stop][关闭 MQTT 客户端异常]", e);
+        }
 
-            // 等待所有资源关闭
-            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][所有服务关闭完成]");
-                        }
-                    });
+        // 3. 关闭 HTTP 服务器
+        try {
+            server.close();
         } catch (Exception e) {
-            log.error("[stop][关闭服务异常]", e);
-            throw new RuntimeException("关闭 IoT 设备上行服务失败", e);
+            log.warn("[stop][关闭 HTTP 服务器异常]", e);
         }
+
+        // 4. 更新状态
+        isRunning = false;
+        log.info("[stop][服务已停止]");
     }
 }

+ 5 - 5
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 → 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<RoutingContext> {
             CommonResult<Boolean> 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"));
         }
     }
 

+ 13 - 24
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 → 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 消息处理器
  * <p>
- * 参考:<a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">设备属性、事件、服务</a>
+ * 参考:<a href=
+ * "https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">设备属性、事件、服务</a>
  */
 @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]);

+ 6 - 6
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 → 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<RoutingContext> {
 
             // 返回成功响应
             // 注意:这里必须返回 {"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<RoutingContext> {
         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<Boolean> result = deviceUpstreamApi.updateDeviceState(updateReqDTO);
         if (result.getCode() != 0 || !result.getData()) {
@@ -120,7 +120,7 @@ public class IotDeviceWebhookVertxHandler implements Handler<RoutingContext> {
         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<Boolean> offlineResult = deviceUpstreamApi.updateDeviceState(offlineReqDTO);
         if (offlineResult.getCode() != 0 || !offlineResult.getData()) {

+ 1 - 0
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

+ 0 - 0
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-emqx/src/main/resources/application.yml → yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-emqx/src/main/resources/application.yml


+ 4 - 4
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/pom.xml → 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">
     <parent>
-        <artifactId>yudao-module-iot-components</artifactId>
+        <artifactId>yudao-module-iot-net-components</artifactId>
         <groupId>cn.iocoder.boot</groupId>
         <version>${revision}</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-    <artifactId>yudao-module-iot-component-http</artifactId>
+    <artifactId>yudao-module-iot-net-component-http</artifactId>
     <packaging>jar</packaging>
 
     <name>${project.artifactId}</name>
     <description>
-        物联网组件 HTTP 模块
+        物联网网络组件 HTTP 模块
     </description>
 
     <dependencies>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-iot-component-core</artifactId>
+            <artifactId>yudao-module-iot-net-component-core</artifactId>
             <version>${revision}</version>
         </dependency>
 

+ 118 - 0
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();
+    }
+}

+ 12 - 4
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 → 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;
 
+    /**
+     * 连接超时时间(毫秒)
+     * <p>
+     * 默认值:10000 毫秒
+     */
+    private Integer connectionTimeoutMs = 10000;
 }

+ 16 - 10
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 → 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} 实现类
  * <p>
  * 但是:由于设备通过 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<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
-        return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持调用设备服务");
+        return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
     }
 
     @Override
     public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
-        return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持获取设备属性");
+        return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
     }
 
     @Override
     public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
-        return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
+        return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
     }
 
     @Override
     public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
-        return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
+        return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
     }
 
     @Override
     public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
-        return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
+        return CommonResult.error(NOT_IMPLEMENTED.getCode(), NOT_SUPPORTED_MSG);
     }
-
 }

+ 97 - 0
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 设备上行服务器
+ * <p>
+ * 处理设备通过 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<Void> 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<Void> stopPromise) {
+        log.info("[stop][IoT 设备上行服务器已停止]");
+        stopPromise.complete();
+    }
+}

+ 49 - 0
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 设备认证提供者
+ * <p>
+ * 用于 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<Void> 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);
+        }
+    }
+}

+ 378 - 0
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
+ * <p>
+ * 统一处理设备属性上报和事件上报的请求。
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
+
+    /**
+     * 属性上报路径
+     */
+    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<String, String> 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<String, String> parseCommonParams(RoutingContext routingContext, String defaultRequestId) {
+        Map<String, String> 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<Boolean> 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<Boolean> 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<Boolean> 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<String, Object> 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<String, Object> parsePropertiesFromBody(JsonObject body) {
+        Map<String, Object> 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<String, Object> 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<String, Object> parseParamsFromBody(JsonObject body) {
+        Map<String, Object> 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;
+    }
+}

+ 1 - 0
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

+ 0 - 0
yudao-module-iot/yudao-module-iot-components/yudao-module-iot-component-http/src/main/resources/application.yml → yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-http/src/main/resources/application.yml