Commit 0e20ca342f67b547fb181773f7008448fd248019
1 parent
c9904f09
【代码优化】IoT: 插件管理
Showing
10 changed files
with
252 additions
and
121 deletions
plugins/enabled.txt
| 1 | +http-plugin@0.0.1 | ... | ... |
plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar
No preview for this file type
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginInfoController.java
| ... | ... | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininfo; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | +import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoImportReqVO; | |
| 6 | 7 | import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO; |
| 7 | 8 | import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoRespVO; |
| 8 | 9 | import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO; |
| ... | ... | @@ -16,11 +17,8 @@ import jakarta.validation.Valid; |
| 16 | 17 | import org.springframework.security.access.prepost.PreAuthorize; |
| 17 | 18 | import org.springframework.validation.annotation.Validated; |
| 18 | 19 | import org.springframework.web.bind.annotation.*; |
| 19 | -import org.springframework.web.multipart.MultipartFile; | |
| 20 | 20 | |
| 21 | -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 22 | 21 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 23 | -import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY; | |
| 24 | 22 | |
| 25 | 23 | @Tag(name = "管理后台 - IoT 插件信息") |
| 26 | 24 | @RestController |
| ... | ... | @@ -72,16 +70,10 @@ public class PluginInfoController { |
| 72 | 70 | return success(BeanUtils.toBean(pageResult, PluginInfoRespVO.class)); |
| 73 | 71 | } |
| 74 | 72 | |
| 75 | - @RequestMapping(value = "/update-jar", | |
| 76 | - method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题 | |
| 77 | - @Operation(summary = "上传Jar包") | |
| 78 | - public CommonResult<Boolean> uploadJar( | |
| 79 | - @RequestParam("id") Long id, | |
| 80 | - @RequestParam("jar") MultipartFile file) throws Exception { | |
| 81 | - if (file.isEmpty()) { | |
| 82 | - throw exception(FILE_IS_EMPTY); | |
| 83 | - } | |
| 84 | - pluginInfoService.uploadJar(id, file); | |
| 73 | + @PostMapping("/upload-file") | |
| 74 | + @Operation(summary = "上传插件文件") | |
| 75 | + public CommonResult<Boolean> uploadFile(@Valid PluginInfoImportReqVO reqVO) { | |
| 76 | + pluginInfoService.uploadFile(reqVO.getId(), reqVO.getFile()); | |
| 85 | 77 | return success(true); |
| 86 | 78 | } |
| 87 | 79 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/vo/PluginInfoImportReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | +import org.springframework.web.multipart.MultipartFile; | |
| 7 | + | |
| 8 | +@Schema(description = "管理后台 - IoT 插件上传 Request VO") | |
| 9 | +@Data | |
| 10 | +public class PluginInfoImportReqVO { | |
| 11 | + | |
| 12 | + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "11546") | |
| 13 | + private Long id; | |
| 14 | + | |
| 15 | + @Schema(description = "插件文件", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 16 | + @NotNull(message = "插件文件不能为空") | |
| 17 | + private MultipartFile file; | |
| 18 | + | |
| 19 | +} | |
| 0 | 20 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.framework.plugin; | |
| 2 | - | |
| 3 | -import org.pf4j.spring.SpringPluginManager; | |
| 4 | -import org.springframework.context.annotation.Bean; | |
| 5 | -import org.springframework.context.annotation.Configuration; | |
| 6 | -import org.springframework.context.annotation.DependsOn; | |
| 7 | - | |
| 8 | -@Configuration | |
| 9 | -public class SpringConfiguration { | |
| 10 | - | |
| 11 | - @Bean | |
| 12 | - @DependsOn("serviceRegistryInitializedMarker") | |
| 13 | - public SpringPluginManager pluginManager() { | |
| 14 | - return new SpringPluginManager(); | |
| 15 | - } | |
| 16 | - | |
| 17 | -} | |
| 18 | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.framework.plugin; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | |
| 4 | 3 | import cn.iocoder.yudao.module.iot.api.ServiceRegistry; |
| 4 | +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | |
| 5 | 5 | import lombok.extern.slf4j.Slf4j; |
| 6 | +import org.pf4j.spring.SpringPluginManager; | |
| 6 | 7 | import org.springframework.context.annotation.Bean; |
| 7 | 8 | import org.springframework.context.annotation.Configuration; |
| 9 | +import org.springframework.context.annotation.DependsOn; | |
| 8 | 10 | |
| 9 | -import javax.annotation.PostConstruct; | |
| 10 | 11 | import javax.annotation.Resource; |
| 11 | 12 | |
| 12 | 13 | @Slf4j |
| 13 | 14 | @Configuration |
| 14 | -public class ServiceRegistryConfiguration { | |
| 15 | +public class UnifiedConfiguration { | |
| 16 | + | |
| 17 | + private static final String SERVICE_REGISTRY_INITIALIZED_MARKER = "serviceRegistryInitializedMarker"; | |
| 15 | 18 | |
| 16 | 19 | @Resource |
| 17 | 20 | private DeviceDataApi deviceDataApi; |
| 18 | 21 | |
| 19 | - @PostConstruct | |
| 20 | - public void init() { | |
| 21 | - // 将主程序中的 DeviceDataApi 实例注册到 ServiceRegistry | |
| 22 | + @Bean(SERVICE_REGISTRY_INITIALIZED_MARKER) | |
| 23 | + public Object serviceRegistryInitializedMarker() { | |
| 22 | 24 | ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi); |
| 23 | 25 | log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]"); |
| 26 | + return new Object(); | |
| 24 | 27 | } |
| 25 | 28 | |
| 26 | - /** | |
| 27 | - * 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成 | |
| 28 | - */ | |
| 29 | - @Bean("serviceRegistryInitializedMarker") // TODO @haohao:1)这个名字,可以搞个 public static final 常量;2)是不是 conditionBefore 啥 | |
| 30 | - public Object serviceRegistryInitializedMarker() { | |
| 31 | - // 返回任意对象即可,这里返回 null 都可以,但最好返回个实际对象 | |
| 32 | - return new Object(); | |
| 29 | + @Bean | |
| 30 | + @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER) | |
| 31 | + public SpringPluginManager pluginManager() { | |
| 32 | + log.info("[init][实例化 SpringPluginManager]"); | |
| 33 | + return new SpringPluginManager(); | |
| 33 | 34 | } |
| 34 | 35 | |
| 35 | 36 | } |
| 36 | 37 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoService.java
| ... | ... | @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; |
| 7 | 7 | import jakarta.validation.Valid; |
| 8 | 8 | import org.springframework.web.multipart.MultipartFile; |
| 9 | 9 | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 10 | 12 | /** |
| 11 | 13 | * IoT 插件信息 Service 接口 |
| 12 | 14 | * |
| ... | ... | @@ -58,7 +60,7 @@ public interface PluginInfoService { |
| 58 | 60 | * @param id 插件id |
| 59 | 61 | * @param file 文件 |
| 60 | 62 | */ |
| 61 | - void uploadJar(Long id, MultipartFile file); | |
| 63 | + void uploadFile(Long id, MultipartFile file); | |
| 62 | 64 | |
| 63 | 65 | /** |
| 64 | 66 | * 更新插件的状态 |
| ... | ... | @@ -67,4 +69,11 @@ public interface PluginInfoService { |
| 67 | 69 | * @param status 状态 |
| 68 | 70 | */ |
| 69 | 71 | void updatePluginStatus(Long id, Integer status); |
| 72 | + | |
| 73 | + /** | |
| 74 | + * 获得启用的插件列表 | |
| 75 | + * | |
| 76 | + * @return 插件列表-插件id | |
| 77 | + */ | |
| 78 | + List<String> getEnabledPlugins(); | |
| 70 | 79 | } |
| 71 | 80 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.service.plugininfo; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.io.IoUtil; | |
| 4 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | 4 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | -import cn.iocoder.yudao.module.infra.api.file.FileApi; | |
| 7 | 5 | import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoPageReqVO; |
| 8 | 6 | import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.vo.PluginInfoSaveReqVO; |
| 9 | 7 | import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; |
| 10 | 8 | import cn.iocoder.yudao.module.iot.dal.mysql.plugininfo.PluginInfoMapper; |
| 11 | 9 | import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; |
| 12 | -import jakarta.annotation.PostConstruct; | |
| 13 | 10 | import jakarta.annotation.Resource; |
| 14 | -import lombok.SneakyThrows; | |
| 15 | 11 | import lombok.extern.slf4j.Slf4j; |
| 16 | 12 | import org.pf4j.PluginDescriptor; |
| 17 | 13 | import org.pf4j.PluginState; |
| ... | ... | @@ -22,11 +18,13 @@ import org.springframework.stereotype.Service; |
| 22 | 18 | import org.springframework.validation.annotation.Validated; |
| 23 | 19 | import org.springframework.web.multipart.MultipartFile; |
| 24 | 20 | |
| 25 | -import java.nio.file.Path; | |
| 21 | +import java.io.File; | |
| 22 | +import java.io.IOException; | |
| 23 | +import java.nio.file.*; | |
| 24 | +import java.util.ArrayList; | |
| 25 | +import java.util.List; | |
| 26 | 26 | import java.util.concurrent.Executors; |
| 27 | 27 | import java.util.concurrent.TimeUnit; |
| 28 | -import java.util.jar.JarEntry; | |
| 29 | -import java.util.jar.JarFile; | |
| 30 | 28 | |
| 31 | 29 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 32 | 30 | import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; |
| ... | ... | @@ -47,9 +45,6 @@ public class PluginInfoServiceImpl implements PluginInfoService { |
| 47 | 45 | @Resource |
| 48 | 46 | private SpringPluginManager pluginManager; |
| 49 | 47 | |
| 50 | - @Resource | |
| 51 | - private FileApi fileApi; | |
| 52 | - | |
| 53 | 48 | @Value("${pf4j.pluginsDir}") |
| 54 | 49 | private String pluginsDir; |
| 55 | 50 | |
| ... | ... | @@ -95,6 +90,19 @@ public class PluginInfoServiceImpl implements PluginInfoService { |
| 95 | 90 | |
| 96 | 91 | // 删除 |
| 97 | 92 | pluginInfoMapper.deleteById(id); |
| 93 | + // 删除插件文件 | |
| 94 | + Executors.newSingleThreadExecutor().submit(() -> { | |
| 95 | + try { | |
| 96 | + TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 | |
| 97 | + File file = new File(pluginsDir, pluginInfoDO.getFile()); | |
| 98 | + if (file.exists() && !file.delete()) { | |
| 99 | + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFile()); | |
| 100 | + } | |
| 101 | + } catch (InterruptedException e) { | |
| 102 | + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFile(), e); | |
| 103 | + } | |
| 104 | + }); | |
| 105 | + | |
| 98 | 106 | } |
| 99 | 107 | |
| 100 | 108 | private PluginInfoDO validatePluginInfoExists(Long id) { |
| ... | ... | @@ -116,73 +124,100 @@ public class PluginInfoServiceImpl implements PluginInfoService { |
| 116 | 124 | } |
| 117 | 125 | |
| 118 | 126 | @Override |
| 119 | - public void uploadJar(Long id, MultipartFile file) { | |
| 120 | - // 1. 校验存在 | |
| 127 | + public void uploadFile(Long id, MultipartFile file) { | |
| 128 | + // 1. 校验插件信息是否存在 | |
| 121 | 129 | PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); |
| 122 | 130 | |
| 123 | - // 2. 判断文件名称与插件 ID 是否匹配 | |
| 131 | + // 2. 获取插件 ID | |
| 124 | 132 | String pluginId = pluginInfoDo.getPluginId(); |
| 125 | 133 | |
| 126 | - // 3. 停止卸载旧的插件 | |
| 127 | - // 3.1. 获取插件信息 | |
| 134 | + // 3. 停止并卸载旧的插件 | |
| 135 | + stopAndUnloadPlugin(pluginId); | |
| 136 | + | |
| 137 | + // 4. 上传新的插件文件 | |
| 138 | + String pluginIdNew = uploadAndLoadNewPlugin(file); | |
| 139 | + | |
| 140 | + // 5. 更新插件启用状态文件 | |
| 141 | + updatePluginStatusFile(pluginIdNew, false); | |
| 142 | + | |
| 143 | + // 6. 更新插件信息 | |
| 144 | + updatePluginInfo(pluginInfoDo, pluginIdNew, file); | |
| 145 | + } | |
| 146 | + | |
| 147 | + // 停止并卸载旧的插件 | |
| 148 | + private void stopAndUnloadPlugin(String pluginId) { | |
| 128 | 149 | PluginWrapper plugin = pluginManager.getPlugin(pluginId); |
| 129 | 150 | if (plugin != null) { |
| 130 | - // 3.2. 如果插件状态是启动的,停止插件 | |
| 131 | 151 | if (plugin.getPluginState().equals(PluginState.STARTED)) { |
| 132 | - pluginManager.stopPlugin(pluginId); | |
| 152 | + pluginManager.stopPlugin(pluginId); // 停止插件 | |
| 133 | 153 | } |
| 134 | - // 3.3. 卸载插件 | |
| 135 | - pluginManager.unloadPlugin(pluginId); | |
| 154 | + pluginManager.unloadPlugin(pluginId); // 卸载插件 | |
| 136 | 155 | } |
| 156 | + } | |
| 137 | 157 | |
| 138 | - // 4. 上传插件 | |
| 139 | - String pluginIdNew; | |
| 158 | + // 上传并加载新的插件文件 | |
| 159 | + private String uploadAndLoadNewPlugin(MultipartFile file) { | |
| 160 | + Path pluginsPath = Paths.get(pluginsDir); | |
| 140 | 161 | try { |
| 141 | - String path = fileApi.createFile(pluginsDir, IoUtil.readBytes(file.getInputStream())); | |
| 142 | - Path pluginPath = Path.of(path); | |
| 143 | - pluginIdNew = pluginManager.loadPlugin(pluginPath); | |
| 162 | + if (!Files.exists(pluginsPath)) { | |
| 163 | + Files.createDirectories(pluginsPath); // 创建插件目录 | |
| 164 | + } | |
| 165 | + String filename = file.getOriginalFilename(); | |
| 166 | + if (filename != null) { | |
| 167 | + Path jarPath = pluginsPath.resolve(filename); | |
| 168 | + Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件 | |
| 169 | + return pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件 | |
| 170 | + } else { | |
| 171 | + throw exception(PLUGIN_INSTALL_FAILED); | |
| 172 | + } | |
| 144 | 173 | } catch (Exception e) { |
| 145 | 174 | throw exception(PLUGIN_INSTALL_FAILED); |
| 146 | 175 | } |
| 176 | + } | |
| 147 | 177 | |
| 148 | - PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew); | |
| 149 | - if (pluginWrapper == null) { | |
| 150 | - throw exception(PLUGIN_INSTALL_FAILED); | |
| 151 | - } | |
| 178 | + // 更新插件状态文件 | |
| 179 | + private void updatePluginStatusFile(String pluginIdNew, boolean isEnabled) { | |
| 180 | + Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt"); | |
| 181 | + Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt"); | |
| 182 | + Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath; | |
| 183 | + Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath; | |
| 152 | 184 | |
| 153 | - // 5. 读取配置文件和脚本 | |
| 154 | - String configJson = ""; | |
| 155 | - String script = ""; | |
| 156 | - try (JarFile jarFile = new JarFile(pluginWrapper.getPluginPath().toFile())) { | |
| 157 | - // 5.1 获取config文件在jar包中的路径 | |
| 158 | - String configFile = "classes/config.json"; | |
| 159 | - JarEntry configEntry = jarFile.getJarEntry(configFile); | |
| 160 | - | |
| 161 | - if (configEntry != null) { | |
| 162 | - // 5.2 读取配置文件 | |
| 163 | - configJson = IoUtil.readUtf8(jarFile.getInputStream(configEntry)); | |
| 164 | - log.info("configJson:{}", configJson); | |
| 185 | + try { | |
| 186 | + PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginIdNew); | |
| 187 | + if (pluginWrapper == null) { | |
| 188 | + throw exception(PLUGIN_INSTALL_FAILED); | |
| 189 | + } | |
| 190 | + String pluginInfo = pluginIdNew + "@" + pluginWrapper.getDescriptor().getVersion(); | |
| 191 | + List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) | |
| 192 | + : new ArrayList<>(); | |
| 193 | + List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) | |
| 194 | + : new ArrayList<>(); | |
| 195 | + | |
| 196 | + if (!targetLines.contains(pluginInfo)) { | |
| 197 | + targetLines.add(pluginInfo); | |
| 198 | + Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, | |
| 199 | + StandardOpenOption.TRUNCATE_EXISTING); | |
| 165 | 200 | } |
| 166 | 201 | |
| 167 | - // 5.3 读取script.js脚本 | |
| 168 | - String scriptFile = "classes/script.js"; | |
| 169 | - JarEntry scriptEntity = jarFile.getJarEntry(scriptFile); | |
| 170 | - if (scriptEntity != null) { | |
| 171 | - // 5.4 读取脚本文件 | |
| 172 | - script = IoUtil.readUtf8(jarFile.getInputStream(scriptEntity)); | |
| 173 | - log.info("script:{}", script); | |
| 202 | + if (oppositeLines.contains(pluginInfo)) { | |
| 203 | + oppositeLines.remove(pluginInfo); | |
| 204 | + Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, | |
| 205 | + StandardOpenOption.TRUNCATE_EXISTING); | |
| 174 | 206 | } |
| 175 | - } catch (Exception e) { | |
| 207 | + } catch (IOException e) { | |
| 176 | 208 | throw exception(PLUGIN_INSTALL_FAILED); |
| 177 | 209 | } |
| 210 | + } | |
| 178 | 211 | |
| 212 | + // 更新插件信息 | |
| 213 | + private void updatePluginInfo(PluginInfoDO pluginInfoDo, String pluginIdNew, MultipartFile file) { | |
| 179 | 214 | pluginInfoDo.setPluginId(pluginIdNew); |
| 180 | 215 | pluginInfoDo.setStatus(IotPluginStatusEnum.STOPPED.getStatus()); |
| 181 | 216 | pluginInfoDo.setFile(file.getOriginalFilename()); |
| 182 | - pluginInfoDo.setConfigSchema(configJson); | |
| 183 | - pluginInfoDo.setScript(script); | |
| 217 | + pluginInfoDo.setScript(""); | |
| 184 | 218 | |
| 185 | - PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor(); | |
| 219 | + PluginDescriptor pluginDescriptor = pluginManager.getPlugin(pluginIdNew).getDescriptor(); | |
| 220 | + pluginInfoDo.setConfigSchema(pluginDescriptor.getPluginDescription()); | |
| 186 | 221 | pluginInfoDo.setVersion(pluginDescriptor.getVersion()); |
| 187 | 222 | pluginInfoDo.setDescription(pluginDescriptor.getPluginDescription()); |
| 188 | 223 | pluginInfoMapper.updateById(pluginInfoDo); |
| ... | ... | @@ -190,52 +225,50 @@ public class PluginInfoServiceImpl implements PluginInfoService { |
| 190 | 225 | |
| 191 | 226 | @Override |
| 192 | 227 | public void updatePluginStatus(Long id, Integer status) { |
| 193 | - // 1. 校验存在 | |
| 228 | + // 1. 校验插件信息是否存在 | |
| 194 | 229 | PluginInfoDO pluginInfoDo = validatePluginInfoExists(id); |
| 195 | 230 | |
| 196 | - // 插件状态无效 | |
| 231 | + // 2. 校验插件状态是否有效 | |
| 197 | 232 | if (!IotPluginStatusEnum.contains(status)) { |
| 198 | 233 | throw exception(PLUGIN_STATUS_INVALID); |
| 199 | 234 | } |
| 200 | 235 | |
| 236 | + // 3. 获取插件ID和插件实例 | |
| 201 | 237 | String pluginId = pluginInfoDo.getPluginId(); |
| 202 | 238 | PluginWrapper plugin = pluginManager.getPlugin(pluginId); |
| 239 | + | |
| 240 | + // 4. 根据状态更新插件 | |
| 203 | 241 | if (plugin != null) { |
| 204 | - if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) && plugin.getPluginState() != PluginState.STARTED) { | |
| 205 | - // 启动插件 | |
| 242 | + // 4.1 如果目标状态是运行且插件未启动,则启动插件 | |
| 243 | + if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) | |
| 244 | + && plugin.getPluginState() != PluginState.STARTED) { | |
| 206 | 245 | pluginManager.startPlugin(pluginId); |
| 207 | - } else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) && plugin.getPluginState() == PluginState.STARTED) { | |
| 208 | - // 停止插件 | |
| 246 | + updatePluginStatusFile(pluginId, true); // 更新插件状态文件为启用 | |
| 247 | + } | |
| 248 | + // 4.2 如果目标状态是停止且插件已启动,则停止插件 | |
| 249 | + else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) | |
| 250 | + && plugin.getPluginState() == PluginState.STARTED) { | |
| 209 | 251 | pluginManager.stopPlugin(pluginId); |
| 252 | + updatePluginStatusFile(pluginId, false); // 更新插件状态文件为禁用 | |
| 210 | 253 | } |
| 211 | 254 | } else { |
| 212 | - // 已经停止,未获取到插件 | |
| 255 | + // 5. 插件不存在且状态为停止,抛出异常 | |
| 213 | 256 | if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginInfoDo.getStatus())) { |
| 214 | 257 | throw exception(PLUGIN_STATUS_INVALID); |
| 215 | 258 | } |
| 216 | 259 | } |
| 260 | + | |
| 261 | + // 6. 更新数据库中的插件状态 | |
| 217 | 262 | pluginInfoDo.setStatus(status); |
| 218 | 263 | pluginInfoMapper.updateById(pluginInfoDo); |
| 219 | 264 | } |
| 220 | 265 | |
| 221 | -// @PostConstruct | |
| 222 | -// public void init() { | |
| 223 | -// Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS); | |
| 224 | -// } | |
| 225 | -// | |
| 226 | -// @SneakyThrows | |
| 227 | -// private void startPlugins() { | |
| 228 | -// for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) { | |
| 229 | -// if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) { | |
| 230 | -// continue; | |
| 231 | -// } | |
| 232 | -// log.info("start plugin:{}", pluginInfoDO.getPluginId()); | |
| 233 | -// try { | |
| 234 | -// pluginManager.startPlugin(pluginInfoDO.getPluginId()); | |
| 235 | -// } catch (Exception e) { | |
| 236 | -// log.error("start plugin error", e); | |
| 237 | -// } | |
| 238 | -// } | |
| 239 | -// } | |
| 266 | + @Override | |
| 267 | + public List<String> getEnabledPlugins() { | |
| 268 | + return pluginInfoMapper.selectList().stream() | |
| 269 | + .filter(pluginInfoDO -> IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) | |
| 270 | + .map(PluginInfoDO::getPluginId) | |
| 271 | + .toList(); | |
| 272 | + } | |
| 240 | 273 | |
| 241 | 274 | } |
| 242 | 275 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/WelcomePlugin.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2012-present the original author or authors. | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package cn.iocoder.yudao.module.iot.plugin; | |
| 17 | + | |
| 18 | +import cn.iocoder.yudao.module.iot.api.Greeting; | |
| 19 | +import org.apache.commons.lang.StringUtils; | |
| 20 | +import org.pf4j.Extension; | |
| 21 | +import org.pf4j.Plugin; | |
| 22 | +import org.pf4j.PluginWrapper; | |
| 23 | +import org.pf4j.RuntimeMode; | |
| 24 | +import com.sun.net.httpserver.HttpServer; | |
| 25 | +import java.io.IOException; | |
| 26 | +import java.io.OutputStream; | |
| 27 | +import java.net.InetSocketAddress; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * 打招呼 测试用例 | |
| 31 | + */ | |
| 32 | +public class WelcomePlugin extends Plugin { | |
| 33 | + | |
| 34 | + private HttpServer server; | |
| 35 | + | |
| 36 | + public WelcomePlugin(PluginWrapper wrapper) { | |
| 37 | + super(wrapper); | |
| 38 | + } | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + public void start() { | |
| 42 | + System.out.println("WelcomePlugin.start()"); | |
| 43 | + // for testing the development mode | |
| 44 | + if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) { | |
| 45 | + System.out.println(StringUtils.upperCase("WelcomePlugin")); | |
| 46 | + } | |
| 47 | + startHttpServer(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + public void stop() { | |
| 52 | + System.out.println("WelcomePlugin.stop()"); | |
| 53 | + stopHttpServer(); | |
| 54 | + } | |
| 55 | + | |
| 56 | + private void startHttpServer() { | |
| 57 | + try { | |
| 58 | + server = HttpServer.create(new InetSocketAddress(9081), 0); | |
| 59 | + server.createContext("/", exchange -> { | |
| 60 | + String response = "Welcome to PF4J HTTP Server"; | |
| 61 | + exchange.sendResponseHeaders(200, response.getBytes().length); | |
| 62 | + OutputStream os = exchange.getResponseBody(); | |
| 63 | + os.write(response.getBytes()); | |
| 64 | + os.close(); | |
| 65 | + }); | |
| 66 | + server.setExecutor(null); | |
| 67 | + server.start(); | |
| 68 | + System.out.println("HTTP server started on port 9081"); | |
| 69 | + } catch (IOException e) { | |
| 70 | + e.printStackTrace(); | |
| 71 | + } | |
| 72 | + } | |
| 73 | + | |
| 74 | + private void stopHttpServer() { | |
| 75 | + if (server != null) { | |
| 76 | + server.stop(0); | |
| 77 | + System.out.println("HTTP server stopped"); | |
| 78 | + } | |
| 79 | + } | |
| 80 | + | |
| 81 | + @Extension | |
| 82 | + public static class WelcomeGreeting implements Greeting { | |
| 83 | + | |
| 84 | + @Override | |
| 85 | + public String getGreeting() { | |
| 86 | + return "Welcome to PF4J"; | |
| 87 | + } | |
| 88 | + | |
| 89 | + } | |
| 90 | + | |
| 91 | +} | |
| 0 | 92 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml
| ... | ... | @@ -24,6 +24,7 @@ |
| 24 | 24 | <plugin.class>cn.iocoder.yudao.module.iot.plugin.HttpPlugin</plugin.class> |
| 25 | 25 | <plugin.version>0.0.1</plugin.version> |
| 26 | 26 | <plugin.provider>ahh</plugin.provider> |
| 27 | + <plugin.description>http-plugin-0.0.1</plugin.description> | |
| 27 | 28 | <plugin.dependencies/> |
| 28 | 29 | </properties> |
| 29 | 30 | |
| ... | ... | @@ -104,6 +105,7 @@ |
| 104 | 105 | <Plugin-Class>${plugin.class}</Plugin-Class> |
| 105 | 106 | <Plugin-Version>${plugin.version}</Plugin-Version> |
| 106 | 107 | <Plugin-Provider>${plugin.provider}</Plugin-Provider> |
| 108 | + <Plugin-Description>${plugin.description}</Plugin-Description> | |
| 107 | 109 | <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies> |
| 108 | 110 | </manifestEntries> |
| 109 | 111 | </archive> | ... | ... |