【功能完善】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; 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.SpringApplication;
import org.springframework.boot.WebApplicationType; import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 独立运行入口
*/
@Slf4j
@SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin") @SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin")
public class HttpPluginSpringbootApplication { public class HttpPluginSpringbootApplication {
public static void main(String[] args) { public static void main(String[] args) {
// 这里可选择 NONE / SERVLET / REACTIVE看你需求
SpringApplication application = new SpringApplication(HttpPluginSpringbootApplication.class); SpringApplication application = new SpringApplication(HttpPluginSpringbootApplication.class);
application.setWebApplicationType(WebApplicationType.NONE); 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.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; 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 lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginWrapper; import org.pf4j.PluginWrapper;
import org.pf4j.spring.SpringPlugin; import org.pf4j.spring.SpringPlugin;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* 负责插件的启动和停止 Vert.x 的生命周期管理
*/
@Slf4j @Slf4j
public class HttpVertxPlugin extends SpringPlugin { public class HttpVertxPlugin extends SpringPlugin {
private static final int PORT = 8092;
private Vertx vertx;
public HttpVertxPlugin(PluginWrapper wrapper) { public HttpVertxPlugin(PluginWrapper wrapper) {
super(wrapper); super(wrapper);
} }
@Override @Override
public void start() { public void start() {
log.info("HttpVertxPlugin.start()"); log.info("[HttpVertxPlugin] start ...");
// 获取 DeviceDataApi 实例 // 1. 获取插件上下文
DeviceDataApi deviceDataApi = SpringUtil.getBean(DeviceDataApi.class); ApplicationContext pluginContext = getApplicationContext();
if (deviceDataApi == null) { if (pluginContext == null) {
log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); log.error("[HttpVertxPlugin] pluginContext is null, start failed.");
return; return;
} }
// 初始化 Vert.x // 2. 启动 Vertx
vertx = Vertx.vertx(); VertxService vertxService = pluginContext.getBean(VertxService.class);
Router router = Router.router(vertx); vertxService.startServer();
// 处理 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());
}
});
} }
@Override @Override
public void stop() { public void stop() {
log.info("HttpVertxPlugin.stop()"); log.info("[HttpVertxPlugin] stop ...");
if (vertx != null) { ApplicationContext pluginContext = getApplicationContext();
vertx.close(ar -> { if (pluginContext != null) {
if (ar.succeeded()) { // 停止服务器
log.info("Vert.x 关闭成功"); VertxService vertxService = pluginContext.getBean(VertxService.class);
} else { vertxService.stopServer();
log.error("Vert.x 关闭失败", ar.cause());
}
});
} }
} }
@Override @Override
protected ApplicationContext createApplicationContext() { protected ApplicationContext createApplicationContext() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext pluginContext = new AnnotationConfigApplicationContext() {
applicationContext.setClassLoader(getWrapper().getPluginClassLoader()); @Override
applicationContext.refresh(); 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; package cn.iocoder.yudao.module.iot.plugin.http.config;
import org.pf4j.DefaultPluginManager; import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
import org.pf4j.PluginWrapper; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/**
* 插件与独立运行都可复用的配置类
*/
@Slf4j
@Configuration @Configuration
public class HttpVertxPluginConfiguration { public class HttpVertxPluginConfiguration {
@Bean(initMethod = "start") /**
public HttpVertxPlugin httpVertxPlugin() { * 可在 application.yml 中配置默认端口 8092
PluginWrapper pluginWrapper = new PluginWrapper(new DefaultPluginManager(), null, null, null); */
return new HttpVertxPlugin(pluginWrapper); @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) { public void handle(RoutingContext ctx) {
String productKey = ctx.pathParam("productKey"); String productKey = ctx.pathParam("productKey");
String deviceName = ctx.pathParam("deviceName"); String deviceName = ctx.pathParam("deviceName");
RequestBody requestBody = ctx.body();
RequestBody requestBody = ctx.body();
JSONObject jsonData; JSONObject jsonData;
try { try {
jsonData = JSONUtil.parseObj(requestBody.asJsonObject()); jsonData = JSONUtil.parseObj(requestBody.asJsonObject());
} catch (Exception e) { } catch (Exception e) {
JSONObject res = createResponseJson( log.error("[HttpVertxHandler] 请求数据解析失败", e);
400, ctx.response().setStatusCode(400)
new JSONObject(),
null,
"请求数据不是合法的 JSON 格式: " + e.getMessage(),
"thing.event.property.post",
"1.0");
ctx.response()
.setStatusCode(400)
.putHeader("Content-Type", "application/json; charset=UTF-8") .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; return;
} }
String id = jsonData.getStr("id", null); String id = jsonData.getStr("id");
try { try {
// 调用主程序的接口保存数据 IotDevicePropertyReportReqDTO reportReqDTO = IotDevicePropertyReportReqDTO.builder()
IotDevicePropertyReportReqDTO createDTO = IotDevicePropertyReportReqDTO.builder()
.productKey(productKey) .productKey(productKey)
.deviceName(deviceName) .deviceName(deviceName)
.params(jsonData) // TODO 芋艿这块要优化 .params(jsonData)
.build(); .build();
deviceDataApi.reportDevicePropertyData(createDTO);
// 构造成功响应内容 deviceDataApi.reportDevicePropertyData(reportReqDTO);
JSONObject successRes = createResponseJson(
200,
new JSONObject(),
id,
"success",
"thing.event.property.post",
"1.0");
ctx.response() ctx.response()
.setStatusCode(200) .setStatusCode(200)
.putHeader("Content-Type", "application/json; charset=UTF-8") .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) { } catch (Exception e) {
JSONObject errorRes = createResponseJson( log.error("[HttpVertxHandler] 上报属性数据失败", e);
500,
new JSONObject(),
id,
"The format of result is error!",
"thing.event.property.post",
"1.0");
ctx.response() ctx.response()
.setStatusCode(500) .setStatusCode(500)
.putHeader("Content-Type", "application/json; charset=UTF-8") .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());
} }
} }
/** private JSONObject createResponseJson(int code, JSONObject data, String id,
* 创建标准化的响应 JSON 对象 String message, String method, String version) {
*
* @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) {
JSONObject res = new JSONObject(); JSONObject res = new JSONObject();
res.set("code", code); res.set("code", code);
res.set("data", data != null ? data : new JSONObject()); res.set("data", data != null ? data : new JSONObject());