From 88ef8ba2e3be7eed2add375dbc07eaf029df8742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com> Date: Sat, 25 Jan 2025 00:12:06 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E3=80=91IoT:=20=E5=88=A0=E9=99=A4=E6=97=A7=E7=89=88=20HTTP=20?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=8C=E9=87=8D=E6=9E=84=20HttpPlugin=20?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E7=8B=AC=E7=AB=8B=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=EF=BC=8C=E6=96=B0=E5=A2=9E=20VertxService=20=E7=AE=A1=E7=90=86?= =?UTF-8?q?=20HTTP=20=E6=9C=8D=E5=8A=A1=E5=99=A8=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=92=8C=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=9B=B8=E5=85=B3=E9=85=8D=E7=BD=AE=E4=BB=A5=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E6=8F=92=E4=BB=B6=E6=80=A7=E8=83=BD=E5=92=8C=E5=8F=AF?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-module-iot-http-plugin-2.2.0-snapshot.jar | Bin 10353 -> 0 bytes .../yudao-module-iot-plugin-http-1.0.0.jar | Bin 10643 -> 12284 bytes .../http/HttpPluginSpringbootApplication.java | 18 +++- .../plugin/http/config/HttpVertxPlugin.java | 84 ++++++++---------- .../config/HttpVertxPluginConfiguration.java | 65 ++++++++++++-- .../iot/plugin/http/config/VertxService.java | 52 +++++++++++ .../plugin/http/service/HttpVertxHandler.java | 66 +++++--------- 7 files changed, 183 insertions(+), 102 deletions(-) delete mode 100644 plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar create mode 100644 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 diff --git a/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar b/plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar deleted file mode 100644 index fef8d5362c38ea47e769e58933c715d492df719f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10353 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 J-dTr&`afenk2e4S diff --git a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java b/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java index 74c5651017..648b7dda65 100644 --- a/yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/HttpPluginSpringbootApplication.java +++ b/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] 独立模式启动完成"); + } } \ No newline at end of file diff --git a/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 b/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 index e6145b93d5..85249cdb85 100644 --- a/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 +++ b/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); - return applicationContext; + super.prepareRefresh(); + } + }; + + pluginContext.setClassLoader(getWrapper().getPluginClassLoader()); + pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.http.config"); + pluginContext.refresh(); + + return pluginContext; } -} \ No newline at end of file + +} diff --git a/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 b/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 index b5e977efbb..55bce6f24a 100644 --- a/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 +++ b/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(); } -} \ No newline at end of file + + /** + * 创建路由 + */ + @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); + } +} diff --git a/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 b/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 new file mode 100644 index 0000000000..5a57be8eee --- /dev/null +++ b/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()); + } + }); + } + } +} diff --git a/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 b/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 index becba2a082..df55c68fd6 100644 --- a/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 +++ b/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 { 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());