【代码优化】重构 HTTP插件并添加自动配置
This commit is contained in:
parent
269dec1b2e
commit
7bfa830628
Binary file not shown.
|
@ -40,9 +40,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
|
|||
@Slf4j
|
||||
public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||
|
||||
// TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的
|
||||
// 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件;
|
||||
// 那就 mac@uuid ?
|
||||
// TODO @haohao:mac@uuid
|
||||
public static final String MAIN_ID = IdUtil.fastSimpleUUID();
|
||||
|
||||
@Resource
|
||||
|
@ -60,32 +58,31 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
|||
@Override
|
||||
public void stopAndUnloadPlugin(String pluginKey) {
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
|
||||
// TODO @haohao:改成 if return 会更简洁一点;
|
||||
if (plugin != null) {
|
||||
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
||||
pluginManager.stopPlugin(pluginKey); // 停止插件
|
||||
log.info("已停止插件: {}", pluginKey);
|
||||
}
|
||||
pluginManager.unloadPlugin(pluginKey); // 卸载插件
|
||||
log.info("已卸载插件: {}", pluginKey);
|
||||
} else {
|
||||
if (plugin == null) {
|
||||
log.warn("插件不存在或已卸载: {}", pluginKey);
|
||||
return;
|
||||
}
|
||||
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
||||
pluginManager.stopPlugin(pluginKey); // 停止插件
|
||||
log.info("已停止插件: {}", pluginKey);
|
||||
}
|
||||
pluginManager.unloadPlugin(pluginKey); // 卸载插件
|
||||
log.info("已卸载插件: {}", pluginKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePluginFile(PluginInfoDO pluginInfoDO) {
|
||||
File file = new File(pluginsDir, pluginInfoDO.getFileName());
|
||||
// TODO @haohao:改成 if return 会更简洁一点;
|
||||
if (file.exists()) {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
|
||||
if (!file.delete()) {
|
||||
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
|
||||
if (!file.delete()) {
|
||||
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,25 +117,25 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
|||
String pluginKey = pluginInfoDo.getPluginKey();
|
||||
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
|
||||
|
||||
// TODO @haohao:改成 if return 会更简洁一点;
|
||||
if (plugin != null) {
|
||||
// 启动插件
|
||||
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
|
||||
&& plugin.getPluginState() != PluginState.STARTED) {
|
||||
pluginManager.startPlugin(pluginKey);
|
||||
log.info("已启动插件: {}", pluginKey);
|
||||
}
|
||||
// 停止插件
|
||||
else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
|
||||
&& plugin.getPluginState() == PluginState.STARTED) {
|
||||
pluginManager.stopPlugin(pluginKey);
|
||||
log.info("已停止插件: {}", pluginKey);
|
||||
}
|
||||
} else {
|
||||
if (plugin == null) {
|
||||
// 插件不存在且状态为停止,抛出异常
|
||||
if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
|
||||
throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动插件
|
||||
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
|
||||
&& plugin.getPluginState() != PluginState.STARTED) {
|
||||
pluginManager.startPlugin(pluginKey);
|
||||
log.info("已启动插件: {}", pluginKey);
|
||||
}
|
||||
// 停止插件
|
||||
else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
|
||||
&& plugin.getPluginState() == PluginState.STARTED) {
|
||||
pluginManager.stopPlugin(pluginKey);
|
||||
log.info("已停止插件: {}", pluginKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,10 +149,10 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
|||
Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream()
|
||||
.collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity()));
|
||||
|
||||
// 1.3 获取本机 IP 和 MAC 地址
|
||||
// 1.3 获取本机 IP 和 MAC 地址,mac@uuid
|
||||
String ip = NetUtil.getLocalhostStr();
|
||||
String mac = NetUtil.getLocalMacAddress();
|
||||
String mainId = MAIN_ID + "-" + mac;
|
||||
String mainId = mac + "@" + MAIN_ID;
|
||||
|
||||
// 2. 遍历插件列表,并保存为插件实例
|
||||
for (PluginWrapper plugin : plugins) {
|
||||
|
@ -173,14 +170,21 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
|||
pluginInfo.getId());
|
||||
if (pluginInstance == null) {
|
||||
// 4.4 如果插件实例不存在,则创建
|
||||
pluginInstance = PluginInstanceDO.builder().pluginId(pluginInfo.getId()).mainId(MAIN_ID + "-" + mac)
|
||||
.ip(ip).port(port).heartbeatAt(System.currentTimeMillis()).build();
|
||||
pluginInstance = PluginInstanceDO.builder()
|
||||
.pluginId(pluginInfo.getId())
|
||||
.mainId(MAIN_ID + "-" + mac)
|
||||
.ip(ip)
|
||||
.port(port)
|
||||
.heartbeatAt(System.currentTimeMillis())
|
||||
.build();
|
||||
pluginInstanceMapper.insert(pluginInstance);
|
||||
} else {
|
||||
// 2.2 情况二:如果存在,则更新 heartbeatAt
|
||||
// TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有)
|
||||
pluginInstance.setHeartbeatAt(System.currentTimeMillis());
|
||||
pluginInstanceMapper.updateById(pluginInstance);
|
||||
PluginInstanceDO updatePluginInstance = PluginInstanceDO.builder()
|
||||
.id(pluginInstance.getId())
|
||||
.heartbeatAt(System.currentTimeMillis())
|
||||
.build();
|
||||
pluginInstanceMapper.updateById(updatePluginInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,24 +5,30 @@ import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
|
|||
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceEventReportReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.api.device.dto.IotDevicePropertyReportReqDTO;
|
||||
import cn.iocoder.yudao.module.iot.api.device.dto.IotDeviceStatusUpdateReqDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
// TODO @haohao:类注释,写一下,比较好
|
||||
/**
|
||||
* 用于通过 {@link RestTemplate} 向远程 IoT 服务发送设备数据相关的请求,
|
||||
* 包括设备状态更新、事件数据上报、属性数据上报等操作。
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DeviceDataApiClient implements DeviceDataApi {
|
||||
|
||||
/**
|
||||
* 用于发送 HTTP 请求的工具
|
||||
*/
|
||||
private final RestTemplate restTemplate;
|
||||
private final String deviceDataUrl;
|
||||
|
||||
// 可以通过构造器把 RestTemplate 和 baseUrl 注入进来
|
||||
// TODO @haohao:可以用 lombok 简化
|
||||
public DeviceDataApiClient(RestTemplate restTemplate, String deviceDataUrl) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.deviceDataUrl = deviceDataUrl;
|
||||
}
|
||||
/**
|
||||
* 远程 IoT 服务的基础 URL
|
||||
* 例如:http://127.0.0.1:8080
|
||||
*/
|
||||
private final String deviceDataUrl;
|
||||
|
||||
// TODO @haohao:返回结果,不用 CommonResult 哈。
|
||||
@Override
|
||||
|
@ -43,17 +49,51 @@ public class DeviceDataApiClient implements DeviceDataApi {
|
|||
return doPost(url, reportReqDTO, "reportDevicePropertyData");
|
||||
}
|
||||
|
||||
// TODO @haohao:未来可能有 get 类型哈
|
||||
|
||||
/**
|
||||
* 将与远程服务交互的通用逻辑抽取成一个私有方法
|
||||
* 发送 GET 请求
|
||||
*
|
||||
* @param <T> 请求体类型
|
||||
* @param url 请求 URL
|
||||
* @param requestBody 请求体
|
||||
* @param actionName 操作名称
|
||||
* @return 响应结果
|
||||
*/
|
||||
private <T> CommonResult<Boolean> doGet(String url, T requestBody, String actionName) {
|
||||
log.info("[{}] Sending request to URL: {}", actionName, url);
|
||||
try {
|
||||
CommonResult<?> response = restTemplate.getForObject(url, CommonResult.class);
|
||||
if (response != null && response.isSuccess()) {
|
||||
return success(true);
|
||||
} else {
|
||||
log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response);
|
||||
return CommonResult.error(500, "Request failed");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[{}] Error sending request to URL: {}", actionName, url, e);
|
||||
return CommonResult.error(400, "Request error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 POST 请求
|
||||
*
|
||||
* @param <T> 请求体类型
|
||||
* @param url 请求 URL
|
||||
* @param requestBody 请求体
|
||||
* @param actionName 操作名称
|
||||
* @return 响应结果
|
||||
*/
|
||||
private <T> CommonResult<Boolean> doPost(String url, T requestBody, String actionName) {
|
||||
log.info("[{}] Sending request to URL: {}", actionName, url);
|
||||
try {
|
||||
// 这里指定返回类型为 CommonResult<?>,根据后台服务返回的实际结构做调整
|
||||
restTemplate.postForObject(url, requestBody, CommonResult.class);
|
||||
// TODO @haohao:check 结果,是否成功
|
||||
return success(true);
|
||||
CommonResult<?> response = restTemplate.postForObject(url, requestBody, CommonResult.class);
|
||||
if (response != null && response.isSuccess()) {
|
||||
return success(true);
|
||||
} else {
|
||||
log.warn("[{}] Request to URL: {} failed with response: {}", actionName, url, response);
|
||||
return CommonResult.error(500, "Request failed");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[{}] Error sending request to URL: {}", actionName, url, e);
|
||||
return CommonResult.error(400, "Request error: " + e.getMessage());
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package cn.iocoder.yudao.module.iot.plugin.common.config;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
|
||||
import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
// TODO @haohao:这个最好是 autoconfiguration
|
||||
@Configuration
|
||||
public class DeviceDataApiInitializer {
|
||||
|
||||
// TODO @haohao:这个要不搞个配置类哈
|
||||
@Value("${iot.device-data.url}")
|
||||
private String deviceDataUrl;
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
// TODO haohao:如果你有更多的自定义需求,比如连接池、超时时间等,可以在这里设置
|
||||
return new RestTemplateBuilder().build();
|
||||
}
|
||||
|
||||
// TODO @haohao:不存在时,才构建
|
||||
@Bean
|
||||
public DeviceDataApi deviceDataApi(RestTemplate restTemplate) {
|
||||
return new DeviceDataApiClient(restTemplate, deviceDataUrl);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package cn.iocoder.yudao.module.iot.plugin.common.config;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
|
||||
import cn.iocoder.yudao.module.iot.plugin.common.api.DeviceDataApiClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 设备数据 API 初始化器
|
||||
*
|
||||
* @author haohao
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class YudaoDeviceDataApiAutoConfiguration {
|
||||
|
||||
|
||||
// TODO @haohao:这个要不搞个配置类哈
|
||||
@Value("${iot.device-data.url}")
|
||||
private String deviceDataUrl;
|
||||
|
||||
/**
|
||||
* 创建 RestTemplate 实例
|
||||
*
|
||||
* @return RestTemplate 实例
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
// 如果你有更多的自定义需求,比如连接池、超时时间等,可以在这里设置
|
||||
return new RestTemplateBuilder()
|
||||
.setConnectTimeout(Duration.ofMillis(5000)) // 设置连接超时时间
|
||||
.setReadTimeout(Duration.ofMillis(5000)) // 设置读取超时时间
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 DeviceDataApi 实例
|
||||
*
|
||||
* @param restTemplate RestTemplate 实例
|
||||
* @return DeviceDataApi 实例
|
||||
*/
|
||||
@Bean
|
||||
public DeviceDataApi deviceDataApi(RestTemplate restTemplate) {
|
||||
return new DeviceDataApiClient(restTemplate, deviceDataUrl);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
cn.iocoder.yudao.module.iot.plugin.common.config.YudaoDeviceDataApiAutoConfiguration
|
|
@ -11,7 +11,7 @@ import org.springframework.context.ConfigurableApplicationContext;
|
|||
* 独立运行入口
|
||||
*/
|
||||
@Slf4j
|
||||
@SpringBootApplication(scanBasePackages = "cn.iocoder.yudao.module.iot.plugin") // TODO @haohao:建议不扫描 cn.iocoder.yudao.module.iot.plugin;而是通过自动配置,初始化 common 的
|
||||
@SpringBootApplication
|
||||
public class HttpPluginSpringbootApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
@ -21,6 +21,7 @@ public class HttpPluginSpringbootApplication {
|
|||
|
||||
// 手动获取 VertxService 并启动
|
||||
// TODO @haohao:可以放在 bean 的 init 里么?
|
||||
// 会和插件模式冲突
|
||||
VertxService vertxService = context.getBean(VertxService.class);
|
||||
vertxService.startServer();
|
||||
|
||||
|
|
|
@ -21,30 +21,41 @@ public class HttpVertxPlugin extends SpringPlugin {
|
|||
|
||||
@Override
|
||||
public void start() {
|
||||
// TODO @haohao:这种最好启动中,启动完成,成对打印日志,方便定位问题
|
||||
log.info("[HttpVertxPlugin][start ...]");
|
||||
log.info("[HttpVertxPlugin][start][begin] 开始启动 HttpVertxPlugin 插件...");
|
||||
|
||||
// 1. 获取插件上下文
|
||||
ApplicationContext pluginContext = getApplicationContext();
|
||||
if (pluginContext == null) {
|
||||
log.error("[HttpVertxPlugin] pluginContext is null, start failed.");
|
||||
return;
|
||||
try {
|
||||
// 1. 获取插件上下文
|
||||
ApplicationContext pluginContext = getApplicationContext();
|
||||
if (pluginContext == null) {
|
||||
log.error("[HttpVertxPlugin][start][fail] pluginContext is null, 启动失败!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 启动 Vert.x
|
||||
VertxService vertxService = pluginContext.getBean(VertxService.class);
|
||||
vertxService.startServer();
|
||||
|
||||
log.info("[HttpVertxPlugin][start][end] 启动完成");
|
||||
} catch (Exception e) {
|
||||
log.error("[HttpVertxPlugin][start][exception] 启动过程出现异常!", e);
|
||||
}
|
||||
|
||||
// 2. 启动 Vertx
|
||||
VertxService vertxService = pluginContext.getBean(VertxService.class);
|
||||
vertxService.startServer();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
log.info("[HttpVertxPlugin][stop ...]");
|
||||
ApplicationContext pluginContext = getApplicationContext();
|
||||
if (pluginContext != null) {
|
||||
// 停止服务器
|
||||
VertxService vertxService = pluginContext.getBean(VertxService.class);
|
||||
vertxService.stopServer();
|
||||
log.info("[HttpVertxPlugin][stop][begin] 开始停止 HttpVertxPlugin 插件...");
|
||||
|
||||
try {
|
||||
ApplicationContext pluginContext = getApplicationContext();
|
||||
if (pluginContext != null) {
|
||||
// 停止服务器
|
||||
VertxService vertxService = pluginContext.getBean(VertxService.class);
|
||||
vertxService.stopServer();
|
||||
}
|
||||
|
||||
log.info("[HttpVertxPlugin][stop][end] 停止完成");
|
||||
} catch (Exception e) {
|
||||
log.error("[HttpVertxPlugin][stop][exception] 停止过程出现异常!", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,5 +79,4 @@ public class HttpVertxPlugin extends SpringPlugin {
|
|||
|
||||
return pluginContext;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -34,6 +34,10 @@ public class HttpVertxPluginConfiguration {
|
|||
|
||||
/**
|
||||
* 创建路由
|
||||
*
|
||||
* @param vertx Vertx 实例
|
||||
* @param httpVertxHandler HttpVertxHandler 实例
|
||||
* @return Router 实例
|
||||
*/
|
||||
@Bean
|
||||
public Router router(Vertx vertx, HttpVertxHandler httpVertxHandler) {
|
||||
|
@ -50,6 +54,12 @@ public class HttpVertxPluginConfiguration {
|
|||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 HttpVertxHandler 实例
|
||||
*
|
||||
* @param deviceDataApi DeviceDataApi 实例
|
||||
* @return HttpVertxHandler 实例
|
||||
*/
|
||||
@Bean
|
||||
public HttpVertxHandler httpVertxHandler(DeviceDataApi deviceDataApi) {
|
||||
return new HttpVertxHandler(deviceDataApi);
|
||||
|
@ -58,6 +68,10 @@ public class HttpVertxPluginConfiguration {
|
|||
/**
|
||||
* 定义一个 VertxService 来管理服务器启动逻辑
|
||||
* 无论是独立运行还是插件方式,都可以共用此类
|
||||
*
|
||||
* @param vertx Vertx 实例
|
||||
* @param router Router 实例
|
||||
* @return VertxService 实例
|
||||
*/
|
||||
@Bean
|
||||
public VertxService vertxService(Vertx vertx, Router router) {
|
||||
|
|
|
@ -5,3 +5,8 @@ spring:
|
|||
iot:
|
||||
device-data:
|
||||
url: http://127.0.0.1:48080
|
||||
|
||||
plugin:
|
||||
http:
|
||||
server:
|
||||
port: 8092
|
||||
|
|
Loading…
Reference in New Issue