浏览代码

【功能完善】IoT: 删除旧版 HTTP 插件,重构 HttpPlugin 以支持独立启动,新增 VertxService 管理 HTTP 服务器逻辑,优化代码结构和异常处理,更新相关配置以提升插件性能和可维护性。

安浩浩 6 月之前
父节点
当前提交
88ef8ba2e3

二进制
plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar


二进制
plugins/yudao-module-iot-plugin-http-1.0.0.jar


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

@@ -1,16 +1,30 @@
 package cn.iocoder.yudao.module.iot.plugin.http;
 
+import cn.iocoder.yudao.module.iot.plugin.http.config.VertxService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.WebApplicationType;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
 
+/**
+ * 独立运行入口
+ */
+@Slf4j
 @SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin")
 public class HttpPluginSpringbootApplication {
 
     public static void main(String[] args) {
+        // 这里可选择 NONE / SERVLET / REACTIVE,看你需求
         SpringApplication application = new SpringApplication(HttpPluginSpringbootApplication.class);
         application.setWebApplicationType(WebApplicationType.NONE);
-        application.run(args);
-    }
 
+        ConfigurableApplicationContext context = application.run(args);
+
+        // 手动获取 VertxService 并启动
+        VertxService vertxService = context.getBean(VertxService.class);
+        vertxService.startServer();
+
+        log.info("[HttpPluginSpringbootApplication] 独立模式启动完成");
+    }
 }

+ 37 - 47
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

@@ -2,80 +2,70 @@ package cn.iocoder.yudao.module.iot.plugin.http.config;
 
 import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
-import cn.iocoder.yudao.module.iot.plugin.http.service.HttpVertxHandler;
-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.pf4j.PluginWrapper;
 import org.pf4j.spring.SpringPlugin;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
