【功能完善】IoT: 新增插件启动逻辑,重构插件状态管理,删除不再使用的状态文件更新方法

This commit is contained in:
安浩浩 2025-01-08 17:59:32 +08:00
parent d39e2c1bc4
commit 0af6d5a758
9 changed files with 80 additions and 60 deletions

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -11,7 +13,8 @@ public class PluginInfoPageReqVO extends PageParam {
@Schema(description = "插件名称", example = "http")
private String name;
@Schema(description = "状态")
@Schema(description = "状态", example = "1")
@InEnum(IotPluginStatusEnum.class)
private Integer status;
}

View File

@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.iot.framework.plugin;
import java.util.List;
import javax.annotation.Resource;
import org.pf4j.spring.SpringPluginManager;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
@Component
@Slf4j
public class PluginStart implements ApplicationRunner {
@Resource
private PluginInfoService pluginInfoService;
@Resource
private SpringPluginManager pluginManager;
@Override
public void run(ApplicationArguments args) {
TenantUtils.executeIgnore(() -> { // 1. 忽略租户上下文执行
List<PluginInfoDO> pluginInfoList = pluginInfoService
.getPluginInfoListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); // 2. 获取运行中的插件列表
if (CollUtil.isEmpty(pluginInfoList)) { // 3. 检查插件列表是否为空
log.info("[run] 没有需要启动的插件"); // 4. 日志记录没有插件需要启动
return;
}
pluginInfoList.forEach(pluginInfo -> { // 5. 使用lambda表达式遍历插件列表
try {
log.info("[run][启动插件] pluginKey = {}", pluginInfo.getPluginKey()); // 6. 日志记录插件启动信息
pluginManager.startPlugin(pluginInfo.getPluginKey()); // 7. 启动插件
} catch (Exception e) {
log.error("[run][启动插件失败] pluginKey = {}", pluginInfo.getPluginKey(), e); // 8. 记录启动失败的日志
}
});
});
}
}

View File

@ -31,7 +31,13 @@ public class UnifiedConfiguration {
@DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER)
public SpringPluginManager pluginManager() {
log.info("[init][实例化 SpringPluginManager]");
SpringPluginManager springPluginManager = new SpringPluginManager();
SpringPluginManager springPluginManager = new SpringPluginManager() {
@Override
public void startPlugins() {
// 禁用插件启动避免插件启动时启动所有插件
log.info("[init][禁用默认启动所有插件]");
}
};
springPluginManager.addPluginStateListener(new CustomPluginStateListener());
return springPluginManager;
}

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import jakarta.validation.Valid;
import org.springframework.web.multipart.MultipartFile;
@ -66,7 +67,7 @@ public interface PluginInfoService {
* 更新插件的状态
*
* @param id 插件id
* @param status 状态
* @param status 状态 {@link IotPluginStatusEnum}
*/
void updatePluginStatus(Long id, Integer status);
@ -80,7 +81,7 @@ public interface PluginInfoService {
/**
* 根据状态获得插件信息列表
*
* @param status 状态
* @param status 状态 {@link IotPluginStatusEnum}
* @return 插件信息列表
*/
List<PluginInfoDO> getPluginInfoListByStatus(Integer status);

View File

@ -102,7 +102,6 @@ public class PluginInfoServiceImpl implements PluginInfoService {
// 3 上传新的插件文件,更新插件启用状态文件
String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file);
pluginInstanceService.updatePluginStatusFile(pluginKeyNew, false);
// 4. 更新插件信息
updatePluginInfo(pluginInfoDo, pluginKeyNew, file);

View File

@ -37,14 +37,6 @@ public interface PluginInstanceService {
*/
String uploadAndLoadNewPlugin(MultipartFile file);
/**
* 更新插件状态文件
*
* @param pluginKeyNew 插件标识符
* @param isEnabled 是否启用
*/
void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled);
/**
* 更新插件状态
*

View File

@ -21,11 +21,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -119,44 +115,6 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
return pluginKeyNew;
}
@Override
public void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) {
// TODO @haohao疑问这里写 enabled.txt disabled.txt 的目的是啥哈
// pf4j 的插件状态文件需要 2 个文件一个 enabled.txt 一个 disabled.txt
Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt");
Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt");
Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath;
Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath;
try {
PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew);
if (pluginWrapper == null) {
throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED);
}
List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath)
: new ArrayList<>();
List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath)
: new ArrayList<>();
if (!targetLines.contains(pluginKeyNew)) {
targetLines.add(pluginKeyNew);
Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
log.info("已添加插件 {} 到 {}", pluginKeyNew, targetFilePath.getFileName());
}
if (oppositeLines.contains(pluginKeyNew)) {
oppositeLines.remove(pluginKeyNew);
Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
log.info("已从 {} 移除插件 {}", oppositeFilePath.getFileName(), pluginKeyNew);
}
} catch (IOException e) {
log.error("[updatePluginStatusFile][更新插件状态文件失败]", e);
throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
}
}
@Override
public void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status) {
String pluginKey = pluginInfoDo.getPluginKey();
@ -167,14 +125,12 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
&& plugin.getPluginState() != PluginState.STARTED) {
pluginManager.startPlugin(pluginKey);
updatePluginStatusFile(pluginKey, true);
log.info("已启动插件: {}", pluginKey);
}
// 停止插件
else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
&& plugin.getPluginState() == PluginState.STARTED) {
pluginManager.stopPlugin(pluginKey);
updatePluginStatusFile(pluginKey, false);
log.info("已停止插件: {}", pluginKey);
}
} else {
@ -208,7 +164,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey);
if (pluginInfo == null) {
// 4.2 插件信息不存在记录错误并跳过
log.error("插件信息不存在,插件包标识符 = {}", pluginKey);
log.error("插件信息不存在,pluginKey = {}", pluginKey);
continue;
}

View File

@ -5,12 +5,15 @@ import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
import org.pf4j.spring.SpringPlugin;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HttpVertxPlugin extends Plugin {
public class HttpVertxPlugin extends SpringPlugin {
private static final int PORT = 8092;
private Vertx vertx;
@ -67,4 +70,13 @@ public class HttpVertxPlugin extends Plugin {
});
}
}
@Override
protected ApplicationContext createApplicationContext() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
applicationContext.refresh();
return applicationContext;
}
}