Merge pull request #4118 from dataease/pr@dev@feat_distribute_plugin_operate

feat(插件管理): 集群环境依赖redis的插件操作功能
This commit is contained in:
fit2cloud-chenyw 2022-12-16 16:36:43 +08:00 committed by GitHub
commit 52d96c0cfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 170 additions and 7 deletions

View File

@ -3,6 +3,7 @@ package io.dataease.commons.utils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.util.Arrays; import java.util.Arrays;
public class IPUtils { public class IPUtils {
@ -42,4 +43,12 @@ public class IPUtils {
ipStr = Arrays.stream(ipStr.split(",")).filter(item -> StringUtils.isNotBlank(item) && !StringUtils.equalsIgnoreCase(UNKNOWN, item.trim())).findFirst().orElse(ipStr); ipStr = Arrays.stream(ipStr.split(",")).filter(item -> StringUtils.isNotBlank(item) && !StringUtils.equalsIgnoreCase(UNKNOWN, item.trim())).findFirst().orElse(ipStr);
return StringUtils.equals(LOCAL_IP_KEY, ipStr) ? LOCAL_IP_VAL : ipStr; return StringUtils.equals(LOCAL_IP_KEY, ipStr) ? LOCAL_IP_VAL : ipStr;
} }
public static String domain() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
return LOCAL_IP_VAL;
}
}
} }

View File

@ -0,0 +1,20 @@
package io.dataease.plugins.entity;
import io.dataease.plugins.common.base.domain.MyPlugin;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PluginOperate implements Serializable {
private String type;
private MyPlugin plugin;
private String senderIp;
}

View File

@ -1,13 +1,57 @@
package io.dataease.service.redis.impl; package io.dataease.service.redis.impl;
import com.google.gson.Gson;
import io.dataease.commons.utils.IPUtils;
import io.dataease.commons.utils.LogUtil;
import io.dataease.plugins.common.base.domain.MyPlugin;
import io.dataease.plugins.entity.PluginOperate;
import io.dataease.service.redis.RedisMessageBroadcast; import io.dataease.service.redis.RedisMessageBroadcast;
import io.dataease.service.sys.PluginService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service @Service
public class PluginMsgService implements RedisMessageBroadcast { public class PluginMsgService implements RedisMessageBroadcast {
private static Gson json = new Gson();
@Resource
private PluginService pluginService;
@Override @Override
public void messageCallBack(Object arg) { public void messageCallBack(Object arg) {
PluginOperate operate = json.fromJson(json.toJson(arg), PluginOperate.class);
String domain = IPUtils.domain();
if (StringUtils.equals(domain, operate.getSenderIp())) return;
String operateType = operate.getType();
MyPlugin plugin = operate.getPlugin();
if (StringUtils.equals("install", operateType)) {
LogUtil.info("start install plugin [{}] in domain {}", plugin.getName(), domain);
install(plugin);
}
if (StringUtils.equals("uninstall", operateType)) {
LogUtil.info("start uninstall plugin [{}] in domain {}", plugin.getName(), domain);
uninstall(plugin);
}
if (StringUtils.equals("update", operateType)) {
LogUtil.info("start update plugin [{}] in domain {}", plugin.getName(), domain);
updateInstall(plugin);
}
}
private void install(MyPlugin plugin) {
pluginService.redisBroadcastInstall(plugin);
}
private void uninstall(MyPlugin plugin) {
pluginService.redisBroadcastUnInstall(plugin);
}
private void updateInstall(MyPlugin plugin) {
if (pluginService.redisBroadcastUnInstall(plugin)) {
pluginService.redisBroadcastInstall(plugin);
}
} }
} }

View File

@ -0,0 +1,32 @@
package io.dataease.service.sys;
import io.dataease.commons.condition.RedisStatusCondition;
import io.dataease.commons.constants.RedisConstants;
import io.dataease.commons.model.RedisMessage;
import io.dataease.plugins.entity.PluginOperate;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Conditional;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@Conditional({RedisStatusCondition.class})
public class DistributedPluginService {
@Resource
private RedisTemplate redisTemplate;
public void pushBroadcast(PluginOperate operate) {
if (ObjectUtils.isEmpty(operate) || ObjectUtils.isEmpty(operate.getPlugin()) || StringUtils.isBlank(operate.getSenderIp()))
return;
RedisMessage<PluginOperate> msg = new RedisMessage();
msg.setType(RedisConstants.PLUGIN_INSTALL_MSG);
msg.setData(operate);
redisTemplate.convertAndSend(RedisConstants.GLOBAL_REDIS_TOPIC, msg);
}
}

