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

This commit is contained in:
安浩浩 2025-01-25 00:12:06 +08:00
parent 698cec92bd
commit 88ef8ba2e3
7 changed files with 183 additions and 102 deletions

View File

@ -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] 独立模式启动完成");
}
}

View File

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

View File

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

View File

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

View File

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