Commit 0af6d5a7581532b4658160a19680c09ffd427279
1 parent
d39e2c1b
【功能完善】IoT: 新增插件启动逻辑,重构插件状态管理,删除不再使用的状态文件更新方法
Showing
9 changed files
with
80 additions
and
60 deletions
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/plugin/vo/PluginInfoPageReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.controller.admin.plugin.vo; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | +import cn.iocoder.yudao.framework.common.validation.InEnum; | |
| 5 | +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; | |
| 4 | 6 | import io.swagger.v3.oas.annotations.media.Schema; |
| 5 | 7 | import lombok.Data; |
| 6 | 8 | |
| ... | ... | @@ -11,7 +13,8 @@ public class PluginInfoPageReqVO extends PageParam { |
| 11 | 13 | @Schema(description = "插件名称", example = "http") |
| 12 | 14 | private String name; |
| 13 | 15 | |
| 14 | - @Schema(description = "状态") | |
| 16 | + @Schema(description = "状态", example = "1") | |
| 17 | + @InEnum(IotPluginStatusEnum.class) | |
| 15 | 18 | private Integer status; |
| 16 | 19 | |
| 17 | 20 | } |
| 18 | 21 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/PluginStart.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.framework.plugin; | |
| 2 | + | |
| 3 | +import java.util.List; | |
| 4 | + | |
| 5 | +import javax.annotation.Resource; | |
| 6 | + | |
| 7 | +import org.pf4j.spring.SpringPluginManager; | |
| 8 | +import org.springframework.boot.ApplicationArguments; | |
| 9 | +import org.springframework.boot.ApplicationRunner; | |
| 10 | +import org.springframework.stereotype.Component; | |
| 11 | + | |
| 12 | +import lombok.extern.slf4j.Slf4j; | |
| 13 | + | |
| 14 | +import cn.iocoder.yudao.module.iot.service.plugin.PluginInfoService; | |
| 15 | +import cn.hutool.core.collection.CollUtil; | |
| 16 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |
| 17 | +import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; | |
| 18 | +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; | |
| 19 | + | |
| 20 | +@Component | |
| 21 | +@Slf4j | |
| 22 | +public class PluginStart implements ApplicationRunner { | |
| 23 | + | |
| 24 | + @Resource | |
| 25 | + private PluginInfoService pluginInfoService; | |
| 26 | + | |
| 27 | + @Resource | |
| 28 | + private SpringPluginManager pluginManager; | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public void run(ApplicationArguments args) { | |
| 32 | + TenantUtils.executeIgnore(() -> { // 1. 忽略租户上下文执行 | |
| 33 | + List<PluginInfoDO> pluginInfoList = pluginInfoService | |
| 34 | + .getPluginInfoListByStatus(IotPluginStatusEnum.RUNNING.getStatus()); // 2. 获取运行中的插件列表 | |
| 35 | + if (CollUtil.isEmpty(pluginInfoList)) { // 3. 检查插件列表是否为空 | |
| 36 | + log.info("[run] 没有需要启动的插件"); // 4. 日志记录没有插件需要启动 | |
| 37 | + return; | |
| 38 | + } | |
| 39 | + pluginInfoList.forEach(pluginInfo -> { // 5. 使用lambda表达式遍历插件列表 | |
| 40 | + try { | |
| 41 | + log.info("[run][启动插件] pluginKey = {}", pluginInfo.getPluginKey()); // 6. 日志记录插件启动信息 | |
| 42 | + pluginManager.startPlugin(pluginInfo.getPluginKey()); // 7. 启动插件 | |
| 43 | + } catch (Exception e) { | |
| 44 | + log.error("[run][启动插件失败] pluginKey = {}", pluginInfo.getPluginKey(), e); // 8. 记录启动失败的日志 | |
| 45 | + } | |
| 46 | + }); | |
| 47 | + }); | |
| 48 | + | |
| 49 | + } | |
| 50 | + | |
| 51 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/UnifiedConfiguration.java
| ... | ... | @@ -31,7 +31,13 @@ public class UnifiedConfiguration { |
| 31 | 31 | @DependsOn(SERVICE_REGISTRY_INITIALIZED_MARKER) |
| 32 | 32 | public SpringPluginManager pluginManager() { |
| 33 | 33 | log.info("[init][实例化 SpringPluginManager]"); |
| 34 | - SpringPluginManager springPluginManager = new SpringPluginManager(); | |
| 34 | + SpringPluginManager springPluginManager = new SpringPluginManager() { | |
| 35 | + @Override | |
| 36 | + public void startPlugins() { | |
| 37 | + // 禁用插件启动,避免插件启动时,启动所有插件 | |
| 38 | + log.info("[init][禁用默认启动所有插件]"); | |
| 39 | + } | |
| 40 | + }; | |
| 35 | 41 | springPluginManager.addPluginStateListener(new CustomPluginStateListener()); |
| 36 | 42 | return springPluginManager; |
| 37 | 43 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoService.java
| ... | ... | @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoPageReqVO; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.PluginInfoSaveReqVO; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.dal.dataobject.plugininfo.PluginInfoDO; |
| 7 | +import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum; | |
| 7 | 8 | import jakarta.validation.Valid; |
| 8 | 9 | import org.springframework.web.multipart.MultipartFile; |
| 9 | 10 | |
| ... | ... | @@ -66,7 +67,7 @@ public interface PluginInfoService { |
| 66 | 67 | * 更新插件的状态 |
| 67 | 68 | * |
| 68 | 69 | * @param id 插件id |
| 69 | - * @param status 状态 | |
| 70 | + * @param status 状态 {@link IotPluginStatusEnum} | |
| 70 | 71 | */ |
| 71 | 72 | void updatePluginStatus(Long id, Integer status); |
| 72 | 73 | |
| ... | ... | @@ -80,7 +81,7 @@ public interface PluginInfoService { |
| 80 | 81 | /** |
| 81 | 82 | * 根据状态获得插件信息列表 |
| 82 | 83 | * |
| 83 | - * @param status 状态 | |
| 84 | + * @param status 状态 {@link IotPluginStatusEnum} | |
| 84 | 85 | * @return 插件信息列表 |
| 85 | 86 | */ |
| 86 | 87 | List<PluginInfoDO> getPluginInfoListByStatus(Integer status); | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java
| ... | ... | @@ -102,7 +102,6 @@ public class PluginInfoServiceImpl implements PluginInfoService { |
| 102 | 102 | |
| 103 | 103 | // 3 上传新的插件文件,更新插件启用状态文件 |
| 104 | 104 | String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); |
| 105 | - pluginInstanceService.updatePluginStatusFile(pluginKeyNew, false); | |
| 106 | 105 | |
| 107 | 106 | // 4. 更新插件信息 |
| 108 | 107 | updatePluginInfo(pluginInfoDo, pluginKeyNew, file); | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java
| ... | ... | @@ -38,14 +38,6 @@ public interface PluginInstanceService { |
| 38 | 38 | String uploadAndLoadNewPlugin(MultipartFile file); |
| 39 | 39 | |
| 40 | 40 | /** |
| 41 | - * 更新插件状态文件 | |
| 42 | - * | |
| 43 | - * @param pluginKeyNew 插件标识符 | |
| 44 | - * @param isEnabled 是否启用 | |
| 45 | - */ | |
| 46 | - void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled); | |
| 47 | - | |
| 48 | - /** | |
| 49 | 41 | * 更新插件状态 |
| 50 | 42 | * |
| 51 | 43 | * @param pluginInfoDo 插件信息 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java
| ... | ... | @@ -21,11 +21,7 @@ import org.springframework.web.multipart.MultipartFile; |
| 21 | 21 | |
| 22 | 22 | import java.io.File; |
| 23 | 23 | import java.io.IOException; |
| 24 | -import java.net.Inet4Address; | |
| 25 | -import java.net.InetAddress; | |
| 26 | 24 | import java.nio.file.*; |
| 27 | -import java.util.ArrayList; | |
| 28 | -import java.util.LinkedHashSet; | |
| 29 | 25 | import java.util.List; |
| 30 | 26 | import java.util.Map; |
| 31 | 27 | import java.util.concurrent.TimeUnit; |
| ... | ... | @@ -120,44 +116,6 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { |
| 120 | 116 | } |
| 121 | 117 | |
| 122 | 118 | @Override |
| 123 | - public void updatePluginStatusFile(String pluginKeyNew, boolean isEnabled) { | |
| 124 | - // TODO @haohao:疑问,这里写 enabled.txt 和 disabled.txt 的目的是啥哈? | |
| 125 | - // pf4j 的插件状态文件,需要 2 个文件,一个 enabled.txt 一个 disabled.txt | |
| 126 | - Path enabledFilePath = Paths.get(pluginsDir, "enabled.txt"); | |
| 127 | - Path disabledFilePath = Paths.get(pluginsDir, "disabled.txt"); | |
| 128 | - Path targetFilePath = isEnabled ? enabledFilePath : disabledFilePath; | |
| 129 | - Path oppositeFilePath = isEnabled ? disabledFilePath : enabledFilePath; | |
| 130 | - | |
| 131 | - try { | |
| 132 | - PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginKeyNew); | |
| 133 | - if (pluginWrapper == null) { | |
| 134 | - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED); | |
| 135 | - } | |
| 136 | - List<String> targetLines = Files.exists(targetFilePath) ? Files.readAllLines(targetFilePath) | |
| 137 | - : new ArrayList<>(); | |
| 138 | - List<String> oppositeLines = Files.exists(oppositeFilePath) ? Files.readAllLines(oppositeFilePath) | |
| 139 | - : new ArrayList<>(); | |
| 140 | - | |
| 141 | - if (!targetLines.contains(pluginKeyNew)) { | |
| 142 | - targetLines.add(pluginKeyNew); | |
| 143 | - Files.write(targetFilePath, targetLines, StandardOpenOption.CREATE, | |
| 144 | - StandardOpenOption.TRUNCATE_EXISTING); | |
| 145 | - log.info("已添加插件 {} 到 {}", pluginKeyNew, targetFilePath.getFileName()); | |
| 146 | - } | |
| 147 | - | |
| 148 | - if (oppositeLines.contains(pluginKeyNew)) { | |
| 149 | - oppositeLines.remove(pluginKeyNew); | |
| 150 | - Files.write(oppositeFilePath, oppositeLines, StandardOpenOption.CREATE, | |
| 151 | - StandardOpenOption.TRUNCATE_EXISTING); | |
| 152 | - log.info("已从 {} 移除插件 {}", oppositeFilePath.getFileName(), pluginKeyNew); | |
| 153 | - } | |
| 154 | - } catch (IOException e) { | |
| 155 | - log.error("[updatePluginStatusFile][更新插件状态文件失败]", e); | |
| 156 | - throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e); | |
| 157 | - } | |
| 158 | - } | |
| 159 | - | |
| 160 | - @Override | |
| 161 | 119 | public void updatePluginStatus(PluginInfoDO pluginInfoDo, Integer status) { |
| 162 | 120 | String pluginKey = pluginInfoDo.getPluginKey(); |
| 163 | 121 | PluginWrapper plugin = pluginManager.getPlugin(pluginKey); |
| ... | ... | @@ -167,14 +125,12 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { |
| 167 | 125 | if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) |
| 168 | 126 | && plugin.getPluginState() != PluginState.STARTED) { |
| 169 | 127 | pluginManager.startPlugin(pluginKey); |
| 170 | - updatePluginStatusFile(pluginKey, true); | |
| 171 | 128 | log.info("已启动插件: {}", pluginKey); |
| 172 | 129 | } |
| 173 | 130 | // 停止插件 |
| 174 | 131 | else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus()) |
| 175 | 132 | && plugin.getPluginState() == PluginState.STARTED) { |
| 176 | 133 | pluginManager.stopPlugin(pluginKey); |
| 177 | - updatePluginStatusFile(pluginKey, false); | |
| 178 | 134 | log.info("已停止插件: {}", pluginKey); |
| 179 | 135 | } |
| 180 | 136 | } else { |
| ... | ... | @@ -208,7 +164,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { |
| 208 | 164 | PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); |
| 209 | 165 | if (pluginInfo == null) { |
| 210 | 166 | // 4.2 插件信息不存在,记录错误并跳过 |
| 211 | - log.error("插件信息不存在,插件包标识符 = {}", pluginKey); | |
| 167 | + log.error("插件信息不存在,pluginKey = {}", pluginKey); | |
| 212 | 168 | continue; |
| 213 | 169 | } |
| 214 | 170 | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpVertxPlugin.java
| ... | ... | @@ -5,12 +5,15 @@ import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; |
| 5 | 5 | import io.vertx.core.Vertx; |
| 6 | 6 | import io.vertx.ext.web.Router; |
| 7 | 7 | import io.vertx.ext.web.handler.BodyHandler; |
| 8 | -import org.pf4j.Plugin; | |
| 9 | 8 | import org.pf4j.PluginWrapper; |
| 9 | +import org.pf4j.spring.SpringPlugin; | |
| 10 | +import org.springframework.context.ApplicationContext; | |
| 11 | +import org.springframework.context.annotation.AnnotationConfigApplicationContext; | |
| 12 | + | |
| 10 | 13 | import lombok.extern.slf4j.Slf4j; |
| 11 | 14 | |
| 12 | 15 | @Slf4j |
| 13 | -public class HttpVertxPlugin extends Plugin { | |
| 16 | +public class HttpVertxPlugin extends SpringPlugin { | |
| 14 | 17 | |
| 15 | 18 | private static final int PORT = 8092; |
| 16 | 19 | private Vertx vertx; |
| ... | ... | @@ -67,4 +70,13 @@ public class HttpVertxPlugin extends Plugin { |
| 67 | 70 | }); |
| 68 | 71 | } |
| 69 | 72 | } |
| 73 | + | |
| 74 | + @Override | |
| 75 | + protected ApplicationContext createApplicationContext() { | |
| 76 | + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); | |
| 77 | + applicationContext.setClassLoader(getWrapper().getPluginClassLoader()); | |
| 78 | + applicationContext.refresh(); | |
| 79 | + | |
| 80 | + return applicationContext; | |
| 81 | + } | |
| 70 | 82 | } |
| 71 | 83 | \ No newline at end of file | ... | ... |