【代码评审】IoT:插件机制
This commit is contained in:
parent
0af6d5a758
commit
aad0581777
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"java.compile.nullAnalysis.mode": "automatic",
|
|
||||||
"java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx2G -Xms100m -Xlog:disable",
|
|
||||||
"java.configuration.updateBuildConfiguration": "interactive"
|
|
||||||
}
|
|
|
@ -13,8 +13,8 @@ import java.util.Arrays;
|
||||||
@Getter
|
@Getter
|
||||||
public enum IotPluginDeployTypeEnum implements IntArrayValuable {
|
public enum IotPluginDeployTypeEnum implements IntArrayValuable {
|
||||||
|
|
||||||
DEPLOY_VIA_JAR(0, "通过 jar 部署"), // TODO @haohao:UPLOAD 和 ALONE 感觉有点冲突,前者是部署方式,后者是运行方式。这个后续再讨论下哈
|
JAR(0, "JAR 部署"),
|
||||||
DEPLOY_STANDALONE(1, "独立部署");
|
STANDALONE(1, "独立部署");
|
||||||
|
|
||||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray();
|
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(IotPluginDeployTypeEnum::getDeployType).toArray();
|
||||||
|
|
||||||
|
@ -48,4 +48,5 @@ public enum IotPluginDeployTypeEnum implements IntArrayValuable {
|
||||||
public int[] array() {
|
public int[] array() {
|
||||||
return ARRAYS;
|
return ARRAYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,19 +33,22 @@ public class PluginInstanceDO extends BaseDO {
|
||||||
*/
|
*/
|
||||||
private String mainId;
|
private String mainId;
|
||||||
/**
|
/**
|
||||||
* 插件id
|
* 插件 ID
|
||||||
* <p>
|
* <p>
|
||||||
* 关联 {@link PluginInfoDO#getId()}
|
* 关联 {@link PluginInfoDO#getId()}
|
||||||
*/
|
*/
|
||||||
private Long pluginId;
|
private Long pluginId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件主程序所在ip
|
* 插件主程序所在 IP
|
||||||
*/
|
*/
|
||||||
private String ip;
|
private String ip;
|
||||||
/**
|
/**
|
||||||
* 插件主程序端口
|
* 插件主程序端口
|
||||||
*/
|
*/
|
||||||
private Integer port;
|
private Integer port;
|
||||||
|
|
||||||
|
// TODO @haohao:字段改成 heartbeatTime,LocalDateTime
|
||||||
/**
|
/**
|
||||||
* 心跳时间,心路时间超过 30 秒需要剔除
|
* 心跳时间,心路时间超过 30 秒需要剔除
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -100,7 +100,7 @@ public class PluginInfoServiceImpl implements PluginInfoService {
|
||||||
// 2. 停止并卸载旧的插件
|
// 2. 停止并卸载旧的插件
|
||||||
pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey());
|
pluginInstanceService.stopAndUnloadPlugin(pluginInfoDo.getPluginKey());
|
||||||
|
|
||||||
// 3 上传新的插件文件,更新插件启用状态文件
|
// 3 上传新的插件文件,更新插件启用状态文件
|
||||||
String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file);
|
String pluginKeyNew = pluginInstanceService.uploadAndLoadNewPlugin(file);
|
||||||
|
|
||||||
// 4. 更新插件信息
|
// 4. 更新插件信息
|
||||||
|
|
|
@ -11,7 +11,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||||
public interface PluginInstanceService {
|
public interface PluginInstanceService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报插件实例
|
* 上报插件实例(心跳)
|
||||||
*/
|
*/
|
||||||
void reportPluginInstances();
|
void reportPluginInstances();
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,8 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
|
||||||
public class PluginInstanceServiceImpl implements PluginInstanceService {
|
public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||||
|
|
||||||
// TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的
|
// TODO @haohao:这个可以后续确认下,有没更合适的标识。例如说 mac 地址之类的
|
||||||
// 简化的UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件
|
// 简化的 UUID + mac 地址 会不会好一些,一台机子有可能会部署多个插件;
|
||||||
|
// 那就 mac@uuid ?
|
||||||
public static final String MAIN_ID = IdUtil.fastSimpleUUID();
|
public static final String MAIN_ID = IdUtil.fastSimpleUUID();
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
|
@ -53,13 +54,13 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||||
|
|
||||||
@Value("${pf4j.pluginsDir}")
|
@Value("${pf4j.pluginsDir}")
|
||||||
private String pluginsDir;
|
private String pluginsDir;
|
||||||
|
|
||||||
@Value("${server.port:48080}")
|
@Value("${server.port:48080}")
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopAndUnloadPlugin(String pluginKey) {
|
public void stopAndUnloadPlugin(String pluginKey) {
|
||||||
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
|
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
|
||||||
|
// TODO @haohao:改成 if return 会更简洁一点;
|
||||||
if (plugin != null) {
|
if (plugin != null) {
|
||||||
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
if (plugin.getPluginState().equals(PluginState.STARTED)) {
|
||||||
pluginManager.stopPlugin(pluginKey); // 停止插件
|
pluginManager.stopPlugin(pluginKey); // 停止插件
|
||||||
|
@ -75,6 +76,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||||
@Override
|
@Override
|
||||||
public void deletePluginFile(PluginInfoDO pluginInfoDO) {
|
public void deletePluginFile(PluginInfoDO pluginInfoDO) {
|
||||||
File file = new File(pluginsDir, pluginInfoDO.getFileName());
|
File file = new File(pluginsDir, pluginInfoDO.getFileName());
|
||||||
|
// TODO @haohao:改成 if return 会更简洁一点;
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
try {
|
try {
|
||||||
TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
|
TimeUnit.SECONDS.sleep(1); // 等待 1 秒,避免插件未卸载完毕
|
||||||
|
@ -82,9 +84,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||||
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
|
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName());
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(),
|
log.error("[deletePluginInfo][删除插件文件({}) 失败]", pluginInfoDO.getFileName(), e);
|
||||||
e);
|
|
||||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||||
String pluginKey = pluginInfoDo.getPluginKey();
|
String pluginKey = pluginInfoDo.getPluginKey();
|
||||||
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
|
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
|
||||||
|
|
||||||
|
// TODO @haohao:改成 if return 会更简洁一点;
|
||||||
if (plugin != null) {
|
if (plugin != null) {
|
||||||
// 启动插件
|
// 启动插件
|
||||||
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
|
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
|
||||||
|
@ -143,46 +144,41 @@ public class PluginInstanceServiceImpl implements PluginInstanceService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reportPluginInstances() {
|
public void reportPluginInstances() {
|
||||||
// 1. 获取 pf4j 插件列表
|
// 1.1 获取 pf4j 插件列表
|
||||||
List<PluginWrapper> plugins = pluginManager.getPlugins();
|
List<PluginWrapper> plugins = pluginManager.getPlugins();
|
||||||
|
|
||||||
// 2. 获取插件信息列表并转换为 Map 以便快速查找
|
// 1.2 获取插件信息列表并转换为 Map 以便快速查找
|
||||||
List<PluginInfoDO> pluginInfos = pluginInfoMapper.selectList();
|
List<PluginInfoDO> pluginInfos = pluginInfoMapper.selectList();
|
||||||
Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream()
|
Map<String, PluginInfoDO> pluginInfoMap = pluginInfos.stream()
|
||||||
.collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity()));
|
.collect(Collectors.toMap(PluginInfoDO::getPluginKey, Function.identity()));
|
||||||
|
|
||||||
// 3. 获取本机 IP 和 MAC 地址
|
// 1.3 获取本机 IP 和 MAC 地址
|
||||||
String ip = NetUtil.getLocalhostStr();
|
String ip = NetUtil.getLocalhostStr();
|
||||||
String mac = NetUtil.getLocalMacAddress();
|
String mac = NetUtil.getLocalMacAddress();
|
||||||
String mainId = MAIN_ID + "-" + mac;
|
String mainId = MAIN_ID + "-" + mac;
|
||||||
|
|
||||||
// 4. 遍历插件列表,并保存为插件实例
|
// 2. 遍历插件列表,并保存为插件实例
|
||||||
for (PluginWrapper plugin : plugins) {
|
for (PluginWrapper plugin : plugins) {
|
||||||
String pluginKey = plugin.getPluginId();
|
String pluginKey = plugin.getPluginId();
|
||||||
|
|
||||||
// 4.1 查找插件信息
|
// 2.1 查找插件信息
|
||||||
PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey);
|
PluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey);
|
||||||
if (pluginInfo == null) {
|
if (pluginInfo == null) {
|
||||||
// 4.2 插件信息不存在,记录错误并跳过
|
|
||||||
log.error("插件信息不存在,pluginKey = {}", pluginKey);
|
log.error("插件信息不存在,pluginKey = {}", pluginKey);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4.3 查询插件实例
|
// 2.2 情况一:如果插件实例不存在,则创建
|
||||||
PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId,
|
PluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId,
|
||||||
pluginInfo.getId());
|
pluginInfo.getId());
|
||||||
if (pluginInstance == null) {
|
if (pluginInstance == null) {
|
||||||
// 4.4 如果插件实例不存在,则创建
|
// 4.4 如果插件实例不存在,则创建
|
||||||
pluginInstance = PluginInstanceDO.builder()
|
pluginInstance = PluginInstanceDO.builder().pluginId(pluginInfo.getId()).mainId(MAIN_ID + "-" + mac)
|
||||||
.pluginId(pluginInfo.getId())
|
.ip(ip).port(port).heartbeatAt(System.currentTimeMillis()).build();
|
||||||
.mainId(MAIN_ID + "-" + mac)
|
|
||||||
.ip(ip)
|
|
||||||
.port(port)
|
|
||||||
.heartbeatAt(System.currentTimeMillis())
|
|
||||||
.build();
|
|
||||||
pluginInstanceMapper.insert(pluginInstance);
|
pluginInstanceMapper.insert(pluginInstance);
|
||||||
} else {
|
} else {
|
||||||
// 4.5 如果插件实例存在,则更新心跳时间
|
// 2.2 情况二:如果存在,则更新 heartbeatAt
|
||||||
|
// TODO @haohao:这里最好 new 去 update;避免并发更新(虽然目前没有)
|
||||||
pluginInstance.setHeartbeatAt(System.currentTimeMillis());
|
pluginInstance.setHeartbeatAt(System.currentTimeMillis());
|
||||||
pluginInstanceMapper.updateById(pluginInstance);
|
pluginInstanceMapper.updateById(pluginInstance);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
// TODO 芋艿:后续 review 下
|
||||||
/**
|
/**
|
||||||
* 插件实例 RPC 接口
|
* 插件实例 RPC 接口
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue