refactor(系统设置): 插件加载重构
This commit is contained in:
parent
bfc70f58f9
commit
69e312e87b
|
@ -51,7 +51,7 @@ public class Plugin implements Serializable {
|
||||||
@Schema(description = "插件描述")
|
@Schema(description = "插件描述")
|
||||||
private String 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})
|
@NotBlank(message = "{plugin.scenario.not_blank}", groups = {Created.class})
|
||||||
@Size(min = 1, max = 50, message = "{plugin.scenario.length_range}", groups = {Created.class, Updated.class})
|
@Size(min = 1, max = 50, message = "{plugin.scenario.length_range}", groups = {Created.class, Updated.class})
|
||||||
private String scenario;
|
private String scenario;
|
||||||
|
|
|
@ -72,7 +72,7 @@ CREATE TABLE IF NOT EXISTS novice_statistics
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS plugin
|
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 '插件名称' ,
|
`name` VARCHAR(255) NOT NULL COMMENT '插件名称' ,
|
||||||
`plugin_id` VARCHAR(300) NOT NULL COMMENT '插件ID(名称加版本号)' ,
|
`plugin_id` VARCHAR(300) NOT NULL COMMENT '插件ID(名称加版本号)' ,
|
||||||
`file_name` VARCHAR(300) NOT NULL COMMENT '文件名' ,
|
`file_name` VARCHAR(300) NOT NULL COMMENT '文件名' ,
|
||||||
|
@ -83,7 +83,7 @@ CREATE TABLE IF NOT EXISTS plugin
|
||||||
`global` BIT NOT NULL DEFAULT 1 COMMENT '是否是全局插件' ,
|
`global` BIT NOT NULL DEFAULT 1 COMMENT '是否是全局插件' ,
|
||||||
`xpack` BIT NOT NULL DEFAULT 0 COMMENT '是否是企业版插件' ,
|
`xpack` BIT NOT NULL DEFAULT 0 COMMENT '是否是企业版插件' ,
|
||||||
`description` VARCHAR(500) 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)
|
PRIMARY KEY (id)
|
||||||
) ENGINE = InnoDB
|
) ENGINE = InnoDB
|
||||||
DEFAULT CHARSET = utf8mb4
|
DEFAULT CHARSET = utf8mb4
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.metersphere.plugin.api.api;
|
||||||
|
|
||||||
|
import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
|
||||||
|
|
||||||
|
public abstract class AbstractApiProtocolPlugin extends AbstractMsPlugin {
|
||||||
|
}
|
|
@ -3,14 +3,9 @@ package io.metersphere.plugin.platform.api;
|
||||||
import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
|
import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
|
||||||
|
|
||||||
public abstract class AbstractPlatformPlugin extends 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_INTEGRATION_SCRIPT_ID = "integration";
|
||||||
private static final String DEFAULT_PROJECT_SCRIPT_ID = "project";
|
private static final String DEFAULT_PROJECT_SCRIPT_ID = "project";
|
||||||
private static final String DEFAULT_ACCOUNT_SCRIPT_ID = "account";
|
private static final String DEFAULT_ACCOUNT_SCRIPT_ID = "account";
|
||||||
@Override
|
|
||||||
public String getType() {
|
|
||||||
return DEFAULT_PLATFORM_PLUGIN_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回插件的描述信息
|
* 返回插件的描述信息
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package io.metersphere.plugin.platform.api;
|
package io.metersphere.plugin.platform.api;
|
||||||
|
|
||||||
|
import org.pf4j.ExtensionPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 平台对接相关业务接口
|
* 平台对接相关业务接口
|
||||||
* @author jianxing.chen
|
* @author jianxing.chen
|
||||||
*/
|
*/
|
||||||
public interface Platform {
|
public interface Platform extends ExtensionPoint {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验服务集成配置
|
* 校验服务集成配置
|
||||||
|
|
|
@ -44,5 +44,10 @@
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-annotations</artifactId>
|
<artifactId>jackson-annotations</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.pf4j</groupId>
|
||||||
|
<artifactId>pf4j</artifactId>
|
||||||
|
<version>${pf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
|
@ -1,6 +1,6 @@
|
||||||
package io.metersphere.plugin.sdk.api;
|
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";
|
private static final String SCRIPT_DIR = "script";
|
||||||
|
|
||||||
|
@ -12,14 +12,4 @@ public abstract class AbstractMsPlugin implements MsPlugin {
|
||||||
public String getScriptDir() {
|
public String getScriptDir() {
|
||||||
return SCRIPT_DIR;
|
return SCRIPT_DIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPluginId() {
|
|
||||||
return getKey().toLowerCase() + "-" + getVersion();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,28 @@
|
||||||
package io.metersphere.plugin.sdk.api;
|
package io.metersphere.plugin.sdk.api;
|
||||||
|
|
||||||
|
import org.pf4j.Plugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件的基本信息
|
* 插件的基本信息
|
||||||
*
|
*
|
||||||
* @author jianxing.chen
|
* @author jianxing.chen
|
||||||
*/
|
*/
|
||||||
public interface MsPlugin {
|
public abstract class MsPlugin extends Plugin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 返回该插件是否是开源的,默认是
|
* @return 返回该插件是否是开源的,默认是
|
||||||
*/
|
*/
|
||||||
boolean isXpack();
|
abstract public boolean isXpack();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return 返回插件的类型
|
|
||||||
* 目前支持接口插件和平台(API、PLATFORM)
|
|
||||||
*/
|
|
||||||
String getType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return 返回插件的关键字,例如 Jira
|
|
||||||
*/
|
|
||||||
String getKey();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 返回插件的名称
|
* @return 返回插件的名称
|
||||||
* 默认返回 key
|
* 默认返回 key
|
||||||
*/
|
*/
|
||||||
String getName();
|
abstract public String getName();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return 返回插件的ID
|
|
||||||
* 默认是 key + 版本号
|
|
||||||
*/
|
|
||||||
String getPluginId();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return 返回插件的版本
|
|
||||||
*/
|
|
||||||
String getVersion();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 返回该加载前端配置文件的目录,默认是 script
|
* @return 返回该加载前端配置文件的目录,默认是 script
|
||||||
* 可以重写定制
|
* 可以重写定制
|
||||||
*/
|
*/
|
||||||
String getScriptDir();
|
abstract public String getScriptDir();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package io.metersphere.sdk.config;
|
package io.metersphere.sdk.config;
|
||||||
|
|
||||||
|
|
||||||
|
import io.metersphere.sdk.file.FileCenter;
|
||||||
import io.metersphere.sdk.file.FileRepository;
|
import io.metersphere.sdk.file.FileRepository;
|
||||||
import io.metersphere.sdk.file.FileRequest;
|
import io.metersphere.sdk.file.FileRequest;
|
||||||
import io.metersphere.sdk.log.constants.OperationLogModule;
|
import io.metersphere.sdk.log.constants.OperationLogModule;
|
||||||
import io.metersphere.sdk.util.RsaKey;
|
import io.metersphere.sdk.util.RsaKey;
|
||||||
import io.metersphere.sdk.util.RsaUtil;
|
import io.metersphere.sdk.util.RsaUtil;
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.apache.commons.lang3.SerializationUtils;
|
import org.apache.commons.lang3.SerializationUtils;
|
||||||
import org.springframework.boot.ApplicationArguments;
|
import org.springframework.boot.ApplicationArguments;
|
||||||
import org.springframework.boot.ApplicationRunner;
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
@ -14,8 +14,6 @@ import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class RsaConfig implements ApplicationRunner {
|
public class RsaConfig implements ApplicationRunner {
|
||||||
@Resource
|
|
||||||
private FileRepository fileRepository;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(ApplicationArguments args) throws Exception {
|
public void run(ApplicationArguments args) throws Exception {
|
||||||
|
@ -23,7 +21,8 @@ public class RsaConfig implements ApplicationRunner {
|
||||||
request.setFileName("rsa.key");
|
request.setFileName("rsa.key");
|
||||||
request.setProjectId("system");
|
request.setProjectId("system");
|
||||||
request.setResourceId(OperationLogModule.SYSTEM_PARAMETER_SETTING);
|
request.setResourceId(OperationLogModule.SYSTEM_PARAMETER_SETTING);
|
||||||
//
|
FileRepository fileRepository = FileCenter.getDefaultRepository();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] file = fileRepository.getFile(request);
|
byte[] file = fileRepository.getFile(request);
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
package io.metersphere.sdk.constants;
|
package io.metersphere.sdk.constants;
|
||||||
|
|
||||||
public enum PluginScenarioType {
|
public enum PluginScenarioType {
|
||||||
API, PLATFORM, JDBC_DRIVER
|
/**
|
||||||
|
* 接口协议插件
|
||||||
|
*/
|
||||||
|
API_PROTOCOL,
|
||||||
|
/**
|
||||||
|
* 项目关联平台插件
|
||||||
|
*/
|
||||||
|
PLATFORM,
|
||||||
|
/**
|
||||||
|
* jdbc 驱动插件
|
||||||
|
*/
|
||||||
|
JDBC_DRIVER
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package io.metersphere.sdk.constants;
|
|
||||||
|
|
||||||
public enum StorageConstants {
|
|
||||||
MINIO, GIT, FILE_REF
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.metersphere.sdk.constants;
|
||||||
|
|
||||||
|
public enum StorageType {
|
||||||
|
MINIO, GIT, FILE_REF, LOCAL
|
||||||
|
}
|
|
@ -2,39 +2,30 @@ package io.metersphere.sdk.controller.environment;
|
||||||
|
|
||||||
import io.metersphere.sdk.constants.PermissionConstants;
|
import io.metersphere.sdk.constants.PermissionConstants;
|
||||||
import io.metersphere.sdk.domain.Environment;
|
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.EnvironmentConfigRequest;
|
||||||
import io.metersphere.sdk.dto.environment.dataSource.DataSource;
|
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.service.environment.EnvironmentService;
|
||||||
import io.metersphere.sdk.util.SessionUtils;
|
import io.metersphere.sdk.util.SessionUtils;
|
||||||
import io.metersphere.validation.groups.Created;
|
import io.metersphere.validation.groups.Created;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.sql.Driver;
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(value = "/project/environment")
|
@RequestMapping(value = "/project/environment")
|
||||||
@Tag(name = "项目管理-环境")
|
@Tag(name = "项目管理-环境")
|
||||||
public class EnvironmentController {
|
public class EnvironmentController {
|
||||||
|
|
||||||
@Resource
|
|
||||||
private PluginLoadService pluginLoadService;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private EnvironmentService environmentService;
|
private EnvironmentService environmentService;
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/list/{projectId}")
|
@GetMapping("/list/{projectId}")
|
||||||
@Operation(summary = "项目管理-环境-环境目录-列表")
|
@Operation(summary = "项目管理-环境-环境目录-列表")
|
||||||
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
|
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
|
||||||
|
@ -76,26 +67,13 @@ public class EnvironmentController {
|
||||||
@Operation(summary = "项目管理-环境-数据库配置-校验")
|
@Operation(summary = "项目管理-环境-数据库配置-校验")
|
||||||
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
|
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
|
||||||
public void validate(@RequestBody DataSource databaseConfig) {
|
public void validate(@RequestBody DataSource databaseConfig) {
|
||||||
try {
|
environmentService.validateDataSource(databaseConfig);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/database/driver-options/{organizationId}")
|
@GetMapping("/database/driver-options/{organizationId}")
|
||||||
@Operation(summary = "项目管理-环境-数据库配置-数据库驱动选项")
|
@Operation(summary = "项目管理-环境-数据库配置-数据库驱动选项")
|
||||||
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
|
@RequiresPermissions(PermissionConstants.PROJECT_ENVIRONMENT_READ)
|
||||||
public Map<String, String> driverOptions(@PathVariable String organizationId) {
|
public List<OptionDTO> driverOptions(@PathVariable String organizationId) {
|
||||||
return environmentService.getDriverOptions(organizationId);
|
return environmentService.getDriverOptions(organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package io.metersphere.sdk.dto.environment;
|
package io.metersphere.sdk.dto.environment;
|
||||||
|
|
||||||
|
|
||||||
import io.metersphere.sdk.constants.StorageConstants;
|
import io.metersphere.sdk.constants.StorageType;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
@ -20,6 +20,6 @@ public class BodyFile {
|
||||||
private String refResourceId;
|
private String refResourceId;
|
||||||
|
|
||||||
public boolean isRef() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
package io.metersphere.sdk.file;
|
package io.metersphere.sdk.file;
|
||||||
|
|
||||||
|
import io.metersphere.sdk.constants.StorageType;
|
||||||
import io.metersphere.sdk.util.CommonBeanFactory;
|
import io.metersphere.sdk.util.CommonBeanFactory;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class FileCenter {
|
public class FileCenter {
|
||||||
|
|
||||||
// 多种实现时打开
|
public static FileRepository getRepository(StorageType storageType) {
|
||||||
/*public static FileRepository getRepository(String storage) {
|
Map<StorageType, FileRepository> repositoryMap = new HashMap<>() {{
|
||||||
if (StringUtils.equals(StorageConstants.GIT.name(), storage)) {
|
put(StorageType.MINIO, CommonBeanFactory.getBean(MinioRepository.class));
|
||||||
LogUtils.info("扩展GIT存储方式");
|
put(StorageType.LOCAL, CommonBeanFactory.getBean(LocalFileRepository.class));
|
||||||
return null;
|
}};
|
||||||
} else {
|
FileRepository fileRepository = repositoryMap.get(storageType);
|
||||||
return getDefaultRepository();
|
return fileRepository == null ? getDefaultRepository() : fileRepository;
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
public static FileRepository getDefaultRepository() {
|
public static FileRepository getDefaultRepository() {
|
||||||
return CommonBeanFactory.getBean(MinioRepository.class);
|
return CommonBeanFactory.getBean(MinioRepository.class);
|
||||||
|
|
|
@ -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<String> 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, Set<String>> readClasspathStorages() {
|
||||||
|
LogUtils.debug("Reading extensions storages from classpath");
|
||||||
|
Map<String, Set<String>> result = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
final Set<String> bucket = new HashSet<>();
|
||||||
|
try {
|
||||||
|
Enumeration<URL> 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<String, Set<String>> readPluginsStorages() {
|
||||||
|
LogUtils.debug("Reading extensions storages from plugins");
|
||||||
|
Map<String, Set<String>> result = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
List<PluginWrapper> plugins = pluginManager.getPlugins();
|
||||||
|
for (PluginWrapper plugin : plugins) {
|
||||||
|
String pluginId = plugin.getDescriptor().getPluginId();
|
||||||
|
LogUtils.debug("Reading extensions storages for plugin '{}'", pluginId);
|
||||||
|
final Set<String> bucket = new HashSet<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Enumeration<URL> 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<URL> urls, Set<String> 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<String> 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<String> readExtensions(Path extensionPath) throws IOException {
|
||||||
|
final Set<String> result = new HashSet<>();
|
||||||
|
Files.walkFileTree(extensionPath, Collections.<FileVisitOption>emptySet(), 1, new SimpleFileVisitor<Path>() {
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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<Class> clazzSet = new HashSet<>();
|
|
||||||
/**
|
|
||||||
* 加载重试次数
|
|
||||||
*/
|
|
||||||
protected final static int CLASS_RELOAD_TIME = 20;
|
|
||||||
/**
|
|
||||||
* 保存加载失败的类,之后重试
|
|
||||||
*/
|
|
||||||
protected Map<String, ByteArrayWrapper> loadErrorMap = new HashMap<>();
|
|
||||||
|
|
||||||
private class ByteArrayWrapper {
|
|
||||||
private byte[] values;
|
|
||||||
|
|
||||||
public ByteArrayWrapper(byte[] values) {
|
|
||||||
this.values = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getValues() {
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Class> 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<JarEntry> 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<String> 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<String, PluginClassLoader> classLoaderMap = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 缓存查找过的类
|
|
||||||
* 内层 map
|
|
||||||
* key 未接口的类
|
|
||||||
* value 为实现类
|
|
||||||
*/
|
|
||||||
protected Map<String, Map<Class, Class>> 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<String, PluginClassLoader> 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 <T> Class<T> getImplClass(String pluginId, Class<T> superClazz) {
|
|
||||||
PluginClassLoader classLoader = getPluginClassLoader(pluginId);
|
|
||||||
Map<Class, Class> classes = implClassCache.get(pluginId);
|
|
||||||
if (classes == null) {
|
|
||||||
classes = new HashMap<>();
|
|
||||||
implClassCache.put(pluginId, classes);
|
|
||||||
}
|
|
||||||
if (classes.get(superClazz) != null) {
|
|
||||||
return classes.get(superClazz);
|
|
||||||
}
|
|
||||||
LinkedHashSet<Class<T>> result = new LinkedHashSet<>();
|
|
||||||
Set<Class> 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> T getImplInstance(String pluginId, Class<T> superClazz) {
|
|
||||||
return this.getImplInstance(pluginId, superClazz, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T getImplInstance(String pluginId, Class<T> superClazz, Object param) {
|
|
||||||
try {
|
|
||||||
Class<T> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<String> getFolderFileNames(String dirName) throws Exception {
|
|
||||||
FileRequest request = getFileRequest(dirName);
|
|
||||||
List<String> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<String> getFolderFileNames(String dirName) throws Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除文件
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
void delete() throws Exception;
|
|
||||||
}
|
|
|
@ -1,13 +1,21 @@
|
||||||
package io.metersphere.sdk.service;
|
package io.metersphere.sdk.service;
|
||||||
|
|
||||||
|
import io.metersphere.sdk.constants.PluginScenarioType;
|
||||||
import io.metersphere.sdk.exception.MSException;
|
import io.metersphere.sdk.exception.MSException;
|
||||||
import io.metersphere.system.domain.Plugin;
|
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 io.metersphere.system.mapper.PluginMapper;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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_ENABLE;
|
||||||
import static io.metersphere.sdk.controller.handler.result.CommonResultCode.PLUGIN_PERMISSION;
|
import static io.metersphere.sdk.controller.handler.result.CommonResultCode.PLUGIN_PERMISSION;
|
||||||
|
|
||||||
|
@ -31,4 +39,33 @@ public class BasePluginService {
|
||||||
throw new MSException(PLUGIN_PERMISSION);
|
throw new MSException(PLUGIN_PERMISSION);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Plugin get(String pluginId) {
|
||||||
|
return pluginMapper.selectByPrimaryKey(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Plugin> getOrgEnabledPlugins(String orgId, PluginScenarioType pluginScenarioType) {
|
||||||
|
List<Plugin> plugins = getEnabledPlugins(pluginScenarioType);
|
||||||
|
List<String> unGlobalIds = plugins.stream().filter(i -> !i.getGlobal()).map(Plugin::getId).toList();
|
||||||
|
// 如果没有非全局,直接返回全局插件
|
||||||
|
if (CollectionUtils.isEmpty(unGlobalIds)) {
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
// 查询当前组织下的插件列表
|
||||||
|
List<PluginOrganization> pluginOrganizations = basePluginOrganizationService.getByPluginIds(unGlobalIds);
|
||||||
|
Set<String> 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<Plugin> getEnabledPlugins(PluginScenarioType PluginScenarioType) {
|
||||||
|
PluginExample example = new PluginExample();
|
||||||
|
example.createCriteria()
|
||||||
|
.andEnableEqualTo(true)
|
||||||
|
.andScenarioEqualTo(PluginScenarioType.name());
|
||||||
|
return pluginMapper.selectByExample(example);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
package io.metersphere.sdk.service;
|
package io.metersphere.sdk.service;
|
||||||
|
|
||||||
import io.metersphere.sdk.constants.PluginScenarioType;
|
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.Plugin;
|
||||||
import io.metersphere.system.domain.PluginExample;
|
|
||||||
import io.metersphere.system.mapper.PluginMapper;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.sql.Driver;
|
import java.sql.Driver;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -19,25 +22,71 @@ public class JdbcDriverPluginService {
|
||||||
@Resource
|
@Resource
|
||||||
private PluginLoadService pluginLoadService;
|
private PluginLoadService pluginLoadService;
|
||||||
@Resource
|
@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<String> getJdbcDriverClass(String orgId) {
|
||||||
|
List<Plugin> plugins = basePluginService.getOrgEnabledPlugins(orgId, PluginScenarioType.JDBC_DRIVER);
|
||||||
|
List<Class> drivers = new ArrayList<>(10);
|
||||||
|
for (Plugin plugin : plugins) {
|
||||||
|
drivers.addAll(pluginLoadService.getMsPluginManager().getExtensionClasses(Driver.class, plugin.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> 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
|
* @return
|
||||||
*/
|
*/
|
||||||
public List<String> getJdbcDriverClass() {
|
public List<OptionDTO> getJdbcDriverOption(String orgId) {
|
||||||
PluginExample example = new PluginExample();
|
List<Plugin> plugins = basePluginService.getOrgEnabledPlugins(orgId, PluginScenarioType.JDBC_DRIVER);
|
||||||
example.createCriteria().andScenarioEqualTo(PluginScenarioType.JDBC_DRIVER.name());
|
List<OptionDTO> options = new ArrayList<>();
|
||||||
List<Plugin> plugins = pluginMapper.selectByExample(example);
|
for (Plugin plugin : plugins) {
|
||||||
List<String> pluginDriverClassNames = plugins.stream().map(plugin ->
|
List<Class<? extends Driver>> extensionClasses = pluginLoadService.getMsPluginManager().getExtensionClasses(Driver.class, plugin.getId());
|
||||||
pluginLoadService.getImplClass(plugin.getId(), Driver.class)
|
extensionClasses.forEach(driver -> {
|
||||||
).map(Class::getName).collect(Collectors.toList());
|
options.add(new OptionDTO(plugin.getId() + DRIVER_OPTION_SEPARATOR + driver.getName(), driver.getName()));
|
||||||
// 已经内置了 mysql 依赖
|
});
|
||||||
pluginDriverClassNames.add("com.mysql.jdbc.Driver");
|
}
|
||||||
return pluginDriverClassNames;
|
List<String> 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<Driver> extensions = pluginLoadService.getMsPluginManager().getExtensions(Driver.class, pluginId);
|
||||||
|
return extensions.stream().filter(driver -> StringUtils.equals(driver.getClass().getName(), className))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new MSException("未找到对应的驱动"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,15 @@ package io.metersphere.sdk.service;
|
||||||
import io.metersphere.plugin.platform.api.Platform;
|
import io.metersphere.plugin.platform.api.Platform;
|
||||||
import io.metersphere.plugin.platform.dto.PlatformRequest;
|
import io.metersphere.plugin.platform.dto.PlatformRequest;
|
||||||
import io.metersphere.sdk.constants.PluginScenarioType;
|
import io.metersphere.sdk.constants.PluginScenarioType;
|
||||||
import io.metersphere.system.domain.*;
|
import io.metersphere.system.domain.Plugin;
|
||||||
import io.metersphere.system.mapper.PluginMapper;
|
import io.metersphere.system.domain.ServiceIntegration;
|
||||||
|
import io.metersphere.system.domain.ServiceIntegrationExample;
|
||||||
import io.metersphere.system.mapper.ServiceIntegrationMapper;
|
import io.metersphere.system.mapper.ServiceIntegrationMapper;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
@ -25,10 +22,6 @@ public class PlatformPluginService {
|
||||||
@Resource
|
@Resource
|
||||||
private ServiceIntegrationMapper serviceIntegrationMapper;
|
private ServiceIntegrationMapper serviceIntegrationMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private PluginMapper pluginMapper;
|
|
||||||
@Resource
|
|
||||||
private BasePluginOrganizationService basePluginOrganizationService;
|
|
||||||
@Resource
|
|
||||||
private BasePluginService basePluginService;
|
private BasePluginService basePluginService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,12 +37,10 @@ public class PlatformPluginService {
|
||||||
PlatformRequest pluginRequest = new PlatformRequest();
|
PlatformRequest pluginRequest = new PlatformRequest();
|
||||||
pluginRequest.setIntegrationConfig(integrationConfig);
|
pluginRequest.setIntegrationConfig(integrationConfig);
|
||||||
pluginRequest.setOrganizationId(orgId);
|
pluginRequest.setOrganizationId(orgId);
|
||||||
return pluginLoadService.getImplInstance(pluginId, Platform.class, pluginRequest);
|
return pluginLoadService.getImplInstance(Platform.class, pluginId, pluginRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Platform getPlatform(String pluginId, String orgId) {
|
public Platform getPlatform(String pluginId, String orgId) {
|
||||||
// 这里会校验插件是否存在
|
|
||||||
pluginLoadService.getMsPluginInstance(pluginId);
|
|
||||||
ServiceIntegration serviceIntegration = getServiceIntegrationByPluginId(pluginId);
|
ServiceIntegration serviceIntegration = getServiceIntegrationByPluginId(pluginId);
|
||||||
return getPlatform(pluginId, orgId, new String(serviceIntegration.getConfiguration()));
|
return getPlatform(pluginId, orgId, new String(serviceIntegration.getConfiguration()));
|
||||||
}
|
}
|
||||||
|
@ -60,28 +51,7 @@ public class PlatformPluginService {
|
||||||
return serviceIntegrationMapper.selectByExampleWithBLOBs(example).get(0);
|
return serviceIntegrationMapper.selectByExampleWithBLOBs(example).get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Plugin> getEnabledPlatformPlugins() {
|
|
||||||
PluginExample example = new PluginExample();
|
|
||||||
example.createCriteria()
|
|
||||||
.andEnableEqualTo(true)
|
|
||||||
.andScenarioEqualTo(PluginScenarioType.PLATFORM.name());
|
|
||||||
return pluginMapper.selectByExample(example);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Plugin> getOrgEnabledPlatformPlugins(String orgId) {
|
public List<Plugin> getOrgEnabledPlatformPlugins(String orgId) {
|
||||||
List<Plugin> plugins = getEnabledPlatformPlugins();
|
return basePluginService.getOrgEnabledPlugins(orgId, PluginScenarioType.PLATFORM);
|
||||||
List<String> unGlobalIds = plugins.stream().filter(i -> !i.getGlobal()).map(Plugin::getId).toList();
|
|
||||||
// 如果没有非全局,直接返回全局插件
|
|
||||||
if (CollectionUtils.isEmpty(unGlobalIds)) {
|
|
||||||
return plugins;
|
|
||||||
}
|
|
||||||
// 查询当前组织下的插件列表
|
|
||||||
List<PluginOrganization> pluginOrganizations = basePluginOrganizationService.getByPluginIds(unGlobalIds);
|
|
||||||
Set<String> 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,39 @@
|
||||||
package io.metersphere.sdk.service;
|
package io.metersphere.sdk.service;
|
||||||
|
|
||||||
import io.metersphere.plugin.platform.api.AbstractPlatformPlugin;
|
|
||||||
import io.metersphere.plugin.sdk.api.MsPlugin;
|
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.exception.MSException;
|
||||||
import io.metersphere.sdk.plugin.loader.PluginClassLoader;
|
import io.metersphere.sdk.file.FileCenter;
|
||||||
import io.metersphere.sdk.plugin.loader.PluginManager;
|
import io.metersphere.sdk.file.FileRequest;
|
||||||
import io.metersphere.sdk.plugin.storage.MsStorageStrategy;
|
import io.metersphere.sdk.plugin.MsPluginManager;
|
||||||
import io.metersphere.sdk.plugin.storage.StorageStrategy;
|
|
||||||
import io.metersphere.sdk.util.JSON;
|
import io.metersphere.sdk.util.JSON;
|
||||||
import io.metersphere.sdk.util.LogUtils;
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
|
import io.metersphere.sdk.util.MsFileUtils;
|
||||||
import io.metersphere.system.domain.Plugin;
|
import io.metersphere.system.domain.Plugin;
|
||||||
import io.metersphere.system.domain.PluginExample;
|
import io.metersphere.system.domain.PluginExample;
|
||||||
import io.metersphere.system.domain.PluginScript;
|
import io.metersphere.system.domain.PluginScript;
|
||||||
import io.metersphere.system.mapper.PluginMapper;
|
import io.metersphere.system.mapper.PluginMapper;
|
||||||
import io.metersphere.system.mapper.PluginScriptMapper;
|
import io.metersphere.system.mapper.PluginScriptMapper;
|
||||||
import jakarta.annotation.Resource;
|
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.IOUtil;
|
||||||
import org.codehaus.plexus.util.StringUtils;
|
import org.pf4j.PluginWrapper;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
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.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jianxing
|
* @author jianxing
|
||||||
|
@ -36,44 +42,102 @@ import java.util.stream.Collectors;
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public class PluginLoadService {
|
public class PluginLoadService {
|
||||||
|
|
||||||
private final PluginManager pluginManager = new PluginManager();
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private PluginMapper pluginMapper;
|
private PluginMapper pluginMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private PluginScriptMapper pluginScriptMapper;
|
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 {
|
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) {
|
} catch (Exception e) {
|
||||||
LogUtils.error(e);
|
LogUtils.error(e);
|
||||||
throw new MSException("文件上传异常", 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 返回前端渲染需要的数据
|
* @return 返回前端渲染需要的数据
|
||||||
* 默认会返回 resources下的 script 下的 json 文件
|
* 默认会返回 resources下的 script 下的 json 文件
|
||||||
*/
|
*/
|
||||||
public List<String> getFrontendScripts(String pluginId) {
|
public List<String> getFrontendScripts(String pluginId) {
|
||||||
MsPlugin msPluginInstance = getMsPluginInstance(pluginId);
|
MsPlugin msPluginInstance = (MsPlugin) msPluginManager.getPlugin(pluginId).getPlugin();
|
||||||
String scriptDir = msPluginInstance.getScriptDir();
|
String scriptDir = msPluginInstance.getScriptDir();
|
||||||
StorageStrategy storageStrategy = pluginManager.getClassLoader(pluginId).getStorageStrategy();
|
|
||||||
try {
|
try {
|
||||||
// 查询脚本文件名
|
List<String> scripts = new ArrayList<>(10);
|
||||||
List<String> folderFileNames = storageStrategy.getFolderFileNames(scriptDir);
|
String jarPath = msPluginManager.getPlugin(pluginId).getPluginPath().toString();
|
||||||
// 获取脚本内容
|
JarFile jarFile = new JarFile(jarPath);
|
||||||
List<String> scripts = new ArrayList<>(folderFileNames.size());
|
Enumeration<JarEntry> entries = jarFile.entries();
|
||||||
for (String folderFileName : folderFileNames) {
|
while (entries.hasMoreElements()) {
|
||||||
InputStream in = storageStrategy.get(folderFileName);
|
JarEntry jarEntry = entries.nextElement();
|
||||||
if (in == null) {
|
//获取文件路径
|
||||||
continue;
|
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;
|
return scripts;
|
||||||
} catch (Exception e) {
|
} 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() {
|
public synchronized void loadPlugins() {
|
||||||
List<Plugin> plugins = pluginMapper.selectByExample(new PluginExample());
|
List<Plugin> plugins = pluginMapper.selectByExample(new PluginExample());
|
||||||
plugins.forEach(plugin -> {
|
plugins.forEach(plugin -> {
|
||||||
String id = plugin.getId();
|
String fileName = plugin.getFileName();
|
||||||
StorageStrategy storageStrategy = getStorageStrategy(id);
|
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = storageStrategy.get(plugin.getFileName());
|
loadPlugin(fileName);
|
||||||
loadPlugin(id, inputStream, storageStrategy, false);
|
msPluginManager.startPlugin(plugin.getId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.error("初始化插件异常" + plugin.getFileName(), e);
|
LogUtils.error("初始化插件异常" + plugin.getFileName(), e);
|
||||||
}
|
}
|
||||||
|
@ -163,75 +166,43 @@ public class PluginLoadService {
|
||||||
* 卸载插件
|
* 卸载插件
|
||||||
*/
|
*/
|
||||||
public void unloadPlugin(String pluginId) {
|
public void unloadPlugin(String pluginId) {
|
||||||
pluginManager.deletePlugin(pluginId);
|
if (msPluginManager.getPlugin(pluginId) != null) {
|
||||||
|
msPluginManager.deletePlugin(pluginId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasPlugin(String pluginId) {
|
||||||
public PluginClassLoader getClassLoader(String pluginId) {
|
return msPluginManager.getPlugin(pluginId) != null;
|
||||||
return pluginManager.getClassLoader(pluginId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除插件
|
* 删除插件
|
||||||
*/
|
*/
|
||||||
public void deletePlugin(String pluginId) {
|
public void deletePluginFile(String fileName) {
|
||||||
// 删除文件
|
FileRequest fileRequest = getFileRequest(fileName);
|
||||||
PluginClassLoader classLoader = pluginManager.getClassLoader(pluginId);
|
|
||||||
try {
|
try {
|
||||||
if (classLoader != null) {
|
FileCenter.getRepository(StorageType.LOCAL).delete(fileRequest);
|
||||||
classLoader.getStorageStrategy().delete();
|
FileCenter.getDefaultRepository().delete(fileRequest);
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.error(e);
|
LogUtils.error(e);
|
||||||
throw new MSException("删除插件异常 ", e);
|
|
||||||
}
|
}
|
||||||
unloadPlugin(pluginId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MsPlugin getMsPluginInstance(String id) {
|
/**
|
||||||
return pluginManager.getImplInstance(id, MsPlugin.class);
|
* 删除本地插件
|
||||||
}
|
* @param fileName
|
||||||
|
*/
|
||||||
public <T> T getImplInstance(String pluginId, Class<T> superClazz, Object param) {
|
public void deleteLocalPluginFile(String fileName) {
|
||||||
return pluginManager.getImplInstance(pluginId, superClazz, param);
|
FileRequest fileRequest = getFileRequest(fileName);
|
||||||
}
|
try {
|
||||||
|
FileCenter.getRepository(StorageType.LOCAL).delete(fileRequest);
|
||||||
public <T> T getImplInstance(String pluginId, Class<T> superClazz) {
|
} catch (Exception e) {
|
||||||
return pluginManager.getImplInstance(pluginId, superClazz);
|
LogUtils.error(e);
|
||||||
}
|
|
||||||
|
|
||||||
public List<AbstractPlatformPlugin> getPlatformPluginInstanceList() {
|
|
||||||
return getImplInstanceList(AbstractPlatformPlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AbstractPlatformPlugin getPlatformPluginInstance(String pluginId) {
|
|
||||||
return getImplInstance(pluginId, AbstractPlatformPlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> List<T> getImplInstanceList(Class<T> 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getResourceAsStream(String pluginId, String name) {
|
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) {
|
public Map getPluginScriptConfig(String pluginId, String scriptId) {
|
||||||
|
@ -243,7 +214,66 @@ public class PluginLoadService {
|
||||||
return getPluginScriptConfig(pluginId, scriptId).get("script");
|
return getPluginScriptConfig(pluginId, scriptId).get("script");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Class getImplClass(String pluginId, Class<Driver> driverClass) {
|
public PluginWrapper getPluginWrapper(String id) {
|
||||||
return pluginManager.getImplClass(pluginId, driverClass);
|
return msPluginManager.getPlugin(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件中的是实现类列表
|
||||||
|
* @param clazz
|
||||||
|
* @return
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public <T> List<T> getExtensions(Class<T> clazz) {
|
||||||
|
return msPluginManager.getExtensions(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件中的是实现类
|
||||||
|
* @param clazz
|
||||||
|
* @param pluginId
|
||||||
|
* @return
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public <T> Class<? extends T> getExtensionsClass(Class<T> clazz, String pluginId) {
|
||||||
|
List<Class<? extends T>> classes = msPluginManager.getExtensionClasses(clazz, pluginId);
|
||||||
|
return CollectionUtils.isEmpty(classes) ? null : classes.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MsPluginManager getMsPluginManager() {
|
||||||
|
return msPluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getImplInstance(Class<T> extensionClazz, String pluginId, Object param) {
|
||||||
|
try {
|
||||||
|
Class<? extends T> clazz = getExtensionsClass(extensionClazz, pluginId);
|
||||||
|
if (clazz == null) {
|
||||||
|
throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE);
|
||||||
|
}
|
||||||
|
if (param == null) {
|
||||||
|
return clazz.getConstructor().newInstance();
|
||||||
|
} else {
|
||||||
|
return clazz.getConstructor(param.getClass()).newInstance(param);
|
||||||
|
}
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
LogUtils.error(e.getTargetException());
|
||||||
|
throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE, e.getTargetException().getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.error(e);
|
||||||
|
throw new MSException(CommonResultCode.PLUGIN_GET_INSTANCE, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePluginAddNotified(String pluginId, String fileName) {
|
||||||
|
if (!hasPlugin(pluginId)) {
|
||||||
|
loadPluginFromRepository(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePluginDeleteNotified(String pluginId, String fileName) {
|
||||||
|
if (hasPlugin(pluginId)) {
|
||||||
|
unloadPlugin(pluginId);
|
||||||
|
deleteLocalPluginFile(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +1,66 @@
|
||||||
package io.metersphere.sdk.service.environment;
|
package io.metersphere.sdk.service.environment;
|
||||||
|
|
||||||
import io.metersphere.sdk.constants.PluginScenarioType;
|
|
||||||
import io.metersphere.sdk.domain.Environment;
|
import io.metersphere.sdk.domain.Environment;
|
||||||
import io.metersphere.sdk.domain.EnvironmentBlob;
|
import io.metersphere.sdk.domain.EnvironmentBlob;
|
||||||
import io.metersphere.sdk.domain.EnvironmentBlobExample;
|
import io.metersphere.sdk.domain.EnvironmentBlobExample;
|
||||||
import io.metersphere.sdk.domain.EnvironmentExample;
|
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.EnvironmentConfig;
|
||||||
import io.metersphere.sdk.dto.environment.EnvironmentConfigRequest;
|
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.exception.MSException;
|
||||||
import io.metersphere.sdk.file.FileRequest;
|
import io.metersphere.sdk.file.FileRequest;
|
||||||
import io.metersphere.sdk.file.MinioRepository;
|
import io.metersphere.sdk.file.MinioRepository;
|
||||||
import io.metersphere.sdk.mapper.EnvironmentBlobMapper;
|
import io.metersphere.sdk.mapper.EnvironmentBlobMapper;
|
||||||
import io.metersphere.sdk.mapper.EnvironmentMapper;
|
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.uid.UUID;
|
||||||
import io.metersphere.sdk.util.JSON;
|
import io.metersphere.sdk.util.JSON;
|
||||||
import io.metersphere.sdk.util.LogUtils;
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
import io.metersphere.sdk.util.Translator;
|
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.annotation.Resource;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.sql.Driver;
|
import java.sql.Driver;
|
||||||
|
import java.sql.DriverManager;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Transactional
|
@Transactional
|
||||||
public class EnvironmentService {
|
public class EnvironmentService {
|
||||||
|
|
||||||
@Resource
|
|
||||||
private PluginMapper pluginMapper;
|
|
||||||
@Resource
|
|
||||||
private PluginLoadService pluginLoadService;
|
|
||||||
@Resource
|
|
||||||
private PluginOrganizationMapper pluginOrganizationMapper;
|
|
||||||
@Resource
|
@Resource
|
||||||
private EnvironmentMapper environmentMapper;
|
private EnvironmentMapper environmentMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private EnvironmentBlobMapper environmentBlobMapper;
|
private EnvironmentBlobMapper environmentBlobMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private MinioRepository minioRepository;
|
private MinioRepository minioRepository;
|
||||||
|
@Resource
|
||||||
|
private JdbcDriverPluginService jdbcDriverPluginService;
|
||||||
|
|
||||||
public Map<String, String> getDriverOptions(String organizationId) {
|
public List<OptionDTO> getDriverOptions(String organizationId) {
|
||||||
Map<String, String> pluginDriverClassNames = new HashMap<>();
|
return jdbcDriverPluginService.getJdbcDriverOption(organizationId);
|
||||||
PluginExample example = new PluginExample();
|
}
|
||||||
example.createCriteria().andScenarioEqualTo(PluginScenarioType.JDBC_DRIVER.name()).andEnableEqualTo(true);
|
|
||||||
List<Plugin> plugins = pluginMapper.selectByExample(example);
|
public void validateDataSource(DataSource databaseConfig) {
|
||||||
plugins.forEach(plugin -> {
|
try {
|
||||||
if (BooleanUtils.isTrue(plugin.getGlobal())) {
|
if (StringUtils.isNotBlank(databaseConfig.getDriverId())) {
|
||||||
pluginDriverClassNames.put(plugin.getId(), pluginLoadService.getImplClass(plugin.getId(), Driver.class).getName());
|
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 {
|
} else {
|
||||||
//判断组织id
|
DriverManager.getConnection(databaseConfig.getDbUrl(), databaseConfig.getUsername(), databaseConfig.getPassword());
|
||||||
if (StringUtils.isNotBlank(organizationId)) {
|
|
||||||
//判断组织id是否在插件组织id中
|
|
||||||
PluginOrganizationExample pluginOrganizationExample = new PluginOrganizationExample();
|
|
||||||
pluginOrganizationExample.createCriteria().andPluginIdEqualTo(plugin.getId()).andOrganizationIdEqualTo(organizationId);
|
|
||||||
List<PluginOrganization> pluginOrganizations = pluginOrganizationMapper.selectByExample(pluginOrganizationExample);
|
|
||||||
if (pluginOrganizations.size() > 0) {
|
|
||||||
pluginDriverClassNames.put(plugin.getId(), pluginLoadService.getImplClass(plugin.getId(), Driver.class).getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
} catch (Exception e) {
|
||||||
// 已经内置了 mysql 依赖
|
throw new RuntimeException(e);
|
||||||
pluginDriverClassNames.put(StringUtils.EMPTY, "com.mysql.jdbc.Driver");
|
}
|
||||||
return pluginDriverClassNames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
|
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -194,11 +194,9 @@ public class ProjectApplicationService {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Object getPluginScript(String pluginId) {
|
public Object getPluginScript(String pluginId) {
|
||||||
this.checkResourceExist(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());
|
return pluginLoadService.getPluginScriptContent(pluginId, platformPlugin.getProjectScriptId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -395,7 +395,7 @@ public class ProjectApplicationControllerTests extends BaseTest {
|
||||||
.getPath()
|
.getPath()
|
||||||
);
|
);
|
||||||
FileInputStream inputStream = new FileInputStream(jarFile);
|
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.setName("测试插件1");
|
||||||
request.setGlobal(true);
|
request.setGlobal(true);
|
||||||
request.setEnable(true);
|
request.setEnable(true);
|
||||||
|
|
Binary file not shown.
|
@ -20,7 +20,6 @@ public enum SystemResultCode implements IResultCode {
|
||||||
*/
|
*/
|
||||||
NO_ORG_USER_ROLE_PERMISSION(101007, "organization_user_role_permission_error"),
|
NO_ORG_USER_ROLE_PERMISSION(101007, "organization_user_role_permission_error"),
|
||||||
PLUGIN_EXIST(101008, "plugin.exist"),
|
PLUGIN_EXIST(101008, "plugin.exist"),
|
||||||
PLUGIN_TYPE_EXIST(101009, "plugin.type.exist"),
|
|
||||||
PLUGIN_SCRIPT_EXIST(101010, "plugin.script.exist"),
|
PLUGIN_SCRIPT_EXIST(101010, "plugin.script.exist"),
|
||||||
PLUGIN_SCRIPT_FORMAT(101011, "plugin.script.format"),
|
PLUGIN_SCRIPT_FORMAT(101011, "plugin.script.format"),
|
||||||
NO_PROJECT_USER_ROLE_PERMISSION(101012, "project_user_role_permission_error");
|
NO_PROJECT_USER_ROLE_PERMISSION(101012, "project_user_role_permission_error");
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -4,7 +4,9 @@ package io.metersphere.system.listener;
|
||||||
import io.metersphere.sdk.constants.KafkaPluginTopicType;
|
import io.metersphere.sdk.constants.KafkaPluginTopicType;
|
||||||
import io.metersphere.sdk.constants.KafkaTopicConstants;
|
import io.metersphere.sdk.constants.KafkaTopicConstants;
|
||||||
import io.metersphere.sdk.service.PluginLoadService;
|
import io.metersphere.sdk.service.PluginLoadService;
|
||||||
|
import io.metersphere.sdk.util.JSON;
|
||||||
import io.metersphere.sdk.util.LogUtils;
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
|
import io.metersphere.system.dto.PluginNotifiedDTO;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||||
import org.springframework.kafka.annotation.KafkaListener;
|
import org.springframework.kafka.annotation.KafkaListener;
|
||||||
|
@ -21,17 +23,17 @@ public class PluginListener {
|
||||||
// groupId 必须是每个实例唯一
|
// groupId 必须是每个实例唯一
|
||||||
@KafkaListener(id = PLUGIN_CONSUMER, topics = KafkaTopicConstants.PLUGIN, groupId = PLUGIN_CONSUMER + "_" + "${random.uuid}")
|
@KafkaListener(id = PLUGIN_CONSUMER, topics = KafkaTopicConstants.PLUGIN, groupId = PLUGIN_CONSUMER + "_" + "${random.uuid}")
|
||||||
public void handlePluginChange(ConsumerRecord<?, String> record) {
|
public void handlePluginChange(ConsumerRecord<?, String> record) {
|
||||||
LogUtils.info("Service consume platform_plugin message: " + record);
|
LogUtils.info("Service consume platform_plugin message: " + record.value());
|
||||||
String[] info = record.value().split(":");
|
PluginNotifiedDTO pluginNotifiedDTO = JSON.parseObject(record.value(), PluginNotifiedDTO.class);
|
||||||
String operate = info[0];
|
String operate = pluginNotifiedDTO.getOperate();
|
||||||
String pluginId = info[1];
|
String pluginId = pluginNotifiedDTO.getPluginId();
|
||||||
|
String fileName = pluginNotifiedDTO.getFileName();
|
||||||
switch (operate) {
|
switch (operate) {
|
||||||
case KafkaPluginTopicType.ADD:
|
case KafkaPluginTopicType.ADD:
|
||||||
String pluginName = info[2];
|
pluginLoadService.handlePluginAddNotified(pluginId, fileName);
|
||||||
pluginLoadService.loadPlugin(pluginId, pluginName);
|
|
||||||
break;
|
break;
|
||||||
case KafkaPluginTopicType.DELETE:
|
case KafkaPluginTopicType.DELETE:
|
||||||
pluginLoadService.unloadPlugin(pluginId);
|
pluginLoadService.handlePluginDeleteNotified(pluginId, fileName);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package io.metersphere.system.service;
|
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.plugin.sdk.api.MsPlugin;
|
||||||
import io.metersphere.sdk.constants.KafkaPluginTopicType;
|
import io.metersphere.sdk.constants.KafkaPluginTopicType;
|
||||||
import io.metersphere.sdk.constants.KafkaTopicConstants;
|
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.BaseUserService;
|
||||||
import io.metersphere.sdk.service.JdbcDriverPluginService;
|
import io.metersphere.sdk.service.JdbcDriverPluginService;
|
||||||
import io.metersphere.sdk.service.PluginLoadService;
|
import io.metersphere.sdk.service.PluginLoadService;
|
||||||
import io.metersphere.sdk.uid.UUID;
|
|
||||||
import io.metersphere.sdk.util.BeanUtils;
|
import io.metersphere.sdk.util.BeanUtils;
|
||||||
|
import io.metersphere.sdk.util.JSON;
|
||||||
import io.metersphere.sdk.util.ServiceUtils;
|
import io.metersphere.sdk.util.ServiceUtils;
|
||||||
import io.metersphere.system.domain.Plugin;
|
import io.metersphere.system.domain.Plugin;
|
||||||
import io.metersphere.system.domain.PluginExample;
|
import io.metersphere.system.domain.PluginExample;
|
||||||
import io.metersphere.system.dto.PluginDTO;
|
import io.metersphere.system.dto.PluginDTO;
|
||||||
|
import io.metersphere.system.dto.PluginNotifiedDTO;
|
||||||
import io.metersphere.system.mapper.ExtPluginMapper;
|
import io.metersphere.system.mapper.ExtPluginMapper;
|
||||||
import io.metersphere.system.mapper.PluginMapper;
|
import io.metersphere.system.mapper.PluginMapper;
|
||||||
import io.metersphere.system.request.PluginUpdateRequest;
|
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.collections.CollectionUtils;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.pf4j.PluginDescriptor;
|
||||||
|
import org.pf4j.PluginWrapper;
|
||||||
import org.springframework.kafka.core.KafkaTemplate;
|
import org.springframework.kafka.core.KafkaTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
@ -37,7 +42,6 @@ import java.sql.Driver;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_EXIST;
|
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_EXIST;
|
||||||
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_TYPE_EXIST;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jianxing
|
* @author jianxing
|
||||||
|
@ -58,7 +62,7 @@ public class PluginService {
|
||||||
@Resource
|
@Resource
|
||||||
private PluginLoadService pluginLoadService;
|
private PluginLoadService pluginLoadService;
|
||||||
@Resource
|
@Resource
|
||||||
JdbcDriverPluginService jdbcDriverPluginService;
|
private JdbcDriverPluginService jdbcDriverPluginService;
|
||||||
@Resource
|
@Resource
|
||||||
private KafkaTemplate<String, String> kafkaTemplate;
|
private KafkaTemplate<String, String> kafkaTemplate;
|
||||||
@Resource
|
@Resource
|
||||||
|
@ -90,10 +94,9 @@ public class PluginService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plugin add(PluginUpdateRequest request, MultipartFile file) {
|
public Plugin add(PluginUpdateRequest request, MultipartFile file) {
|
||||||
String id = UUID.randomUUID().toString();
|
String id = null;
|
||||||
Plugin plugin = new Plugin();
|
Plugin plugin = new Plugin();
|
||||||
BeanUtils.copyBean(plugin, request);
|
BeanUtils.copyBean(plugin, request);
|
||||||
plugin.setId(id);
|
|
||||||
plugin.setFileName(file.getOriginalFilename());
|
plugin.setFileName(file.getOriginalFilename());
|
||||||
plugin.setCreateTime(System.currentTimeMillis());
|
plugin.setCreateTime(System.currentTimeMillis());
|
||||||
plugin.setUpdateTime(System.currentTimeMillis());
|
plugin.setUpdateTime(System.currentTimeMillis());
|
||||||
|
@ -105,34 +108,25 @@ public class PluginService {
|
||||||
checkPluginAddExist(plugin);
|
checkPluginAddExist(plugin);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 加载插件
|
// 上传插件到本地文件系统
|
||||||
pluginLoadService.loadPlugin(id, file);
|
pluginLoadService.uploadPlugin2Local(file);
|
||||||
// 上传插件
|
|
||||||
pluginLoadService.uploadPlugin(id, file);
|
|
||||||
|
|
||||||
if (jdbcDriverPluginService.isJdbcDriver(id)) {
|
// 从文件系统中加载插件
|
||||||
Class implClass = pluginLoadService.getImplClass(id, Driver.class);
|
id = pluginLoadService.loadPlugin(file.getOriginalFilename());
|
||||||
// mysql 已经内置了依赖,不允许上传
|
pluginLoadService.getMsPluginManager().startPlugin(id);
|
||||||
if (implClass.getName().startsWith("com.mysql")) {
|
plugin.setId(id);
|
||||||
throw new MSException(PLUGIN_TYPE_EXIST);
|
|
||||||
}
|
List<Driver> extensions = pluginLoadService.getMsPluginManager().getExtensions(Driver.class, id);
|
||||||
plugin.setScenario(PluginScenarioType.JDBC_DRIVER.name());
|
|
||||||
plugin.setXpack(false);
|
if (CollectionUtils.isNotEmpty(extensions)) {
|
||||||
|
plugin = jdbcDriverPluginService.wrapperPlugin(plugin);
|
||||||
plugin.setPluginId(file.getOriginalFilename());
|
plugin.setPluginId(file.getOriginalFilename());
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
plugin = wrapperPlugin(id, plugin);
|
||||||
|
|
||||||
// 非数据库驱动插件,解析脚本和插件信息
|
// 非数据库驱动插件,解析脚本和插件信息
|
||||||
// 获取插件前端配置脚本
|
|
||||||
List<String> frontendScript = pluginLoadService.getFrontendScripts(id);
|
List<String> 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);
|
pluginScriptService.add(id, frontendScript);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,20 +137,32 @@ public class PluginService {
|
||||||
|
|
||||||
pluginMapper.insert(plugin);
|
pluginMapper.insert(plugin);
|
||||||
|
|
||||||
|
// 上传插件到对象存储
|
||||||
|
pluginLoadService.uploadPlugin2Repository(file);
|
||||||
|
|
||||||
// 通知其他节点加载插件
|
// 通知其他节点加载插件
|
||||||
notifiedPluginAdd(id, plugin.getFileName());
|
notifiedPluginAdd(id, plugin.getFileName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 删除插件
|
// 删除插件
|
||||||
pluginLoadService.deletePlugin(id);
|
pluginLoadService.unloadPlugin(id);
|
||||||
|
pluginLoadService.deletePluginFile(file.getOriginalFilename());
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPluginKeyExist(String pluginId, String pluginKey) {
|
public Plugin wrapperPlugin(String id, Plugin plugin) {
|
||||||
if (pluginLoadService.hasPluginKey(pluginId, pluginKey)) {
|
PluginWrapper pluginWrapper = pluginLoadService.getPluginWrapper(id);
|
||||||
throw new MSException(PLUGIN_TYPE_EXIST);
|
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) {
|
public Plugin checkResourceExist(String id) {
|
||||||
|
@ -183,8 +189,15 @@ public class PluginService {
|
||||||
* @param fileName
|
* @param fileName
|
||||||
*/
|
*/
|
||||||
public void notifiedPluginAdd(String pluginId, String fileName) {
|
public void notifiedPluginAdd(String pluginId, String fileName) {
|
||||||
// 初始化项目默认节点
|
notifiedPluginOperate(pluginId, fileName, KafkaPluginTopicType.ADD);
|
||||||
kafkaTemplate.send(KafkaTopicConstants.PLUGIN, String.format("%s:%s:%s", KafkaPluginTopicType.ADD, pluginId, fileName));
|
}
|
||||||
|
|
||||||
|
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
|
* @param pluginId
|
||||||
*/
|
*/
|
||||||
public void notifiedPluginDelete(String pluginId) {
|
public void notifiedPluginDelete(String pluginId, String fileName) {
|
||||||
// 初始化项目默认节点
|
notifiedPluginOperate(pluginId, fileName, KafkaPluginTopicType.DELETE);
|
||||||
kafkaTemplate.send(KafkaTopicConstants.PLUGIN, String.format("%s:%s", KafkaPluginTopicType.DELETE, pluginId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Plugin update(PluginUpdateRequest request) {
|
public Plugin update(PluginUpdateRequest request) {
|
||||||
|
@ -212,7 +224,7 @@ public class PluginService {
|
||||||
request.setOrganizationIds(new ArrayList<>(0));
|
request.setOrganizationIds(new ArrayList<>(0));
|
||||||
}
|
}
|
||||||
pluginOrganizationService.update(plugin.getId(), request.getOrganizationIds());
|
pluginOrganizationService.update(plugin.getId(), request.getOrganizationIds());
|
||||||
return plugin;
|
return pluginMapper.selectByPrimaryKey(request.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPluginUpdateExist(Plugin plugin) {
|
private void checkPluginUpdateExist(Plugin plugin) {
|
||||||
|
@ -230,14 +242,16 @@ public class PluginService {
|
||||||
|
|
||||||
public void delete(String id) {
|
public void delete(String id) {
|
||||||
checkResourceExist(id);
|
checkResourceExist(id);
|
||||||
|
Plugin plugin = pluginMapper.selectByPrimaryKey(id);
|
||||||
pluginMapper.deleteByPrimaryKey(id);
|
pluginMapper.deleteByPrimaryKey(id);
|
||||||
// 删除插件脚本
|
// 删除插件脚本
|
||||||
pluginScriptService.deleteByPluginId(id);
|
pluginScriptService.deleteByPluginId(id);
|
||||||
// 删除和组织的关联关系
|
// 删除和组织的关联关系
|
||||||
pluginOrganizationService.deleteByPluginId(id);
|
pluginOrganizationService.deleteByPluginId(id);
|
||||||
// 删除和卸载插件
|
// 删除和卸载插件
|
||||||
pluginLoadService.deletePlugin(id);
|
pluginLoadService.unloadPlugin(id);
|
||||||
notifiedPluginDelete(id);
|
pluginLoadService.deletePluginFile(plugin.getFileName());
|
||||||
|
notifiedPluginDelete(id, plugin.getFileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getScript(String pluginId, String scriptId) {
|
public String getScript(String pluginId, String scriptId) {
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
package io.metersphere.system.service;
|
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.domain.ServiceIntegration;
|
||||||
import io.metersphere.system.request.ServiceIntegrationUpdateRequest;
|
import io.metersphere.system.request.ServiceIntegrationUpdateRequest;
|
||||||
import io.metersphere.sdk.constants.OperationLogConstants;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
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;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
/**
|
/**
|
||||||
* @author jianxing
|
* @author jianxing
|
||||||
|
@ -22,7 +22,7 @@ public class ServiceIntegrationLogService {
|
||||||
@Resource
|
@Resource
|
||||||
private ServiceIntegrationService serviceIntegrationService;
|
private ServiceIntegrationService serviceIntegrationService;
|
||||||
@Resource
|
@Resource
|
||||||
private PluginLoadService pluginLoadService;
|
private BasePluginService basePluginService;
|
||||||
|
|
||||||
public LogDTO addLog(ServiceIntegrationUpdateRequest request) {
|
public LogDTO addLog(ServiceIntegrationUpdateRequest request) {
|
||||||
LogDTO dto = new LogDTO(
|
LogDTO dto = new LogDTO(
|
||||||
|
@ -38,7 +38,7 @@ public class ServiceIntegrationLogService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getName(String pluginId) {
|
private String getName(String pluginId) {
|
||||||
return pluginLoadService.getPlatformPluginInstance(pluginId).getName();
|
return basePluginService.get(pluginId).getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogDTO updateLog(ServiceIntegrationUpdateRequest request) {
|
public LogDTO updateLog(ServiceIntegrationUpdateRequest request) {
|
||||||
|
|
|
@ -55,14 +55,14 @@ public class ServiceIntegrationService {
|
||||||
|
|
||||||
List<Plugin> plugins = platformPluginService.getOrgEnabledPlatformPlugins(organizationId);
|
List<Plugin> plugins = platformPluginService.getOrgEnabledPlatformPlugins(organizationId);
|
||||||
return plugins.stream().map(plugin -> {
|
return plugins.stream().map(plugin -> {
|
||||||
AbstractPlatformPlugin msPluginInstance = pluginLoadService.getPlatformPluginInstance(plugin.getId());
|
AbstractPlatformPlugin msPlugin = (AbstractPlatformPlugin) pluginLoadService.getPluginWrapper(plugin.getId()).getPlugin();
|
||||||
// 获取插件基础信息
|
// 获取插件基础信息
|
||||||
ServiceIntegrationDTO serviceIntegrationDTO = new ServiceIntegrationDTO();
|
ServiceIntegrationDTO serviceIntegrationDTO = new ServiceIntegrationDTO();
|
||||||
serviceIntegrationDTO.setTitle(msPluginInstance.getName());
|
serviceIntegrationDTO.setTitle(msPlugin.getName());
|
||||||
serviceIntegrationDTO.setEnable(false);
|
serviceIntegrationDTO.setEnable(false);
|
||||||
serviceIntegrationDTO.setConfig(false);
|
serviceIntegrationDTO.setConfig(false);
|
||||||
serviceIntegrationDTO.setDescription(msPluginInstance.getDescription());
|
serviceIntegrationDTO.setDescription(msPlugin.getDescription());
|
||||||
serviceIntegrationDTO.setLogo(String.format(PLUGIN_IMAGE_GET_PATH, plugin.getId(), msPluginInstance.getLogo()));
|
serviceIntegrationDTO.setLogo(String.format(PLUGIN_IMAGE_GET_PATH, plugin.getId(), msPlugin.getLogo()));
|
||||||
serviceIntegrationDTO.setPluginId(plugin.getId());
|
serviceIntegrationDTO.setPluginId(plugin.getId());
|
||||||
ServiceIntegration serviceIntegration = serviceIntegrationMap.get(plugin.getId());
|
ServiceIntegration serviceIntegration = serviceIntegrationMap.get(plugin.getId());
|
||||||
if (serviceIntegration != null) {
|
if (serviceIntegration != null) {
|
||||||
|
@ -149,7 +149,7 @@ public class ServiceIntegrationService {
|
||||||
|
|
||||||
public Object getPluginScript(String pluginId) {
|
public Object getPluginScript(String pluginId) {
|
||||||
pluginService.checkResourceExist(pluginId);
|
pluginService.checkResourceExist(pluginId);
|
||||||
AbstractPlatformPlugin platformPlugin = pluginLoadService.getImplInstance(pluginId, AbstractPlatformPlugin.class);
|
AbstractPlatformPlugin platformPlugin = (AbstractPlatformPlugin) pluginLoadService.getPluginWrapper(pluginId).getPlugin();
|
||||||
return pluginLoadService.getPluginScriptContent(pluginId, platformPlugin.getIntegrationScriptId());
|
return pluginLoadService.getPluginScriptContent(pluginId, platformPlugin.getIntegrationScriptId());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -100,7 +100,7 @@ public class PluginControllerTests extends BaseTest {
|
||||||
Assertions.assertEquals(plugin.getGlobal(), request.getGlobal());
|
Assertions.assertEquals(plugin.getGlobal(), request.getGlobal());
|
||||||
Assertions.assertEquals(plugin.getXpack(), false);
|
Assertions.assertEquals(plugin.getXpack(), false);
|
||||||
Assertions.assertEquals(plugin.getFileName(), jarFile.getName());
|
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(new ArrayList<>(0), getOrgIdsByPlugId(plugin.getId()));
|
||||||
Assertions.assertEquals(Arrays.asList("connect", "disconnect", "pub", "sub"), getScriptIdsByPlugId(plugin.getId()));
|
Assertions.assertEquals(Arrays.asList("connect", "disconnect", "pub", "sub"), getScriptIdsByPlugId(plugin.getId()));
|
||||||
addPlugin = plugin;
|
addPlugin = plugin;
|
||||||
|
@ -135,7 +135,7 @@ public class PluginControllerTests extends BaseTest {
|
||||||
);
|
);
|
||||||
this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
|
this.requestMultipartWithOkAndReturn(DEFAULT_ADD,
|
||||||
getDefaultMultiPartParam(request, myDriver));
|
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,
|
assertErrorCode(this.requestMultipart(DEFAULT_ADD,
|
||||||
getDefaultMultiPartParam(request, jarFile)), PLUGIN_EXIST);
|
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(
|
File scriptParseFile = new File(
|
||||||
|
@ -296,10 +281,10 @@ public class PluginControllerTests extends BaseTest {
|
||||||
@Order(5)
|
@Order(5)
|
||||||
public void getPluginImg() throws Exception {
|
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());
|
.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
|
@Test
|
||||||
|
|
|
@ -200,7 +200,7 @@ public class ServiceIntegrationControllerTests extends BaseTest {
|
||||||
serviceIntegrationDTO.getConfiguration());
|
serviceIntegrationDTO.getConfiguration());
|
||||||
Assertions.assertEquals(serviceIntegration.getEnable(), serviceIntegrationDTO.getEnable());
|
Assertions.assertEquals(serviceIntegration.getEnable(), serviceIntegrationDTO.getEnable());
|
||||||
Assertions.assertEquals(serviceIntegration.getPluginId(), serviceIntegrationDTO.getPluginId());
|
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.getDescription(), msPluginInstance.getDescription());
|
||||||
Assertions.assertEquals(serviceIntegrationDTO.getOrganizationId(), defaultOrg.getId());
|
Assertions.assertEquals(serviceIntegrationDTO.getOrganizationId(), defaultOrg.getId());
|
||||||
Assertions.assertEquals(serviceIntegrationDTO.getTitle(), msPluginInstance.getName());
|
Assertions.assertEquals(serviceIntegrationDTO.getTitle(), msPluginInstance.getName());
|
||||||
|
@ -333,7 +333,7 @@ public class ServiceIntegrationControllerTests extends BaseTest {
|
||||||
.getPath()
|
.getPath()
|
||||||
);
|
);
|
||||||
FileInputStream inputStream = new FileInputStream(jarFile);
|
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.setName("测试插件");
|
||||||
request.setGlobal(true);
|
request.setGlobal(true);
|
||||||
request.setEnable(true);
|
request.setEnable(true);
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -209,7 +209,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'system.plugin.interfaceTest',
|
label: 'system.plugin.interfaceTest',
|
||||||
value: 'API',
|
value: 'API_PROTOCOL',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'system.plugin.projectManagement',
|
label: 'system.plugin.projectManagement',
|
||||||
|
@ -280,7 +280,7 @@
|
||||||
|
|
||||||
function getScenarioType(scenario: string) {
|
function getScenarioType(scenario: string) {
|
||||||
switch (scenario) {
|
switch (scenario) {
|
||||||
case 'API':
|
case 'API_PROTOCOL':
|
||||||
return t('system.plugin.interfaceTest');
|
return t('system.plugin.interfaceTest');
|
||||||
case 'JDBC_DRIVER':
|
case 'JDBC_DRIVER':
|
||||||
return t('system.plugin.databaseDriver');
|
return t('system.plugin.databaseDriver');
|
||||||
|
|
1
pom.xml
1
pom.xml
|
@ -93,6 +93,7 @@
|
||||||
<skipAntRunForJenkins>false</skipAntRunForJenkins>
|
<skipAntRunForJenkins>false</skipAntRunForJenkins>
|
||||||
<commons-dbcp2-version>2.9.0</commons-dbcp2-version>
|
<commons-dbcp2-version>2.9.0</commons-dbcp2-version>
|
||||||
<jacoco.version>0.8.10</jacoco.version>
|
<jacoco.version>0.8.10</jacoco.version>
|
||||||
|
<pf4j.version>3.10.0</pf4j.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
|
Loading…
Reference in New Issue