View File

@ -3,6 +3,7 @@ package io.dataease.service.sys;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil; import cn.hutool.core.util.ZipUtil;
import com.google.gson.Gson; import com.google.gson.Gson;
import io.dataease.commons.utils.IPUtils;
import io.dataease.dto.MyPluginDTO; import io.dataease.dto.MyPluginDTO;
import io.dataease.ext.ExtSysPluginMapper; import io.dataease.ext.ExtSysPluginMapper;
import io.dataease.ext.query.GridExample; import io.dataease.ext.query.GridExample;
@ -17,6 +18,7 @@ import io.dataease.listener.util.CacheUtils;
import io.dataease.plugins.common.base.domain.MyPlugin; import io.dataease.plugins.common.base.domain.MyPlugin;
import io.dataease.plugins.common.base.mapper.MyPluginMapper; import io.dataease.plugins.common.base.mapper.MyPluginMapper;
import io.dataease.plugins.config.LoadjarUtil; import io.dataease.plugins.config.LoadjarUtil;
import io.dataease.plugins.entity.PluginOperate;
import io.dataease.service.datasource.DatasourceService; import io.dataease.service.datasource.DatasourceService;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
@ -56,6 +58,9 @@ public class PluginService {
@Autowired @Autowired
private LoadjarUtil loadjarUtil; private LoadjarUtil loadjarUtil;
@Autowired(required = false)
private DistributedPluginService distributedPluginService;
@Value("${version}") @Value("${version}")
private String version; private String version;
@ -71,7 +76,7 @@ public class PluginService {
* @param file * @param file
* @return * @return
*/ */
public Map<String, Object> localInstall(MultipartFile file) throws Exception{ public Map<String, Object> localInstall(MultipartFile file) throws Exception {
//1.上传文件到服务器pluginDir目录下 //1.上传文件到服务器pluginDir目录下
File dest = DeFileUtils.upload(file, pluginDir + "temp/"); File dest = DeFileUtils.upload(file, pluginDir + "temp/");
//2.解压目标文件dest 得到plugin.json和jar //2.解压目标文件dest 得到plugin.json和jar
@ -113,7 +118,7 @@ public class PluginService {
} }
if (pluginExist(myPlugin)) { if (pluginExist(myPlugin)) {
String msg = "插件【"+myPlugin.getName()+"】已存在,请先卸载"; String msg = "插件【" + myPlugin.getName() + "】已存在,请先卸载";
LogUtil.error(msg); LogUtil.error(msg);
DEException.throwException(msg); DEException.throwException(msg);
} }
@ -123,7 +128,7 @@ public class PluginService {
targetDir = makeTargetDir(myPlugin); targetDir = makeTargetDir(myPlugin);
String jarPath; String jarPath;
jarPath = DeFileUtils.copy(jarFile, targetDir); jarPath = DeFileUtils.copy(jarFile, targetDir);
if(myPlugin.getCategory().equalsIgnoreCase("datasource")){ if (myPlugin.getCategory().equalsIgnoreCase("datasource")) {
DeFileUtils.copyFolder(folder + "/" + myPlugin.getDsType() + "Driver", targetDir + myPlugin.getDsType() + "Driver"); DeFileUtils.copyFolder(folder + "/" + myPlugin.getDsType() + "Driver", targetDir + myPlugin.getDsType() + "Driver");
} }
loadJar(jarPath, myPlugin); loadJar(jarPath, myPlugin);
@ -142,13 +147,49 @@ public class PluginService {
DeFileUtils.deleteFile(pluginDir + "temp/"); DeFileUtils.deleteFile(pluginDir + "temp/");
DeFileUtils.deleteFile(folder); DeFileUtils.deleteFile(folder);
} }
distributeOperate(myPlugin, "install");
return null; return null;
} }
public void distributeOperate(MyPlugin plugin, String type) {
if (ObjectUtils.isNotEmpty(distributedPluginService)) {
PluginOperate operate = new PluginOperate();
operate.setPlugin(plugin);
operate.setSenderIp(IPUtils.domain());
operate.setType(type);
if (ObjectUtils.isEmpty(plugin) || StringUtils.isBlank(type) || StringUtils.isBlank(operate.getSenderIp()))
return;
distributedPluginService.pushBroadcast(operate);
}
}
public void loadJar(String jarPath, MyPlugin myPlugin) throws Exception { public void loadJar(String jarPath, MyPlugin myPlugin) throws Exception {
loadjarUtil.loadJar(jarPath, myPlugin); loadjarUtil.loadJar(jarPath, myPlugin);
} }
public void redisBroadcastInstall(MyPlugin plugin) {
String path = getPath(plugin);
try {
if (FileUtil.exist(path)) {
loadJar(path, plugin);
} else {
LogUtil.error("插件路径不存在 {} ", path);
}
} catch (Exception e) {
LogUtil.error(e);
}
}
public String getPath(MyPlugin plugin) {
String store = plugin.getStore();
String version = plugin.getVersion();
String moduleName = plugin.getModuleName();
String fileName = moduleName + "-" + version + ".jar";
String path = pluginDir + store + "/" + fileName;
return path;
}
private String makeTargetDir(MyPlugin myPlugin) { private String makeTargetDir(MyPlugin myPlugin) {
String store = myPlugin.getStore(); String store = myPlugin.getStore();
String dir = pluginDir + store + "/"; String dir = pluginDir + store + "/";
@ -161,6 +202,7 @@ public class PluginService {
/** /**
* 检测插件是否已存在 * 检测插件是否已存在
*
* @param myPlugin * @param myPlugin
* @return * @return
*/ */
@ -190,13 +232,29 @@ public class PluginService {
CacheUtils.removeAll(AuthConstants.USER_ROLE_CACHE_NAME); CacheUtils.removeAll(AuthConstants.USER_ROLE_CACHE_NAME);
CacheUtils.removeAll(AuthConstants.USER_PERMISSION_CACHE_NAME); CacheUtils.removeAll(AuthConstants.USER_PERMISSION_CACHE_NAME);
if(myPlugin.getCategory().equalsIgnoreCase("datasource")){ if (myPlugin.getCategory().equalsIgnoreCase("datasource")) {
if(CollectionUtils.isNotEmpty(datasourceService.selectByType(myPlugin.getDsType()))){ if (CollectionUtils.isNotEmpty(datasourceService.selectByType(myPlugin.getDsType()))) {
DEException.throwException(Translator.get("i18n_plugin_not_allow_delete")); DEException.throwException(Translator.get("i18n_plugin_not_allow_delete"));
} }
loadjarUtil.deleteModule(myPlugin.getModuleName() + "-" + myPlugin.getVersion()); loadjarUtil.deleteModule(myPlugin.getModuleName() + "-" + myPlugin.getVersion());
} }
myPluginMapper.deleteByPrimaryKey(pluginId); myPluginMapper.deleteByPrimaryKey(pluginId);
distributeOperate(myPlugin, "uninstall");
return true;
}
public Boolean redisBroadcastUnInstall(MyPlugin myPlugin) {
CacheUtils.removeAll(AuthConstants.USER_ROLE_CACHE_NAME);
CacheUtils.removeAll(AuthConstants.USER_CACHE_NAME);
CacheUtils.removeAll(AuthConstants.USER_PERMISSION_CACHE_NAME);
if (myPlugin.getCategory().equalsIgnoreCase("datasource")) {
if (CollectionUtils.isNotEmpty(datasourceService.selectByType(myPlugin.getDsType()))) {
DEException.throwException(Translator.get("i18n_plugin_not_allow_delete"));
}
loadjarUtil.deleteModule(myPlugin.getModuleName() + "-" + myPlugin.getVersion());
}
myPluginMapper.deleteByPrimaryKey(myPlugin.getPluginId());
return true; return true;
} }
@ -208,7 +266,7 @@ public class PluginService {
File jarFile = new File(path); File jarFile = new File(path);
FileUtil.del(jarFile); FileUtil.del(jarFile);
if(plugin.getCategory().equalsIgnoreCase("datasource")){ if (plugin.getCategory().equalsIgnoreCase("datasource")) {
File driverFile = new File(pluginDir + plugin.getStore() + "/" + plugin.getDsType() + "Driver"); File driverFile = new File(pluginDir + plugin.getStore() + "/" + plugin.getDsType() + "Driver");
FileUtil.del(driverFile); FileUtil.del(driverFile);
} }
@ -259,7 +317,7 @@ public class PluginService {
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
e.printStackTrace(); e.printStackTrace();
} }
if(result.getCategory().equalsIgnoreCase("datasource") && (StringUtils.isEmpty(result.getStore()) || !result.getStore().equalsIgnoreCase("default"))){ if (result.getCategory().equalsIgnoreCase("datasource") && (StringUtils.isEmpty(result.getStore()) || !result.getStore().equalsIgnoreCase("default"))) {
result.setStore("thirdpart"); result.setStore("thirdpart");
} }