【功能完善】IoT: 删除旧版 HTTP 插件,重构 HttpPlugin 以支持独立启动,新增 VertxService 管理 HTTP 服务器逻辑,优化代码结构和异常处理,更新相关配置以提升插件性能和可维护性。
This commit is contained in:
parent
698cec92bd
commit
88ef8ba2e3
Binary file not shown.
Binary file not shown.
|
@ -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] 独立模式启动完成");
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in New Issue