Bladeren bron

【代码优化】重构 HTTP插件并添加自动配置

安浩浩 6 maanden geleden
bovenliggende
commit
7bfa830628
10 gewijzigde bestanden met toevoegingen van 204 en 109 verwijderingen
  1. BIN
      plugins/yudao-module-iot-plugin-http-1.0.0.jar
  2. 47 43
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java
  3. 54 14
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/api/DeviceDataApiClient.java
  4. 0 31
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/DeviceDataApiInitializer.java
  5. 51 0
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/YudaoDeviceDataApiAutoConfiguration.java
  6. 1 0
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  7. 2 1
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java
  8. 30 20
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPlugin.java
  9. 14 0
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPluginConfiguration.java
  10. 5 0
      yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml

BIN
plugins/yudao-module-iot-plugin-http-1.0.0.jar


+ 47 - 43
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java

@@ -40,9 +40,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
 @Slf4j
 public class PluginInstanceServiceImpl implements PluginInstanceService {
 
-    // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的
-    // 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件;
-    // 那就 mac@uuid ?
+    // TODO @haohao:mac@uuid 
     public static final String MAIN_ID = IdUtil.fastSimpleUUID();
 
     @Resource
@@ -60,32 +58,31 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
     @Override
     public void stopAndUnloadPlugin(String pluginKey) {
         PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
-        // TODO @haohao:改成 if return 会更简洁一点;
-        if (plugin != null) {
-            if (plugin.getPluginState().equals(PluginState.STARTED)) {
-                pluginManager.stopPlugin(pluginKey); // 停止插件
-                log.info("已停止插件: {}", pluginKey);
-            }
-            pluginManager.unloadPlugin(pluginKey); // 卸载插件
-            log.info("已卸载插件: {}", pluginKey);
-        } else {
+        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
     public void deletePluginFile(PluginInfoDO pluginInfoDO) {
         File file = new File(pluginsDir, pluginInfoDO.getFileName());
-        // TODO @haohao:改成 if return 会更简洁一点;
-        if (file.exists()) {
-            try {
-                TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
-                if (!file.delete()) {
-                    log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
-                }
-            } catch (InterruptedException e) {
-                log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
+        if (!file.exists()) {
+            return;
+        }
+        try {
+            TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
+            if (!file.delete()) {
+                log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
             }
+        } catch (InterruptedException e) {
+            log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
         }
     }
 
@@ -120,25 +117,25 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
         String pluginKey = pluginInfoDo.getPluginKey();
         PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
 
-        // TODO @haohao:改成 if return 会更简洁一点;
-        if (plugin != null) {
-            // 启动插件
-            if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
-                    && plugin.getPluginState() != PluginState.STARTED) {
-                pluginManager.startPlugin(pluginKey);
-                log.info("已启动插件: {}", pluginKey);
-            }
-            // 停止插件
-            else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
-                    && plugin.getPluginState() == PluginState.STARTED) {
-                pluginManager.stopPlugin(pluginKey);
-                log.info("已停止插件: {}", pluginKey);
-            }
-        } else {
+        if (plugin == null) {
             // 插件不存在且状态为停止,抛出异常
             if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
                 throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
             }
+            return;
+        }
+
+        // 启动插件
+        if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
+                && plugin.getPluginState() != PluginState.STARTED) {
+            pluginManager.startPlugin(pluginKey);
+            log.info("已启动插件: {}", pluginKey);
+        }
+        // 停止插件
+        else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
+                && plugin.getPluginState() == PluginState.STARTED) {
+            pluginManager.stopPlugin(pluginKey);
+            log.info("已停止插件: {}", pluginKey);
         }
     }
 
@@ -152,10 +149,10 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
         Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream()
                 .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity()));
 
-        // 1.3 获取本机 IP 和 MAC 地址
+        // 1.3 获取本机 IP 和 MAC 地址,mac@uuid
         String ip = NetUtil.getLocalhostStr();
         String mac = NetUtil.getLocalMacAddress();
