From 69e312e87be6dd194a04e63eeab892bff2495a5f Mon Sep 17 00:00:00 2001 From: AgAngle <1323481023@qq.com> Date: Wed, 13 Sep 2023 18:08:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E7=B3=BB=E7=BB=9F=E8=AE=BE=E7=BD=AE):?= =?UTF-8?q?=20=E6=8F=92=E4=BB=B6=E5=8A=A0=E8=BD=BD=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/metersphere/system/domain/Plugin.java | 2 +- .../3.0.0/ddl/V3.0.0_11__system_setting.sql | 4 +- .../plugin/api/api/AbstractApiPlugin.java | 11 - .../api/api/AbstractApiProtocolPlugin.java | 6 + .../platform/api/AbstractPlatformPlugin.java | 5 - .../plugin/platform/api/Platform.java | 4 +- .../plugin/metersphere-plugin-sdk/pom.xml | 5 + .../plugin/sdk/api/AbstractMsPlugin.java | 12 +- .../metersphere/plugin/sdk/api/MsPlugin.java | 32 +- .../io/metersphere/sdk/config/RsaConfig.java | 7 +- .../sdk/constants/PluginScenarioType.java | 13 +- .../sdk/constants/StorageConstants.java | 5 - .../sdk/constants/StorageType.java | 5 + .../environment/EnvironmentController.java | 28 +- .../sdk/dto/environment/BodyFile.java | 4 +- .../io/metersphere/sdk/file/FileCenter.java | 21 +- .../sdk/file/LocalFileRepository.java | 90 +++++ .../plugin/JdbcDriverPluginDescriptor.java | 47 +++ .../JdbcDriverPluginDescriptorFinder.java | 85 +++++ ...cDriverServiceProviderExtensionFinder.java | 142 ++++++++ .../sdk/plugin/MsPluginManager.java | 27 ++ .../plugin/loader/PlatformPluginManager.java | 23 -- .../sdk/plugin/loader/PluginClassLoader.java | 230 ------------- .../sdk/plugin/loader/PluginManager.java | 167 --------- .../sdk/plugin/storage/MsStorageStrategy.java | 64 ---- .../sdk/plugin/storage/StorageStrategy.java | 43 --- .../sdk/service/BasePluginService.java | 37 ++ .../sdk/service/JdbcDriverPluginService.java | 81 ++++- .../sdk/service/PlatformPluginService.java | 40 +-- .../sdk/service/PluginLoadService.java | 316 ++++++++++-------- .../environment/EnvironmentService.java | 60 ++-- .../io/metersphere/sdk/util/MsFileUtils.java | 23 ++ .../service/ProjectApplicationService.java | 4 +- .../ProjectApplicationControllerTests.java | 2 +- .../file/metersphere-jira-plugin-3.x.jar | Bin 41971 -> 46161 bytes .../controller/result/SystemResultCode.java | 1 - .../system/dto/PluginNotifiedDTO.java | 10 + .../system/listener/PluginListener.java | 16 +- .../system/service/PluginService.java | 94 +++--- .../service/ServiceIntegrationLogService.java | 16 +- .../service/ServiceIntegrationService.java | 10 +- .../controller/PluginControllerTests.java | 23 +- .../ServiceIntegrationControllerTests.java | 4 +- .../file/metersphere-jira-plugin-3.x.jar | Bin 41971 -> 46161 bytes .../file/metersphere-mqtt-plugin-3.x.jar | Bin 6706 -> 7295 bytes .../metersphere-mqtt-plugin-repeat-key.jar | Bin 6706 -> 0 bytes ...ersphere-plugin-script-id-repeat-error.jar | Bin 6707 -> 7294 bytes .../metersphere-plugin-script-parse-error.jar | Bin 6710 -> 7298 bytes .../src/test/resources/file/my-driver-1.0.jar | Bin 2390 -> 2856 bytes .../resources/file/test-mysql-driver-1.0.jar | Bin 2542 -> 0 bytes .../pluginManager/components/pluginTable.vue | 4 +- pom.xml | 1 + 52 files changed, 874 insertions(+), 950 deletions(-) delete mode 100644 backend/framework/plugin/metersphere-api-plugin-sdk/src/main/java/io/metersphere/plugin/api/api/AbstractApiPlugin.java create mode 100644 backend/framework/plugin/metersphere-api-plugin-sdk/src/main/java/io/metersphere/plugin/api/api/AbstractApiProtocolPlugin.java delete mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/StorageConstants.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/StorageType.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/file/LocalFileRepository.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverPluginDescriptor.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverPluginDescriptorFinder.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/JdbcDriverServiceProviderExtensionFinder.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/MsPluginManager.java delete mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PlatformPluginManager.java delete mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginClassLoader.java delete mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginManager.java delete mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/storage/MsStorageStrategy.java delete mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/storage/StorageStrategy.java create mode 100644 backend/framework/sdk/src/main/java/io/metersphere/sdk/util/MsFileUtils.java create mode 100644 backend/services/system-setting/src/main/java/io/metersphere/system/dto/PluginNotifiedDTO.java delete mode 100644 backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-repeat-key.jar delete mode 100644 backend/services/system-setting/src/test/resources/file/test-mysql-driver-1.0.jar 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 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 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 c8a6ea2338f7f162367e6ade82cf0a7eac40ceb6..c19d1732dc2711182270754e6710f76090484aec 100644 GIT binary patch literal 46161 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 MKA>(4P9psFKOe5mCIA2c delta 35629 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

HuK`2YH5)ho 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 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 c8a6ea2338f7f162367e6ade82cf0a7eac40ceb6..c19d1732dc2711182270754e6710f76090484aec 100644 GIT binary patch literal 46161 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 MKA>(4P9psFKOe5mCIA2c delta 35629 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

HuK`2YH5)ho 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 delta 1801 zcmexwvB`utz?+$civa}GS{Wzus_?0{GKQb9TCleWDAd6)(O#co`RaDQwc*IkR%+QB~JY)t{~cog%U8r{pY%eOAR*m9{5l zPYeUXQSB{^ljRxZm_f7yqZXLXWi$lS3mMG>f#Ts{*Fy>U$?F;A1QQD~^-;}`U{V6B zE%j8+sA6PbSjNP_pbt`84^rz}SW*&@Q<|Qcr-i^7CyjuO_;;6TWQV_9wnY#TfQ^~94dUfub?GQ`tUrH(me0_P^)$IMHS(pIh7B? zC6s<&n(}IbqqazN;1P+g%cgAoUO$yeZ}q&85aN0GD|NG^^Q0y7%DkTj%~X86^p^P6 z)sruO{&w>suk*ID^YeG)6>MH^K1KQLJvrx9Su@u?T9#Fqtl_aT>+ZzUSC(j=Yj5r= zn6)RFqkr43I~j%URfQ9HR_`m3+?G}UUv7F|kJtBG&+fAS-?iPO$bW_L$1@YOMV9x@ zSWwlyWT~tF)Qkw{<=TBq%WIy6Z-3Hw!(qxlri2JruFOx8T}~a!KGnZuHx*C0c+&Wj z@$B~r`E{plZg$M?QS>&m%sRkSwC{n^G^Psc2IpI+S!VuoZaenGD2%B{U14j^pHimS zA9BrE$tL!!bLYBz4!#(=Kl?2^EaEsAfRVfTJ@Xz`P;75r$LYlgX8h*b#Ka6tteZFR zim`wglTQk2f$498mSEaQD3I?;6l1vMLigk{U}ST%Or9v@Tt79~+rPy?Vo(02|BVI9 zQ)7kCE;~KX#4vJkXMe@o4=2l7w{3g+aR2tkrVUMQd2{pAZuzlqTK)NqwqvH+k8AM@ zFMMw}u9RqV_m_YF^@B_IcTeM1nrh*cW8?k2>a~2&MCqR5C?@xiOFtMqR*2lcfA>+b zNYw17hhFb<+q}~Bb!lj5{lcuf8s`obw8rOs=f9@3obmCk0J*~+>u#9vUH(hd2A#rd;p;L2FB>=2KlQ25 zoN(ybfnXIbWp3l==BJAr9|bPr^KoGEf3v?}QgMGh--UXcJpcKb0#nb3YFs$^+oiLr zdFHe)#eW{JU|z91dw$RtR%nEZFo0t;e4*hY^I~9jtp#G2$qqu6lRrqaOpce~ntV)5 zyk65cv_Qu<+`u5jz+|(L*=8e?umV25Km#KKqi0XO^iS#Qe(>3N_RRIuzPg^eT6!lu zbiJNF_0rKg>ErFA$LOWl`TCjIv?(vdo=jPm&eW^N%$5G^`AwM)?JC29OkBcG76bF=5ffDy-!A=>=6o)P9c%(2w5fTkc_M{ zL*{qXTf7?Y^?l!SuJivt*Y&@Dzh~d~bN_U}AncQXgTfw$W%#Y~&jTOxhA8VR@TzO6 z@az0zh6iB7n6*k}KSy9bM`HfuN6jD#TIwpwdis12l~qVbyC#^IuZIZC%k{dW>oHW| zjqv*^1dqn+Rvs{~A|CE!-o->p?mAYls`MD>X)sjJJ%g)+oQ{C(mN1mU?{qtKl^pq{ zV-4wDm98P|roo8ftV;1tX=YQ7V*9F08bxmUFwrsFblT^Hk8RE6n#+J6eFOj^LjOG! z5T=hXm!E_9)qv!?fs>894Z;m>XM?c$i!tS|#&E}LS7FY7!O;E@lz z*k8c77_gfa0tP>90q@)7TvKPU^T7fD3V;9r|9`gt!Sqq*+YtGz94*}3ZX~p60ToEY z_if`Ll**TN_($u)n3iX5lIFJHses77DAFv*&kG2@7GbZwg?m25YYEGb!>Ut2K}o`E zrl@pkyZYJo{Px7@I;{TFcnUGh=}m;pgwLL=1?e=neyW9sI@300id=IB2N+0|BD@R@ z9KFX4shPKX6X)U;xXCZ+Hc?0SiJpkl_{w~32suMdMmWA@^Z1O?&Adzx@DwzKX(E4! zs=WAC-|G5c50O%ZM_1NUXZYiL;)yJsqx4Ee_9>+w3hZ62IMfR5(FyHu@;F~dgv7#A zYpn3%DxQtlxvKiO#uFCTx}T}A->1JFi`3W{nnvU9KdTf$Xx|rJ?uh3mzjxzKwogo6 zzOr_bosrT#?$~@!ziGqD$)ExG#692@BjuB@l{3+bkt~|NTW3*ji9Kz?E5d1uS6r^Q z%M3J90DX-v zd&2%kJzWWiW345ky*=su;_lw6OYEM1ukX_8vP5srltu6(#TU-q0oc)tK1h2rbHqDE zP2iX8c_yH23v+yOoM0ff{=??7$o?nwKGy(yO{xPzi@m2pMI(#H)x<4JD|v?l>!J!2 zvy60tOFj>QN^{i(IaWko74BVh!dpmX2kH-VH^RGAold=};B*Hpu(QS3c69pKO(#!$ zeQGkH0wEaaaK@{Xw&wln>S&Rcuu&l%vMq-_cAlUjfTVU`B+O2-0K5zZ6&KXn%a@c# zmnZwtJGBek;VZCZsf(kbTyeZ%LZT!m>ZAIYSE(`39KYT^R7#=vt>twkN?kXTz$~K1 zxu+-e=z@o9ae|msfOs(89^l#95t*%>tj62hyXG5Vd4lFS(D4g#kcd`f#>J$UGsK|O zwue!VYfGf= zLe}h()}&<<{&IqJF9`=tMD8@0XAsAQTMF>fJs zF7e&=gxYf{Q!1bPCH{m9cuZ4%oY6dIB=aIC;aZtV2CT`nf+d8MbC-j8c1YXQmLsAX zpsknc#GjZO=Ve-vF{NI)t;cUoYLjrqZQjn)QwY_%vXCoP%h?cVoY&)}%^Sju@JB{|AOmHA$-q@(uw ziIE}EhN0aMP}N?QzJc}7%?7kt^jQNe;lTk*hKWsz zo=n*)+Z3ZP;%BpirRFYDw4!hu#niJ<)g09^nr&9J?=hcg z@JM8+fZwxi^NTR7Ra9;GBtxj)$1HOKbP`1sj<7)WfUMxh2u>l8x1YN)pI zkQW^hl?r?cTp0oczg?B2)RPu{QRSh*PW6I~dVtP8cHD!e7G^S!sK(XK!rt&SJ3l(- zw1`W>utst+T_?HU;;m?V-J98m2xY@LY_&O#Afv)QIP-PiJ8#_6#P4Q8debD?7CY69 zWP$PMpm-_g{o$*54kYx>u^+w1@wK5}QrZSbWQcM^skp?%T|I^AV{gh8sGknikmdLm;}X5 zk^mMaFstca*gNLFJtEoPkz=B;3?>R6ToC>V)!*E=HO%d(2X~6E*8mBUhA##$k|Nge z{cv$|#1q&YZTo`Ij;S5d`8;@;+?g^ytMBW5Iivae`enn_D?Zs$X{YR%uj~symXNbT z-krRS1iO2eh)y=~=RQ!z!McprWOv-ys@nA#*{}JOIYu%vMM?CPs&f3*K(4e*PF0kg zZrb#F$}hkiq%qTs)W8NZ{?%q-`#>$v>BwjILEyXxt-`RdS8Dh7Q3V`qcG@R(rDm?l z6>v6>fKZU!u^3d;*SAQHwcB22F42A*gf&>J1mk{CeSUT`ha7wN`U&Yd86GYiF=uP8 zCmQRgybap#SXlID<4Cr=PuFS8ce>cV+qYp`zPD=>mzi~0KK-qVtT{AD~UA;YyJNaK(JG4>r$+WlucPTe&LD1nov>2(8|@y5nst zBU$bHa@CJ+i$o^N=G@+efO~fM8Y?q!*^SIZ%Tw?U>r$@*+4GAJye4U?Y=_=kz%OVM zJW0mIXJ?9df`Sc*I)XH~z$F&xLJ7q*UKF@W_RWLO)xipP)dEj0WtUi_s4XBA6KnWJ zMedd@)(@y!@k`jGtep#&ZIBmvcTyC1HWUz9p zZcRML=V-Qii~F7wOw4s}B8&ur`z7U9I&uSI)e4l77$+ObbLhg9SnI93UR_ZDzH}wL zd`>yIm%8JcsI$7#SwMtAxMV0FHC~Wq$lKzW)igu7hkSew<;1C`{4g@v>+8$r+;)RYM*$aJMq{ggZ^OT#gcof^mhvaU0|$rn{!njUNxZ!pRT= ziOYgzWz9-lxAnhn8}OER%lE0UMY!nl^NkD|$0uA2)neL@io#=uCK*p@Rlizo4WouT zNl#GEkM>Xdc0~rbpEf{%_Y$w3RSXqkZf8cXnO-R|E_#I@-XEGfgbihD{JcGkZhtr| z{;<;g`kOUdF86S*@vsYYIvH-w$c8cg3C~*tv3}_U=r&dPzO_eNF=eEP)%nKH=$C$Y z^R`y$XZ2T54*Z7DqRkTmitmYnFX!D`)LIU*Qp3{8_M^tA4oeV%(1Z zD(&H>J;=8>IZ6gTVzLuoOipo-NFCj@Za+4y5m=jCfwVSllY!m07yDM2*NTc}O(7#K zf4F0KN-A2*hu(c-32zW2$idFcn(d{_Pcxyw)+Hw5-LJscwmz8BU zs+v#lokvlDTm(x+Pxa#)^`tk|G+pj&>Cv=sjr^oDmbmnB8?Imbq3P43#CkEplYSv@ zpESF|^kicN8RAGPZ&t^DjBjH}AVAUGHfRy@0AW#<+g5~#M1hnDV<_xL6ksk+?y`&O zJMlIHBM{3XS`*WA_IF>uQRy*DRePs=F+7^n`jb>H@x|NzxGI;U{1uu@2e?45=DhL0 znyDF{u`WJWazzI_n1+ZENDg^xFYjSGes$>C03gVl>QwjYNFcO4p>VFPzGb#=>>RFY z2EVGLaATO8J?TQlu-lx=u)RJ?wq&i_5y5DY1qtRyckrm69J*s~VSSQCrAH`1Ex1)u zH}90sPL-uMb=IcKR1ok)i4McGlSv+N-+ITDTtIj|i)}Q;1)KuW=B6i)l1$q?s8PI9 zcv(vM_otT#i`@c2xcL68ZWa75~uy!6sp2M0&T6CGfQ_UBB32l)?@+`;77r=cxKCIP9J!=u$Cqs7{zuWL7^%XS@vr<0m5;Al48 z-28;T!#efhwH`L%6ISS4a?Vr07x~W)3MR{|3H;fwEnFov7+G^ub{?o9q{$iEMLz~| zROFC$19$^V-T9phZwWvR7pG$QZka7+oF7AS8r3F;F5d^ytW@W62O30dnQNI&g{!+z zHv3i$N$$}I7(OK??^`?%iXpmfy*+5MJ!~O!g(0!^JzVGF+S@E4u{-W3tBI`9L@QZ& z3)2PL4@>VC_%=>KYf+eh~bgjp^ymg7!=iLF2noab6`$Z60A26;tCc zDuaT{f>oR|Tx^H5a8bpp9oz~q_RW6tobHXlm&_J2=q>HIclG%%Vv-4%lOz-!WxyYO z<{6L)v`9A-psIfFCP3{z`PdlxgB!U?+El}Qt;*v<(fMlz208Xk`2Rl zAd=c5j-rF9>%KHKC6xi5XcCJzn!IU~9Zwk+oujL%J)Iw3MBb73266)Wj$zHDjK{U# zu+&bsPdGSWO4ExozG1pfkM$%Af$&H1cDJApy1B%f@<8o{7x0^IaRJ2L0W!|oSuoP1 z4hoL$w(Sd1J|QW|Yi>Ie_d)lo9kQTnI6rpdcby?eFjw3SvylFn|Aqh8g2E9la2td> z%*IX6L%S2qtHGlV(N!zbUxM&}J13@~#GDWVI1j{7KtNwW?s~!2W*JsUyebM_~K0Sigb~Lfav@;#cr5 zf$q;-2chneYvn(=j-0P!f$v8fzYl#sTFDDiA^T;p$NKxHHvS2Ohx7V-r+nBH6(*JV g>4v_~>;IyuLl>h@(oUJLawn35j!Wb+ zvgjrv66IH%a_QvKloe6R+0?1yoac}4^Ln1|`}6sJKkw)BeBPg$tzQjf9LZvXQrb8n_rWlvUO=tUTRpKczZUW&1w<;6^LIx~fN$Enm!Z1*X$bUh$EyEawf@8^*Io zpJ!Qweu*g#(X}o=hlU;Ct|n6CLQ})!dE8HJ)dETUd`}ttaqba@R0YOwW2)+Qxacxiw;x8yxHzZkIu9B0jHNN1>F zd^5~B9*1FrpsV!>gH+B~LALaTZmW{&ex*CxQRkQe@nv|}_T$j{Y@%XvVu#D8jMl-y z$#h5Wfv-*JA4~LH+{Oh6E7}dVhQ-<;kNx8ZKSb0Ynwoqw$gBM&7oCt0LdsipGTwA7 zeLLmryBLoOmHBH}-ZXq0hgBbE6cj>U=-quImK|~AWk(u9d3esMsMnZ;{a|}n+UUuW zteq~CtieOXzfTzwBnS%n>fV(cW-z8IEu2Yl>x0HhcO8AgjNo5!rC+k_meu~9hSd`= z(r_g$MHwcnH^%!6!mXpdKg+z)1N+*sHAir8Pd^$2`{=%sRzc=%BSU&d_s#nY#NLdQ z2m9OGhjO;!FQgwH@5T7;?&-I$omX%V{tlM$scy)65dobZtJz%!ET znccg@bddMno_>oAnv;wahZSt&s0CC{nCWM$qpzjGS{Zjin?Qnb7|CkJ>)Eq5?e=E@ zp)+ZjZ?`S{**)@sg_O$4L;Egj?jUo$Ym++e_IvIOC9Qtmo%rSB)bs@Zd{gh~R>u=6 z)ScKNw8TS)Pm<5V-jsoEv5uv7|34Om<1UvG>QA z&BZO;o>(V)!$(ufIeX~@z%_s3r2RtQ&Aheu7PgRR_)(})Kn6c)gfL+qC?4H{Oa5vS zhYulOi!l0!JUgy;PIJPB-u2y1&J^D?uLysgp-k|vah+a$Q~&C0_vZ+w=wMvA31NR* zeCG9{9e%RI4hluaclbXt`x|TRQq@o~5q;4z*j39HBVF#>MY$tqC|akY_98ljtlDtG zf8mG$KnempjIv$Z3y81T2p#B?5Q1c zq|pUm-&*p+Om>K$!nhkE+1Tl6({3z3 z9b#i)pYvf*%+@Oj(ru=FZZk4yN{UbDXs-EZQ_YP!LxMrNF zeBMg9SZ>2+wpUEx?>M_lp{;OvqfL=^$|=bdclvqvN2f|^+%L8KG4^DUc(AinXJ4Bg zrLiQ@jO{;2ale-WO^|$~{6I~84hV_db6d4=fJ*2{IqL3=Q3|INaH3o4_So+zbUVW; zmdYhop{OmdJf04RSINAb@0>k=J51EtUFzPv_CfCi^FjUPFGG)IUs$)DI(ie4V2ZG* z%+KW+5%?Z?v-yc94D9WFff zZghQu@h)AtE9h!~Ly{1`6Xeh{_QI9Zuq8J97MrC`58vADbbfq*`kd8utax5RQ0txM;6k0+choTv-CKAm!7^@ozG}T+`Ku$vudoNAF_^~H&8UmxWwf&L-X?{a+7Xn! z#Aue3eTv1m=8Z zBXc^yMYDd%$!~FARjt1X&$D$V2>!oJrlx@kwQHRg`JSh9DAB!nh@CwE=knZQ1V;=ON2~S>z~hTgEDEP_bOFjJ>Yn`EFSK zpz})^@yypI)&PZ=AK2yYCWYNMQC{fezNx>Q`p4j4>f6d|!~_lPyx4hW`baW zB_R@am+XNoyKBp16iAMt*p4)Ey4rV#I!Q6W}6d>%ZCw&_sT#^S{MbSWCVSB)~UE3k$YSps+Zt@U=pnW&g$lB7?2p z48R^m#nu0Ija`BC0GH&|{)54jcJwg W@=zLf`Gu^KlGGL$0O%`-%>DwGYgW_% delta 2106 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 T8IOU42AUY^Isll%DHi<&23zt* 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 03f71224aa13371b70b5d67f56fbfe1906972c2d..4a4c897cb179fb87c2173e15084402cbb95c4488 100644 GIT binary patch delta 1407 zcmca6v_gzGz?+$civa{yHt9~}RpDLHq#J%a@>6LQP-y-{JL&q^zKvYX1_BP>oBuf1 zDIC<(-I2Lmti|sxfAP`9o>NY1etj>xL}b@K^F7xSpNWf>`cFEPyzcGEa5kxz>~S7- zKLlSIi~km7Tj#VwBxd)uZ8JB>pD5cAV0Pi0%-08sh0hi>S)EdB+TH#=Zs(6v^Lg)F z6449{yJGvfJTZY8>^2U#<8H_?gWUL2)s%q&R@z!NxfSC^tyvdW9wLlJ>e1O>uO#ftd5LyOw=coMmx}S^;3=XUe z3_1*xH*)CJ_fGcDzHK0J?EO!XdG9ApD%&6^GF>hzEaSS@p`}4=w_us#7pKo{P&#yQ4>bJ+& z-TU+J zHV6Hl|02<*$MJdJoOdhAMO;42RQ~P|akpv3<=T?})*^ht_eElIb{t!we(k>0-|+Xp zOTWr6Ep{(^;BiZ7QJ7C}6hrm~HshV3RLu|0a^O@Mz9?te(#hvoBlw$xxkY$-MS0Gy z>ET(+b9S;RTR1ow^|P6vX3EJI*i21;2@NF!FauLD1PCy^b-WB@X66IaKO>U}Gdz1w zwq%#C{{oT9%1q3w$jnPeQG+d4Aqfwfl{SFY>ZF>RP&z=nB%o4 Un{f*9@i6c(_yfImm>r}70QH3R&;S4c delta 928 zcmZ1>c1?&kz?+$civa|_g^5q(RpI>_CLVtKkeOc*P^e>~opk+Y-!rFuymj?1@_OrP zojY@WbCAIm;|EWRbiB@;(DBxF;$VNWBy5V9_5qcOopZW_guR1;OXg~=6_McfA8wkKv! z3_5Roi>?~QI(YgU0-e`MkzR4 z|MgwyR^Hs3Q_RoVKEHGL>7>f&sUPKfM7_>U;yg7;LM43Xgrho@ckPyypPb|7BK?*1 z&e3_gZ*^{3E?+JYp2II>c=iY11S8hC_0xW^rQTERY8O_ye66c_O{Ub9820*uq0@Xn z{4Uz~kx$gSbxWToU-C}TVChxdM`v;y{?C<8TKs6$r9~{wYgX~H zTkTZ+QS)@sCBxtb>*a5>-Bb4my;v-kdQaN^%WC(-&wi@^J1Tk1pemqb!UcI)?0_R9 z+(hP;)?`8U2!2`b%c4C!qCC87e0tmk+*(W0OOYjm=>gfYGGhWqZs4l zEJm5h`kY+QWYWti&D6#`c@?KD*y2+_?q+6Ss*u$|vp5`P2#8jNa|9Uv|9=D0U@VWB zy5Oo9mNe>5*5}dyB{d|25#dw}R9Xte#t1DyJ$qQe>63+9jE{?fi{Sv!OATxw6#%o} BLp1;Z 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 9b9b32b78d7fc317a9a9e1b797290b610652e2e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2542 zcmWIWW@h1H00H(e@o+E$N^k;cU)K;vT~9wZ{Q#&k4hA-$vfGEu{EC3m9Y8FCtjyQZ z&(qB{I7H9a?X&Nh(>~t1dKY=Ub+yi&Ilno`;EM5sr$stm=T7K&>pF3;KUoqsMNIpE zO2y7O-9f_MLBS<+wbqKra0^W~4ic>qJ{|N~r01*77oN(UbG(d;y%aWS?wnaU^Qfw8 zr|M5vfliUw^;2>d#6GKHt4iAwvnPfD=0;4fumIhdoSzH#1*Qs65P($VRu&iL;87=t zqAsN-2V4PmIMDQ`o!#A(JyS6QG zT(DEYaltL|r7m-I9(vsjqIlc;ap4&Yg9q(wY`e6FePoJ93hvHIKLY1wC<^q+D4 za?Y_U%S|KvKZ#}>R*lhryt8W}?;o>;lV9FD9__lgN1uDwS(oi|)25enXN51Ez4EK^ z^kpA~*SIduW?h`7m+{Q)tkB}?0`tq>=Uv;Ix>&mPTIi>1xpSLt>Zaanc>CXIyZ21< zhhf(`=Ik`l+uc$b^D+0&8q4VwA&z?7!cIZspCrImP^(?ejbSM)|9a?lo?7IvHu? z7%9~JL`!_r3|0N}-(UI~Ef>7#R?AerG2PqRd)v8}n#V+MH@f&3?_*u!(|CQg>OQBL z<(-cnvYy3dD7vCyrWmzUN{v#bDH^oS^s&% zsM+NSQWYou3rkI!==C&UU)kgZie4WCuU#}TR*=0b?!mP<>+g)<{Qd>0Q}r4jFxp(0 zQTV92@VR5`wbltMnO}3rJ!CK1FYxO7#JqWV*Jrn1H9oj|vD1eD&o8(4|JoLxI!pQ? z>v7IsVm+501a4S7XI`e5BxNLpK!bxFIu(Ub>{bD z<$u3kEU4LcdcVbC6*pejYR zv);O1I-b6P9;-q=`099{ef%_tkG)9N`?6?Hk0=lC8XuiCJiVfGnw>>@czSv&cV6oL z-CjnQg(iD9JlZ++XshKeP1l*JD_!@*I4rU-wm*L@ z;JkzRjq`>IHxApkOy>rc{)|i_%($yEpm%^kfZ?qph=%72gf{F|8%Qw-ENN5+l5lNE z6&=C^%v=Skcpv~|@o69vQVrpDD#QaIJs=%#p*mpI5^jB-E6Y}zrZEqopal|2w3!0`Y7L1v_&gOx-u$6$sUs4PNgeut?UwPeC=5U6ZI z7^Du24R{1XT!Cy5T1f>nm4g9WSp_q9Nnfalse 2.9.0 0.8.10 + 3.10.0