Commit aad05817773d465c5fc335bc914c04ac0ad011c7
1 parent
0af6d5a7
【代码评审】IoT:插件机制
Showing
7 changed files
with
27 additions
and
31 deletions
.vscode/settings.json deleted
| 1 | -{ | ||
| 2 | - "java.compile.nullAnalysis.mode": "automatic", | ||
| 3 | - "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable", | ||
| 4 | - "java.configuration.updateBuildConfiguration": "interactive" | ||
| 5 | -} | ||
| 6 | \ No newline at end of file | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/plugin/IotPluginDeployTypeEnum.java
| @@ -13,8 +13,8 @@ import java.util.Arrays; | @@ -13,8 +13,8 @@ import java.util.Arrays; | ||
| 13 | @Getter | 13 | @Getter |
| 14 | public enum IotPluginDeployTypeEnum implements IntArrayValuable { | 14 | public enum IotPluginDeployTypeEnum implements IntArrayValuable { |
| 15 | 15 | ||
| 16 | - DEPLOY_VIA_JAR(0, "通过 jar 部署"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈 | ||
| 17 | - DEPLOY_STANDALONE(1, "独立部署"); | 16 | + JAR(0, "JAR 部署"), |
| 17 | + STANDALONE(1, "独立部署"); | ||
| 18 | 18 | ||
| 19 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); | 19 | public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray(); |
| 20 | 20 | ||
| @@ -48,4 +48,5 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable { | @@ -48,4 +48,5 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable { | ||
| 48 | public int[] array() { | 48 | public int[] array() { |
| 49 | return ARRAYS; | 49 | return ARRAYS; |
| 50 | } | 50 | } |
| 51 | + | ||
| 51 | } | 52 | } |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugininstance/PluginInstanceDO.java
| @@ -33,19 +33,22 @@ public class PluginInstanceDO extends BaseDO { | @@ -33,19 +33,22 @@ public class PluginInstanceDO extends BaseDO { | ||
| 33 | */ | 33 | */ |
| 34 | private String mainId; | 34 | private String mainId; |
| 35 | /** | 35 | /** |
| 36 | - * 插件id | 36 | + * 插件 ID |
| 37 | * <p> | 37 | * <p> |
| 38 | * 关联 {@link PluginInfoDO#getId()} | 38 | * 关联 {@link PluginInfoDO#getId()} |
| 39 | */ | 39 | */ |
| 40 | private Long pluginId; | 40 | private Long pluginId; |
| 41 | + | ||
| 41 | /** | 42 | /** |
| 42 | - * 插件主程序所在ip | 43 | + * 插件主程序所在 IP |
| 43 | */ | 44 | */ |
| 44 | private String ip; | 45 | private String ip; |
| 45 | /** | 46 | /** |
| 46 | * 插件主程序端口 | 47 | * 插件主程序端口 |
| 47 | */ | 48 | */ |
| 48 | private Integer port; | 49 | private Integer port; |
| 50 | + | ||
| 51 | + // TODO @haohao:字段改成 heartbeatTime,LocalDateTime | ||
| 49 | /** | 52 | /** |
| 50 | * 心跳时间,心路时间超过 30 秒需要剔除 | 53 | * 心跳时间,心路时间超过 30 秒需要剔除 |
| 51 | */ | 54 | */ |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInfoServiceImpl.java
| @@ -100,7 +100,7 @@ public class PluginInfoServiceImpl implements PluginInfoService { | @@ -100,7 +100,7 @@ public class PluginInfoServiceImpl implements PluginInfoService { | ||
| 100 | // 2. 停止并卸载旧的插件 | 100 | // 2. 停止并卸载旧的插件 |
| 101 | pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); | 101 | pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey()); |
| 102 | 102 | ||
| 103 | - // 3 上传新的插件文件,更新插件启用状态文件 | 103 | + // 3 上传新的插件文件,更新插件启用状态文件 |
| 104 | String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); | 104 | String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file); |
| 105 | 105 | ||
| 106 | // 4. 更新插件信息 | 106 | // 4. 更新插件信息 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceService.java
| @@ -11,7 +11,7 @@ import org.springframework.web.multipart.MultipartFile; | @@ -11,7 +11,7 @@ import org.springframework.web.multipart.MultipartFile; | ||
| 11 | public interface PluginInstanceService { | 11 | public interface PluginInstanceService { |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| 14 | - * 上报插件实例 | 14 | + * 上报插件实例(心跳) |
| 15 | */ | 15 | */ |
| 16 | void reportPluginInstances(); | 16 | void reportPluginInstances(); |
| 17 | 17 |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/PluginInstanceServiceImpl.java
| @@ -41,7 +41,8 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU | @@ -41,7 +41,8 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU | ||
| 41 | public class PluginInstanceServiceImpl implements PluginInstanceService { | 41 | public class PluginInstanceServiceImpl implements PluginInstanceService { |
| 42 | 42 | ||
| 43 | // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 | 43 | // TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的 |
| 44 | - // 简化的UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件 | 44 | + // 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件; |
| 45 | + // 那就 mac@uuid ? | ||
| 45 | public static final String MAIN_ID = IdUtil.fastSimpleUUID(); | 46 | public static final String MAIN_ID = IdUtil.fastSimpleUUID(); |
| 46 | 47 | ||
| 47 | @Resource | 48 | @Resource |
| @@ -53,13 +54,13 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | @@ -53,13 +54,13 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | ||
| 53 | 54 | ||
| 54 | @Value("${pf4j.pluginsDir}") | 55 | @Value("${pf4j.pluginsDir}") |
| 55 | private String pluginsDir; | 56 | private String pluginsDir; |
| 56 | - | ||
| 57 | @Value("${server.port:48080}") | 57 | @Value("${server.port:48080}") |
| 58 | private int port; | 58 | private int port; |
| 59 | 59 | ||
| 60 | @Override | 60 | @Override |
| 61 | public void stopAndUnloadPlugin(String pluginKey) { | 61 | public void stopAndUnloadPlugin(String pluginKey) { |
| 62 | PluginWrapper plugin = pluginManager.getPlugin(pluginKey); | 62 | PluginWrapper plugin = pluginManager.getPlugin(pluginKey); |
| 63 | + // TODO @haohao:改成 if return 会更简洁一点; | ||
| 63 | if (plugin != null) { | 64 | if (plugin != null) { |
| 64 | if (plugin.getPluginState().equals(PluginState.STARTED)) { | 65 | if (plugin.getPluginState().equals(PluginState.STARTED)) { |
| 65 | pluginManager.stopPlugin(pluginKey); // 停止插件 | 66 | pluginManager.stopPlugin(pluginKey); // 停止插件 |
| @@ -75,6 +76,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | @@ -75,6 +76,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | ||
| 75 | @Override | 76 | @Override |
| 76 | public void deletePluginFile(PluginInfoDO pluginInfoDO) { | 77 | public void deletePluginFile(PluginInfoDO pluginInfoDO) { |
| 77 | File file = new File(pluginsDir, pluginInfoDO.getFileName()); | 78 | File file = new File(pluginsDir, pluginInfoDO.getFileName()); |
| 79 | + // TODO @haohao:改成 if return 会更简洁一点; | ||
| 78 | if (file.exists()) { | 80 | if (file.exists()) { |
| 79 | try { | 81 | try { |
| 80 | TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 | 82 | TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕 |
| @@ -82,9 +84,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | @@ -82,9 +84,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | ||
| 82 | log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); | 84 | log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName()); |
| 83 | } | 85 | } |
| 84 | } catch (InterruptedException e) { | 86 | } catch (InterruptedException e) { |
| 85 | - log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), | ||
| 86 | - e); | ||
| 87 | - Thread.currentThread().interrupt(); // 恢复中断状态 | 87 | + log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e); |
| 88 | } | 88 | } |
| 89 | } | 89 | } |
| 90 | } | 90 | } |
| @@ -120,6 +120,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | @@ -120,6 +120,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | ||
| 120 | String pluginKey = pluginInfoDo.getPluginKey(); | 120 | String pluginKey = pluginInfoDo.getPluginKey(); |
| 121 | PluginWrapper plugin = pluginManager.getPlugin(pluginKey); | 121 | PluginWrapper plugin = pluginManager.getPlugin(pluginKey); |
| 122 | 122 | ||
| 123 | + // TODO @haohao:改成 if return 会更简洁一点; | ||
| 123 | if (plugin != null) { | 124 | if (plugin != null) { |
| 124 | // 启动插件 | 125 | // 启动插件 |
| 125 | if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) | 126 | if (status.equals(IotPluginStatusEnum.RUNNING.getStatus()) |
| @@ -143,46 +144,41 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | @@ -143,46 +144,41 @@ public class PluginInstanceServiceImpl implements PluginInstanceService { | ||
| 143 | 144 | ||
| 144 | @Override | 145 | @Override |
| 145 | public void reportPluginInstances() { | 146 | public void reportPluginInstances() { |
| 146 | - // 1. 获取 pf4j 插件列表 | 147 | + // 1.1 获取 pf4j 插件列表 |
| 147 | List<PluginWrapper> plugins = pluginManager.getPlugins(); | 148 | List<PluginWrapper> plugins = pluginManager.getPlugins(); |
| 148 | 149 | ||
| 149 | - // 2. 获取插件信息列表并转换为 Map 以便快速查找 | 150 | + // 1.2 获取插件信息列表并转换为 Map 以便快速查找 |
| 150 | List<PluginInfoDO> pluginInfos = pluginInfoMapper.selectList(); | 151 | List<PluginInfoDO> pluginInfos = pluginInfoMapper.selectList(); |
| 151 | Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream() | 152 | Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream() |
| 152 | .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); | 153 | .collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity())); |
| 153 | 154 | ||
| 154 | - // 3. 获取本机 IP 和 MAC 地址 | 155 | + // 1.3 获取本机 IP 和 MAC 地址 |
| 155 | String ip = NetUtil.getLocalhostStr(); | 156 | String ip = NetUtil.getLocalhostStr(); |
| 156 | String mac = NetUtil.getLocalMacAddress(); | 157 | String mac = NetUtil.getLocalMacAddress(); |
| 157 | String mainId = MAIN_ID + "-" + mac; | 158 | String mainId = MAIN_ID + "-" + mac; |
| 158 | 159 | ||
| 159 | - // 4. 遍历插件列表,并保存为插件实例 | 160 | + // 2. 遍历插件列表,并保存为插件实例 |
| 160 | for (PluginWrapper plugin : plugins) { | 161 | for (PluginWrapper plugin : plugins) { |
| 161 | String pluginKey = plugin.getPluginId(); | 162 | String pluginKey = plugin.getPluginId(); |
| 162 | 163 | ||
| 163 | - // 4.1 查找插件信息 | 164 | + // 2.1 查找插件信息 |
| 164 | PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); | 165 | PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey); |
| 165 | if (pluginInfo == null) { | 166 | if (pluginInfo == null) { |
| 166 | - // 4.2 插件信息不存在,记录错误并跳过 | ||
| 167 | log.error("插件信息不存在,pluginKey = {}", pluginKey); | 167 | log.error("插件信息不存在,pluginKey = {}", pluginKey); |
| 168 | continue; | 168 | continue; |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | - // 4.3 查询插件实例 | 171 | + // 2.2 情况一:如果插件实例不存在,则创建 |
| 172 | PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId, | 172 | PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId, |
| 173 | pluginInfo.getId()); | 173 | pluginInfo.getId()); |
| 174 | if (pluginInstance == null) { | 174 | if (pluginInstance == null) { |
| 175 | // 4.4 如果插件实例不存在,则创建 | 175 | // 4.4 如果插件实例不存在,则创建 |
| 176 | - pluginInstance = PluginInstanceDO.builder() | ||
| 177 | - .pluginId(pluginInfo.getId()) | ||
| 178 | - .mainId(MAIN_ID + "-" + mac) | ||
| 179 | - .ip(ip) | ||
| 180 | - .port(port) | ||
| 181 | - .heartbeatAt(System.currentTimeMillis()) | ||
| 182 | - .build(); | 176 | + pluginInstance = PluginInstanceDO.builder().pluginId(pluginInfo.getId()).mainId(MAIN_ID + "-" + mac) |
| 177 | + .ip(ip).port(port).heartbeatAt(System.currentTimeMillis()).build(); | ||
| 183 | pluginInstanceMapper.insert(pluginInstance); | 178 | pluginInstanceMapper.insert(pluginInstance); |
| 184 | } else { | 179 | } else { |
| 185 | - // 4.5 如果插件实例存在,则更新心跳时间 | 180 | + // 2.2 情况二:如果存在,则更新 heartbeatAt |
| 181 | + // TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有) | ||
| 186 | pluginInstance.setHeartbeatAt(System.currentTimeMillis()); | 182 | pluginInstance.setHeartbeatAt(System.currentTimeMillis()); |
| 187 | pluginInstanceMapper.updateById(pluginInstance); | 183 | pluginInstanceMapper.updateById(pluginInstance); |
| 188 | } | 184 | } |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/controller/RpcController.java
| @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController; | @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController; | ||
| 11 | import javax.annotation.Resource; | 11 | import javax.annotation.Resource; |
| 12 | import java.util.concurrent.CompletableFuture; | 12 | import java.util.concurrent.CompletableFuture; |
| 13 | 13 | ||
| 14 | +// TODO 芋艿:后续 review 下 | ||
| 14 | /** | 15 | /** |
| 15 | * 插件实例 RPC 接口 | 16 | * 插件实例 RPC 接口 |
| 16 | * | 17 | * |