Commit 88ef8ba2e3be7eed2add375dbc07eaf029df8742
1 parent
698cec92
【功能完善】IoT: 删除旧版 HTTP 插件,重构 HttpPlugin 以支持独立启动,新增 VertxService 管理 HTTP 服务器逻辑,优化代码…
…结构和异常处理,更新相关配置以提升插件性能和可维护性。
Showing
7 changed files
with
183 additions
and
102 deletions
plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar deleted
No preview for this file type
plugins/yudao-module-iot-plugin-http-1.0.0.jar
No preview for this file type
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 | package cn.iocoder.yudao.module.iot.plugin.http; | 1 | package cn.iocoder.yudao.module.iot.plugin.http; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.module.iot.plugin.http.config.VertxService; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 3 | import org.springframework.boot.SpringApplication; | 5 | import org.springframework.boot.SpringApplication; |
| 4 | import org.springframework.boot.WebApplicationType; | 6 | import org.springframework.boot.WebApplicationType; |
| 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; | 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 8 | +import org.springframework.context.ConfigurableApplicationContext; | ||
| 6 | 9 | ||
| 10 | +/** | ||
| 11 | + * 独立运行入口 | ||
| 12 | + */ | ||
| 13 | +@Slf4j | ||
| 7 | @SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin") | 14 | @SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin") |
| 8 | public class HttpPluginSpringbootApplication { | 15 | public class HttpPluginSpringbootApplication { |
| 9 | 16 | ||
| 10 | public static void main(String[] args) { | 17 | public static void main(String[] args) { |
| 18 | + // 这里可选择 NONE / SERVLET / REACTIVE,看你需求 | ||
| 11 | SpringApplication application = new SpringApplication(HttpPluginSpringbootApplication.class); | 19 | SpringApplication application = new SpringApplication(HttpPluginSpringbootApplication.class); |
| 12 | application.setWebApplicationType(WebApplicationType.NONE); | 20 | application.setWebApplicationType(WebApplicationType.NONE); |
| 13 | - application.run(args); | ||
| 14 | - } | ||
| 15 | 21 | ||
| 22 | + ConfigurableApplicationContext context = application.run(args); | ||
| 23 | + | ||
| 24 | + // 手动获取 VertxService 并启动 | ||
| 25 | + VertxService vertxService = context.getBean(VertxService.class); | ||
| 26 | + vertxService.startServer(); | ||
| 27 | + | ||
| 28 | + log.info("[HttpPluginSpringbootApplication] 独立模式启动完成"); | ||
| 29 | + } | ||
| 16 | } | 30 | } |
| 17 | \ No newline at end of file | 31 | \ No newline at end of file |
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; | @@ -2,80 +2,70 @@ package cn.iocoder.yudao.module.iot.plugin.http.config; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.extra.spring.SpringUtil; | 3 | import cn.hutool.extra.spring.SpringUtil; |
| 4 | import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | 4 | import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; |
| 5 | -import cn.iocoder.yudao.module.iot.plugin.http.service.HttpVertxHandler; | ||
| 6 | -import io.vertx.core.Vertx; | ||
| 7 | -import io.vertx.ext.web.Router; | ||
| 8 | -import io.vertx.ext.web.handler.BodyHandler; | ||
| 9 | import lombok.extern.slf4j.Slf4j; | 5 | import lombok.extern.slf4j.Slf4j; |
| 10 | import org.pf4j.PluginWrapper; | 6 | import org.pf4j.PluginWrapper; |
| 11 | import org.pf4j.spring.SpringPlugin; | 7 | import org.pf4j.spring.SpringPlugin; |
| 8 | +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | ||
| 12 | import org.springframework.context.ApplicationContext; | 9 | import org.springframework.context.ApplicationContext; |
| 13 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; | 10 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; |
| 14 | 11 | ||
| 12 | +/** | ||
| 13 | + * 负责插件的启动和停止,与 Vert.x 的生命周期管理 | ||
| 14 | + */ | ||
| 15 | @Slf4j | 15 | @Slf4j |
| 16 | public class HttpVertxPlugin extends SpringPlugin { | 16 | public class HttpVertxPlugin extends SpringPlugin { |
| 17 | 17 | ||
| 18 | - private static final int PORT = 8092; | ||
| 19 | - private Vertx vertx; | ||
| 20 | - | ||
| 21 | public HttpVertxPlugin(PluginWrapper wrapper) { | 18 | public HttpVertxPlugin(PluginWrapper wrapper) { |
| 22 | super(wrapper); | 19 | super(wrapper); |
| 23 | } | 20 | } |
| 24 | 21 | ||
| 25 | @Override | 22 | @Override |
| 26 | public void start() { | 23 | public void start() { |
| 27 | - log.info("HttpVertxPlugin.start()"); | 24 | + log.info("[HttpVertxPlugin] start ..."); |
| 28 | 25 | ||
| 29 | - // 获取 DeviceDataApi 实例 | ||
| 30 | - DeviceDataApi deviceDataApi = SpringUtil.getBean(DeviceDataApi.class); | ||
| 31 | - if (deviceDataApi == null) { | ||
| 32 | - log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); | 26 | + // 1. 获取插件上下文 |
| 27 | + ApplicationContext pluginContext = getApplicationContext(); | ||
| 28 | + if (pluginContext == null) { | ||
| 29 | + log.error("[HttpVertxPlugin] pluginContext is null, start failed."); | ||
| 33 | return; | 30 | return; |
| 34 | } | 31 | } |
| 35 | 32 | ||
| 36 | - // 初始化 Vert.x | ||
| 37 | - vertx = Vertx.vertx(); | ||
| 38 | - Router router = Router.router(vertx); | ||
| 39 | - | ||
| 40 | - // 处理 Body | ||
| 41 | - router.route().handler(BodyHandler.create()); | ||
| 42 | - | ||
| 43 | - // 设置路由 | ||
| 44 | - router.post("/sys/:productKey/:deviceName/thing/event/property/post") | ||
| 45 | - .handler(new HttpVertxHandler(deviceDataApi)); | ||
| 46 | - | ||
| 47 | - // 启动 HTTP 服务器 | ||
| 48 | - vertx.createHttpServer() | ||
| 49 | - .requestHandler(router) | ||
| 50 | - .listen(PORT, http -> { | ||
| 51 | - if (http.succeeded()) { | ||
| 52 | - log.info("HTTP 服务器启动成功,端口为: {}", PORT); | ||
| 53 | - } else { | ||
| 54 | - log.error("HTTP 服务器启动失败", http.cause()); | ||
| 55 | - } | ||
| 56 | - }); | 33 | + // 2. 启动 Vertx |
| 34 | + VertxService vertxService = pluginContext.getBean(VertxService.class); | ||
| 35 | + vertxService.startServer(); | ||
| 57 | } | 36 | } |
| 58 | 37 | ||
| 38 | + | ||
| 59 | @Override | 39 | @Override |
| 60 | public void stop() { | 40 | public void stop() { |
| 61 | - log.info("HttpVertxPlugin.stop()"); | ||
| 62 | - if (vertx != null) { | ||
| 63 | - vertx.close(ar -> { | ||
| 64 | - if (ar.succeeded()) { | ||
| 65 | - log.info("Vert.x 关闭成功"); | ||
| 66 | - } else { | ||
| 67 | - log.error("Vert.x 关闭失败", ar.cause()); | ||
| 68 | - } | ||
| 69 | - }); | 41 | + log.info("[HttpVertxPlugin] stop ..."); |
| 42 | + ApplicationContext pluginContext = getApplicationContext(); | ||
| 43 | + if (pluginContext != null) { | ||
| 44 | + // 停止服务器 | ||
| 45 | + VertxService vertxService = pluginContext.getBean(VertxService.class); | ||
| 46 | + vertxService.stopServer(); | ||
| 70 | } | 47 | } |
| 71 | } | 48 | } |
| 72 | 49 | ||
| 73 | @Override | 50 | @Override |
| 74 | protected ApplicationContext createApplicationContext() { | 51 | protected ApplicationContext createApplicationContext() { |
| 75 | - AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); | ||
| 76 | - applicationContext.setClassLoader(getWrapper().getPluginClassLoader()); | ||
| 77 | - applicationContext.refresh(); | 52 | + AnnotationConfigApplicationContext pluginContext = new AnnotationConfigApplicationContext() { |
| 53 | + @Override | ||
| 54 | + protected void prepareRefresh() { | ||
| 55 | + // 在刷新容器前注册主程序中的 Bean | ||
| 56 | + ConfigurableListableBeanFactory beanFactory = this.getBeanFactory(); | ||
| 57 | + DeviceDataApi deviceDataApi = SpringUtil.getBean(DeviceDataApi.class); | ||
| 58 | + beanFactory.registerSingleton("deviceDataApi", deviceDataApi); | ||
| 59 | + | ||
| 60 | + super.prepareRefresh(); | ||
| 61 | + } | ||
| 62 | + }; | ||
| 78 | 63 | ||
| 79 | - return applicationContext; | 64 | + pluginContext.setClassLoader(getWrapper().getPluginClassLoader()); |
| 65 | + pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.http.config"); | ||
| 66 | + pluginContext.refresh(); | ||
| 67 | + | ||
| 68 | + return pluginContext; | ||
| 80 | } | 69 | } |
| 81 | -} | ||
| 82 | \ No newline at end of file | 70 | \ No newline at end of file |
| 71 | + | ||
| 72 | +} |
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 | package cn.iocoder.yudao.module.iot.plugin.http.config; | 1 | package cn.iocoder.yudao.module.iot.plugin.http.config; |
| 2 | 2 | ||
| 3 | -import org.pf4j.DefaultPluginManager; | ||
| 4 | -import org.pf4j.PluginWrapper; | 3 | +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; |
| 4 | +import cn.iocoder.yudao.module.iot.plugin.http.service.HttpVertxHandler; | ||
| 5 | +import io.vertx.core.Vertx; | ||
| 6 | +import io.vertx.ext.web.Router; | ||
| 7 | +import io.vertx.ext.web.handler.BodyHandler; | ||
| 8 | +import lombok.extern.slf4j.Slf4j; | ||
| 9 | +import org.springframework.beans.factory.annotation.Value; | ||
| 5 | import org.springframework.context.annotation.Bean; | 10 | import org.springframework.context.annotation.Bean; |
| 6 | import org.springframework.context.annotation.Configuration; | 11 | import org.springframework.context.annotation.Configuration; |
| 7 | 12 | ||
| 13 | +/** | ||
| 14 | + * 插件与独立运行都可复用的配置类 | ||
| 15 | + */ | ||
| 16 | +@Slf4j | ||
| 8 | @Configuration | 17 | @Configuration |
| 9 | public class HttpVertxPluginConfiguration { | 18 | public class HttpVertxPluginConfiguration { |
| 10 | 19 | ||
| 11 | - @Bean(initMethod = "start") | ||
| 12 | - public HttpVertxPlugin httpVertxPlugin() { | ||
| 13 | - PluginWrapper pluginWrapper = new PluginWrapper(new DefaultPluginManager(), null, null, null); | ||
| 14 | - return new HttpVertxPlugin(pluginWrapper); | 20 | + /** |
| 21 | + * 可在 application.yml 中配置,默认端口 8092 | ||
| 22 | + */ | ||
| 23 | + @Value("${plugin.http.server.port:8092}") | ||
| 24 | + private Integer port; | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 创建 Vert.x 实例 | ||
| 28 | + */ | ||
| 29 | + @Bean | ||
| 30 | + public Vertx vertx() { | ||
| 31 | + return Vertx.vertx(); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 创建路由 | ||
| 36 | + */ | ||
| 37 | + @Bean | ||
| 38 | + public Router router(Vertx vertx, HttpVertxHandler httpVertxHandler) { | ||
| 39 | + Router router = Router.router(vertx); | ||
| 40 | + | ||
| 41 | + // 处理 Body | ||
| 42 | + router.route().handler(BodyHandler.create()); | ||
| 43 | + | ||
| 44 | + // 设置路由 | ||
| 45 | + router.post("/sys/:productKey/:deviceName/thing/event/property/post") | ||
| 46 | + .handler(httpVertxHandler); | ||
| 47 | + | ||
| 48 | + return router; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 注入你的 Http 处理器 Handler,依赖 DeviceDataApi | ||
| 53 | + */ | ||
| 54 | + @Bean | ||
| 55 | + public HttpVertxHandler httpVertxHandler(DeviceDataApi deviceDataApi) { | ||
| 56 | + return new HttpVertxHandler(deviceDataApi); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + /** | ||
| 60 | + * 定义一个 VertxService 来管理服务器启动逻辑 | ||
| 61 | + * 无论是独立运行还是插件方式,都可以共用此类 | ||
| 62 | + */ | ||
| 63 | + @Bean | ||
| 64 | + public VertxService vertxService(Vertx vertx, Router router) { | ||
| 65 | + return new VertxService(port, vertx, router); | ||
| 15 | } | 66 | } |
| 16 | -} | ||
| 17 | \ No newline at end of file | 67 | \ No newline at end of file |
| 68 | +} |
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 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.plugin.http.config; | ||
| 2 | + | ||
| 3 | +import io.vertx.core.Vertx; | ||
| 4 | +import io.vertx.ext.web.Router; | ||
| 5 | +import lombok.extern.slf4j.Slf4j; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 封装 Vert.x HTTP 服务的启动/关闭逻辑 | ||
| 9 | + */ | ||
| 10 | +@Slf4j | ||
| 11 | +public class VertxService { | ||
| 12 | + | ||
| 13 | + private final Integer port; | ||
| 14 | + private final Vertx vertx; | ||
| 15 | + private final Router router; | ||
| 16 | + | ||
| 17 | + public VertxService(Integer port, Vertx vertx, Router router) { | ||
| 18 | + this.port = port; | ||
| 19 | + this.vertx = vertx; | ||
| 20 | + this.router = router; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 启动 HTTP 服务器 | ||
| 25 | + */ | ||
| 26 | + public void startServer() { | ||
| 27 | + vertx.createHttpServer() | ||
| 28 | + .requestHandler(router) | ||
| 29 | + .listen(port, http -> { | ||
| 30 | + if (http.succeeded()) { | ||
| 31 | + log.info("[VertxService] HTTP 服务器启动成功, 端口: {}", port); | ||
| 32 | + } else { | ||
| 33 | + log.error("[VertxService] HTTP 服务器启动失败", http.cause()); | ||
| 34 | + } | ||
| 35 | + }); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * 关闭 HTTP 服务器 | ||
| 40 | + */ | ||
| 41 | + public void stopServer() { | ||
| 42 | + if (vertx != null) { | ||
| 43 | + vertx.close(ar -> { | ||
| 44 | + if (ar.succeeded()) { | ||
| 45 | + log.info("[VertxService] Vert.x 关闭成功"); | ||
| 46 | + } else { | ||
| 47 | + log.error("[VertxService] Vert.x 关闭失败", ar.cause()); | ||
| 48 | + } | ||
| 49 | + }); | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | +} |
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> { | @@ -22,77 +22,51 @@ public class HttpVertxHandler implements Handler<RoutingContext> { | ||
| 22 | public void handle(RoutingContext ctx) { | 22 | public void handle(RoutingContext ctx) { |
| 23 | String productKey = ctx.pathParam("productKey"); | 23 | String productKey = ctx.pathParam("productKey"); |
| 24 | String deviceName = ctx.pathParam("deviceName"); | 24 | String deviceName = ctx.pathParam("deviceName"); |
| 25 | - RequestBody requestBody = ctx.body(); | ||
| 26 | 25 | ||
| 26 | + RequestBody requestBody = ctx.body(); | ||
| 27 | JSONObject jsonData; | 27 | JSONObject jsonData; |
| 28 | try { | 28 | try { |
| 29 | jsonData = JSONUtil.parseObj(requestBody.asJsonObject()); | 29 | jsonData = JSONUtil.parseObj(requestBody.asJsonObject()); |
| 30 | } catch (Exception e) { | 30 | } catch (Exception e) { |
| 31 | - JSONObject res = createResponseJson( | ||
| 32 | - 400, | ||
| 33 | - new JSONObject(), | ||
| 34 | - null, | ||
| 35 | - "请求数据不是合法的 JSON 格式: " + e.getMessage(), | ||
| 36 | - "thing.event.property.post", | ||
| 37 | - "1.0"); | ||
| 38 | - ctx.response() | ||
| 39 | - .setStatusCode(400) | 31 | + log.error("[HttpVertxHandler] 请求数据解析失败", e); |
| 32 | + ctx.response().setStatusCode(400) | ||
| 40 | .putHeader("Content-Type", "application/json; charset=UTF-8") | 33 | .putHeader("Content-Type", "application/json; charset=UTF-8") |
| 41 | - .end(res.toString()); | 34 | + .end(createResponseJson(400, null, null, |
| 35 | + "请求数据不是合法的 JSON 格式: " + e.getMessage(), | ||
| 36 | + "thing.event.property.post", "1.0").toString()); | ||
| 42 | return; | 37 | return; |
| 43 | } | 38 | } |
| 44 | 39 | ||
| 45 | - String id = jsonData.getStr("id", null); | 40 | + String id = jsonData.getStr("id"); |
| 46 | 41 | ||
| 47 | try { | 42 | try { |
| 48 | - // 调用主程序的接口保存数据 | ||
| 49 | - IotDevicePropertyReportReqDTO createDTO = IotDevicePropertyReportReqDTO.builder() | 43 | + IotDevicePropertyReportReqDTO reportReqDTO = IotDevicePropertyReportReqDTO.builder() |
| 50 | .productKey(productKey) | 44 | .productKey(productKey) |
| 51 | .deviceName(deviceName) | 45 | .deviceName(deviceName) |
| 52 | - .params(jsonData) // TODO 芋艿:这块要优化 | 46 | + .params(jsonData) |
| 53 | .build(); | 47 | .build(); |
| 54 | - deviceDataApi.reportDevicePropertyData(createDTO); | ||
| 55 | 48 | ||
| 56 | - // 构造成功响应内容 | ||
| 57 | - JSONObject successRes = createResponseJson( | ||
| 58 | - 200, | ||
| 59 | - new JSONObject(), | ||
| 60 | - id, | ||
| 61 | - "success", | ||
| 62 | - "thing.event.property.post", | ||
| 63 | - "1.0"); | 49 | + deviceDataApi.reportDevicePropertyData(reportReqDTO); |
| 50 | + | ||
| 64 | ctx.response() | 51 | ctx.response() |
| 65 | .setStatusCode(200) | 52 | .setStatusCode(200) |
| 66 | .putHeader("Content-Type", "application/json; charset=UTF-8") | 53 | .putHeader("Content-Type", "application/json; charset=UTF-8") |
| 67 | - .end(successRes.toString()); | 54 | + .end(createResponseJson(200, new JSONObject(), id, "success", |
| 55 | + "thing.event.property.post", "1.0").toString()); | ||
| 56 | + | ||
| 68 | } catch (Exception e) { | 57 | } catch (Exception e) { |
| 69 | - JSONObject errorRes = createResponseJson( | ||
| 70 | - 500, | ||
| 71 | - new JSONObject(), | ||
| 72 | - id, | ||
| 73 | - "The format of result is error!", | ||
| 74 | - "thing.event.property.post", | ||
| 75 | - "1.0"); | 58 | + log.error("[HttpVertxHandler] 上报属性数据失败", e); |
| 76 | ctx.response() | 59 | ctx.response() |
| 77 | .setStatusCode(500) | 60 | .setStatusCode(500) |
| 78 | .putHeader("Content-Type", "application/json; charset=UTF-8") | 61 | .putHeader("Content-Type", "application/json; charset=UTF-8") |
| 79 | - .end(errorRes.toString()); | 62 | + .end(createResponseJson(500, new JSONObject(), id, |
| 63 | + "The format of result is error!", | ||
| 64 | + "thing.event.property.post", "1.0").toString()); | ||
| 80 | } | 65 | } |
| 81 | } | 66 | } |
| 82 | 67 | ||
| 83 | - /** | ||
| 84 | - * 创建标准化的响应 JSON 对象 | ||
| 85 | - * | ||
| 86 | - * @param code 响应状态码(业务层面的) | ||
| 87 | - * @param data 返回的数据对象(JSON) | ||
| 88 | - * @param id 请求的 id(可选) | ||
| 89 | - * @param message 返回的提示信息 | ||
| 90 | - * @param method 返回的 method 标识 | ||
| 91 | - * @param version 返回的版本号 | ||
| 92 | - * @return 构造好的 JSON 对象 | ||
| 93 | - */ | ||
| 94 | - private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, | ||
| 95 | - String version) { | 68 | + private JSONObject createResponseJson(int code, JSONObject data, String id, |
| 69 | + String message, String method, String version) { | ||
| 96 | JSONObject res = new JSONObject(); | 70 | JSONObject res = new JSONObject(); |
| 97 | res.set("code", code); | 71 | res.set("code", code); |
| 98 | res.set("data", data != null ? data : new JSONObject()); | 72 | res.set("data", data != null ? data : new JSONObject()); |