feat(系统设置): 插件管理支持上传JDBC驱动

This commit is contained in:
jianxing 2023-08-22 11:22:16 +08:00 committed by fit2-zhao
parent 2aba451d92
commit 3a77833613
8 changed files with 134 additions and 39 deletions

View File

@ -1,5 +1,5 @@
package io.metersphere.sdk.constants;
public enum PluginScenarioType {
API, PLATFORM
API, PLATFORM, JDBC_DRIVER
}

View File

@ -142,21 +142,25 @@ public class PluginManager {
if (impClazz == superClazz) {
return true;
}
Type[] interfaces = impClazz.getGenericInterfaces();
try {
Type[] interfaces = impClazz.getGenericInterfaces();
if (interfaces != null && interfaces.length > 0) {
for (Type genericInterface : interfaces) {
if (genericInterface instanceof Class && isImplClazz(superClazz, (Class) genericInterface)) {
return true;
if (interfaces != null && interfaces.length > 0) {
for (Type genericInterface : interfaces) {
if (genericInterface instanceof Class && isImplClazz(superClazz, (Class) genericInterface)) {
return true;
}
}
}
}
Type superclass = impClazz.getGenericSuperclass();
if (superclass != null
&& superclass instanceof Class
&& isImplClazz(superClazz, (Class) superclass)) {
return true;
Type superclass = impClazz.getGenericSuperclass();
if (superclass != null
&& superclass instanceof Class
&& isImplClazz(superClazz, (Class) superclass)) {
return true;
}
} catch (Throwable e) {
LogUtils.error(e);
}
return false;
}

View File

@ -0,0 +1,43 @@
package io.metersphere.sdk.service;
import io.metersphere.sdk.constants.PluginScenarioType;
import io.metersphere.system.domain.Plugin;
import io.metersphere.system.domain.PluginExample;
import io.metersphere.system.mapper.PluginMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Driver;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class JdbcDriverPluginService {
@Resource
private PluginLoadService pluginLoadService;
@Resource
private PluginMapper pluginMapper;
public boolean isJdbcDriver(String pluginId) {
return pluginLoadService.getImplClass(pluginId, Driver.class) != null;
}
/**
* 获取所有的 JDBC 驱动的实现类
* @return
*/
public List<String> getJdbcDriverClass() {
PluginExample example = new PluginExample();
example.createCriteria().andScenarioEqualTo(PluginScenarioType.JDBC_DRIVER.name());
List<Plugin> plugins = pluginMapper.selectByExample(example);
List<String> pluginDriverClassNames = plugins.stream().map(plugin ->
pluginLoadService.getImplClass(plugin.getId(), Driver.class)
).map(Class::getName).collect(Collectors.toList());
// 已经内置了 mysql 依赖
pluginDriverClassNames.add("com.mysql.jdbc.Driver");
return pluginDriverClassNames;
}
}

View File

@ -23,6 +23,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Driver;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -210,9 +211,15 @@ public class PluginLoadService {
public boolean hasPluginKey(String currentPluginId, String pluginKey) {
for (String pluginId : pluginManager.getClassLoaderMap().keySet()) {
MsPlugin msPlugin = getMsPluginInstance(pluginId);
if (!StringUtils.equals(currentPluginId, pluginId) && StringUtils.equals(msPlugin.getKey(), pluginKey)) {
return true;
MsPlugin msPlugin;
try {
msPlugin = getMsPluginInstance(pluginId);
if (!StringUtils.equals(currentPluginId, pluginId) && StringUtils.equals(msPlugin.getKey(), pluginKey)) {
return true;
}
} catch (MSException e) {
// jdbc 驱动没有实现 MsPlugin 接口
LogUtils.info(String.format("插件%s未实现 MsPlugin 接口", pluginId));
}
}
return false;
@ -230,4 +237,8 @@ public class PluginLoadService {
public Object getPluginScriptContent(String pluginId, String scriptId) {
return getPluginScriptConfig(pluginId, scriptId).get("script");
}
public Class getImplClass(String pluginId, Class<Driver> driverClass) {
return pluginManager.getImplClass(pluginId, driverClass);
}
}

View File

@ -4,9 +4,11 @@ package io.metersphere.system.service;
import io.metersphere.plugin.sdk.api.MsPlugin;
import io.metersphere.sdk.constants.KafkaPluginTopicType;
import io.metersphere.sdk.constants.KafkaTopicConstants;
import io.metersphere.sdk.constants.PluginScenarioType;
import io.metersphere.sdk.controller.handler.result.CommonResultCode;
import io.metersphere.sdk.dto.OptionDTO;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.service.JdbcDriverPluginService;
import io.metersphere.sdk.service.PluginLoadService;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.system.domain.Plugin;
@ -28,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Driver;
import java.util.*;
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_EXIST;
@ -52,6 +55,8 @@ public class PluginService {
@Resource
private PluginLoadService pluginLoadService;
@Resource
JdbcDriverPluginService jdbcDriverPluginService;
@Resource
private KafkaTemplate<String, String> kafkaTemplate;
public List<PluginDTO> list() {
@ -60,8 +65,10 @@ public class PluginService {
Map<String, List<OptionDTO>> scripteMap = pluginScriptService.getScripteMap(pluginIds);
Map<String, List<OptionDTO>> orgMap = pluginOrganizationService.getOrgMap(pluginIds);
plugins.forEach(plugin -> {
plugin.setPluginForms(scripteMap.get(plugin.getId()));
plugin.setOrganizations(orgMap.get(plugin.getId()));
List<OptionDTO> pluginForms = scripteMap.get(plugin.getId());
List<OptionDTO> organizations = orgMap.get(plugin.getId());
plugin.setPluginForms(pluginForms == null ? new ArrayList<>(0) : pluginForms);
plugin.setOrganizations(organizations == null ? new ArrayList<>(0) : organizations);
});
return plugins;
}
@ -90,19 +97,32 @@ public class PluginService {
pluginLoadService.loadPlugin(id, file);
// 上传插件
pluginLoadService.uploadPlugin(id, file);
// 获取插件前端配置脚本
List<String> frontendScript = pluginLoadService.getFrontendScripts(id);
MsPlugin msPlugin = pluginLoadService.getMsPluginInstance(id);
plugin.setScenario(msPlugin.getType());
plugin.setXpack(msPlugin.isXpack());
plugin.setPluginId(msPlugin.getPluginId());
if (jdbcDriverPluginService.isJdbcDriver(id)) {
Class implClass = pluginLoadService.getImplClass(id, Driver.class);
// mysql 已经内置了依赖不允许上传
if (implClass.getName().startsWith("com.mysql")) {
throw new MSException(PLUGIN_TYPE_EXIST);
}
plugin.setScenario(PluginScenarioType.JDBC_DRIVER.name());
plugin.setXpack(false);
plugin.setPluginId(file.getOriginalFilename());
} else {
// 非数据库驱动插件解析脚本和插件信息
// 获取插件前端配置脚本
List<String> frontendScript = pluginLoadService.getFrontendScripts(id);
// 校验插件类型是否重复
checkPluginKeyExist(id, msPlugin.getKey());
MsPlugin msPlugin = pluginLoadService.getMsPluginInstance(id);
plugin.setScenario(msPlugin.getType());
plugin.setXpack(msPlugin.isXpack());
plugin.setPluginId(msPlugin.getPluginId());
// 保存插件脚本
pluginScriptService.add(id, frontendScript);
// 校验插件类型是否重复
checkPluginKeyExist(id, msPlugin.getKey());
// 保存插件脚本
pluginScriptService.add(id, frontendScript);
}
// 保存插件和组织的关联关系
if (!request.getGlobal()) {

View File

@ -5,6 +5,7 @@ import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.constants.PluginScenarioType;
import io.metersphere.sdk.dto.OptionDTO;
import io.metersphere.sdk.log.constants.OperationLogType;
import io.metersphere.sdk.service.JdbcDriverPluginService;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.controller.param.PluginUpdateRequestDefinition;
import io.metersphere.system.domain.*;
@ -52,6 +53,8 @@ public class PluginControllerTests extends BaseTest {
private PluginScriptMapper pluginScriptMapper;
@Resource
private OrganizationService organizationService;
@Resource
private JdbcDriverPluginService jdbcDriverPluginService;
private static Plugin addPlugin;
private static Plugin anotherAddPlugin;
@ -77,10 +80,6 @@ public class PluginControllerTests extends BaseTest {
this.getClass().getClassLoader().getResource("file/metersphere-mqtt-plugin-3.x.jar")
.getPath()
);
File anotherJarFile = new File(
this.getClass().getClassLoader().getResource("file/metersphere-jira-plugin-3.x.jar")
.getPath()
);
request.setName("test");
request.setDescription("test desc");
@ -112,6 +111,10 @@ public class PluginControllerTests extends BaseTest {
request.setEnable(true);
request.setName("test2");
request.setOrganizationIds(Arrays.asList(org.getId()));
File anotherJarFile = new File(
this.getClass().getClassLoader().getResource("file/metersphere-jira-plugin-3.x.jar")
.getPath()
);
MvcResult antoherMvcResult = this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
getDefaultMultiPartParam(request, anotherJarFile));
Plugin antoherPlugin = pluginMapper.selectByPrimaryKey(getResultData(antoherMvcResult, Plugin.class).getId());
@ -121,6 +124,17 @@ public class PluginControllerTests extends BaseTest {
anotherAddPlugin = antoherPlugin;
addPlugin = plugin;
// 校验数据库驱动上传成功
request.setName("my-driver");
request.setOrganizationIds(Arrays.asList(org.getId()));
File myDriver = new File(
this.getClass().getClassLoader().getResource("file/my-driver-1.0.jar")
.getPath()
);
this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
getDefaultMultiPartParam(request, myDriver));
Assertions.assertEquals(jdbcDriverPluginService.getJdbcDriverClass(), Arrays.asList("io.jianxing.MyDriver", "com.mysql.jdbc.Driver"));
// @@重名校验异常
// 校验插件名称重名
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
@ -139,6 +153,14 @@ public class PluginControllerTests extends BaseTest {
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
getDefaultMultiPartParam(request, typeRepeatFile)), PLUGIN_TYPE_EXIST);
// @@校验禁止上传mysql驱动
File mysqlDriver = new File(
this.getClass().getClassLoader().getResource("file/test-mysql-driver-1.0.jar")
.getPath()
);
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
getDefaultMultiPartParam(request, mysqlDriver)), PLUGIN_TYPE_EXIST);
// @@校验插件脚本解析失败
File scriptParseFile = new File(
this.getClass().getClassLoader().getResource("file/metersphere-plugin-script-parse-error.jar")
@ -153,7 +175,7 @@ public class PluginControllerTests extends BaseTest {
.getPath()
);
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
getDefaultMultiPartParam(request, scriptIdRepeatFile)), PLUGIN_SCRIPT_EXIST);
getDefaultMultiPartParam(request, scriptIdRepeatFile)), PLUGIN_SCRIPT_EXIST); // @@校验插件脚本ID重复
// @@校验日志
checkLog(this.addPlugin.getId(), OperationLogType.ADD);
@ -237,14 +259,9 @@ public class PluginControllerTests extends BaseTest {
MvcResult mvcResult = this.requestGetWithOkAndReturn(DEFAULT_LIST);
// 校验数据是否正确
List<PluginDTO> pluginList = getResultDataArray(mvcResult, PluginDTO.class);
Assertions.assertEquals(2, pluginList.size());
Assertions.assertEquals(3, pluginList.size());
for (PluginDTO pluginDTO : pluginList) {
Plugin comparePlugin = null;
if (StringUtils.equals(pluginDTO.getId(), addPlugin.getId())) {
comparePlugin = pluginMapper.selectByPrimaryKey(addPlugin.getId());
} else if (StringUtils.equals(pluginDTO.getId(), anotherAddPlugin.getId())) {
comparePlugin = pluginMapper.selectByPrimaryKey(anotherAddPlugin.getId());
}
Plugin comparePlugin = pluginMapper.selectByPrimaryKey(pluginDTO.getId());
Plugin plugin = JSON.parseObject(JSON.toJSONString(pluginDTO), Plugin.class);
List<String> scriptIds = pluginDTO.getPluginForms().stream().map(OptionDTO::getId).toList();
Assertions.assertEquals(plugin, comparePlugin);