diff --git a/backend/framework/domain/src/main/java/io/metersphere/system/domain/Plugin.java b/backend/framework/domain/src/main/java/io/metersphere/system/domain/Plugin.java
index a6b0068cf0..b1c5752459 100644
--- a/backend/framework/domain/src/main/java/io/metersphere/system/domain/Plugin.java
+++ b/backend/framework/domain/src/main/java/io/metersphere/system/domain/Plugin.java
@@ -51,7 +51,7 @@ public class Plugin implements Serializable {
@Schema(description = "插件描述")
private String description;
- @Schema(description = "插件使用场景PAI/PLATFORM", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Schema(description = "插件使用场景API_PROTOCOL/PLATFORM", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{plugin.scenario.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{plugin.scenario.length_range}", groups = {Created.class, Updated.class})
private String scenario;
diff --git a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_11__system_setting.sql b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_11__system_setting.sql
index 0f09fa9b34..59894f2ca5 100644
--- a/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_11__system_setting.sql
+++ b/backend/framework/domain/src/main/resources/migration/3.0.0/ddl/V3.0.0_11__system_setting.sql
@@ -72,7 +72,7 @@ CREATE TABLE IF NOT EXISTS novice_statistics
CREATE TABLE IF NOT EXISTS plugin
(
- `id` VARCHAR(50) NOT NULL COMMENT 'ID' ,
+ `id` VARCHAR(100) NOT NULL COMMENT 'ID' ,
`name` VARCHAR(255) NOT NULL COMMENT '插件名称' ,
`plugin_id` VARCHAR(300) NOT NULL COMMENT '插件ID(名称加版本号)' ,
`file_name` VARCHAR(300) NOT NULL COMMENT '文件名' ,
@@ -83,7 +83,7 @@ CREATE TABLE IF NOT EXISTS plugin
`global` BIT NOT NULL DEFAULT 1 COMMENT '是否是全局插件' ,
`xpack` BIT NOT NULL DEFAULT 0 COMMENT '是否是企业版插件' ,
`description` VARCHAR(500) COMMENT '插件描述' ,
- `scenario` VARCHAR(50) NOT NULL COMMENT '插件使用场景PAI/PLATFORM' ,
+ `scenario` VARCHAR(50) NOT NULL COMMENT '插件使用场景API_PROTOCOL/PLATFORM' ,
PRIMARY KEY (id)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
diff --git a/backend/framework/plugin/metersphere-api-plugin-sdk/src/main/java/io/metersphere/plugin/api/api/AbstractApiPlugin.java b/backend/framework/plugin/metersphere-api-plugin-sdk/src/main/java/io/metersphere/plugin/api/api/AbstractApiPlugin.java
deleted file mode 100644
index 9675aa8061..0000000000
--- a/backend/framework/plugin/metersphere-api-plugin-sdk/src/main/java/io/metersphere/plugin/api/api/AbstractApiPlugin.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package io.metersphere.plugin.api.api;
-
-import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
-
-public abstract class AbstractApiPlugin extends AbstractMsPlugin {
- private static final String API_PLUGIN_TYPE = "API";
- @Override
- public String getType() {
- return API_PLUGIN_TYPE;
- }
-}
diff --git a/backend/framework/plugin/metersphere-api-plugin-sdk/src/main/java/io/metersphere/plugin/api/api/AbstractApiProtocolPlugin.java b/backend/framework/plugin/metersphere-api-plugin-sdk/src/main/java/io/metersphere/plugin/api/api/AbstractApiProtocolPlugin.java
new file mode 100644
index 0000000000..34f998ff03
--- /dev/null
+++ b/backend/framework/plugin/metersphere-api-plugin-sdk/src/main/java/io/metersphere/plugin/api/api/AbstractApiProtocolPlugin.java
@@ -0,0 +1,6 @@
+package io.metersphere.plugin.api.api;
+
+import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
+
+public abstract class AbstractApiProtocolPlugin extends AbstractMsPlugin {
+}
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatformPlugin.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatformPlugin.java
index 9dfa0c244b..06aa3ac315 100644
--- a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatformPlugin.java
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatformPlugin.java
@@ -3,14 +3,9 @@ package io.metersphere.plugin.platform.api;
import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
public abstract class AbstractPlatformPlugin extends AbstractMsPlugin {
- private static final String DEFAULT_PLATFORM_PLUGIN_TYPE = "PLATFORM";
private static final String DEFAULT_INTEGRATION_SCRIPT_ID = "integration";
private static final String DEFAULT_PROJECT_SCRIPT_ID = "project";
private static final String DEFAULT_ACCOUNT_SCRIPT_ID = "account";
- @Override
- public String getType() {
- return DEFAULT_PLATFORM_PLUGIN_TYPE;
- }
/**
* 返回插件的描述信息
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/Platform.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/Platform.java
index c570791df1..868e1e563b 100644
--- a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/Platform.java
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/Platform.java
@@ -1,10 +1,12 @@
package io.metersphere.plugin.platform.api;
+import org.pf4j.ExtensionPoint;
+
/**
* 平台对接相关业务接口
* @author jianxing.chen
*/
-public interface Platform {
+public interface Platform extends ExtensionPoint {
/**
* 校验服务集成配置
diff --git a/backend/framework/plugin/metersphere-plugin-sdk/pom.xml b/backend/framework/plugin/metersphere-plugin-sdk/pom.xml
index 7a9f1f6ead..5f19e2a421 100644
--- a/backend/framework/plugin/metersphere-plugin-sdk/pom.xml
+++ b/backend/framework/plugin/metersphere-plugin-sdk/pom.xml
@@ -44,5 +44,10 @@
com.fasterxml.jackson.core
jackson-annotations
+
+ org.pf4j
+ pf4j
+ ${pf4j.version}
+
\ No newline at end of file
diff --git a/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/api/AbstractMsPlugin.java b/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/api/AbstractMsPlugin.java
index 73c5b021b5..1905016925 100644
--- a/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/api/AbstractMsPlugin.java
+++ b/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/api/AbstractMsPlugin.java
@@ -1,6 +1,6 @@
package io.metersphere.plugin.sdk.api;
-public abstract class AbstractMsPlugin implements MsPlugin {
+public abstract class AbstractMsPlugin extends MsPlugin {
private static final String SCRIPT_DIR = "script";
@@ -12,14 +12,4 @@ public abstract class AbstractMsPlugin implements MsPlugin {
public String getScriptDir() {
return SCRIPT_DIR;
}
-
- @Override
- public String getName() {
- return getKey();
- }
-
- @Override
- public String getPluginId() {
- return getKey().toLowerCase() + "-" + getVersion();
- }
}
diff --git a/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/api/MsPlugin.java b/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/api/MsPlugin.java
index 623ab6b834..b5cf42e5bc 100644
--- a/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/api/MsPlugin.java
+++ b/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/api/MsPlugin.java
@@ -1,48 +1,28 @@
package io.metersphere.plugin.sdk.api;
+import org.pf4j.Plugin;
+
/**
* 插件的基本信息
*
* @author jianxing.chen
*/
-public interface MsPlugin {
+public abstract class MsPlugin extends Plugin {
/**
* @return 返回该插件是否是开源的,默认是
*/
- boolean isXpack();
-
- /**
- * @return 返回插件的类型
- * 目前支持接口插件和平台(API、PLATFORM)
- */
- String getType();
-
- /**
- * @return 返回插件的关键字,例如 Jira
- */
- String getKey();
+ abstract public boolean isXpack();
/**
* @return 返回插件的名称
* 默认返回 key
*/
- String getName();
-
- /**
- * @return 返回插件的ID
- * 默认是 key + 版本号
- */
- String getPluginId();
-
- /**
- * @return 返回插件的版本
- */
- String getVersion();
+ abstract public String getName();
/**
* @return 返回该加载前端配置文件的目录,默认是 script
* 可以重写定制
*/
- String getScriptDir();
+ abstract public String getScriptDir();
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/config/RsaConfig.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/config/RsaConfig.java
index 9d7b0d0beb..48bf78fd33 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/config/RsaConfig.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/config/RsaConfig.java
@@ -1,12 +1,12 @@
package io.metersphere.sdk.config;
+import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRepository;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.log.constants.OperationLogModule;
import io.metersphere.sdk.util.RsaKey;
import io.metersphere.sdk.util.RsaUtil;
-import jakarta.annotation.Resource;
import org.apache.commons.lang3.SerializationUtils;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
@@ -14,8 +14,6 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class RsaConfig implements ApplicationRunner {
- @Resource
- private FileRepository fileRepository;
@Override
public void run(ApplicationArguments args) throws Exception {
@@ -23,7 +21,8 @@ public class RsaConfig implements ApplicationRunner {
request.setFileName("rsa.key");
request.setProjectId("system");
request.setResourceId(OperationLogModule.SYSTEM_PARAMETER_SETTING);
- //
+ FileRepository fileRepository = FileCenter.getDefaultRepository();
+
try {
byte[] file = fileRepository.getFile(request);
if (file != null) {
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PluginScenarioType.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PluginScenarioType.java
index fb240c13dd..0edfc5936f 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PluginScenarioType.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/PluginScenarioType.java
@@ -1,5 +1,16 @@
package io.metersphere.sdk.constants;
public enum PluginScenarioType {
- API, PLATFORM, JDBC_DRIVER
+ /**
+ * 接口协议插件
+ */
+ API_PROTOCOL,
+ /**
+ * 项目关联平台插件
+ */
+ PLATFORM,
+ /**
+ * jdbc 驱动插件
+ */
+ JDBC_DRIVER
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/StorageConstants.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/StorageConstants.java
deleted file mode 100644
index e11ba695e6..0000000000
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/StorageConstants.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.metersphere.sdk.constants;
-
-public enum StorageConstants {
- MINIO, GIT, FILE_REF
-}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/StorageType.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/StorageType.java
new file mode 100644
index 0000000000..bdf25013ff
--- /dev/null
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/StorageType.java
@@ -0,0 +1,5 @@
+package io.metersphere.sdk.constants;
+
+public enum StorageType {
+ MINIO, GIT, FILE_REF, LOCAL
+}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/environment/EnvironmentController.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/environment/EnvironmentController.java
index d92fe186a3..be9a5768c2 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/environment/EnvironmentController.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/environment/EnvironmentController.java
@@ -2,39 +2,30 @@ package io.metersphere.sdk.controller.environment;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.domain.Environment;
+import io.metersphere.sdk.dto.OptionDTO;
import io.metersphere.sdk.dto.environment.EnvironmentConfigRequest;
import io.metersphere.sdk.dto.environment.dataSource.DataSource;
-import io.metersphere.sdk.service.PluginLoadService;
import io.metersphere.sdk.service.environment.EnvironmentService;
import io.metersphere.sdk.util.SessionUtils;
import io.metersphere.validation.groups.Created;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
-import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
-import java.sql.Driver;
-import java.sql.DriverManager;
import java.util.List;
-import java.util.Map;
-import java.util.Properties;
@RestController
@RequestMapping(value = "/project/environment")
@Tag(name = "项目管理-环境")
public class EnvironmentController {
- @Resource
- private PluginLoadService pluginLoadService;
-
@Resource
private EnvironmentService environmentService;
-
@GetMapping("/list/{projectId}")
@Operation(summary = "项目管理-环境-环境目录-列表")
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
@@ -76,26 +67,13 @@ public class EnvironmentController {
@Operation(summary = "项目管理-环境-数据库配置-校验")
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
public void validate(@RequestBody DataSource databaseConfig) {
- try {
- if (StringUtils.isNotBlank(databaseConfig.getDriverId())) {
- ClassLoader classLoader = pluginLoadService.getClassLoader(databaseConfig.getDriverId());
- Driver driver = (Driver) classLoader.loadClass(databaseConfig.getDriver()).newInstance();
- Properties properties = new Properties();
- properties.setProperty("user", databaseConfig.getUsername());
- properties.setProperty("password", databaseConfig.getPassword());
- driver.connect(databaseConfig.getDbUrl(), properties);
- } else {
- DriverManager.getConnection(databaseConfig.getDbUrl(), databaseConfig.getUsername(), databaseConfig.getPassword());
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
+ environmentService.validateDataSource(databaseConfig);
}
@GetMapping("/database/driver-options/{organizationId}")
@Operation(summary = "项目管理-环境-数据库配置-数据库驱动选项")
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
- public Map driverOptions(@PathVariable String organizationId) {
+ public List driverOptions(@PathVariable String organizationId) {
return environmentService.getDriverOptions(organizationId);
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/environment/BodyFile.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/environment/BodyFile.java
index 2cefb2719d..7671117ca1 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/environment/BodyFile.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/dto/environment/BodyFile.java
@@ -1,7 +1,7 @@
package io.metersphere.sdk.dto.environment;
-import io.metersphere.sdk.constants.StorageConstants;
+import io.metersphere.sdk.constants.StorageType;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
@@ -20,6 +20,6 @@ public class BodyFile {
private String refResourceId;
public boolean isRef() {
- return StringUtils.equals(storage, StorageConstants.FILE_REF.name()) && StringUtils.isNotEmpty(fileId);
+ return StringUtils.equals(storage, StorageType.FILE_REF.name()) && StringUtils.isNotEmpty(fileId);
}
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/file/FileCenter.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/file/FileCenter.java
index 68705afb7e..6aceb0d794 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/file/FileCenter.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/file/FileCenter.java
@@ -1,18 +1,21 @@
package io.metersphere.sdk.file;
+import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.util.CommonBeanFactory;
+import java.util.HashMap;
+import java.util.Map;
+
public class FileCenter {
- // 多种实现时打开
- /*public static FileRepository getRepository(String storage) {
- if (StringUtils.equals(StorageConstants.GIT.name(), storage)) {
- LogUtils.info("扩展GIT存储方式");
- return null;
- } else {
- return getDefaultRepository();
- }
- }*/
+ public static FileRepository getRepository(StorageType storageType) {
+ Map repositoryMap = new HashMap<>() {{
+ put(StorageType.MINIO, CommonBeanFactory.getBean(MinioRepository.class));
+ put(StorageType.LOCAL, CommonBeanFactory.getBean(LocalFileRepository.class));
+ }};
+ FileRepository fileRepository = repositoryMap.get(storageType);
+ return fileRepository == null ? getDefaultRepository() : fileRepository;
+ }
public static FileRepository getDefaultRepository() {
return CommonBeanFactory.getBean(MinioRepository.class);
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/file/LocalFileRepository.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/file/LocalFileRepository.java
new file mode 100644
index 0000000000..3969a1dcc3
--- /dev/null
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/file/LocalFileRepository.java
@@ -0,0 +1,90 @@
+package io.metersphere.sdk.file;
+
+import io.metersphere.sdk.util.MsFileUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.aspectj.util.FileUtil;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.util.List;
+
+@Component
+public class LocalFileRepository implements FileRepository {
+
+ @Override
+ public String saveFile(MultipartFile multipartFile, FileRequest request) throws IOException {
+ if (multipartFile == null || request == null || StringUtils.isEmpty(request.getFileName()) || StringUtils.isEmpty(request.getProjectId())) {
+ return null;
+ }
+ MsFileUtils.validateFileName(request.getProjectId(), request.getFileName());
+ createFileDir(request);
+ File file = new File(getFilePath(request));
+ FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), file);
+ return file.getPath();
+ }
+
+ private void createFileDir(FileRequest request) {
+ String dir = getFileDir(request);
+ File fileDir = new File(dir);
+ if (!fileDir.exists()) {
+ fileDir.mkdirs();
+ }
+ }
+
+ @Override
+ public String saveFile(byte[] bytes, FileRequest request) throws IOException {
+ File file = new File(getFilePath(request));
+ try (OutputStream ops = new FileOutputStream(file)) {
+ ops.write(bytes);
+ return file.getPath();
+ } catch (Exception e) {
+ throw e;
+ }
+ }
+
+ @Override
+ public void delete(FileRequest request) throws Exception {
+ String path = StringUtils.join(getFilePath(request));
+ File file = new File(path);
+ FileUtil.deleteContents(file);
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+
+ @Override
+ public void deleteFolder(FileRequest request) throws Exception {
+ this.delete(request);
+ }
+
+ @Override
+ public byte[] getFile(FileRequest request) throws Exception {
+ File file = new File(getFilePath(request));
+ return Files.readAllBytes(file.toPath());
+ }
+
+ @Override
+ public InputStream getFileAsStream(FileRequest request) throws Exception {
+ return new FileInputStream(getFilePath(request));
+ }
+
+ @Override
+ public void downloadFile(FileRequest request, String localPath) {
+ }
+
+ @Override
+ public List getFolderFileNames(FileRequest request) {
+ return null;
+ }
+
+ private String getFilePath(FileRequest request) {
+ return StringUtils.join(getFileDir(request), "/", request.getFileName());
+ }
+
+ private String getFileDir(FileRequest request) {
+ return StringUtils.join(MsFileUtils.DATE_ROOT_DIR, "/", request.getProjectId());
+ }
+}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverPluginDescriptor.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverPluginDescriptor.java
new file mode 100644
index 0000000000..976b12b8c1
--- /dev/null
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverPluginDescriptor.java
@@ -0,0 +1,47 @@
+package io.metersphere.sdk.plugin;
+
+import org.pf4j.DefaultPluginDescriptor;
+import org.pf4j.PluginDescriptor;
+
+/**
+ * @author jainxing
+ * 由于 DefaultPluginDescriptor 中的 set 方法是 protected 的,JdbcDriverPluginDescriptorFinder 无法访问
+ * 这里重写一下,让相同包名下的 JdbcDriverPluginDescriptorFinder 可以访问
+ */
+public class JdbcDriverPluginDescriptor extends DefaultPluginDescriptor {
+
+ @Override
+ protected DefaultPluginDescriptor setPluginId(String pluginId) {
+ return super.setPluginId(pluginId);
+ }
+
+ @Override
+ protected PluginDescriptor setPluginDescription(String pluginDescription) {
+ return super.setPluginDescription(pluginDescription);
+ }
+
+ @Override
+ protected PluginDescriptor setPluginClass(String pluginClassName) {
+ return super.setPluginClass(pluginClassName);
+ }
+
+ @Override
+ protected DefaultPluginDescriptor setPluginVersion(String version) {
+ return super.setPluginVersion(version);
+ }
+
+ @Override
+ protected PluginDescriptor setProvider(String provider) {
+ return super.setProvider(provider);
+ }
+
+ @Override
+ protected PluginDescriptor setDependencies(String dependencies) {
+ return super.setDependencies(dependencies);
+ }
+
+ @Override
+ protected PluginDescriptor setRequires(String requires) {
+ return super.setRequires(requires);
+ }
+}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverPluginDescriptorFinder.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverPluginDescriptorFinder.java
new file mode 100644
index 0000000000..1e1ba1502c
--- /dev/null
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverPluginDescriptorFinder.java
@@ -0,0 +1,85 @@
+package io.metersphere.sdk.plugin;
+
+import org.apache.commons.io.IOUtils;
+import org.pf4j.ManifestPluginDescriptorFinder;
+import org.pf4j.PluginDescriptor;
+import org.pf4j.PluginRuntimeException;
+import org.pf4j.util.FileUtils;
+import org.pf4j.util.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+/**
+ * @author jianxing
+ * 支持解析 jdbc 驱动中的 MANIFEST.MF
+ * 如果使用 ManifestPluginDescriptorFinder 解析,由于 jdbc 驱动没有按照 pf4j 的规范打包
+ * 无法解析出 PluginDescriptor ,这里重写后,按照自定义的规则解析
+ */
+public class JdbcDriverPluginDescriptorFinder extends ManifestPluginDescriptorFinder {
+
+ private String driverClass;
+
+ @Override
+ public boolean isApplicable(Path pluginPath) {
+ return Files.exists(pluginPath, new LinkOption[0]) && (Files.isDirectory(pluginPath, new LinkOption[0]) || FileUtils.isZipOrJarFile(pluginPath))
+ && hasDriverFile(pluginPath); // 这里前判断时候包含 META-INF/services/java.sql.Driver 文件,不包含则走 ManifestPluginDescriptorFinder
+ }
+
+ /**
+ * 判断是否有SPI的驱动实现类
+ * @param jarPath
+ * @return
+ */
+ protected boolean hasDriverFile(Path jarPath) {
+ try (JarFile jar = new JarFile(jarPath.toFile())) {
+ JarEntry jarEntry = jar.getJarEntry("META-INF/services/java.sql.Driver");
+ if (jarEntry == null) {
+ return false;
+ }
+ InputStream inputStream = jar.getInputStream(jarEntry);
+ // 获取SPI中定义的类名
+ driverClass = IOUtils.toString(inputStream);
+ return true;
+ } catch (IOException e) {
+ throw new PluginRuntimeException(e, "Cannot read META-INF/services/java.sql.Driver from {}", jarPath);
+ }
+ }
+
+ @Override
+ protected PluginDescriptor createPluginDescriptor(Manifest manifest) {
+ JdbcDriverPluginDescriptor pluginDescriptor = this.createJdbcDriverPluginDescriptorInstance();
+ Attributes attributes = manifest.getMainAttributes();
+ // 将类名作为ID
+ String id = driverClass;
+ pluginDescriptor.setPluginId(id.split("\n")[0]);
+ if (StringUtils.isNullOrEmpty(id)) {
+ return null;
+ }
+ String description = attributes.getValue("Plugin-Description");
+ if (StringUtils.isNullOrEmpty(description)) {
+ pluginDescriptor.setPluginDescription("");
+ } else {
+ pluginDescriptor.setPluginDescription(description);
+ }
+ String version = attributes.getValue("Implementation-Version");
+ if (StringUtils.isNotNullOrEmpty(version)) {
+ pluginDescriptor.setPluginVersion(version);
+ }
+
+ String provider = attributes.getValue("Implementation-Vendor");
+ pluginDescriptor.setProvider(provider);
+ return pluginDescriptor;
+ }
+
+ protected JdbcDriverPluginDescriptor createJdbcDriverPluginDescriptorInstance() {
+ return new JdbcDriverPluginDescriptor();
+ }
+}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverServiceProviderExtensionFinder.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverServiceProviderExtensionFinder.java
new file mode 100644
index 0000000000..37328a2ed8
--- /dev/null
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverServiceProviderExtensionFinder.java
@@ -0,0 +1,142 @@
+package io.metersphere.sdk.plugin;
+
+
+import io.metersphere.sdk.util.LogUtils;
+import org.pf4j.PluginClassLoader;
+import org.pf4j.PluginManager;
+import org.pf4j.PluginWrapper;
+import org.pf4j.ServiceProviderExtensionFinder;
+import org.pf4j.processor.ExtensionStorage;
+import org.pf4j.processor.ServiceProviderExtensionStorage;
+import org.pf4j.util.FileUtils;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.*;
+
+/**
+ * @author jianxing
+ * 支持加载 jdbc 驱动
+ * pf4j 中 ServiceProviderExtensionFinder 本身是支持 SPI
+ * 默认会读取 META-INF/services 下的文件
+ * 但是遍历 JarEntry 发现 jdbc 资源中没有 META-INF/services 只有 META-INF/services/java.sql.Driver
+ * 所以使用默认的 ServiceProviderExtensionFinder 会无法加载,这里重写后只修改了 EXTENSIONS_RESOURCE
+ */
+public class JdbcDriverServiceProviderExtensionFinder extends ServiceProviderExtensionFinder {
+
+ // 重写后只修改了这个常量
+ public static final String EXTENSIONS_RESOURCE = ServiceProviderExtensionStorage.EXTENSIONS_RESOURCE + "/java.sql.Driver";
+
+ public JdbcDriverServiceProviderExtensionFinder(PluginManager pluginManager) {
+ super(pluginManager);
+ }
+
+ @Override
+ public Map> readClasspathStorages() {
+ LogUtils.debug("Reading extensions storages from classpath");
+ Map> result = new LinkedHashMap<>();
+
+ final Set bucket = new HashSet<>();
+ try {
+ Enumeration urls = getClass().getClassLoader().getResources(EXTENSIONS_RESOURCE);
+ if (urls.hasMoreElements()) {
+ collectExtensions(urls, bucket);
+ } else {
+ LogUtils.debug("Cannot find '{}'", EXTENSIONS_RESOURCE);
+ }
+
+ debugExtensions(bucket);
+
+ result.put(null, bucket);
+ } catch (IOException | URISyntaxException e) {
+ LogUtils.error(e.getMessage(), e);
+ }
+
+ return result;
+ }
+
+ @Override
+ public Map> readPluginsStorages() {
+ LogUtils.debug("Reading extensions storages from plugins");
+ Map> result = new LinkedHashMap<>();
+
+ List plugins = pluginManager.getPlugins();
+ for (PluginWrapper plugin : plugins) {
+ String pluginId = plugin.getDescriptor().getPluginId();
+ LogUtils.debug("Reading extensions storages for plugin '{}'", pluginId);
+ final Set bucket = new HashSet<>();
+
+ try {
+ Enumeration urls = ((PluginClassLoader) plugin.getPluginClassLoader()).findResources(ServiceProviderExtensionStorage.EXTENSIONS_RESOURCE);
+ if (urls.hasMoreElements()) {
+ // 如果 ServiceProviderExtensionFinder 无法从 "META-INF/services" 加载,才加载
+ return result;
+ }
+
+ urls = ((PluginClassLoader) plugin.getPluginClassLoader()).findResources(EXTENSIONS_RESOURCE);
+ if (urls.hasMoreElements()) {
+ collectExtensions(urls, bucket);
+ } else {
+ LogUtils.debug("Cannot find '{}'", EXTENSIONS_RESOURCE);
+ }
+
+ debugExtensions(bucket);
+
+ result.put(pluginId, bucket);
+ } catch (IOException | URISyntaxException e) {
+ LogUtils.error(e.getMessage(), e);
+ }
+ }
+
+ return result;
+ }
+
+ private void collectExtensions(Enumeration urls, Set bucket) throws URISyntaxException, IOException {
+ while (urls.hasMoreElements()) {
+ URL url = urls.nextElement();
+ LogUtils.debug("Read '{}'", url.getFile());
+ collectExtensions(url, bucket);
+ }
+ }
+
+ private void collectExtensions(URL url, Set bucket) throws URISyntaxException, IOException {
+ Path extensionPath;
+
+ if (url.toURI().getScheme().equals("jar")) {
+ extensionPath = FileUtils.getPath(url.toURI(), EXTENSIONS_RESOURCE);
+ } else {
+ extensionPath = Paths.get(url.toURI());
+ }
+
+ try {
+ bucket.addAll(readExtensions(extensionPath));
+ } finally {
+ FileUtils.closePath(extensionPath);
+ }
+ }
+
+ private Set readExtensions(Path extensionPath) throws IOException {
+ final Set result = new HashSet<>();
+ Files.walkFileTree(extensionPath, Collections.emptySet(), 1, new SimpleFileVisitor() {
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ LogUtils.debug("Read '{}'", file);
+ try (Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
+ ExtensionStorage.read(reader, result);
+ }
+
+ return FileVisitResult.CONTINUE;
+ }
+
+ });
+
+ return result;
+ }
+
+}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/MsPluginManager.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/MsPluginManager.java
new file mode 100644
index 0000000000..f266068e3c
--- /dev/null
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/MsPluginManager.java
@@ -0,0 +1,27 @@
+package io.metersphere.sdk.plugin;
+
+import org.pf4j.*;
+
+/**
+ * @author jianxing
+ * 为了支持加载使用 SPI 机制加载的 jdbc 驱动
+ * 这里加入自定义的 JdbcDriverServiceProviderExtensionFinder 和 JdbcDriverPluginDescriptorFinder
+ */
+public class MsPluginManager extends DefaultPluginManager {
+ @Override
+ protected ExtensionFinder createExtensionFinder() {
+ DefaultExtensionFinder extensionFinder = (DefaultExtensionFinder) super.createExtensionFinder();
+ extensionFinder.addServiceProviderExtensionFinder();
+ extensionFinder.add(new JdbcDriverServiceProviderExtensionFinder(this));
+ return extensionFinder;
+ }
+
+ @Override
+ protected PluginDescriptorFinder createPluginDescriptorFinder() {
+ // 需要保证 JdbcDriverPluginDescriptorFinder 在 ManifestPluginDescriptorFinder 之前解析
+ return (new CompoundPluginDescriptorFinder())
+ .add(new PropertiesPluginDescriptorFinder())
+ .add(new JdbcDriverPluginDescriptorFinder())
+ .add(new ManifestPluginDescriptorFinder());
+ }
+}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PlatformPluginManager.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PlatformPluginManager.java
deleted file mode 100644
index e425ef9dea..0000000000
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PlatformPluginManager.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package io.metersphere.sdk.plugin.loader;
-
-import io.metersphere.plugin.platform.api.Platform;
-import io.metersphere.plugin.platform.dto.PlatformRequest;
-
-/**
- * @author jianxing.chen
- */
-public class PlatformPluginManager extends PluginManager {
-
-
-
- /**
- * 获取对应插件的 Platform 对象
- * @param pluginId 插件ID
- * @param request
- * @return
- */
- public Platform getPlatform(String pluginId, PlatformRequest request) {
- return getImplInstance(pluginId, Platform.class, request);
- }
-
-}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginClassLoader.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginClassLoader.java
deleted file mode 100644
index dad09a64f3..0000000000
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginClassLoader.java
+++ /dev/null
@@ -1,230 +0,0 @@
-package io.metersphere.sdk.plugin.loader;
-
-import io.metersphere.sdk.plugin.storage.StorageStrategy;
-import io.metersphere.sdk.util.LogUtils;
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.*;
-import java.util.*;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarInputStream;
-
-/**
- * @author jianxing.chen
- */
-public class PluginClassLoader extends ClassLoader {
-
- /**
- * 记录加载的类
- */
- protected final Set clazzSet = new HashSet<>();
- /**
- * 加载重试次数
- */
- protected final static int CLASS_RELOAD_TIME = 20;
- /**
- * 保存加载失败的类,之后重试
- */
- protected Map loadErrorMap = new HashMap<>();
-
- private class ByteArrayWrapper {
- private byte[] values;
-
- public ByteArrayWrapper(byte[] values) {
- this.values = values;
- }
-
- public byte[] getValues() {
- return values;
- }
- }
-
- public Set getClazzSet() {
- return clazzSet;
- }
-
- /**
- * jar包的静态资源的存储策略
- * 可以扩展为对象存储
- */
- protected StorageStrategy storageStrategy;
-
- protected boolean isNeedUploadFile;
-
- public PluginClassLoader() {
- // 将父加载器设置成当前的类加载器,目的是由父加载器加载接口,实现类由该加载器加载
- super(PluginClassLoader.class.getClassLoader());
- }
-
- public PluginClassLoader(StorageStrategy storageStrategy) {
- this(storageStrategy, true);
- }
-
- public PluginClassLoader(StorageStrategy storageStrategy, boolean isNeedUploadFile) {
- this();
- this.storageStrategy = storageStrategy;
- this.isNeedUploadFile = isNeedUploadFile;
- }
-
- public StorageStrategy getStorageStrategy() {
- return storageStrategy;
- }
-
- /**
- * 扫描目录或 jar 包
- * 加载 clazz
- *
- * @param file
- */
- protected void scanJarFile(File file) throws IOException {
- if (file.exists()) {
- if (file.isFile() && file.getName().endsWith(".jar")) {
- try {
- readJar(new JarFile(file));
- } catch (IOException e) {
- throw e;
- }
- } else if (file.isDirectory()) {
- for (File f : file.listFiles()) {
- scanJarFile(f);
- }
- }
- }
- }
-
- /**
- * 加载对应目录下的 jar 包
- *
- * @param jarfileDir
- */
- public void loadJar(String jarfileDir) throws IOException {
- if (StringUtils.isBlank(jarfileDir)) {
- throw new IllegalArgumentException("basePath can not be empty!");
- }
- File dir = new File(jarfileDir);
- if (!dir.exists()) {
- throw new IllegalArgumentException("basePath not exists:" + jarfileDir);
- }
- scanJarFile(new File(jarfileDir));
- }
-
- /**
- * 从输入流中加载 jar 包
- *
- * @param in
- */
- public void loadJar(InputStream in) throws Exception {
- if (in != null) {
- try (JarInputStream jis = new JarInputStream(in)) {
- JarEntry je;
- while ((je = jis.getNextJarEntry()) != null) {
- loadJar(jis, je);
- }
- reloadErrorClazz();
- } catch (IOException e) {
- throw e;
- }
- }
- }
-
- /**
- * 读取 jar 包中的 clazz
- *
- * @param jar
- * @throws IOException
- */
- protected void readJar(JarFile jar) {
- Enumeration en = jar.entries();
- while (en.hasMoreElements()) {
- JarEntry je = en.nextElement();
- try (InputStream in = jar.getInputStream(je)) {
- loadJar(in, je);
- } catch (Exception e) {
- LogUtils.error(e);
- }
- }
- reloadErrorClazz();
- }
-
- /**
- * 加载 jar 包的 class,并存储静态资源
- *
- * @param in
- * @param je
- * @throws IOException
- */
- protected void loadJar(InputStream in, JarEntry je) throws Exception {
- je.getName();
- String name = je.getName();
- if (name.endsWith(".class")) {
- String className = name.replace("\\", ".")
- .replace("/", ".")
- .replace(".class", "");
- BufferedInputStream bis;
- byte[] bytes = null;
- try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
- int line;
- bytes = new byte[1024];
- bis = new BufferedInputStream(in);
- while ((line = bis.read(bytes)) != -1) {
- bos.write(bytes, 0, line);
- }
- bos.flush();
- bytes = bos.toByteArray();
- Class> clazz = defineClass(className, bytes, 0, bytes.length);
- clazzSet.add(clazz);
- } catch (NoClassDefFoundError e) {
- loadErrorMap.put(className, new ByteArrayWrapper(bytes));
- } catch (Throwable e) {
- LogUtils.error(e);
- }
- } else if (!name.endsWith("/")) {
- // 非目录即静态资源
- if (storageStrategy != null && isNeedUploadFile) {
- storageStrategy.store(name, in);
- }
- }
- }
-
- /**
- * 由于 loadJar 中是按照条目加载的
- * 加载顺序不确定,如果父类没有先加载会加载失败
- * 这里针对加载失败的类,再次加载
- */
- private synchronized void reloadErrorClazz() {
- for (int i = 0; i < CLASS_RELOAD_TIME; i++) {
- Iterator iterator = loadErrorMap.keySet().iterator();
- while (iterator.hasNext()) {
- String className = iterator.next();
- try {
- LogUtils.info("reload class: " + className);
- byte[] bytes = loadErrorMap.get(className).getValues();
- Class> clazz = defineClass(className, bytes, 0, bytes.length);
- clazzSet.add(clazz);
- iterator.remove();
- } catch (Throwable e) {
- LogUtils.error(e);
- }
- }
- }
- }
-
- /**
- * 从存储策略中加载静态资源
- * @param name
- * @return
- */
- @Override
- public InputStream getResourceAsStream(String name) {
- if (null != storageStrategy) {
- try {
- return storageStrategy.get(name);
- } catch (Exception e) {
- LogUtils.error(e);
- return null;
- }
- }
- return super.getResourceAsStream(name);
- }
-}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginManager.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginManager.java
deleted file mode 100644
index dabbbc4ca2..0000000000
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginManager.java
+++ /dev/null
@@ -1,167 +0,0 @@
-package io.metersphere.sdk.plugin.loader;
-
-import io.metersphere.sdk.controller.handler.result.CommonResultCode;
-import io.metersphere.sdk.exception.MSException;
-import io.metersphere.sdk.plugin.storage.StorageStrategy;
-import io.metersphere.sdk.util.LogUtils;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Type;
-import java.util.*;
-
-/**
- * @author jianxing.chen
- */
-public class PluginManager {
-
- /**
- * 自定义类加载器
- */
- protected Map classLoaderMap = new HashMap<>();
-
- /**
- * 缓存查找过的类
- * 内层 map
- * key 未接口的类
- * value 为实现类
- */
- protected Map> implClassCache = new HashMap<>();
-
- public PluginClassLoader getClassLoader(String pluginId) {
- return classLoaderMap.get(pluginId);
- }
-
- /**
- * 加载对应目录下的 jar 包
- */
- public PluginManager loadJar(String pluginId, String jarfileDir, StorageStrategy storageStrategy) throws IOException {
- PluginClassLoader pluginClassLoader = new PluginClassLoader(storageStrategy);
- classLoaderMap.put(pluginId, pluginClassLoader);
- pluginClassLoader.loadJar(jarfileDir);
- return this;
- }
-
- public PluginManager loadJar(String pluginId, String jarfileDir) throws IOException {
- return this.loadJar(pluginId, jarfileDir, null);
- }
-
- public Map getClassLoaderMap() {
- return classLoaderMap;
- }
-
- public void deletePlugin(String id) {
- classLoaderMap.remove(id);
- implClassCache.remove(id);
- }
-
- /**
- * 从输入流中加载 jar 包
- *
- * @param in
- */
- public PluginManager loadJar(String pluginId, InputStream in, StorageStrategy storageStrategy, boolean isNeedUploadFile) throws Exception {
- PluginClassLoader pluginClassLoader = new PluginClassLoader(storageStrategy, isNeedUploadFile);
- classLoaderMap.put(pluginId, pluginClassLoader);
- pluginClassLoader.loadJar(in);
- return this;
- }
-
- public PluginManager loadJar(String pluginId, InputStream in, boolean isNeedUploadFile) throws Exception {
- return this.loadJar(pluginId, in, null, isNeedUploadFile);
- }
-
- /**
- * 获取接口的单一实现类
- */
- public Class getImplClass(String pluginId, Class superClazz) {
- PluginClassLoader classLoader = getPluginClassLoader(pluginId);
- Map classes = implClassCache.get(pluginId);
- if (classes == null) {
- classes = new HashMap<>();
- implClassCache.put(pluginId, classes);
- }
- if (classes.get(superClazz) != null) {
- return classes.get(superClazz);
- }
- LinkedHashSet> result = new LinkedHashSet<>();
- Set clazzSet = classLoader.getClazzSet();
- for (Class item : clazzSet) {
- if (isImplClazz(superClazz, item) && !result.contains(item)) {
- classes.put(superClazz, item);
- return item;
- }
- }
- return null;
- }
-
- private PluginClassLoader getPluginClassLoader(String pluginId) {
- PluginClassLoader classLoader = classLoaderMap.get(pluginId);
- if (classLoader == null) {
- throw new MSException("插件未加载");
- }
- return classLoader;
- }
-
- /**
- * 获取指定接口最后一次加载的实现类实例
- */
- public T getImplInstance(String pluginId, Class superClazz) {
- return this.getImplInstance(pluginId, superClazz, null);
- }
-
- public T getImplInstance(String pluginId, Class superClazz, Object param) {
- try {
- Class clazz = getImplClass(pluginId, superClazz);
- if (clazz == null) {
- throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE);
- }
- if (param == null) {
- return clazz.getConstructor().newInstance();
- } else {
- return clazz.getConstructor(param.getClass()).newInstance(param);
- }
- } catch (InvocationTargetException e) {
- LogUtils.error(e.getTargetException());
- throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE, e.getTargetException().getMessage());
- } catch (Exception e) {
- LogUtils.error(e);
- throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE, e.getMessage());
- }
- }
-
-
- /**
- * 判断 impClazz 是否是 superClazz 的实现类
- *
- * @param impClazz
- * @return
- */
- private boolean isImplClazz(Class superClazz, Class impClazz) {
- if (impClazz == superClazz) {
- return true;
- }
- 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;
- }
- }
- }
- Type superclass = impClazz.getGenericSuperclass();
- if (superclass != null
- && superclass instanceof Class
- && isImplClazz(superClazz, (Class) superclass)) {
- return true;
-
- }
- } catch (Throwable e) {
- LogUtils.error(e);
- }
- return false;
- }
-}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/storage/MsStorageStrategy.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/storage/MsStorageStrategy.java
deleted file mode 100644
index 959b949afb..0000000000
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/storage/MsStorageStrategy.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package io.metersphere.sdk.plugin.storage;
-
-import io.metersphere.sdk.file.FileCenter;
-import io.metersphere.sdk.file.FileRepository;
-import io.metersphere.sdk.file.FileRequest;
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * jar包静态资源存储策略
- * @author jianxing
- */
-public class MsStorageStrategy implements StorageStrategy {
-
-
- private final FileRepository fileRepository;
- private final String pluginId;
-
- public static final String DIR_PATH = "system/plugin";
-
- public MsStorageStrategy(String pluginId) {
- this.pluginId = pluginId;
- fileRepository = FileCenter.getDefaultRepository();
- }
-
- @Override
- public String store(String name, InputStream in) throws Exception {
- FileRequest request = getFileRequest(name);
- return fileRepository.saveFile(in.readAllBytes(), request);
- }
-
- @Override
- public InputStream get(String name) throws Exception {
- FileRequest request = getFileRequest(name);
- return fileRepository.getFileAsStream(request);
- }
-
- @Override
- public List getFolderFileNames(String dirName) throws Exception {
- FileRequest request = getFileRequest(dirName);
- List fileNames = fileRepository.getFolderFileNames(request);
- return fileNames.stream().map(s -> s.replace(getPluginDir(), StringUtils.EMPTY)).toList();
- }
-
- @Override
- public void delete() throws Exception {
- FileRequest request = new FileRequest();
- request.setProjectId(getPluginDir());
- fileRepository.deleteFolder(request);
- }
-
- private FileRequest getFileRequest(String name) {
- FileRequest request = new FileRequest();
- request.setProjectId(getPluginDir());
- request.setFileName(name);
- return request;
- }
-
- private String getPluginDir() {
- return DIR_PATH + "/" + this.pluginId;
- }
-}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/storage/StorageStrategy.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/storage/StorageStrategy.java
deleted file mode 100644
index 5ce880f51f..0000000000
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/storage/StorageStrategy.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package io.metersphere.sdk.plugin.storage;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * jar包、图片、前端配置文件等静态资源存储策略
- * @author jianxing
- */
-public interface StorageStrategy {
-
- /**
- * 存储文件
- * @param name
- * @param in
- * @return
- * @throws IOException
- */
- String store(String name, InputStream in) throws Exception;
-
- /**
- * 获取文件
- * @param path
- * @return
- * @throws IOException
- */
- InputStream get(String path) throws Exception;
-
- /**
- * 获取指定文件夹下的文件名列表
- *
- * @param dirName
- * @throws Exception
- */
- List getFolderFileNames(String dirName) throws Exception;
-
- /**
- * 删除文件
- * @throws IOException
- */
- void delete() throws Exception;
-}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/BasePluginService.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/BasePluginService.java
index b262d05efc..7df63a16c5 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/BasePluginService.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/BasePluginService.java
@@ -1,13 +1,21 @@
package io.metersphere.sdk.service;
+import io.metersphere.sdk.constants.PluginScenarioType;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.system.domain.Plugin;
+import io.metersphere.system.domain.PluginExample;
+import io.metersphere.system.domain.PluginOrganization;
import io.metersphere.system.mapper.PluginMapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
import static io.metersphere.sdk.controller.handler.result.CommonResultCode.PLUGIN_ENABLE;
import static io.metersphere.sdk.controller.handler.result.CommonResultCode.PLUGIN_PERMISSION;
@@ -31,4 +39,33 @@ public class BasePluginService {
throw new MSException(PLUGIN_PERMISSION);
}
}
+
+ public Plugin get(String pluginId) {
+ return pluginMapper.selectByPrimaryKey(pluginId);
+ }
+
+ public List getOrgEnabledPlugins(String orgId, PluginScenarioType pluginScenarioType) {
+ List plugins = getEnabledPlugins(pluginScenarioType);
+ List unGlobalIds = plugins.stream().filter(i -> !i.getGlobal()).map(Plugin::getId).toList();
+ // 如果没有非全局,直接返回全局插件
+ if (CollectionUtils.isEmpty(unGlobalIds)) {
+ return plugins;
+ }
+ // 查询当前组织下的插件列表
+ List pluginOrganizations = basePluginOrganizationService.getByPluginIds(unGlobalIds);
+ Set orgPluginIdSet = pluginOrganizations.stream()
+ .filter(i -> StringUtils.equals(i.getOrganizationId(), orgId))
+ .map(PluginOrganization::getPluginId)
+ .collect(Collectors.toSet());
+ // 返回全局插件和当前组织下的插件
+ return plugins.stream().filter(i -> i.getGlobal() || orgPluginIdSet.contains(i.getId())).collect(Collectors.toList());
+ }
+
+ public List getEnabledPlugins(PluginScenarioType PluginScenarioType) {
+ PluginExample example = new PluginExample();
+ example.createCriteria()
+ .andEnableEqualTo(true)
+ .andScenarioEqualTo(PluginScenarioType.name());
+ return pluginMapper.selectByExample(example);
+ }
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/JdbcDriverPluginService.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/JdbcDriverPluginService.java
index aec45e3afe..543193977f 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/JdbcDriverPluginService.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/JdbcDriverPluginService.java
@@ -1,14 +1,17 @@
package io.metersphere.sdk.service;
import io.metersphere.sdk.constants.PluginScenarioType;
+import io.metersphere.sdk.dto.OptionDTO;
+import io.metersphere.sdk.exception.MSException;
+import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.domain.Plugin;
-import io.metersphere.system.domain.PluginExample;
-import io.metersphere.system.mapper.PluginMapper;
import jakarta.annotation.Resource;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Driver;
+import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -19,25 +22,71 @@ public class JdbcDriverPluginService {
@Resource
private PluginLoadService pluginLoadService;
@Resource
- private PluginMapper pluginMapper;
+ private BasePluginService basePluginService;
+ public static final String MYSQL_DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver";
+ public static final String DRIVER_OPTION_SEPARATOR = "&";
+ public static final String SYSTEM_PLUGIN_ID = "system";
- public boolean isJdbcDriver(String pluginId) {
- return pluginLoadService.getImplClass(pluginId, Driver.class) != null;
+
+ public List getJdbcDriverClass(String orgId) {
+ List plugins = basePluginService.getOrgEnabledPlugins(orgId, PluginScenarioType.JDBC_DRIVER);
+ List drivers = new ArrayList<>(10);
+ for (Plugin plugin : plugins) {
+ drivers.addAll(pluginLoadService.getMsPluginManager().getExtensionClasses(Driver.class, plugin.getId()));
+ }
+
+ List pluginDriverClassNames = drivers.stream()
+ .map(Class::getName)
+ .collect(Collectors.toList());
+ if (!pluginDriverClassNames.contains(MYSQL_DRIVER_CLASS_NAME)) {
+ // 如果不包含新版本 mysql 的驱动,则添加内置的 mysql 驱动
+ pluginDriverClassNames.add(MYSQL_DRIVER_CLASS_NAME);
+ }
+ return pluginDriverClassNames;
}
/**
- * 获取所有的 JDBC 驱动的实现类
+ * 获取所有的 JDBC 驱动的实现类选项
* @return
*/
- public List getJdbcDriverClass() {
- PluginExample example = new PluginExample();
- example.createCriteria().andScenarioEqualTo(PluginScenarioType.JDBC_DRIVER.name());
- List plugins = pluginMapper.selectByExample(example);
- List 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;
+ public List getJdbcDriverOption(String orgId) {
+ List plugins = basePluginService.getOrgEnabledPlugins(orgId, PluginScenarioType.JDBC_DRIVER);
+ List options = new ArrayList<>();
+ for (Plugin plugin : plugins) {
+ List> extensionClasses = pluginLoadService.getMsPluginManager().getExtensionClasses(Driver.class, plugin.getId());
+ extensionClasses.forEach(driver -> {
+ options.add(new OptionDTO(plugin.getId() + DRIVER_OPTION_SEPARATOR + driver.getName(), driver.getName()));
+ });
+ }
+ List pluginDriverClassNames = options.stream().map(OptionDTO::getName).toList();
+ if (!pluginDriverClassNames.contains(MYSQL_DRIVER_CLASS_NAME)) {
+ // 如果不包含新版本 mysql 的驱动,则添加内置的 mysql 驱动
+ options.add(new OptionDTO(SYSTEM_PLUGIN_ID + DRIVER_OPTION_SEPARATOR + MYSQL_DRIVER_CLASS_NAME, MYSQL_DRIVER_CLASS_NAME));
+ }
+ return options;
+ }
+
+ public Plugin wrapperPlugin(Plugin plugin) {
+ plugin.setScenario(PluginScenarioType.JDBC_DRIVER.name());
+ plugin.setXpack(false);
+ return plugin;
+ }
+
+ public Driver getDriverByOptionId(String driverId) {
+ String[] split = driverId.split(DRIVER_OPTION_SEPARATOR);
+ String pluginId = split[0];
+ String className = split[1];
+ if (StringUtils.equals(pluginId, SYSTEM_PLUGIN_ID)) {
+ try {
+ return (Driver) Class.forName(MYSQL_DRIVER_CLASS_NAME).getConstructor().newInstance();
+ } catch (Exception e) {
+ LogUtils.error(e);
+ throw new MSException(e);
+ }
+ }
+ List extensions = pluginLoadService.getMsPluginManager().getExtensions(Driver.class, pluginId);
+ return extensions.stream().filter(driver -> StringUtils.equals(driver.getClass().getName(), className))
+ .findFirst()
+ .orElseThrow(() -> new MSException("未找到对应的驱动"));
}
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PlatformPluginService.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PlatformPluginService.java
index 2d49c0906a..690b4bb792 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PlatformPluginService.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PlatformPluginService.java
@@ -3,18 +3,15 @@ package io.metersphere.sdk.service;
import io.metersphere.plugin.platform.api.Platform;
import io.metersphere.plugin.platform.dto.PlatformRequest;
import io.metersphere.sdk.constants.PluginScenarioType;
-import io.metersphere.system.domain.*;
-import io.metersphere.system.mapper.PluginMapper;
+import io.metersphere.system.domain.Plugin;
+import io.metersphere.system.domain.ServiceIntegration;
+import io.metersphere.system.domain.ServiceIntegrationExample;
import io.metersphere.system.mapper.ServiceIntegrationMapper;
import jakarta.annotation.Resource;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
@@ -25,10 +22,6 @@ public class PlatformPluginService {
@Resource
private ServiceIntegrationMapper serviceIntegrationMapper;
@Resource
- private PluginMapper pluginMapper;
- @Resource
- private BasePluginOrganizationService basePluginOrganizationService;
- @Resource
private BasePluginService basePluginService;
/**
@@ -44,12 +37,10 @@ public class PlatformPluginService {
PlatformRequest pluginRequest = new PlatformRequest();
pluginRequest.setIntegrationConfig(integrationConfig);
pluginRequest.setOrganizationId(orgId);
- return pluginLoadService.getImplInstance(pluginId, Platform.class, pluginRequest);
+ return pluginLoadService.getImplInstance(Platform.class, pluginId, pluginRequest);
}
public Platform getPlatform(String pluginId, String orgId) {
- // 这里会校验插件是否存在
- pluginLoadService.getMsPluginInstance(pluginId);
ServiceIntegration serviceIntegration = getServiceIntegrationByPluginId(pluginId);
return getPlatform(pluginId, orgId, new String(serviceIntegration.getConfiguration()));
}
@@ -60,28 +51,7 @@ public class PlatformPluginService {
return serviceIntegrationMapper.selectByExampleWithBLOBs(example).get(0);
}
- public List getEnabledPlatformPlugins() {
- PluginExample example = new PluginExample();
- example.createCriteria()
- .andEnableEqualTo(true)
- .andScenarioEqualTo(PluginScenarioType.PLATFORM.name());
- return pluginMapper.selectByExample(example);
- }
-
public List getOrgEnabledPlatformPlugins(String orgId) {
- List plugins = getEnabledPlatformPlugins();
- List unGlobalIds = plugins.stream().filter(i -> !i.getGlobal()).map(Plugin::getId).toList();
- // 如果没有非全局,直接返回全局插件
- if (CollectionUtils.isEmpty(unGlobalIds)) {
- return plugins;
- }
- // 查询当前组织下的插件列表
- List pluginOrganizations = basePluginOrganizationService.getByPluginIds(unGlobalIds);
- Set orgPluginIdSet = pluginOrganizations.stream()
- .filter(i -> StringUtils.equals(i.getOrganizationId(), orgId))
- .map(PluginOrganization::getPluginId)
- .collect(Collectors.toSet());
- // 返回全局插件和当前组织下的插件
- return plugins.stream().filter(i -> i.getGlobal() || orgPluginIdSet.contains(i.getId())).collect(Collectors.toList());
+ return basePluginService.getOrgEnabledPlugins(orgId, PluginScenarioType.PLATFORM);
}
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PluginLoadService.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PluginLoadService.java
index 922caa5669..dd69629313 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PluginLoadService.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PluginLoadService.java
@@ -1,33 +1,39 @@
package io.metersphere.sdk.service;
-import io.metersphere.plugin.platform.api.AbstractPlatformPlugin;
import io.metersphere.plugin.sdk.api.MsPlugin;
+import io.metersphere.sdk.constants.StorageType;
+import io.metersphere.sdk.controller.handler.result.CommonResultCode;
import io.metersphere.sdk.exception.MSException;
-import io.metersphere.sdk.plugin.loader.PluginClassLoader;
-import io.metersphere.sdk.plugin.loader.PluginManager;
-import io.metersphere.sdk.plugin.storage.MsStorageStrategy;
-import io.metersphere.sdk.plugin.storage.StorageStrategy;
+import io.metersphere.sdk.file.FileCenter;
+import io.metersphere.sdk.file.FileRequest;
+import io.metersphere.sdk.plugin.MsPluginManager;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
+import io.metersphere.sdk.util.MsFileUtils;
import io.metersphere.system.domain.Plugin;
import io.metersphere.system.domain.PluginExample;
import io.metersphere.system.domain.PluginScript;
import io.metersphere.system.mapper.PluginMapper;
import io.metersphere.system.mapper.PluginScriptMapper;
import jakarta.annotation.Resource;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.FileUtils;
import org.codehaus.plexus.util.IOUtil;
-import org.codehaus.plexus.util.StringUtils;
+import org.pf4j.PluginWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
-import java.io.IOException;
+import java.io.File;
import java.io.InputStream;
-import java.sql.Driver;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
/**
* @author jianxing
@@ -36,44 +42,102 @@ import java.util.stream.Collectors;
@Transactional(rollbackFor = Exception.class)
public class PluginLoadService {
- private final PluginManager pluginManager = new PluginManager();
-
@Resource
private PluginMapper pluginMapper;
@Resource
private PluginScriptMapper pluginScriptMapper;
+ private MsPluginManager msPluginManager = new MsPluginManager();
/**
- * 上传插件到 minio
+ * 从文件系统中加载jar
+ *
+ * @param fileName
+ * @return
*/
- public void uploadPlugin(String id, MultipartFile file) {
+ public String loadPlugin(String fileName) {
+ return msPluginManager.loadPlugin(Paths.get(MsFileUtils.PLUGIN_DIR + "/" + fileName));
+ }
+
+
+ /**
+ * 从默认的对象存储下载插件到本地,再加载
+ * @param fileName
+ * @return
+ * @throws Exception
+ */
+ public void loadPluginFromRepository(String fileName) {
+ String filePath = MsFileUtils.PLUGIN_DIR + "/" + fileName;
+ File file = new File(filePath);
try {
- getStorageStrategy(id).store(file.getOriginalFilename(), file.getInputStream());
+ if (!file.exists()) {
+ InputStream fileAsStream = FileCenter.getDefaultRepository().getFileAsStream(getFileRequest(fileName));
+ FileUtils.copyInputStreamToFile(fileAsStream, file);
+ }
+ msPluginManager.loadPlugin(Paths.get(filePath));
+ } catch (Exception e) {
+ LogUtils.error("从对象存储加载插件异常", e);
+ }
+ }
+
+ /**
+ * 将插件上传到本地文件系统中
+ *
+ * @param file
+ * @return
+ */
+ public String uploadPlugin2Local(MultipartFile file) {
+ try {
+ return FileCenter.getRepository(StorageType.LOCAL).saveFile(file, getFileRequest(file.getOriginalFilename()));
} catch (Exception e) {
LogUtils.error(e);
throw new MSException("文件上传异常", e);
}
}
+ /**
+ * 将文件上传到默认的对象存储中
+ *
+ * @param file
+ */
+ public void uploadPlugin2Repository(MultipartFile file) {
+ try {
+ FileCenter.getDefaultRepository().saveFile(file, getFileRequest(file.getOriginalFilename()));
+ } catch (Exception e) {
+ LogUtils.error(e);
+ throw new MSException("文件上传异常", e);
+ }
+ }
+
+ private FileRequest getFileRequest(String name) {
+ FileRequest request = new FileRequest();
+ request.setProjectId(MsFileUtils.PLUGIN_DIR_NAME);
+ request.setFileName(name);
+ return request;
+ }
+
/**
* @return 返回前端渲染需要的数据
* 默认会返回 resources下的 script 下的 json 文件
*/
public List getFrontendScripts(String pluginId) {
- MsPlugin msPluginInstance = getMsPluginInstance(pluginId);
+ MsPlugin msPluginInstance = (MsPlugin) msPluginManager.getPlugin(pluginId).getPlugin();
String scriptDir = msPluginInstance.getScriptDir();
- StorageStrategy storageStrategy = pluginManager.getClassLoader(pluginId).getStorageStrategy();
try {
- // 查询脚本文件名
- List folderFileNames = storageStrategy.getFolderFileNames(scriptDir);
- // 获取脚本内容
- List scripts = new ArrayList<>(folderFileNames.size());
- for (String folderFileName : folderFileNames) {
- InputStream in = storageStrategy.get(folderFileName);
- if (in == null) {
- continue;
+ List scripts = new ArrayList<>(10);
+ String jarPath = msPluginManager.getPlugin(pluginId).getPluginPath().toString();
+ JarFile jarFile = new JarFile(jarPath);
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry = entries.nextElement();
+ //获取文件路径
+ String innerPath = jarEntry.getName();
+ if (innerPath.startsWith(scriptDir) && !jarEntry.isDirectory()) {
+ //获取到文件流
+ InputStream inputStream = msPluginManager.getPluginClassLoader(pluginId).getResourceAsStream(innerPath);
+ if (inputStream != null) {
+ scripts.add(IOUtil.toString(inputStream));
+ }
}
- scripts.add(IOUtil.toString(storageStrategy.get(folderFileName)));
}
return scripts;
} catch (Exception e) {
@@ -82,77 +146,16 @@ public class PluginLoadService {
}
}
- private static StorageStrategy getStorageStrategy(String id) {
- return new MsStorageStrategy(id);
- }
-
- public void loadPlugin(String id, MultipartFile file) {
- // 加载 jar
- InputStream inputStream;
- try {
- inputStream = file.getInputStream();
- } catch (IOException e) {
- LogUtils.error(e);
- throw new MSException("获取文件输入流异常", e);
- }
- loadPlugin(id, inputStream, true);
- }
-
- public void loadPlugin(String pluginId, String fileName) {
- PluginClassLoader classLoader = pluginManager.getClassLoader(pluginId);
- if (classLoader != null) {
- return;
- }
- // 加载 jar
- InputStream inputStream;
- try {
- inputStream = classLoader.getStorageStrategy().get(fileName);
- } catch (Exception e) {
- LogUtils.error(e);
- throw new MSException("下载文件异常", e);
- }
- loadPlugin(pluginId, inputStream, false);
- }
-
- public void loadPlugin(String id, InputStream inputStream, boolean isNeedUploadFile) {
- if (inputStream == null) {
- return;
- }
- loadPlugin(id, inputStream, new MsStorageStrategy(id), isNeedUploadFile);
- }
-
- /**
- * 加载插件
- *
- * @param id 插件ID
- * @param inputStream 输入流
- * @param storageStrategy 静态文件及jar包存储策略
- */
- public void loadPlugin(String id, InputStream inputStream, StorageStrategy storageStrategy, boolean isNeedUploadFile) {
- if (inputStream == null || pluginManager.getClassLoader(id) != null) {
- return;
- }
- // 加载 jar
- try {
- pluginManager.loadJar(id, inputStream,
- storageStrategy == null ? getStorageStrategy(id) : storageStrategy, isNeedUploadFile);
- } catch (Exception e) {
- LogUtils.error(e);
- throw new MSException("加载插件异常", e);
- }
- }
-
/**
* 项目启动时加载插件
*/
public synchronized void loadPlugins() {
List plugins = pluginMapper.selectByExample(new PluginExample());
plugins.forEach(plugin -> {
- String id = plugin.getId();
- StorageStrategy storageStrategy = getStorageStrategy(id);
+ String fileName = plugin.getFileName();
try {
- InputStream inputStream = storageStrategy.get(plugin.getFileName());
- loadPlugin(id, inputStream, storageStrategy, false);
+ loadPlugin(fileName);
+ msPluginManager.startPlugin(plugin.getId());
} catch (Exception e) {
LogUtils.error("初始化插件异常" + plugin.getFileName(), e);
}
@@ -163,75 +166,43 @@ public class PluginLoadService {
* 卸载插件
*/
public void unloadPlugin(String pluginId) {
- pluginManager.deletePlugin(pluginId);
+ if (msPluginManager.getPlugin(pluginId) != null) {
+ msPluginManager.deletePlugin(pluginId);
+ }
}
-
- public PluginClassLoader getClassLoader(String pluginId) {
- return pluginManager.getClassLoader(pluginId);
+ public boolean hasPlugin(String pluginId) {
+ return msPluginManager.getPlugin(pluginId) != null;
}
/**
* 删除插件
*/
- public void deletePlugin(String pluginId) {
- // 删除文件
- PluginClassLoader classLoader = pluginManager.getClassLoader(pluginId);
+ public void deletePluginFile(String fileName) {
+ FileRequest fileRequest = getFileRequest(fileName);
try {
- if (classLoader != null) {
- classLoader.getStorageStrategy().delete();
- }
+ FileCenter.getRepository(StorageType.LOCAL).delete(fileRequest);
+ FileCenter.getDefaultRepository().delete(fileRequest);
} catch (Exception e) {
LogUtils.error(e);
- throw new MSException("删除插件异常 ", e);
}
- unloadPlugin(pluginId);
}
- public MsPlugin getMsPluginInstance(String id) {
- return pluginManager.getImplInstance(id, MsPlugin.class);
- }
-
- public T getImplInstance(String pluginId, Class superClazz, Object param) {
- return pluginManager.getImplInstance(pluginId, superClazz, param);
- }
-
- public T getImplInstance(String pluginId, Class superClazz) {
- return pluginManager.getImplInstance(pluginId, superClazz);
- }
-
- public List getPlatformPluginInstanceList() {
- return getImplInstanceList(AbstractPlatformPlugin.class);
- }
-
- public AbstractPlatformPlugin getPlatformPluginInstance(String pluginId) {
- return getImplInstance(pluginId, AbstractPlatformPlugin.class);
- }
-
- public List getImplInstanceList(Class clazz, Object... initArgs) {
- return pluginManager.getClassLoaderMap().keySet().stream()
- .map(pluginId -> pluginManager.getImplInstance(pluginId, clazz, initArgs)
- ).collect(Collectors.toList());
- }
-
- public boolean hasPluginKey(String currentPluginId, String pluginKey) {
- for (String pluginId : pluginManager.getClassLoaderMap().keySet()) {
- 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));
- }
+ /**
+ * 删除本地插件
+ * @param fileName
+ */
+ public void deleteLocalPluginFile(String fileName) {
+ FileRequest fileRequest = getFileRequest(fileName);
+ try {
+ FileCenter.getRepository(StorageType.LOCAL).delete(fileRequest);
+ } catch (Exception e) {
+ LogUtils.error(e);
}
- return false;
}
public InputStream getResourceAsStream(String pluginId, String name) {
- return pluginManager.getClassLoaderMap().get(pluginId).getResourceAsStream(name);
+ return msPluginManager.getPluginClassLoader(pluginId).getResourceAsStream(name);
}
public Map getPluginScriptConfig(String pluginId, String scriptId) {
@@ -243,7 +214,66 @@ public class PluginLoadService {
return getPluginScriptConfig(pluginId, scriptId).get("script");
}
- public Class getImplClass(String pluginId, Class driverClass) {
- return pluginManager.getImplClass(pluginId, driverClass);
+ public PluginWrapper getPluginWrapper(String id) {
+ return msPluginManager.getPlugin(id);
+ }
+
+ /**
+ * 获取插件中的是实现类列表
+ * @param clazz
+ * @return
+ * @param
+ */
+ public List getExtensions(Class clazz) {
+ return msPluginManager.getExtensions(clazz);
+ }
+
+ /**
+ * 获取插件中的是实现类
+ * @param clazz
+ * @param pluginId
+ * @return
+ * @param
+ */
+ public Class extends T> getExtensionsClass(Class clazz, String pluginId) {
+ List> classes = msPluginManager.getExtensionClasses(clazz, pluginId);
+ return CollectionUtils.isEmpty(classes) ? null : classes.get(0);
+ }
+
+ public MsPluginManager getMsPluginManager() {
+ return msPluginManager;
+ }
+
+ public T getImplInstance(Class extensionClazz, String pluginId, Object param) {
+ try {
+ Class extends T> clazz = getExtensionsClass(extensionClazz, pluginId);
+ if (clazz == null) {
+ throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE);
+ }
+ if (param == null) {
+ return clazz.getConstructor().newInstance();
+ } else {
+ return clazz.getConstructor(param.getClass()).newInstance(param);
+ }
+ } catch (InvocationTargetException e) {
+ LogUtils.error(e.getTargetException());
+ throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE, e.getTargetException().getMessage());
+ } catch (Exception e) {
+ LogUtils.error(e);
+ throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE, e.getMessage());
+ }
+ }
+
+ public void handlePluginAddNotified(String pluginId, String fileName) {
+ if (!hasPlugin(pluginId)) {
+ loadPluginFromRepository(fileName);
+ }
+ }
+
+ public void handlePluginDeleteNotified(String pluginId, String fileName) {
+ if (hasPlugin(pluginId)) {
+ unloadPlugin(pluginId);
+ deleteLocalPluginFile(fileName);
+ }
}
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/environment/EnvironmentService.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/environment/EnvironmentService.java
index c84eea5c15..9be22827b0 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/environment/EnvironmentService.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/environment/EnvironmentService.java
@@ -1,82 +1,66 @@
package io.metersphere.sdk.service.environment;
-import io.metersphere.sdk.constants.PluginScenarioType;
import io.metersphere.sdk.domain.Environment;
import io.metersphere.sdk.domain.EnvironmentBlob;
import io.metersphere.sdk.domain.EnvironmentBlobExample;
import io.metersphere.sdk.domain.EnvironmentExample;
+import io.metersphere.sdk.dto.OptionDTO;
import io.metersphere.sdk.dto.environment.EnvironmentConfig;
import io.metersphere.sdk.dto.environment.EnvironmentConfigRequest;
+import io.metersphere.sdk.dto.environment.dataSource.DataSource;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.MinioRepository;
import io.metersphere.sdk.mapper.EnvironmentBlobMapper;
import io.metersphere.sdk.mapper.EnvironmentMapper;
-import io.metersphere.sdk.service.PluginLoadService;
+import io.metersphere.sdk.service.JdbcDriverPluginService;
import io.metersphere.sdk.uid.UUID;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.sdk.util.Translator;
-import io.metersphere.system.domain.Plugin;
-import io.metersphere.system.domain.PluginExample;
-import io.metersphere.system.domain.PluginOrganization;
-import io.metersphere.system.domain.PluginOrganizationExample;
-import io.metersphere.system.mapper.PluginMapper;
-import io.metersphere.system.mapper.PluginOrganizationMapper;
import jakarta.annotation.Resource;
import jakarta.transaction.Transactional;
import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.sql.Driver;
+import java.sql.DriverManager;
import java.util.*;
@Service
@Transactional
public class EnvironmentService {
-
- @Resource
- private PluginMapper pluginMapper;
- @Resource
- private PluginLoadService pluginLoadService;
- @Resource
- private PluginOrganizationMapper pluginOrganizationMapper;
@Resource
private EnvironmentMapper environmentMapper;
@Resource
private EnvironmentBlobMapper environmentBlobMapper;
@Resource
private MinioRepository minioRepository;
+ @Resource
+ private JdbcDriverPluginService jdbcDriverPluginService;
- public Map getDriverOptions(String organizationId) {
- Map pluginDriverClassNames = new HashMap<>();
- PluginExample example = new PluginExample();
- example.createCriteria().andScenarioEqualTo(PluginScenarioType.JDBC_DRIVER.name()).andEnableEqualTo(true);
- List plugins = pluginMapper.selectByExample(example);
- plugins.forEach(plugin -> {
- if (BooleanUtils.isTrue(plugin.getGlobal())) {
- pluginDriverClassNames.put(plugin.getId(), pluginLoadService.getImplClass(plugin.getId(), Driver.class).getName());
+ public List getDriverOptions(String organizationId) {
+ return jdbcDriverPluginService.getJdbcDriverOption(organizationId);
+ }
+
+ public void validateDataSource(DataSource databaseConfig) {
+ try {
+ if (StringUtils.isNotBlank(databaseConfig.getDriverId())) {
+ Driver driver = jdbcDriverPluginService.getDriverByOptionId(databaseConfig.getDriverId());
+ Properties properties = new Properties();
+ properties.setProperty("user", databaseConfig.getUsername());
+ properties.setProperty("password", databaseConfig.getPassword());
+ driver.connect(databaseConfig.getDbUrl(), properties);
} else {
- //判断组织id
- if (StringUtils.isNotBlank(organizationId)) {
- //判断组织id是否在插件组织id中
- PluginOrganizationExample pluginOrganizationExample = new PluginOrganizationExample();
- pluginOrganizationExample.createCriteria().andPluginIdEqualTo(plugin.getId()).andOrganizationIdEqualTo(organizationId);
- List pluginOrganizations = pluginOrganizationMapper.selectByExample(pluginOrganizationExample);
- if (pluginOrganizations.size() > 0) {
- pluginDriverClassNames.put(plugin.getId(), pluginLoadService.getImplClass(plugin.getId(), Driver.class).getName());
- }
- }
+ DriverManager.getConnection(databaseConfig.getDbUrl(), databaseConfig.getUsername(), databaseConfig.getPassword());
}
- });
- // 已经内置了 mysql 依赖
- pluginDriverClassNames.put(StringUtils.EMPTY, "com.mysql.jdbc.Driver");
- return pluginDriverClassNames;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
public void delete(String id) {
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/MsFileUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/MsFileUtils.java
new file mode 100644
index 0000000000..37399e00ad
--- /dev/null
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/MsFileUtils.java
@@ -0,0 +1,23 @@
+package io.metersphere.sdk.util;
+
+import io.metersphere.sdk.exception.MSException;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+
+public class MsFileUtils {
+
+ public static final String DATE_ROOT_DIR = "/opt/metersphere/data/app";
+ public static final String PLUGIN_DIR_NAME = "plugins";
+ public static final String PLUGIN_DIR = DATE_ROOT_DIR + "/" + PLUGIN_DIR_NAME;
+
+ public static void validateFileName(String... fileNames) {
+ if (fileNames != null) {
+ for (String fileName : fileNames) {
+ if (StringUtils.isNotEmpty(fileName) && StringUtils.contains(fileName, "." + File.separator)) {
+ throw new MSException(Translator.get("invalid_parameter"));
+ }
+ }
+ }
+ }
+}
diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/service/ProjectApplicationService.java b/backend/services/project-management/src/main/java/io/metersphere/project/service/ProjectApplicationService.java
index cf2d7fe62d..27a32b5ed7 100644
--- a/backend/services/project-management/src/main/java/io/metersphere/project/service/ProjectApplicationService.java
+++ b/backend/services/project-management/src/main/java/io/metersphere/project/service/ProjectApplicationService.java
@@ -194,11 +194,9 @@ public class ProjectApplicationService {
return options;
}
-
-
public Object getPluginScript(String pluginId) {
this.checkResourceExist(pluginId);
- AbstractPlatformPlugin platformPlugin = pluginLoadService.getImplInstance(pluginId, AbstractPlatformPlugin.class);
+ AbstractPlatformPlugin platformPlugin = (AbstractPlatformPlugin) pluginLoadService.getMsPluginManager().getPlugin(pluginId).getPlugin();
return pluginLoadService.getPluginScriptContent(pluginId, platformPlugin.getProjectScriptId());
}
diff --git a/backend/services/project-management/src/test/java/io/metersphere/project/controller/ProjectApplicationControllerTests.java b/backend/services/project-management/src/test/java/io/metersphere/project/controller/ProjectApplicationControllerTests.java
index fcc770fd25..0262dbc7c8 100644
--- a/backend/services/project-management/src/test/java/io/metersphere/project/controller/ProjectApplicationControllerTests.java
+++ b/backend/services/project-management/src/test/java/io/metersphere/project/controller/ProjectApplicationControllerTests.java
@@ -395,7 +395,7 @@ public class ProjectApplicationControllerTests extends BaseTest {
.getPath()
);
FileInputStream inputStream = new FileInputStream(jarFile);
- MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), inputStream);
+ MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), jarFile.getName(), "jar", inputStream);
request.setName("测试插件1");
request.setGlobal(true);
request.setEnable(true);
diff --git a/backend/services/project-management/src/test/resources/file/metersphere-jira-plugin-3.x.jar b/backend/services/project-management/src/test/resources/file/metersphere-jira-plugin-3.x.jar
index c8a6ea2338..c19d1732dc 100644
Binary files a/backend/services/project-management/src/test/resources/file/metersphere-jira-plugin-3.x.jar and b/backend/services/project-management/src/test/resources/file/metersphere-jira-plugin-3.x.jar differ
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/result/SystemResultCode.java b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/result/SystemResultCode.java
index f221e74f1e..220fbc7a75 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/result/SystemResultCode.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/result/SystemResultCode.java
@@ -20,7 +20,6 @@ public enum SystemResultCode implements IResultCode {
*/
NO_ORG_USER_ROLE_PERMISSION(101007, "organization_user_role_permission_error"),
PLUGIN_EXIST(101008, "plugin.exist"),
- PLUGIN_TYPE_EXIST(101009, "plugin.type.exist"),
PLUGIN_SCRIPT_EXIST(101010, "plugin.script.exist"),
PLUGIN_SCRIPT_FORMAT(101011, "plugin.script.format"),
NO_PROJECT_USER_ROLE_PERMISSION(101012, "project_user_role_permission_error");
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/PluginNotifiedDTO.java b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/PluginNotifiedDTO.java
new file mode 100644
index 0000000000..40613b31b2
--- /dev/null
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/PluginNotifiedDTO.java
@@ -0,0 +1,10 @@
+package io.metersphere.system.dto;
+
+import lombok.Data;
+
+@Data
+public class PluginNotifiedDTO {
+ private String operate;
+ private String pluginId;
+ private String fileName;
+}
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/listener/PluginListener.java b/backend/services/system-setting/src/main/java/io/metersphere/system/listener/PluginListener.java
index 39aa408f0a..2f3ba8c294 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/listener/PluginListener.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/listener/PluginListener.java
@@ -4,7 +4,9 @@ package io.metersphere.system.listener;
import io.metersphere.sdk.constants.KafkaPluginTopicType;
import io.metersphere.sdk.constants.KafkaTopicConstants;
import io.metersphere.sdk.service.PluginLoadService;
+import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
+import io.metersphere.system.dto.PluginNotifiedDTO;
import jakarta.annotation.Resource;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
@@ -21,17 +23,17 @@ public class PluginListener {
// groupId 必须是每个实例唯一
@KafkaListener(id = PLUGIN_CONSUMER, topics = KafkaTopicConstants.PLUGIN, groupId = PLUGIN_CONSUMER + "_" + "${random.uuid}")
public void handlePluginChange(ConsumerRecord, String> record) {
- LogUtils.info("Service consume platform_plugin message: " + record);
- String[] info = record.value().split(":");
- String operate = info[0];
- String pluginId = info[1];
+ LogUtils.info("Service consume platform_plugin message: " + record.value());
+ PluginNotifiedDTO pluginNotifiedDTO = JSON.parseObject(record.value(), PluginNotifiedDTO.class);
+ String operate = pluginNotifiedDTO.getOperate();
+ String pluginId = pluginNotifiedDTO.getPluginId();
+ String fileName = pluginNotifiedDTO.getFileName();
switch (operate) {
case KafkaPluginTopicType.ADD:
- String pluginName = info[2];
- pluginLoadService.loadPlugin(pluginId, pluginName);
+ pluginLoadService.handlePluginAddNotified(pluginId, fileName);
break;
case KafkaPluginTopicType.DELETE:
- pluginLoadService.unloadPlugin(pluginId);
+ pluginLoadService.handlePluginDeleteNotified(pluginId, fileName);
break;
default:
break;
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java
index c83eaccedf..288bb76448 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java
@@ -1,6 +1,8 @@
package io.metersphere.system.service;
+import io.metersphere.plugin.api.api.AbstractApiProtocolPlugin;
+import io.metersphere.plugin.platform.api.AbstractPlatformPlugin;
import io.metersphere.plugin.sdk.api.MsPlugin;
import io.metersphere.sdk.constants.KafkaPluginTopicType;
import io.metersphere.sdk.constants.KafkaTopicConstants;
@@ -11,12 +13,13 @@ import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.service.BaseUserService;
import io.metersphere.sdk.service.JdbcDriverPluginService;
import io.metersphere.sdk.service.PluginLoadService;
-import io.metersphere.sdk.uid.UUID;
import io.metersphere.sdk.util.BeanUtils;
+import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.ServiceUtils;
import io.metersphere.system.domain.Plugin;
import io.metersphere.system.domain.PluginExample;
import io.metersphere.system.dto.PluginDTO;
+import io.metersphere.system.dto.PluginNotifiedDTO;
import io.metersphere.system.mapper.ExtPluginMapper;
import io.metersphere.system.mapper.PluginMapper;
import io.metersphere.system.request.PluginUpdateRequest;
@@ -25,6 +28,8 @@ import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
+import org.pf4j.PluginDescriptor;
+import org.pf4j.PluginWrapper;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -37,7 +42,6 @@ import java.sql.Driver;
import java.util.*;
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_EXIST;
-import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_TYPE_EXIST;
/**
* @author jianxing
@@ -58,7 +62,7 @@ public class PluginService {
@Resource
private PluginLoadService pluginLoadService;
@Resource
- JdbcDriverPluginService jdbcDriverPluginService;
+ private JdbcDriverPluginService jdbcDriverPluginService;
@Resource
private KafkaTemplate kafkaTemplate;
@Resource
@@ -90,10 +94,9 @@ public class PluginService {
}
public Plugin add(PluginUpdateRequest request, MultipartFile file) {
- String id = UUID.randomUUID().toString();
+ String id = null;
Plugin plugin = new Plugin();
BeanUtils.copyBean(plugin, request);
- plugin.setId(id);
plugin.setFileName(file.getOriginalFilename());
plugin.setCreateTime(System.currentTimeMillis());
plugin.setUpdateTime(System.currentTimeMillis());
@@ -105,34 +108,25 @@ public class PluginService {
checkPluginAddExist(plugin);
try {
- // 加载插件
- pluginLoadService.loadPlugin(id, file);
- // 上传插件
- pluginLoadService.uploadPlugin(id, file);
+ // 上传插件到本地文件系统
+ pluginLoadService.uploadPlugin2Local(file);
- 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);
+ // 从文件系统中加载插件
+ id = pluginLoadService.loadPlugin(file.getOriginalFilename());
+ pluginLoadService.getMsPluginManager().startPlugin(id);
+ plugin.setId(id);
+
+ List extensions = pluginLoadService.getMsPluginManager().getExtensions(Driver.class, id);
+
+ if (CollectionUtils.isNotEmpty(extensions)) {
+ plugin = jdbcDriverPluginService.wrapperPlugin(plugin);
plugin.setPluginId(file.getOriginalFilename());
} else {
+
+ plugin = wrapperPlugin(id, plugin);
+
// 非数据库驱动插件,解析脚本和插件信息
- // 获取插件前端配置脚本
List frontendScript = pluginLoadService.getFrontendScripts(id);
-
- MsPlugin msPlugin = pluginLoadService.getMsPluginInstance(id);
- plugin.setScenario(msPlugin.getType());
- plugin.setXpack(msPlugin.isXpack());
- plugin.setPluginId(msPlugin.getPluginId());
-
- // 校验插件类型是否重复
- checkPluginKeyExist(id, msPlugin.getKey());
-
- // 保存插件脚本
pluginScriptService.add(id, frontendScript);
}
@@ -143,20 +137,32 @@ public class PluginService {
pluginMapper.insert(plugin);
+ // 上传插件到对象存储
+ pluginLoadService.uploadPlugin2Repository(file);
+
// 通知其他节点加载插件
notifiedPluginAdd(id, plugin.getFileName());
} catch (Exception e) {
// 删除插件
- pluginLoadService.deletePlugin(id);
+ pluginLoadService.unloadPlugin(id);
+ pluginLoadService.deletePluginFile(file.getOriginalFilename());
throw e;
}
return plugin;
}
- private void checkPluginKeyExist(String pluginId, String pluginKey) {
- if (pluginLoadService.hasPluginKey(pluginId, pluginKey)) {
- throw new MSException(PLUGIN_TYPE_EXIST);
+ public Plugin wrapperPlugin(String id, Plugin plugin) {
+ PluginWrapper pluginWrapper = pluginLoadService.getPluginWrapper(id);
+ PluginDescriptor descriptor = pluginWrapper.getDescriptor();
+ MsPlugin msPlugin = (MsPlugin) pluginWrapper.getPlugin();
+ if (msPlugin instanceof AbstractApiProtocolPlugin) {
+ plugin.setScenario(PluginScenarioType.API_PROTOCOL.name());
+ } else if (msPlugin instanceof AbstractPlatformPlugin) {
+ plugin.setScenario(PluginScenarioType.PLATFORM.name());
}
+ plugin.setXpack(msPlugin.isXpack());
+ plugin.setPluginId(descriptor.getPluginId() + "-" + descriptor.getVersion());
+ return plugin;
}
public Plugin checkResourceExist(String id) {
@@ -183,8 +189,15 @@ public class PluginService {
* @param fileName
*/
public void notifiedPluginAdd(String pluginId, String fileName) {
- // 初始化项目默认节点
- kafkaTemplate.send(KafkaTopicConstants.PLUGIN, String.format("%s:%s:%s", KafkaPluginTopicType.ADD, pluginId, fileName));
+ notifiedPluginOperate(pluginId, fileName, KafkaPluginTopicType.ADD);
+ }
+
+ public void notifiedPluginOperate(String pluginId, String fileName, String operate) {
+ PluginNotifiedDTO pluginNotifiedDTO = new PluginNotifiedDTO();
+ pluginNotifiedDTO.setOperate(operate);
+ pluginNotifiedDTO.setPluginId(pluginId);
+ pluginNotifiedDTO.setFileName(fileName);
+ kafkaTemplate.send(KafkaTopicConstants.PLUGIN, JSON.toJSONString(pluginNotifiedDTO));
}
/**
@@ -192,9 +205,8 @@ public class PluginService {
*
* @param pluginId
*/
- public void notifiedPluginDelete(String pluginId) {
- // 初始化项目默认节点
- kafkaTemplate.send(KafkaTopicConstants.PLUGIN, String.format("%s:%s", KafkaPluginTopicType.DELETE, pluginId));
+ public void notifiedPluginDelete(String pluginId, String fileName) {
+ notifiedPluginOperate(pluginId, fileName, KafkaPluginTopicType.DELETE);
}
public Plugin update(PluginUpdateRequest request) {
@@ -212,7 +224,7 @@ public class PluginService {
request.setOrganizationIds(new ArrayList<>(0));
}
pluginOrganizationService.update(plugin.getId(), request.getOrganizationIds());
- return plugin;
+ return pluginMapper.selectByPrimaryKey(request.getId());
}
private void checkPluginUpdateExist(Plugin plugin) {
@@ -230,14 +242,16 @@ public class PluginService {
public void delete(String id) {
checkResourceExist(id);
+ Plugin plugin = pluginMapper.selectByPrimaryKey(id);
pluginMapper.deleteByPrimaryKey(id);
// 删除插件脚本
pluginScriptService.deleteByPluginId(id);
// 删除和组织的关联关系
pluginOrganizationService.deleteByPluginId(id);
// 删除和卸载插件
- pluginLoadService.deletePlugin(id);
- notifiedPluginDelete(id);
+ pluginLoadService.unloadPlugin(id);
+ pluginLoadService.deletePluginFile(plugin.getFileName());
+ notifiedPluginDelete(id, plugin.getFileName());
}
public String getScript(String pluginId, String scriptId) {
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationLogService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationLogService.java
index cb11af9409..cf69987d2a 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationLogService.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationLogService.java
@@ -1,15 +1,15 @@
package io.metersphere.system.service;
-import io.metersphere.sdk.service.PluginLoadService;
+import io.metersphere.sdk.constants.OperationLogConstants;
+import io.metersphere.sdk.dto.LogDTO;
+import io.metersphere.sdk.log.constants.OperationLogModule;
+import io.metersphere.sdk.log.constants.OperationLogType;
+import io.metersphere.sdk.service.BasePluginService;
+import io.metersphere.sdk.util.JSON;
import io.metersphere.system.domain.ServiceIntegration;
import io.metersphere.system.request.ServiceIntegrationUpdateRequest;
-import io.metersphere.sdk.constants.OperationLogConstants;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
-import io.metersphere.sdk.dto.LogDTO;
-import io.metersphere.sdk.util.JSON;
-import io.metersphere.sdk.log.constants.OperationLogType;
-import io.metersphere.sdk.log.constants.OperationLogModule;
import org.springframework.transaction.annotation.Transactional;
/**
* @author jianxing
@@ -22,7 +22,7 @@ public class ServiceIntegrationLogService {
@Resource
private ServiceIntegrationService serviceIntegrationService;
@Resource
- private PluginLoadService pluginLoadService;
+ private BasePluginService basePluginService;
public LogDTO addLog(ServiceIntegrationUpdateRequest request) {
LogDTO dto = new LogDTO(
@@ -38,7 +38,7 @@ public class ServiceIntegrationLogService {
}
private String getName(String pluginId) {
- return pluginLoadService.getPlatformPluginInstance(pluginId).getName();
+ return basePluginService.get(pluginId).getName();
}
public LogDTO updateLog(ServiceIntegrationUpdateRequest request) {
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java
index 3ba29cb520..fe2d6183e7 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java
@@ -55,14 +55,14 @@ public class ServiceIntegrationService {
List plugins = platformPluginService.getOrgEnabledPlatformPlugins(organizationId);
return plugins.stream().map(plugin -> {
- AbstractPlatformPlugin msPluginInstance = pluginLoadService.getPlatformPluginInstance(plugin.getId());
+ AbstractPlatformPlugin msPlugin = (AbstractPlatformPlugin) pluginLoadService.getPluginWrapper(plugin.getId()).getPlugin();
// 获取插件基础信息
ServiceIntegrationDTO serviceIntegrationDTO = new ServiceIntegrationDTO();
- serviceIntegrationDTO.setTitle(msPluginInstance.getName());
+ serviceIntegrationDTO.setTitle(msPlugin.getName());
serviceIntegrationDTO.setEnable(false);
serviceIntegrationDTO.setConfig(false);
- serviceIntegrationDTO.setDescription(msPluginInstance.getDescription());
- serviceIntegrationDTO.setLogo(String.format(PLUGIN_IMAGE_GET_PATH, plugin.getId(), msPluginInstance.getLogo()));
+ serviceIntegrationDTO.setDescription(msPlugin.getDescription());
+ serviceIntegrationDTO.setLogo(String.format(PLUGIN_IMAGE_GET_PATH, plugin.getId(), msPlugin.getLogo()));
serviceIntegrationDTO.setPluginId(plugin.getId());
ServiceIntegration serviceIntegration = serviceIntegrationMap.get(plugin.getId());
if (serviceIntegration != null) {
@@ -149,7 +149,7 @@ public class ServiceIntegrationService {
public Object getPluginScript(String pluginId) {
pluginService.checkResourceExist(pluginId);
- AbstractPlatformPlugin platformPlugin = pluginLoadService.getImplInstance(pluginId, AbstractPlatformPlugin.class);
+ AbstractPlatformPlugin platformPlugin = (AbstractPlatformPlugin) pluginLoadService.getPluginWrapper(pluginId).getPlugin();
return pluginLoadService.getPluginScriptContent(pluginId, platformPlugin.getIntegrationScriptId());
}
}
\ No newline at end of file
diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java
index 4a2a0ed996..1a2dd3746c 100644
--- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java
+++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java
@@ -100,7 +100,7 @@ public class PluginControllerTests extends BaseTest {
Assertions.assertEquals(plugin.getGlobal(), request.getGlobal());
Assertions.assertEquals(plugin.getXpack(), false);
Assertions.assertEquals(plugin.getFileName(), jarFile.getName());
- Assertions.assertEquals(plugin.getScenario(), PluginScenarioType.API.name());
+ Assertions.assertEquals(plugin.getScenario(), PluginScenarioType.API_PROTOCOL.name());
Assertions.assertEquals(new ArrayList<>(0), getOrgIdsByPlugId(plugin.getId()));
Assertions.assertEquals(Arrays.asList("connect", "disconnect", "pub", "sub"), getScriptIdsByPlugId(plugin.getId()));
addPlugin = plugin;
@@ -135,7 +135,7 @@ public class PluginControllerTests extends BaseTest {
);
this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
getDefaultMultiPartParam(request, myDriver));
- Assertions.assertEquals(jdbcDriverPluginService.getJdbcDriverClass(), Arrays.asList("io.jianxing.MyDriver", "com.mysql.jdbc.Driver"));
+ Assertions.assertEquals(jdbcDriverPluginService.getJdbcDriverClass(DEFAULT_ORGANIZATION_ID), Arrays.asList("io.jianxing.MyDriver", "com.mysql.cj.jdbc.Driver"));
// @@重名校验异常
// 校验插件名称重名
@@ -147,21 +147,6 @@ public class PluginControllerTests extends BaseTest {
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
getDefaultMultiPartParam(request, jarFile)), PLUGIN_EXIST);
- // 校验插件 key 重复
- File typeRepeatFile = new File(
- this.getClass().getClassLoader().getResource("file/metersphere-mqtt-plugin-repeat-key.jar")
- .getPath()
- );
- 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(
@@ -296,10 +281,10 @@ public class PluginControllerTests extends BaseTest {
@Order(5)
public void getPluginImg() throws Exception {
// @@请求成功
- mockMvc.perform(getRequestBuilder(PLUGIN_IMAGE, anotherAddPlugin.getId(), "/static/jira.jpg"))
+ mockMvc.perform(getRequestBuilder(PLUGIN_IMAGE, anotherAddPlugin.getId(), "static/jira.jpg"))
.andExpect(status().isOk());
- assertErrorCode(this.requestGet(PLUGIN_IMAGE, anotherAddPlugin.getId(), "/static/jira.doc"), FILE_NAME_ILLEGAL);
+ assertErrorCode(this.requestGet(PLUGIN_IMAGE, anotherAddPlugin.getId(), "static/jira.doc"), FILE_NAME_ILLEGAL);
}
@Test
diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java
index 2f09811a1f..e20de07288 100644
--- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java
+++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java
@@ -200,7 +200,7 @@ public class ServiceIntegrationControllerTests extends BaseTest {
serviceIntegrationDTO.getConfiguration());
Assertions.assertEquals(serviceIntegration.getEnable(), serviceIntegrationDTO.getEnable());
Assertions.assertEquals(serviceIntegration.getPluginId(), serviceIntegrationDTO.getPluginId());
- AbstractPlatformPlugin msPluginInstance = pluginLoadService.getPlatformPluginInstance(plugin.getId());
+ AbstractPlatformPlugin msPluginInstance = (AbstractPlatformPlugin) pluginLoadService.getPluginWrapper(plugin.getId()).getPlugin();
Assertions.assertEquals(serviceIntegrationDTO.getDescription(), msPluginInstance.getDescription());
Assertions.assertEquals(serviceIntegrationDTO.getOrganizationId(), defaultOrg.getId());
Assertions.assertEquals(serviceIntegrationDTO.getTitle(), msPluginInstance.getName());
@@ -333,7 +333,7 @@ public class ServiceIntegrationControllerTests extends BaseTest {
.getPath()
);
FileInputStream inputStream = new FileInputStream(jarFile);
- MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), inputStream);
+ MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), jarFile.getName(), "jar", inputStream);
request.setName("测试插件");
request.setGlobal(true);
request.setEnable(true);
diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar b/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar
index c8a6ea2338..c19d1732dc 100644
Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar differ
diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-3.x.jar b/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-3.x.jar
index 75049c42ff..f1bb0b0ab8 100644
Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-3.x.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-3.x.jar differ
diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-repeat-key.jar b/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-repeat-key.jar
deleted file mode 100644
index 75049c42ff..0000000000
Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-repeat-key.jar and /dev/null differ
diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-id-repeat-error.jar b/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-id-repeat-error.jar
index 23171868f7..1c3cf4b1c0 100644
Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-id-repeat-error.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-id-repeat-error.jar differ
diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-parse-error.jar b/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-parse-error.jar
index 7c9cdb1940..8782fc9134 100644
Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-parse-error.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-parse-error.jar differ
diff --git a/backend/services/system-setting/src/test/resources/file/my-driver-1.0.jar b/backend/services/system-setting/src/test/resources/file/my-driver-1.0.jar
index 03f71224aa..4a4c897cb1 100644
Binary files a/backend/services/system-setting/src/test/resources/file/my-driver-1.0.jar and b/backend/services/system-setting/src/test/resources/file/my-driver-1.0.jar differ
diff --git a/backend/services/system-setting/src/test/resources/file/test-mysql-driver-1.0.jar b/backend/services/system-setting/src/test/resources/file/test-mysql-driver-1.0.jar
deleted file mode 100644
index 9b9b32b78d..0000000000
Binary files a/backend/services/system-setting/src/test/resources/file/test-mysql-driver-1.0.jar and /dev/null differ
diff --git a/frontend/src/views/setting/system/pluginManager/components/pluginTable.vue b/frontend/src/views/setting/system/pluginManager/components/pluginTable.vue
index a835fbde4e..1defe5c8f2 100644
--- a/frontend/src/views/setting/system/pluginManager/components/pluginTable.vue
+++ b/frontend/src/views/setting/system/pluginManager/components/pluginTable.vue
@@ -209,7 +209,7 @@
},
{
label: 'system.plugin.interfaceTest',
- value: 'API',
+ value: 'API_PROTOCOL',
},
{
label: 'system.plugin.projectManagement',
@@ -280,7 +280,7 @@
function getScenarioType(scenario: string) {
switch (scenario) {
- case 'API':
+ case 'API_PROTOCOL':
return t('system.plugin.interfaceTest');
case 'JDBC_DRIVER':
return t('system.plugin.databaseDriver');
diff --git a/pom.xml b/pom.xml
index 509ae9513f..04dc26a54f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -93,6 +93,7 @@
false
2.9.0
0.8.10
+ 3.10.0