-        String mainId = MAIN_ID + "-" + mac;
+        String mainId = mac + "@" + MAIN_ID;
 
         // 2. 遍历插件列表,并保存为插件实例
         for (PluginWrapper plugin : plugins) {
@@ -173,14 +170,21 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
                     pluginInfo.getId());
             if (pluginInstance == null) {
                 // 4.4 如果插件实例不存在,则创建
-                pluginInstance = PluginInstanceDO.builder().pluginId(pluginInfo.getId()).mainId(MAIN_ID + "-" + mac)
-                        .ip(ip).port(port).heartbeatAt(System.currentTimeMillis()).build();
+                pluginInstance = PluginInstanceDO.builder()
+                        .pluginId(pluginInfo.getId())
+                        .mainId(MAIN_ID + "-" + mac)
+                        .ip(ip)
+                        .port(port)
+                        .heartbeatAt(System.currentTimeMillis())
+                        .build();
                 pluginInstanceMapper.insert(pluginInstance);
             } else {
                 // 2.2 情况二:如果存在,则更新 heartbeatAt
-                // TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有)
-                pluginInstance.setHeartbeatAt(System.currentTimeMillis());
-                pluginInstanceMapper.updateById(pluginInstance);
+                PluginInstanceDO updatePluginInstance = PluginInstanceDO.builder()
+                        .id(pluginInstance.getId())
+                        .heartbeatAt(System.currentTimeMillis())
+                        .build();
+                pluginInstanceMapper.updateById(updatePluginInstance);
             }
         }
     }

+ 54 - 14
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/api/DeviceDataApiClient.java

@@ -5,24 +5,30 @@ import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
 import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO;
 import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
 import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.client.RestTemplate;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
-// TODO @haohao:类注释,写一下,比较好
+/**
+ * 用于通过 {@link RestTemplate} 向远程 IoT 服务发送设备数据相关的请求,
+ * 包括设备状态更新、事件数据上报、属性数据上报等操作。
+ */
 @Slf4j
