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 c8a6ea2338..c19d1732dc 100644 Binary files a/backend/services/project-management/src/test/resources/file/metersphere-jira-plugin-3.x.jar and b/backend/services/project-management/src/test/resources/file/metersphere-jira-plugin-3.x.jar differ diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/result/SystemResultCode.java b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/result/SystemResultCode.java index f221e74f1e..220fbc7a75 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/result/SystemResultCode.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/result/SystemResultCode.java @@ -20,7 +20,6 @@ public enum SystemResultCode implements IResultCode { */ NO_ORG_USER_ROLE_PERMISSION(101007, "organization_user_role_permission_error"), PLUGIN_EXIST(101008, "plugin.exist"), - PLUGIN_TYPE_EXIST(101009, "plugin.type.exist"), PLUGIN_SCRIPT_EXIST(101010, "plugin.script.exist"), PLUGIN_SCRIPT_FORMAT(101011, "plugin.script.format"), NO_PROJECT_USER_ROLE_PERMISSION(101012, "project_user_role_permission_error"); diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/PluginNotifiedDTO.java b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/PluginNotifiedDTO.java new file mode 100644 index 0000000000..40613b31b2 --- /dev/null +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/PluginNotifiedDTO.java @@ -0,0 +1,10 @@ +package io.metersphere.system.dto; + +import lombok.Data; + +@Data +public class PluginNotifiedDTO { + private String operate; + private String pluginId; + private String fileName; +} diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/listener/PluginListener.java b/backend/services/system-setting/src/main/java/io/metersphere/system/listener/PluginListener.java index 39aa408f0a..2f3ba8c294 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/listener/PluginListener.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/listener/PluginListener.java @@ -4,7 +4,9 @@ package io.metersphere.system.listener; import io.metersphere.sdk.constants.KafkaPluginTopicType; import io.metersphere.sdk.constants.KafkaTopicConstants; import io.metersphere.sdk.service.PluginLoadService; +import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.LogUtils; +import io.metersphere.system.dto.PluginNotifiedDTO; import jakarta.annotation.Resource; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; @@ -21,17 +23,17 @@ public class PluginListener { // groupId 必须是每个实例唯一 @KafkaListener(id = PLUGIN_CONSUMER, topics = KafkaTopicConstants.PLUGIN, groupId = PLUGIN_CONSUMER + "_" + "${random.uuid}") public void handlePluginChange(ConsumerRecord record) { - LogUtils.info("Service consume platform_plugin message: " + record); - String[] info = record.value().split(":"); - String operate = info[0]; - String pluginId = info[1]; + LogUtils.info("Service consume platform_plugin message: " + record.value()); + PluginNotifiedDTO pluginNotifiedDTO = JSON.parseObject(record.value(), PluginNotifiedDTO.class); + String operate = pluginNotifiedDTO.getOperate(); + String pluginId = pluginNotifiedDTO.getPluginId(); + String fileName = pluginNotifiedDTO.getFileName(); switch (operate) { case KafkaPluginTopicType.ADD: - String pluginName = info[2]; - pluginLoadService.loadPlugin(pluginId, pluginName); + pluginLoadService.handlePluginAddNotified(pluginId, fileName); break; case KafkaPluginTopicType.DELETE: - pluginLoadService.unloadPlugin(pluginId); + pluginLoadService.handlePluginDeleteNotified(pluginId, fileName); break; default: break; diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java index c83eaccedf..288bb76448 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java @@ -1,6 +1,8 @@ package io.metersphere.system.service; +import io.metersphere.plugin.api.api.AbstractApiProtocolPlugin; +import io.metersphere.plugin.platform.api.AbstractPlatformPlugin; import io.metersphere.plugin.sdk.api.MsPlugin; import io.metersphere.sdk.constants.KafkaPluginTopicType; import io.metersphere.sdk.constants.KafkaTopicConstants; @@ -11,12 +13,13 @@ import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.service.BaseUserService; import io.metersphere.sdk.service.JdbcDriverPluginService; import io.metersphere.sdk.service.PluginLoadService; -import io.metersphere.sdk.uid.UUID; import io.metersphere.sdk.util.BeanUtils; +import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.ServiceUtils; import io.metersphere.system.domain.Plugin; import io.metersphere.system.domain.PluginExample; import io.metersphere.system.dto.PluginDTO; +import io.metersphere.system.dto.PluginNotifiedDTO; import io.metersphere.system.mapper.ExtPluginMapper; import io.metersphere.system.mapper.PluginMapper; import io.metersphere.system.request.PluginUpdateRequest; @@ -25,6 +28,8 @@ import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.pf4j.PluginDescriptor; +import org.pf4j.PluginWrapper; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -37,7 +42,6 @@ import java.sql.Driver; import java.util.*; import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_EXIST; -import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_TYPE_EXIST; /** * @author jianxing @@ -58,7 +62,7 @@ public class PluginService { @Resource private PluginLoadService pluginLoadService; @Resource - JdbcDriverPluginService jdbcDriverPluginService; + private JdbcDriverPluginService jdbcDriverPluginService; @Resource private KafkaTemplate kafkaTemplate; @Resource @@ -90,10 +94,9 @@ public class PluginService { } public Plugin add(PluginUpdateRequest request, MultipartFile file) { - String id = UUID.randomUUID().toString(); + String id = null; Plugin plugin = new Plugin(); BeanUtils.copyBean(plugin, request); - plugin.setId(id); plugin.setFileName(file.getOriginalFilename()); plugin.setCreateTime(System.currentTimeMillis()); plugin.setUpdateTime(System.currentTimeMillis()); @@ -105,34 +108,25 @@ public class PluginService { checkPluginAddExist(plugin); try { - // 加载插件 - pluginLoadService.loadPlugin(id, file); - // 上传插件 - pluginLoadService.uploadPlugin(id, file); + // 上传插件到本地文件系统 + pluginLoadService.uploadPlugin2Local(file); - if (jdbcDriverPluginService.isJdbcDriver(id)) { - Class implClass = pluginLoadService.getImplClass(id, Driver.class); - // mysql 已经内置了依赖,不允许上传 - if (implClass.getName().startsWith("com.mysql")) { - throw new MSException(PLUGIN_TYPE_EXIST); - } - plugin.setScenario(PluginScenarioType.JDBC_DRIVER.name()); - plugin.setXpack(false); + // 从文件系统中加载插件 + id = pluginLoadService.loadPlugin(file.getOriginalFilename()); + pluginLoadService.getMsPluginManager().startPlugin(id); + plugin.setId(id); + + List extensions = pluginLoadService.getMsPluginManager().getExtensions(Driver.class, id); + + if (CollectionUtils.isNotEmpty(extensions)) { + plugin = jdbcDriverPluginService.wrapperPlugin(plugin); plugin.setPluginId(file.getOriginalFilename()); } else { + + plugin = wrapperPlugin(id, plugin); + // 非数据库驱动插件,解析脚本和插件信息 - // 获取插件前端配置脚本 List frontendScript = pluginLoadService.getFrontendScripts(id); - - MsPlugin msPlugin = pluginLoadService.getMsPluginInstance(id); - plugin.setScenario(msPlugin.getType()); - plugin.setXpack(msPlugin.isXpack()); - plugin.setPluginId(msPlugin.getPluginId()); - - // 校验插件类型是否重复 - checkPluginKeyExist(id, msPlugin.getKey()); - - // 保存插件脚本 pluginScriptService.add(id, frontendScript); } @@ -143,20 +137,32 @@ public class PluginService { pluginMapper.insert(plugin); + // 上传插件到对象存储 + pluginLoadService.uploadPlugin2Repository(file); + // 通知其他节点加载插件 notifiedPluginAdd(id, plugin.getFileName()); } catch (Exception e) { // 删除插件 - pluginLoadService.deletePlugin(id); + pluginLoadService.unloadPlugin(id); + pluginLoadService.deletePluginFile(file.getOriginalFilename()); throw e; } return plugin; } - private void checkPluginKeyExist(String pluginId, String pluginKey) { - if (pluginLoadService.hasPluginKey(pluginId, pluginKey)) { - throw new MSException(PLUGIN_TYPE_EXIST); + public Plugin wrapperPlugin(String id, Plugin plugin) { + PluginWrapper pluginWrapper = pluginLoadService.getPluginWrapper(id); + PluginDescriptor descriptor = pluginWrapper.getDescriptor(); + MsPlugin msPlugin = (MsPlugin) pluginWrapper.getPlugin(); + if (msPlugin instanceof AbstractApiProtocolPlugin) { + plugin.setScenario(PluginScenarioType.API_PROTOCOL.name()); + } else if (msPlugin instanceof AbstractPlatformPlugin) { + plugin.setScenario(PluginScenarioType.PLATFORM.name()); } + plugin.setXpack(msPlugin.isXpack()); + plugin.setPluginId(descriptor.getPluginId() + "-" + descriptor.getVersion()); + return plugin; } public Plugin checkResourceExist(String id) { @@ -183,8 +189,15 @@ public class PluginService { * @param fileName */ public void notifiedPluginAdd(String pluginId, String fileName) { - // 初始化项目默认节点 - kafkaTemplate.send(KafkaTopicConstants.PLUGIN, String.format("%s:%s:%s", KafkaPluginTopicType.ADD, pluginId, fileName)); + notifiedPluginOperate(pluginId, fileName, KafkaPluginTopicType.ADD); + } + + public void notifiedPluginOperate(String pluginId, String fileName, String operate) { + PluginNotifiedDTO pluginNotifiedDTO = new PluginNotifiedDTO(); + pluginNotifiedDTO.setOperate(operate); + pluginNotifiedDTO.setPluginId(pluginId); + pluginNotifiedDTO.setFileName(fileName); + kafkaTemplate.send(KafkaTopicConstants.PLUGIN, JSON.toJSONString(pluginNotifiedDTO)); } /** @@ -192,9 +205,8 @@ public class PluginService { * * @param pluginId */ - public void notifiedPluginDelete(String pluginId) { - // 初始化项目默认节点 - kafkaTemplate.send(KafkaTopicConstants.PLUGIN, String.format("%s:%s", KafkaPluginTopicType.DELETE, pluginId)); + public void notifiedPluginDelete(String pluginId, String fileName) { + notifiedPluginOperate(pluginId, fileName, KafkaPluginTopicType.DELETE); } public Plugin update(PluginUpdateRequest request) { @@ -212,7 +224,7 @@ public class PluginService { request.setOrganizationIds(new ArrayList<>(0)); } pluginOrganizationService.update(plugin.getId(), request.getOrganizationIds()); - return plugin; + return pluginMapper.selectByPrimaryKey(request.getId()); } private void checkPluginUpdateExist(Plugin plugin) { @@ -230,14 +242,16 @@ public class PluginService { public void delete(String id) { checkResourceExist(id); + Plugin plugin = pluginMapper.selectByPrimaryKey(id); pluginMapper.deleteByPrimaryKey(id); // 删除插件脚本 pluginScriptService.deleteByPluginId(id); // 删除和组织的关联关系 pluginOrganizationService.deleteByPluginId(id); // 删除和卸载插件 - pluginLoadService.deletePlugin(id); - notifiedPluginDelete(id); + pluginLoadService.unloadPlugin(id); + pluginLoadService.deletePluginFile(plugin.getFileName()); + notifiedPluginDelete(id, plugin.getFileName()); } public String getScript(String pluginId, String scriptId) { diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationLogService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationLogService.java index cb11af9409..cf69987d2a 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationLogService.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationLogService.java @@ -1,15 +1,15 @@ package io.metersphere.system.service; -import io.metersphere.sdk.service.PluginLoadService; +import io.metersphere.sdk.constants.OperationLogConstants; +import io.metersphere.sdk.dto.LogDTO; +import io.metersphere.sdk.log.constants.OperationLogModule; +import io.metersphere.sdk.log.constants.OperationLogType; +import io.metersphere.sdk.service.BasePluginService; +import io.metersphere.sdk.util.JSON; import io.metersphere.system.domain.ServiceIntegration; import io.metersphere.system.request.ServiceIntegrationUpdateRequest; -import io.metersphere.sdk.constants.OperationLogConstants; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; -import io.metersphere.sdk.dto.LogDTO; -import io.metersphere.sdk.util.JSON; -import io.metersphere.sdk.log.constants.OperationLogType; -import io.metersphere.sdk.log.constants.OperationLogModule; import org.springframework.transaction.annotation.Transactional; /** * @author jianxing @@ -22,7 +22,7 @@ public class ServiceIntegrationLogService { @Resource private ServiceIntegrationService serviceIntegrationService; @Resource - private PluginLoadService pluginLoadService; + private BasePluginService basePluginService; public LogDTO addLog(ServiceIntegrationUpdateRequest request) { LogDTO dto = new LogDTO( @@ -38,7 +38,7 @@ public class ServiceIntegrationLogService { } private String getName(String pluginId) { - return pluginLoadService.getPlatformPluginInstance(pluginId).getName(); + return basePluginService.get(pluginId).getName(); } public LogDTO updateLog(ServiceIntegrationUpdateRequest request) { diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java index 3ba29cb520..fe2d6183e7 100644 --- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java +++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java @@ -55,14 +55,14 @@ public class ServiceIntegrationService { List plugins = platformPluginService.getOrgEnabledPlatformPlugins(organizationId); return plugins.stream().map(plugin -> { - AbstractPlatformPlugin msPluginInstance = pluginLoadService.getPlatformPluginInstance(plugin.getId()); + AbstractPlatformPlugin msPlugin = (AbstractPlatformPlugin) pluginLoadService.getPluginWrapper(plugin.getId()).getPlugin(); // 获取插件基础信息 ServiceIntegrationDTO serviceIntegrationDTO = new ServiceIntegrationDTO(); - serviceIntegrationDTO.setTitle(msPluginInstance.getName()); + serviceIntegrationDTO.setTitle(msPlugin.getName()); serviceIntegrationDTO.setEnable(false); serviceIntegrationDTO.setConfig(false); - serviceIntegrationDTO.setDescription(msPluginInstance.getDescription()); - serviceIntegrationDTO.setLogo(String.format(PLUGIN_IMAGE_GET_PATH, plugin.getId(), msPluginInstance.getLogo())); + serviceIntegrationDTO.setDescription(msPlugin.getDescription()); + serviceIntegrationDTO.setLogo(String.format(PLUGIN_IMAGE_GET_PATH, plugin.getId(), msPlugin.getLogo())); serviceIntegrationDTO.setPluginId(plugin.getId()); ServiceIntegration serviceIntegration = serviceIntegrationMap.get(plugin.getId()); if (serviceIntegration != null) { @@ -149,7 +149,7 @@ public class ServiceIntegrationService { public Object getPluginScript(String pluginId) { pluginService.checkResourceExist(pluginId); - AbstractPlatformPlugin platformPlugin = pluginLoadService.getImplInstance(pluginId, AbstractPlatformPlugin.class); + AbstractPlatformPlugin platformPlugin = (AbstractPlatformPlugin) pluginLoadService.getPluginWrapper(pluginId).getPlugin(); return pluginLoadService.getPluginScriptContent(pluginId, platformPlugin.getIntegrationScriptId()); } } \ No newline at end of file diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java index 4a2a0ed996..1a2dd3746c 100644 --- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java +++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java @@ -100,7 +100,7 @@ public class PluginControllerTests extends BaseTest { Assertions.assertEquals(plugin.getGlobal(), request.getGlobal()); Assertions.assertEquals(plugin.getXpack(), false); Assertions.assertEquals(plugin.getFileName(), jarFile.getName()); - Assertions.assertEquals(plugin.getScenario(), PluginScenarioType.API.name()); + Assertions.assertEquals(plugin.getScenario(), PluginScenarioType.API_PROTOCOL.name()); Assertions.assertEquals(new ArrayList<>(0), getOrgIdsByPlugId(plugin.getId())); Assertions.assertEquals(Arrays.asList("connect", "disconnect", "pub", "sub"), getScriptIdsByPlugId(plugin.getId())); addPlugin = plugin; @@ -135,7 +135,7 @@ public class PluginControllerTests extends BaseTest { ); this.requestMultipartWithOkAndReturn(DEFAULT_ADD, getDefaultMultiPartParam(request, myDriver)); - Assertions.assertEquals(jdbcDriverPluginService.getJdbcDriverClass(), Arrays.asList("io.jianxing.MyDriver", "com.mysql.jdbc.Driver")); + Assertions.assertEquals(jdbcDriverPluginService.getJdbcDriverClass(DEFAULT_ORGANIZATION_ID), Arrays.asList("io.jianxing.MyDriver", "com.mysql.cj.jdbc.Driver")); // @@重名校验异常 // 校验插件名称重名 @@ -147,21 +147,6 @@ public class PluginControllerTests extends BaseTest { assertErrorCode(this.requestMultipart(DEFAULT_ADD, getDefaultMultiPartParam(request, jarFile)), PLUGIN_EXIST); - // 校验插件 key 重复 - File typeRepeatFile = new File( - this.getClass().getClassLoader().getResource("file/metersphere-mqtt-plugin-repeat-key.jar") - .getPath() - ); - assertErrorCode(this.requestMultipart(DEFAULT_ADD, - getDefaultMultiPartParam(request, typeRepeatFile)), PLUGIN_TYPE_EXIST); - - // @@校验禁止上传mysql驱动 - File mysqlDriver = new File( - this.getClass().getClassLoader().getResource("file/test-mysql-driver-1.0.jar") - .getPath() - ); - assertErrorCode(this.requestMultipart(DEFAULT_ADD, - getDefaultMultiPartParam(request, mysqlDriver)), PLUGIN_TYPE_EXIST); // @@校验插件脚本解析失败 File scriptParseFile = new File( @@ -296,10 +281,10 @@ public class PluginControllerTests extends BaseTest { @Order(5) public void getPluginImg() throws Exception { // @@请求成功 - mockMvc.perform(getRequestBuilder(PLUGIN_IMAGE, anotherAddPlugin.getId(), "/static/jira.jpg")) + mockMvc.perform(getRequestBuilder(PLUGIN_IMAGE, anotherAddPlugin.getId(), "static/jira.jpg")) .andExpect(status().isOk()); - assertErrorCode(this.requestGet(PLUGIN_IMAGE, anotherAddPlugin.getId(), "/static/jira.doc"), FILE_NAME_ILLEGAL); + assertErrorCode(this.requestGet(PLUGIN_IMAGE, anotherAddPlugin.getId(), "static/jira.doc"), FILE_NAME_ILLEGAL); } @Test diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java index 2f09811a1f..e20de07288 100644 --- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java +++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java @@ -200,7 +200,7 @@ public class ServiceIntegrationControllerTests extends BaseTest { serviceIntegrationDTO.getConfiguration()); Assertions.assertEquals(serviceIntegration.getEnable(), serviceIntegrationDTO.getEnable()); Assertions.assertEquals(serviceIntegration.getPluginId(), serviceIntegrationDTO.getPluginId()); - AbstractPlatformPlugin msPluginInstance = pluginLoadService.getPlatformPluginInstance(plugin.getId()); + AbstractPlatformPlugin msPluginInstance = (AbstractPlatformPlugin) pluginLoadService.getPluginWrapper(plugin.getId()).getPlugin(); Assertions.assertEquals(serviceIntegrationDTO.getDescription(), msPluginInstance.getDescription()); Assertions.assertEquals(serviceIntegrationDTO.getOrganizationId(), defaultOrg.getId()); Assertions.assertEquals(serviceIntegrationDTO.getTitle(), msPluginInstance.getName()); @@ -333,7 +333,7 @@ public class ServiceIntegrationControllerTests extends BaseTest { .getPath() ); FileInputStream inputStream = new FileInputStream(jarFile); - MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), inputStream); + MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), jarFile.getName(), "jar", inputStream); request.setName("测试插件"); request.setGlobal(true); request.setEnable(true); diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar b/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar index c8a6ea2338..c19d1732dc 100644 Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar differ diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-3.x.jar b/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-3.x.jar index 75049c42ff..f1bb0b0ab8 100644 Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-3.x.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-3.x.jar differ diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-repeat-key.jar b/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-repeat-key.jar deleted file mode 100644 index 75049c42ff..0000000000 Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-mqtt-plugin-repeat-key.jar and /dev/null differ diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-id-repeat-error.jar b/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-id-repeat-error.jar index 23171868f7..1c3cf4b1c0 100644 Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-id-repeat-error.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-id-repeat-error.jar differ diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-parse-error.jar b/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-parse-error.jar index 7c9cdb1940..8782fc9134 100644 Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-parse-error.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-plugin-script-parse-error.jar differ diff --git a/backend/services/system-setting/src/test/resources/file/my-driver-1.0.jar b/backend/services/system-setting/src/test/resources/file/my-driver-1.0.jar index 03f71224aa..4a4c897cb1 100644 Binary files a/backend/services/system-setting/src/test/resources/file/my-driver-1.0.jar and b/backend/services/system-setting/src/test/resources/file/my-driver-1.0.jar differ diff --git a/backend/services/system-setting/src/test/resources/file/test-mysql-driver-1.0.jar b/backend/services/system-setting/src/test/resources/file/test-mysql-driver-1.0.jar deleted file mode 100644 index 9b9b32b78d..0000000000 Binary files a/backend/services/system-setting/src/test/resources/file/test-mysql-driver-1.0.jar and /dev/null differ diff --git a/frontend/src/views/setting/system/pluginManager/components/pluginTable.vue b/frontend/src/views/setting/system/pluginManager/components/pluginTable.vue index a835fbde4e..1defe5c8f2 100644 --- a/frontend/src/views/setting/system/pluginManager/components/pluginTable.vue +++ b/frontend/src/views/setting/system/pluginManager/components/pluginTable.vue @@ -209,7 +209,7 @@ }, { label: 'system.plugin.interfaceTest', - value: 'API', + value: 'API_PROTOCOL', }, { label: 'system.plugin.projectManagement', @@ -280,7 +280,7 @@ function getScenarioType(scenario: string) { switch (scenario) { - case 'API': + case 'API_PROTOCOL': return t('system.plugin.interfaceTest'); case 'JDBC_DRIVER': return t('system.plugin.databaseDriver'); diff --git a/pom.xml b/pom.xml index 509ae9513f..04dc26a54f 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,7 @@ false 2.9.0 0.8.10 + 3.10.0