+/**
+ * 负责插件的启动和停止,与 Vert.x 的生命周期管理
+ */
 @Slf4j
 public class HttpVertxPlugin extends SpringPlugin {
 
-    private static final int PORT = 8092;
-    private Vertx vertx;
-
     public HttpVertxPlugin(PluginWrapper wrapper) {
         super(wrapper);
     }
 
     @Override
     public void start() {
-        log.info("HttpVertxPlugin.start()");
+        log.info("[HttpVertxPlugin] start ...");
 
-        // 获取 DeviceDataApi 实例
-        DeviceDataApi deviceDataApi = SpringUtil.getBean(DeviceDataApi.class);
-        if (deviceDataApi == null) {
-            log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");
+        // 1. 获取插件上下文
+        ApplicationContext pluginContext = getApplicationContext();
+        if (pluginContext == null) {
+            log.error("[HttpVertxPlugin] pluginContext is null, start failed.");
             return;
         }
 
-        // 初始化 Vert.x
-        vertx = Vertx.vertx();
-        Router router = Router.router(vertx);
-
-        // 处理 Body
-        router.route().handler(BodyHandler.create());
-
-        // 设置路由
-        router.post("/sys/:productKey/:deviceName/thing/event/property/post")
-                .handler(new HttpVertxHandler(deviceDataApi));
-
-        // 启动 HTTP 服务器
-        vertx.createHttpServer()
-                .requestHandler(router)
-                .listen(PORT, http -> {
-                    if (http.succeeded()) {
-                        log.info("HTTP 服务器启动成功,端口为: {}", PORT);
-                    } else {
-                        log.error("HTTP 服务器启动失败", http.cause());
-                    }
-                });
+        // 2. 启动 Vertx
+        VertxService vertxService = pluginContext.getBean(VertxService.class);
+        vertxService.startServer();
     }
 
+
     @Override
     public void stop() {
-        log.info("HttpVertxPlugin.stop()");
-        if (vertx != null) {
-            vertx.close(ar -> {
-                if (ar.succeeded()) {
-                    log.info("Vert.x 关闭成功");
-                } else {
-                    log.error("Vert.x 关闭失败", ar.cause());
-                }
-            });
+        log.info("[HttpVertxPlugin] stop ...");
+        ApplicationContext pluginContext = getApplicationContext();
+        if (pluginContext != null) {
+            // 停止服务器
+            VertxService vertxService = pluginContext.getBean(VertxService.class);
+            vertxService.stopServer();
         }
     }
 
     @Override
     protected ApplicationContext createApplicationContext() {
-        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
-        applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
-        applicationContext.refresh();
+        AnnotationConfigApplicationContext pluginContext = new AnnotationConfigApplicationContext() {
+            @Override
+            protected void prepareRefresh() {
+                // 在刷新容器前注册主程序中的 Bean
+                ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
+                DeviceDataApi deviceDataApi = SpringUtil.getBean(DeviceDataApi.class);
+                beanFactory.registerSingleton("deviceDataApi", deviceDataApi);
+
+                super.prepareRefresh();
+            }
+        };
 
-        return applicationContext;
+        pluginContext.setClassLoader(getWrapper().getPluginClassLoader());
+        pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.http.config");
+        pluginContext.refresh();
+
+        return pluginContext;
     }
-}
+
+}

+ 58 - 7
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

@@ -1,16 +1,67 @@
 package cn.iocoder.yudao.module.iot.plugin.http.config;
 
-import org.pf4j.DefaultPluginManager;
-import org.pf4j.PluginWrapper;
+import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
+import cn.iocoder.yudao.module.iot.plugin.http.service.HttpVertxHandler;
+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.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+/**
+ * 插件与独立运行都可复用的配置类
+ */
+@Slf4j
 @Configuration
 public class HttpVertxPluginConfiguration {
 
-    @Bean(initMethod = "start")
-    public HttpVertxPlugin httpVertxPlugin() {
-        PluginWrapper pluginWrapper = new PluginWrapper(new DefaultPluginManager(), null, null, null);
-        return new HttpVertxPlugin(pluginWrapper);
+    /**
+     * 可在 application.yml 中配置,默认端口 8092
+     */
+    @Value("${plugin.http.server.port:8092}")
+    private Integer port;
+
+    /**
+     * 创建 Vert.x 实例
+     */
+    @Bean
+    public Vertx vertx() {
+        return Vertx.vertx();
+    }
+
+    /**
+     * 创建路由
+     */
+    @Bean
+    public Router router(Vertx vertx, HttpVertxHandler httpVertxHandler) {
+        Router router = Router.router(vertx);
+
+        // 处理 Body
+        router.route().handler(BodyHandler.create());
+
+        // 设置路由
+        router.post("/sys/:productKey/:deviceName/thing/event/property/post")
+              .handler(httpVertxHandler);
+
+        return router;
+    }
+
+    /**
+     * 注入你的 Http 处理器 Handler,依赖 DeviceDataApi
+     */
+    @Bean
+    public HttpVertxHandler httpVertxHandler(DeviceDataApi deviceDataApi) {
+        return new HttpVertxHandler(deviceDataApi);
+    }
+
+    /**
+     * 定义一个 VertxService 来管理服务器启动逻辑
+     * 无论是独立运行还是插件方式,都可以共用此类
+     */
+    @Bean
+    public VertxService vertxService(Vertx vertx, Router router) {
+        return new VertxService(port, vertx, router);
     }
-} 
+}

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

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.iot.plugin.http.config;
+
+import io.vertx.core.Vertx;
+import io.vertx.ext.web.Router;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 封装 Vert.x HTTP 服务的启动/关闭逻辑
+ */
+@Slf4j
+public class VertxService {
+
+    private final Integer port;
+    private final Vertx vertx;
+    private final Router router;
+
+    public VertxService(Integer port, Vertx vertx, Router router) {
+        this.port = port;
+        this.vertx = vertx;
+        this.router = router;
+    }
+
+    /**
+     * 启动 HTTP 服务器
+     */
+    public void startServer() {
+        vertx.createHttpServer()
+             .requestHandler(router)
+             .listen(port, http -> {
+                 if (http.succeeded()) {
+                     log.info("[VertxService] HTTP 服务器启动成功, 端口: {}", port);
+                 } else {
+                     log.error("[VertxService] HTTP 服务器启动失败", http.cause());
+                 }
+             });
+    }
+
+    /**
+     * 关闭 HTTP 服务器
+     */
+    public void stopServer() {
+        if (vertx != null) {
+            vertx.close(ar -> {
+                if (ar.succeeded()) {
+                    log.info("[VertxService] Vert.x 关闭成功");
+                } else {
+                    log.error("[VertxService] Vert.x 关闭失败", ar.cause());
+                }
+            });
+        }
+    }
+}

+ 20 - 46
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/service/HttpVertxHandler.java

@@ -22,77 +22,51 @@ public class HttpVertxHandler implements Handler<RoutingContext> {
     public void handle(RoutingContext ctx) {
         String productKey = ctx.pathParam("productKey");
         String deviceName = ctx.pathParam("deviceName");
-        RequestBody requestBody = ctx.body();
 
+        RequestBody requestBody = ctx.body();
         JSONObject jsonData;
         try {
             jsonData = JSONUtil.parseObj(requestBody.asJsonObject());
         } catch (Exception e) {
-            JSONObject res = createResponseJson(
-                    400,
-                    new JSONObject(),
-                    null,
-                    "请求数据不是合法的 JSON 格式: " + e.getMessage(),
-                    "thing.event.property.post",
-                    "1.0");
-            ctx.response()
-                    .setStatusCode(400)
+            log.error("[HttpVertxHandler] 请求数据解析失败", e);
+            ctx.response().setStatusCode(400)
                     .putHeader("Content-Type", "application/json; charset=UTF-8")
-                    .end(res.toString());
+                    .end(createResponseJson(400, null, null,
+                            "请求数据不是合法的 JSON 格式: " + e.getMessage(),
+                            "thing.event.property.post", "1.0").toString());
             return;
         }
 
-        String id = jsonData.getStr("id", null);
+        String id = jsonData.getStr("id");
 
         try {
-            // 调用主程序的接口保存数据
-            IotDevicePropertyReportReqDTO createDTO = IotDevicePropertyReportReqDTO.builder()
+            IotDevicePropertyReportReqDTO reportReqDTO = IotDevicePropertyReportReqDTO.builder()
                     .productKey(productKey)
                     .deviceName(deviceName)
-                    .params(jsonData) // TODO 芋艿:这块要优化
+                    .params(jsonData)
                     .build();
-            deviceDataApi.reportDevicePropertyData(createDTO);
 
-            // 构造成功响应内容
-            JSONObject successRes = createResponseJson(
-                    200,
-                    new JSONObject(),
-                    id,
-                    "success",
-                    "thing.event.property.post",
-                    "1.0");
+            deviceDataApi.reportDevicePropertyData(reportReqDTO);
+
             ctx.response()
                     .setStatusCode(200)
                     .putHeader("Content-Type", "application/json; charset=UTF-8")
-                    .end(successRes.toString());
+                    .end(createResponseJson(200, new JSONObject(), id, "success",
+                            "thing.event.property.post", "1.0").toString());
+
         } catch (Exception e) {
-            JSONObject errorRes = createResponseJson(
-                    500,
-                    new JSONObject(),
-                    id,
-                    "The format of result is error!",
-                    "thing.event.property.post",
-                    "1.0");
+            log.error("[HttpVertxHandler] 上报属性数据失败", e);
             ctx.response()
                     .setStatusCode(500)
                     .putHeader("Content-Type", "application/json; charset=UTF-8")
-                    .end(errorRes.toString());
+                    .end(createResponseJson(500, new JSONObject(), id,
+                            "The format of result is error!",
+                            "thing.event.property.post", "1.0").toString());
         }
     }
 
-    /**
-     * 创建标准化的响应 JSON 对象
-     *
-     * @param code    响应状态码(业务层面的)
-     * @param data    返回的数据对象(JSON)
-     * @param id      请求的 id(可选)
-     * @param message 返回的提示信息
-     * @param method  返回的 method 标识
-     * @param version 返回的版本号
-     * @return 构造好的 JSON 对象
-     */
-    private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method,
-                                          String version) {
+    private JSONObject createResponseJson(int code, JSONObject data, String id,
+                                          String message, String method, String version) {
         JSONObject res = new JSONObject();
         res.set("code", code);
         res.set("data", data != null ? data : new JSONObject());