【代码优化】IoT: 插件管理
This commit is contained in:
parent
c9904f0994
commit
0e20ca342f
|
@ -0,0 +1 @@
|
||||||
|
http-plugin@0.0.1
|
Binary file not shown.
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
|
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoImportReqVO;
|
||||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
|
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
|
||||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoRespVO;
|
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoRespVO;
|
||||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
|
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
|
||||||
|
@ -16,11 +17,8 @@ import jakarta.validation.Valid;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
||||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
|
|
||||||
|
|
||||||
@Tag(name = "管理后台 - IoT 插件信息")
|
@Tag(name = "管理后台 - IoT 插件信息")
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -72,16 +70,10 @@ public class PluginInfoController {
|
||||||
return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class));
|
return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/update-jar",
|
@PostMapping("/upload-file")
|
||||||
method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题
|
@Operation(summary = "上传插件文件")
|
||||||
@Operation(summary = "上传Jar包")
|
public CommonResult<Boolean> uploadFile(@Valid PluginInfoImportReqVO reqVO) {
|
||||||
public CommonResult<Boolean> uploadJar(
|
pluginInfoService.uploadFile(reqVO.getId(), reqVO.getFile());
|
||||||
@RequestParam("id") Long id,
|
|
||||||
@RequestParam("jar") MultipartFile file) throws Exception {
|
|
||||||
if (file.isEmpty()) {
|
|
||||||
throw exception(FILE_IS_EMPTY);
|
|
||||||
}
|
|
||||||
pluginInfoService.uploadJar(id, file);
|
|
||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@Schema(description = "管理后台 - IoT 插件上传 Request VO")
|
||||||
|
@Data
|
||||||
|
public class PluginInfoImportReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "插件文件不能为空")
|
||||||
|
private MultipartFile file;
|
||||||
|
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
package cn.iocoder.yudao.module.iot.framework.plugin;
|
|
||||||
|
|
||||||
import org.pf4j.spring.SpringPluginManager;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.DependsOn;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class SpringConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@DependsOn("serviceRegistryInitializedMarker")
|
|
||||||
public SpringPluginManager pluginManager() {
|
|
||||||
return new SpringPluginManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,35 +1,36 @@
|
||||||
package cn.iocoder.yudao.module.iot.framework.plugin;
|
package cn.iocoder.yudao.module.iot.framework.plugin;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
|
|
||||||
import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
|
import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
|
||||||
|
import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.pf4j.spring.SpringPluginManager;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Configuration
|
@Configuration
|
||||||
public class ServiceRegistryConfiguration {
|
public class UnifiedConfiguration {
|
||||||
|
|
||||||
|
private static final String SERVICE_REGISTRY_INITIALIZED_MARKER = "serviceRegistryInitializedMarker";
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private DeviceDataApi deviceDataApi;
|
private DeviceDataApi deviceDataApi;
|
||||||
|
|
||||||
@PostConstruct
|
@Bean(SERVICE_REGISTRY_INITIALIZED_MARKER)
|
||||||
public void init() {
|
public Object serviceRegistryInitializedMarker() {
|
||||||
// 将主程序中的 DeviceDataApi 实例注册到 ServiceRegistry
|
|
||||||
ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi);
|
ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi);
|
||||||
log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]");
|
log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]");
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成
|
|
||||||
*/
|
|
||||||
@Bean("serviceRegistryInitializedMarker") // TODO @haohao:1)这个名字,可以搞个 public static final 常量;2)是不是 conditionBefore 啥
|
|
||||||
public Object serviceRegistryInitializedMarker() {
|
|
||||||
// 返回任意对象即可,这里返回 null 都可以,但最好返回个实际对象
|
|
||||||
return new Object();
|
return new Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER)
|
||||||
|
public SpringPluginManager pluginManager() {
|
||||||
|
log.info("[init][实例化 SpringPluginManager]");
|
||||||
|
return new SpringPluginManager();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IoT 插件信息 Service 接口
|
* IoT 插件信息 Service 接口
|
||||||
*
|
*
|
||||||
|
@ -58,7 +60,7 @@ public interface PluginInfoService {
|
||||||
* @param id 插件id
|
* @param id 插件id
|
||||||
* @param file 文件
|
* @param file 文件
|
||||||
*/
|
*/
|
||||||
void uploadJar(Long id, MultipartFile file);
|
void uploadFile(Long id, MultipartFile file);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新插件的状态
|
* 更新插件的状态
|
||||||
|
@ -67,4 +69,11 @@ public interface PluginInfoService {
|
||||||
* @param status 状态
|
* @param status 状态
|
||||||
*/
|
*/
|
||||||
void updatePluginStatus(Long id, Integer status);
|
void updatePluginStatus(Long id, Integer status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获得启用的插件列表
|
||||||
|
*
|
||||||
|
* @return 插件列表-插件id
|
||||||
|
*/
|
||||||
|
List<String> getEnabledPlugins();
|
||||||
}
|
}
|
|
@ -1,17 +1,13 @@
|
||||||
package cn.iocoder.yudao.module.iot.service.plugininfo;
|
package cn.iocoder.yudao.module.iot.service.plugininfo;
|
||||||
|
|
||||||
import cn.hutool.core.io.IoUtil;
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
|
||||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
|
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO;
|
||||||
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
|
import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO;
|
||||||
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO;
|
||||||
import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper;
|
import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper;
|
||||||
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
|
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.PluginDescriptor;
|
import org.pf4j.PluginDescriptor;
|
||||||
import org.pf4j.PluginState;
|
import org.pf4j.PluginState;
|
||||||
|
@ -22,11 +18,13 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.jar.JarEntry;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
||||||
|
@ -47,9 +45,6 @@ public class PluginInfoServiceImpl implements PluginInfoService {
|
||||||
@Resource
|
@Resource
|
||||||
private SpringPluginManager pluginManager;
|
private SpringPluginManager pluginManager;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private FileApi fileApi;
|
|
||||||
|
|
||||||
@Value("${pf4j.pluginsDir}")
|
@Value("${pf4j.pluginsDir}")
|
||||||
private String pluginsDir;
|
private String pluginsDir;
|
||||||
|
|
||||||
|
@ -95,6 +90,19 @@ public class PluginInfoServiceImpl implements PluginInfoService {
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
pluginInfoMapper.deleteById(id);
|
pluginInfoMapper.deleteById(id);
|
||||||
|
// 删除插件文件
|
||||||
|
Executors.newSingleThreadExecutor().submit(() -> {
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
|
||||||
|
File file = new File(pluginsDir, pluginInfoDO.getFile());
|
||||||
|
if (file.exists() && !file.delete()) {
|
||||||
|
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFile());
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFile(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PluginInfoDO validatePluginInfoExists(Long id) {
|
private PluginInfoDO validatePluginInfoExists(Long id) {
|
||||||
|
@ -116,73 +124,100 @@ public class PluginInfoServiceImpl implements PluginInfoService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uploadJar(Long id, MultipartFile file) {
|
public void uploadFile(Long id, MultipartFile file) {
|
||||||
// 1. 校验存在
|
// 1. 校验插件信息是否存在
|
||||||
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
|
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
|
||||||
|
|
||||||
// 2. 判断文件名称与插件 ID 是否匹配
|
// 2. 获取插件 ID
|
||||||
String pluginId = pluginInfoDo.getPluginId();
|
String pluginId = pluginInfoDo.getPluginId();
|
||||||
|
|
||||||
// 3. 停止卸载旧的插件
|
// 3. 停止并卸载旧的插件
|
||||||
// 3.1. 获取插件信息
|
stopAndUnloadPlugin(pluginId);
|
||||||
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
|
||||||
if (plugin != null) {
|
// 4. 上传新的插件文件
|
||||||
// 3.2. 如果插件状态是启动的,停止插件
|
String pluginIdNew = uploadAndLoadNewPlugin(file);
|
||||||
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
|
||||||
pluginManager.stopPlugin(pluginId);
|
// 5. 更新插件启用状态文件
|
||||||
}
|
updatePluginStatusFile(pluginIdNew, false);
|
||||||
// 3.3. 卸载插件
|
|
||||||
pluginManager.unloadPlugin(pluginId);
|
// 6. 更新插件信息
|
||||||
|
updatePluginInfo(pluginInfoDo, pluginIdNew, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 上传插件
|
// 停止并卸载旧的插件
|
||||||
String pluginIdNew;
|
private void stopAndUnloadPlugin(String pluginId) {
|
||||||
|
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
||||||
|
if (plugin != null) {
|
||||||
|
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
||||||
|
pluginManager.stopPlugin(pluginId); // 停止插件
|
||||||
|
}
|
||||||
|
pluginManager.unloadPlugin(pluginId); // 卸载插件
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传并加载新的插件文件
|
||||||
|
private String uploadAndLoadNewPlugin(MultipartFile file) {
|
||||||
|
Path pluginsPath = Paths.get(pluginsDir);
|
||||||
try {
|
try {
|
||||||
String path = fileApi.createFile(pluginsDir, IoUtil.readBytes(file.getInputStream()));
|
if (!Files.exists(pluginsPath)) {
|
||||||
Path pluginPath = Path.of(path);
|
Files.createDirectories(pluginsPath); // 创建插件目录
|
||||||
pluginIdNew = pluginManager.loadPlugin(pluginPath);
|
}
|
||||||
|
String filename = file.getOriginalFilename();
|
||||||
|
if (filename != null) {
|
||||||
|
Path jarPath = pluginsPath.resolve(filename);
|
||||||
|
Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
|
||||||
|
return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
|
||||||
|
} else {
|
||||||
|
throw exception(PLUGIN_INSTALL_FAILED);
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw exception(PLUGIN_INSTALL_FAILED);
|
throw exception(PLUGIN_INSTALL_FAILED);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新插件状态文件
|
||||||
|
private void updatePluginStatusFile(String pluginIdNew, boolean isEnabled) {
|
||||||
|
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(pluginIdNew);
|
PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew);
|
||||||
if (pluginWrapper == null) {
|
if (pluginWrapper == null) {
|
||||||
throw exception(PLUGIN_INSTALL_FAILED);
|
throw exception(PLUGIN_INSTALL_FAILED);
|
||||||
}
|
}
|
||||||
|
String pluginInfo = pluginIdNew + "@" + pluginWrapper.getDescriptor().getVersion();
|
||||||
|
List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath)
|
||||||
|
: new ArrayList<>();
|
||||||
|
List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath)
|
||||||
|
: new ArrayList<>();
|
||||||
|
|
||||||
// 5. 读取配置文件和脚本
|
if (!targetLines.contains(pluginInfo)) {
|
||||||
String configJson = "";
|
targetLines.add(pluginInfo);
|
||||||
String script = "";
|
Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE,
|
||||||
try (JarFile jarFile = new JarFile(pluginWrapper.getPluginPath().toFile())) {
|
StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
// 5.1 获取config文件在jar包中的路径
|
|
||||||
String configFile = "classes/config.json";
|
|
||||||
JarEntry configEntry = jarFile.getJarEntry(configFile);
|
|
||||||
|
|
||||||
if (configEntry != null) {
|
|
||||||
// 5.2 读取配置文件
|
|
||||||
configJson = IoUtil.readUtf8(jarFile.getInputStream(configEntry));
|
|
||||||
log.info("configJson:{}", configJson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.3 读取script.js脚本
|
if (oppositeLines.contains(pluginInfo)) {
|
||||||
String scriptFile = "classes/script.js";
|
oppositeLines.remove(pluginInfo);
|
||||||
JarEntry scriptEntity = jarFile.getJarEntry(scriptFile);
|
Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE,
|
||||||
if (scriptEntity != null) {
|
StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
// 5.4 读取脚本文件
|
|
||||||
script = IoUtil.readUtf8(jarFile.getInputStream(scriptEntity));
|
|
||||||
log.info("script:{}", script);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
throw exception(PLUGIN_INSTALL_FAILED);
|
throw exception(PLUGIN_INSTALL_FAILED);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新插件信息
|
||||||
|
private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginIdNew, MultipartFile file) {
|
||||||
pluginInfoDo.setPluginId(pluginIdNew);
|
pluginInfoDo.setPluginId(pluginIdNew);
|
||||||
pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
|
pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus());
|
||||||
pluginInfoDo.setFile(file.getOriginalFilename());
|
pluginInfoDo.setFile(file.getOriginalFilename());
|
||||||
pluginInfoDo.setConfigSchema(configJson);
|
pluginInfoDo.setScript("");
|
||||||
pluginInfoDo.setScript(script);
|
|
||||||
|
|
||||||
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
|
PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginIdNew).getDescriptor();
|
||||||
|
pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription());
|
||||||
pluginInfoDo.setVersion(pluginDescriptor.getVersion());
|
pluginInfoDo.setVersion(pluginDescriptor.getVersion());
|
||||||
pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
|
pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription());
|
||||||
pluginInfoMapper.updateById(pluginInfoDo);
|
pluginInfoMapper.updateById(pluginInfoDo);
|
||||||
|
@ -190,52 +225,50 @@ public class PluginInfoServiceImpl implements PluginInfoService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updatePluginStatus(Long id, Integer status) {
|
public void updatePluginStatus(Long id, Integer status) {
|
||||||
// 1. 校验存在
|
// 1. 校验插件信息是否存在
|
||||||
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
|
PluginInfoDO pluginInfoDo = validatePluginInfoExists(id);
|
||||||
|
|
||||||
// 插件状态无效
|
// 2. 校验插件状态是否有效
|
||||||
if (!IotPluginStatusEnum.contains(status)) {
|
if (!IotPluginStatusEnum.contains(status)) {
|
||||||
throw exception(PLUGIN_STATUS_INVALID);
|
throw exception(PLUGIN_STATUS_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. 获取插件ID和插件实例
|
||||||
String pluginId = pluginInfoDo.getPluginId();
|
String pluginId = pluginInfoDo.getPluginId();
|
||||||
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
PluginWrapper plugin = pluginManager.getPlugin(pluginId);
|
||||||
|
|
||||||
|
// 4. 根据状态更新插件
|
||||||
if (plugin != null) {
|
if (plugin != null) {
|
||||||
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) && plugin.getPluginState() != PluginState.STARTED) {
|
// 4.1 如果目标状态是运行且插件未启动,则启动插件
|
||||||
// 启动插件
|
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
|
||||||
|
&& plugin.getPluginState() != PluginState.STARTED) {
|
||||||
pluginManager.startPlugin(pluginId);
|
pluginManager.startPlugin(pluginId);
|
||||||
} else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) && plugin.getPluginState() == PluginState.STARTED) {
|
updatePluginStatusFile(pluginId, true); // 更新插件状态文件为启用
|
||||||
// 停止插件
|
}
|
||||||
|
// 4.2 如果目标状态是停止且插件已启动,则停止插件
|
||||||
|
else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
|
||||||
|
&& plugin.getPluginState() == PluginState.STARTED) {
|
||||||
pluginManager.stopPlugin(pluginId);
|
pluginManager.stopPlugin(pluginId);
|
||||||
|
updatePluginStatusFile(pluginId, false); // 更新插件状态文件为禁用
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 已经停止,未获取到插件
|
// 5. 插件不存在且状态为停止,抛出异常
|
||||||
if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
|
if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) {
|
||||||
throw exception(PLUGIN_STATUS_INVALID);
|
throw exception(PLUGIN_STATUS_INVALID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. 更新数据库中的插件状态
|
||||||
pluginInfoDo.setStatus(status);
|
pluginInfoDo.setStatus(status);
|
||||||
pluginInfoMapper.updateById(pluginInfoDo);
|
pluginInfoMapper.updateById(pluginInfoDo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @PostConstruct
|
@Override
|
||||||
// public void init() {
|
public List<String> getEnabledPlugins() {
|
||||||
// Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS);
|
return pluginInfoMapper.selectList().stream()
|
||||||
// }
|
.filter(pluginInfoDO -> IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus()))
|
||||||
//
|
.map(PluginInfoDO::getPluginId)
|
||||||
// @SneakyThrows
|
.toList();
|
||||||
// private void startPlugins() {
|
}
|
||||||
// for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) {
|
|
||||||
// if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// log.info("start plugin:{}", pluginInfoDO.getPluginId());
|
|
||||||
// try {
|
|
||||||
// pluginManager.startPlugin(pluginInfoDO.getPluginId());
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// log.error("start plugin error", e);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package cn.iocoder.yudao.module.iot.plugin;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.iot.api.Greeting;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.pf4j.Extension;
|
||||||
|
import org.pf4j.Plugin;
|
||||||
|
import org.pf4j.PluginWrapper;
|
||||||
|
import org.pf4j.RuntimeMode;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打招呼 测试用例
|
||||||
|
*/
|
||||||
|
public class WelcomePlugin extends Plugin {
|
||||||
|
|
||||||
|
private HttpServer server;
|
||||||
|
|
||||||
|
public WelcomePlugin(PluginWrapper wrapper) {
|
||||||
|
super(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
System.out.println("WelcomePlugin.start()");
|
||||||
|
// for testing the development mode
|
||||||
|
if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
|
||||||
|
System.out.println(StringUtils.upperCase("WelcomePlugin"));
|
||||||
|
}
|
||||||
|
startHttpServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
System.out.println("WelcomePlugin.stop()");
|
||||||
|
stopHttpServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startHttpServer() {
|
||||||
|
try {
|
||||||
|
server = HttpServer.create(new InetSocketAddress(9081), 0);
|
||||||
|
server.createContext("/", exchange -> {
|
||||||
|
String response = "Welcome to PF4J HTTP Server";
|
||||||
|
exchange.sendResponseHeaders(200, response.getBytes().length);
|
||||||
|
OutputStream os = exchange.getResponseBody();
|
||||||
|
os.write(response.getBytes());
|
||||||
|
os.close();
|
||||||
|
});
|
||||||
|
server.setExecutor(null);
|
||||||
|
server.start();
|
||||||
|
System.out.println("HTTP server started on port 9081");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopHttpServer() {
|
||||||
|
if (server != null) {
|
||||||
|
server.stop(0);
|
||||||
|
System.out.println("HTTP server stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public static class WelcomeGreeting implements Greeting {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGreeting() {
|
||||||
|
return "Welcome to PF4J";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,7 @@
|
||||||
<plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpPlugin</plugin.class>
|
<plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpPlugin</plugin.class>
|
||||||
<plugin.version>0.0.1</plugin.version>
|
<plugin.version>0.0.1</plugin.version>
|
||||||
<plugin.provider>ahh</plugin.provider>
|
<plugin.provider>ahh</plugin.provider>
|
||||||
|
<plugin.description>http-plugin-0.0.1</plugin.description>
|
||||||
<plugin.dependencies/>
|
<plugin.dependencies/>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
@ -104,6 +105,7 @@
|
||||||
<Plugin-Class>${plugin.class}</Plugin-Class>
|
<Plugin-Class>${plugin.class}</Plugin-Class>
|
||||||
<Plugin-Version>${plugin.version}</Plugin-Version>
|
<Plugin-Version>${plugin.version}</Plugin-Version>
|
||||||
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
|
<Plugin-Provider>${plugin.provider}</Plugin-Provider>
|
||||||
|
<Plugin-Description>${plugin.description}</Plugin-Description>
|
||||||
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
|
<Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
|
||||||
</manifestEntries>
|
</manifestEntries>
|
||||||
</archive>
|
</archive>
|
||||||
|
|
Loading…
Reference in New Issue