+@RequiredArgsConstructor
 public class DeviceDataApiClient implements DeviceDataApi {
 
+    /**
+     * 用于发送 HTTP 请求的工具
+     */
     private final RestTemplate restTemplate;
-    private final String deviceDataUrl;
 
-    // 可以通过构造器把 RestTemplate 和 baseUrl 注入进来
-    // TODO @haohao:可以用 lombok 简化
-    public DeviceDataApiClient(RestTemplate restTemplate, String deviceDataUrl) {
-        this.restTemplate = restTemplate;
-        this.deviceDataUrl = deviceDataUrl;
-    }
+    /**
+     * 远程 IoT 服务的基础 URL
+     * 例如:http://127.0.0.1:8080
+     */
+    private final String deviceDataUrl;
 
     // TODO @haohao:返回结果,不用 CommonResult 哈。
     @Override
@@ -43,17 +49,51 @@ public class DeviceDataApiClient implements DeviceDataApi {
         return doPost(url, reportReqDTO, "reportDevicePropertyData");
     }
 
-    // TODO @haohao:未来可能有 get 类型哈
+    
+    /**
+     * 发送 GET 请求
+     *
+     * @param <T>         请求体类型
+     * @param url         请求 URL
+     * @param requestBody 请求体
+     * @param actionName  操作名称
+     * @return 响应结果
+     */
+    private <T> CommonResult<Boolean> doGet(String url, T requestBody, String actionName) {
+        log.info("[{}] Sending request to URL: {}", actionName, url);
+        try {
+            CommonResult<?> response = restTemplate.getForObject(url, CommonResult.class);
+            if (response != null && response.isSuccess()) {
+                return success(true);
+            } else {
+                log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response);
+                return CommonResult.error(500, "Request failed");
+            }
+        } catch (Exception e) {
+            log.error("[{}] Error sending request to URL: {}", actionName, url, e);
+            return CommonResult.error(400, "Request error: " + e.getMessage());
+        }
+    }
+
     /**
-     * 将与远程服务交互的通用逻辑抽取成一个私有方法
+     * 发送 POST 请求
+     *
+     * @param <T>         请求体类型
+     * @param url         请求 URL
+     * @param requestBody 请求体
+     * @param actionName  操作名称
+     * @return 响应结果
      */
     private <T> CommonResult<Boolean> doPost(String url, T requestBody, String actionName) {
         log.info("[{}] Sending request to URL: {}", actionName, url);
         try {
-            // 这里指定返回类型为 CommonResult<?>,根据后台服务返回的实际结构做调整
-            restTemplate.postForObject(url, requestBody, CommonResult.class);
-            // TODO @haohao:check 结果,是否成功
-            return success(true);
+            CommonResult<?> response = restTemplate.postForObject(url, requestBody, CommonResult.class);
+            if (response != null && response.isSuccess()) {
+                return success(true);
+            } else {
+                log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response);
+                return CommonResult.error(500, "Request failed");
+            }
         } catch (Exception e) {
             log.error("[{}] Error sending request to URL: {}", actionName, url, e);
             return CommonResult.error(400, "Request error: " + e.getMessage());

+ 0 - 31
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/DeviceDataApiInitializer.java

@@ -1,31 +0,0 @@
-package cn.iocoder.yudao.module.iot.plugin.common.config;
-
-import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
-import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.client.RestTemplate;
-
-// TODO @haohao:这个最好是 autoconfiguration
-@Configuration
-public class DeviceDataApiInitializer {
-
-    // TODO @haohao:这个要不搞个配置类哈
-    @Value("${iot.device-data.url}")
-    private String deviceDataUrl;
-
-    @Bean
-    public RestTemplate restTemplate() {
-        // TODO haohao:如果你有更多的自定义需求,比如连接池、超时时间等,可以在这里设置
-        return new RestTemplateBuilder().build();
-    }
-
-    // TODO @haohao:不存在时,才构建
-    @Bean
-    public DeviceDataApi deviceDataApi(RestTemplate restTemplate) {
-        return new DeviceDataApiClient(restTemplate, deviceDataUrl);
-    }
-
-}

+ 51 - 0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/YudaoDeviceDataApiAutoConfiguration.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.iot.plugin.common.config;
+
+import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
+import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.Duration;
+
+/**
+ * 设备数据 API 初始化器
+ *
+ * @author haohao
+ */
+@AutoConfiguration
+public class YudaoDeviceDataApiAutoConfiguration {
+
+
+    // TODO @haohao:这个要不搞个配置类哈
+    @Value("${iot.device-data.url}")
+    private String deviceDataUrl;
+
+    /**
+     * 创建 RestTemplate 实例
+     *
+     * @return RestTemplate 实例
+     */
+    @Bean
+    public RestTemplate restTemplate() {
+        // 如果你有更多的自定义需求,比如连接池、超时时间等,可以在这里设置
+        return new RestTemplateBuilder()
+                .setConnectTimeout(Duration.ofMillis(5000)) // 设置连接超时时间
+                .setReadTimeout(Duration.ofMillis(5000)) // 设置读取超时时间
+                .build();
+    }
+
+    /**
+     * 创建 DeviceDataApi 实例
+     *
+     * @param restTemplate RestTemplate 实例
+     * @return DeviceDataApi 实例
+     */
+    @Bean
+    public DeviceDataApi deviceDataApi(RestTemplate restTemplate) {
+        return new DeviceDataApiClient(restTemplate, deviceDataUrl);
+    }
+
+}

+ 1 - 0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+cn.iocoder.yudao.module.iot.plugin.common.config.YudaoDeviceDataApiAutoConfiguration

+ 2 - 1
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java

@@ -11,7 +11,7 @@ import org.springframework.context.ConfigurableApplicationContext;
  * 独立运行入口
  */
 @Slf4j
-@SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin") // TODO @haohao:建议不扫描 cn.iocoder.yudao.module.iot.plugin;而是通过自动配置,初始化 common 的
+@SpringBootApplication
 public class HttpPluginSpringbootApplication {
 
     public static void main(String[] args) {
@@ -21,6 +21,7 @@ public class HttpPluginSpringbootApplication {
 
         // 手动获取 VertxService 并启动
         // TODO @haohao:可以放在 bean 的 init 里么?
+        // 会和插件模式冲突
         VertxService vertxService = context.getBean(VertxService.class);
         vertxService.startServer();
 

+ 30 - 20
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPlugin.java

@@ -21,30 +21,41 @@ public class HttpVertxPlugin extends SpringPlugin {
 
     @Override
     public void start() {
-        // TODO @haohao:这种最好启动中,启动完成,成对打印日志,方便定位问题
-        log.info("[HttpVertxPlugin][start ...]");
+        log.info("[HttpVertxPlugin][start][begin] 开始启动 HttpVertxPlugin 插件...");
 
-        // 1. 获取插件上下文
-        ApplicationContext pluginContext = getApplicationContext();
-        if (pluginContext == null) {
-            log.error("[HttpVertxPlugin] pluginContext is null, start failed.");
-            return;
-        }
+        try {
+            // 1. 获取插件上下文
+            ApplicationContext pluginContext = getApplicationContext();
+            if (pluginContext == null) {
+                log.error("[HttpVertxPlugin][start][fail] pluginContext is null, 启动失败!");
+                return;
+            }
 
-        // 2. 启动 Vertx
-        VertxService vertxService = pluginContext.getBean(VertxService.class);
-        vertxService.startServer();
-    }
+            // 2. 启动 Vert.x
+            VertxService vertxService = pluginContext.getBean(VertxService.class);
+            vertxService.startServer();
 
+            log.info("[HttpVertxPlugin][start][end] 启动完成");
+        } catch (Exception e) {
+            log.error("[HttpVertxPlugin][start][exception] 启动过程出现异常!", e);
+        }
+    }
 
     @Override
     public void stop() {
-        log.info("[HttpVertxPlugin][stop ...]");
-        ApplicationContext pluginContext = getApplicationContext();
-        if (pluginContext != null) {
-            // 停止服务器
-            VertxService vertxService = pluginContext.getBean(VertxService.class);
-            vertxService.stopServer();
+        log.info("[HttpVertxPlugin][stop][begin] 开始停止 HttpVertxPlugin 插件...");
+
+        try {
+            ApplicationContext pluginContext = getApplicationContext();
+            if (pluginContext != null) {
+                // 停止服务器
+                VertxService vertxService = pluginContext.getBean(VertxService.class);
+                vertxService.stopServer();
+            }
+
+            log.info("[HttpVertxPlugin][stop][end] 停止完成");
+        } catch (Exception e) {
+            log.error("[HttpVertxPlugin][stop][exception] 停止过程出现异常!", e);
         }
     }
 
@@ -68,5 +79,4 @@ public class HttpVertxPlugin extends SpringPlugin {
 
         return pluginContext;
     }
-
-}
+}

+ 14 - 0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/HttpVertxPluginConfiguration.java

@@ -34,6 +34,10 @@ public class HttpVertxPluginConfiguration {
 
     /**
      * 创建路由
+     * 
+     * @param vertx Vertx 实例
+     * @param httpVertxHandler HttpVertxHandler 实例
+     * @return Router 实例
      */
     @Bean
     public Router router(Vertx vertx, HttpVertxHandler httpVertxHandler) {
@@ -50,6 +54,12 @@ public class HttpVertxPluginConfiguration {
         return router;
     }
 
+    /**
+     * 创建 HttpVertxHandler 实例
+     *
+     * @param deviceDataApi DeviceDataApi 实例
+     * @return HttpVertxHandler 实例
+     */
     @Bean
     public HttpVertxHandler httpVertxHandler(DeviceDataApi deviceDataApi) {
         return new HttpVertxHandler(deviceDataApi);
@@ -58,6 +68,10 @@ public class HttpVertxPluginConfiguration {
     /**
      * 定义一个 VertxService 来管理服务器启动逻辑
      * 无论是独立运行还是插件方式,都可以共用此类
+     * 
+     * @param vertx Vertx 实例
+     * @param router Router 实例
+     * @return VertxService 实例
      */
     @Bean
     public VertxService vertxService(Vertx vertx, Router router) {

+ 5 - 0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml

@@ -5,3 +5,8 @@ spring:
 iot:
   device-data:
     url: http://127.0.0.1:48080
+
+plugin:
+  http:
+    server:
+      port: 8092