diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/pom.xml b/backend/framework/plugin/metersphere-platform-plugin-sdk/pom.xml
index 1ac4dd98f4..c178185fff 100644
--- a/backend/framework/plugin/metersphere-platform-plugin-sdk/pom.xml
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/pom.xml
@@ -18,6 +18,20 @@
metersphere-plugin-sdk
${revision}
+
+
+
+ org.springframework
+ spring-web
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+
+
+ commons-codec
+ commons-codec
+
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatform.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatform.java
new file mode 100644
index 0000000000..d62cef52a0
--- /dev/null
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatform.java
@@ -0,0 +1,25 @@
+package io.metersphere.plugin.platform.api;
+
+import io.metersphere.plugin.platform.dto.PlatformRequest;
+import io.metersphere.plugin.sdk.util.JSON;
+import io.metersphere.plugin.sdk.util.MSPluginException;
+import org.apache.commons.lang3.StringUtils;
+
+public abstract class AbstractPlatform implements Platform {
+ protected PlatformRequest request;
+
+ public AbstractPlatform(PlatformRequest request) {
+ this.request = request;
+ }
+
+ public T getIntegrationConfig(String integrationConfig, Class clazz) {
+ if (StringUtils.isBlank(integrationConfig)) {
+ throw new MSPluginException("服务集成配置为空");
+ }
+ return JSON.parseObject(integrationConfig, clazz);
+ }
+
+ public String getPluginId() {
+ return request.getPluginId();
+ }
+}
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatformPlugin.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatformPlugin.java
index dcead1c8d8..9dfa0c244b 100644
--- a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatformPlugin.java
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/AbstractPlatformPlugin.java
@@ -3,9 +3,48 @@ package io.metersphere.plugin.platform.api;
import io.metersphere.plugin.sdk.api.AbstractMsPlugin;
public abstract class AbstractPlatformPlugin extends AbstractMsPlugin {
- private static final String PLATFORM_PLUGIN_TYPE = "PLATFORM";
+ private static final String DEFAULT_PLATFORM_PLUGIN_TYPE = "PLATFORM";
+ private static final String DEFAULT_INTEGRATION_SCRIPT_ID = "integration";
+ private static final String DEFAULT_PROJECT_SCRIPT_ID = "project";
+ private static final String DEFAULT_ACCOUNT_SCRIPT_ID = "account";
@Override
public String getType() {
- return PLATFORM_PLUGIN_TYPE;
+ return DEFAULT_PLATFORM_PLUGIN_TYPE;
+ }
+
+ /**
+ * 返回插件的描述信息
+ * @return
+ */
+ public abstract String getDescription();
+
+ /**
+ * 返回插件的logo路径,比如:/static/jira.png
+ * @return
+ */
+ public abstract String getLogo();
+
+ /**
+ * 返回服务集成脚本的ID,默认为 integration
+ * @return
+ */
+ public String getIntegrationScriptId() {
+ return DEFAULT_INTEGRATION_SCRIPT_ID;
+ }
+
+ /**
+ * 返回项目配置脚本的ID,默认为 project
+ * @return
+ */
+ public String getProjectScriptId() {
+ return DEFAULT_PROJECT_SCRIPT_ID;
+ }
+
+ /**
+ * 返回个人账号脚本的ID,默认为 account
+ * @return
+ */
+ public String getAccountScriptId() {
+ return DEFAULT_ACCOUNT_SCRIPT_ID;
}
}
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/BaseClient.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/BaseClient.java
new file mode 100644
index 0000000000..a1d652bddd
--- /dev/null
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/BaseClient.java
@@ -0,0 +1,108 @@
+package io.metersphere.plugin.platform.api;
+
+import io.metersphere.plugin.platform.utils.EnvProxySelector;
+import io.metersphere.plugin.platform.utils.PluginCodingUtils;
+import io.metersphere.plugin.sdk.util.JSON;
+import io.metersphere.plugin.sdk.util.MSPluginException;
+import io.metersphere.plugin.sdk.util.PluginLogUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
+import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
+import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
+import org.apache.hc.core5.ssl.SSLContexts;
+import org.apache.hc.core5.ssl.TrustStrategy;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+import javax.net.ssl.SSLContext;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+public abstract class BaseClient {
+
+ protected RestTemplate restTemplate;
+
+ public BaseClient() {
+ try {
+ TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
+
+ SSLContext sslContext = SSLContexts.custom()
+ .loadTrustMaterial(null, acceptingTrustStrategy)
+ .build();
+
+ SSLConnectionSocketFactory csf = SSLConnectionSocketFactoryBuilder
+ .create()
+ .setSslContext(sslContext)
+ .build();
+
+ CloseableHttpClient httpClient = HttpClients.custom()
+ // 可以支持设置系统代理
+ .setRoutePlanner(new SystemDefaultRoutePlanner(new EnvProxySelector()))
+ // 忽略 https
+ .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
+ .setSSLSocketFactory(csf)
+ .build())
+ .build();
+
+ HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
+ requestFactory.setHttpClient(httpClient);
+
+ restTemplate = new RestTemplate(requestFactory);
+ } catch (Exception e) {
+ PluginLogUtils.error(e);
+ }
+ }
+
+ protected HttpHeaders getBasicHttpHeaders(String userName, String passWd) {
+ String authKey = PluginCodingUtils.base64Encoding(userName + ":" + passWd);
+ HttpHeaders headers = new HttpHeaders();
+ headers.setBasicAuth(authKey);
+ headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
+ return headers;
+ }
+
+ protected HttpHeaders getBearHttpHeaders(String token) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setBearerAuth(token);
+ headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
+ return headers;
+ }
+
+ protected String getResult(ResponseEntity response) {
+ int statusCodeValue = response.getStatusCodeValue();
+ PluginLogUtils.info("responseCode: " + statusCodeValue);
+ if (statusCodeValue >= 400) {
+ throw new MSPluginException(response.getBody());
+ }
+ PluginLogUtils.info("result: " + response.getBody());
+ return response.getBody();
+ }
+
+ protected Object getResultForList(Class clazz, ResponseEntity response) {
+ return Arrays.asList(JSON.parseArray(getResult(response), clazz).toArray());
+ }
+
+ protected Object getResultForObject(Class clazz, ResponseEntity response) {
+ return JSON.parseObject(getResult(response), clazz);
+ }
+
+ public void validateProxyUrl(String url, String... path) {
+ try {
+ if (!StringUtils.containsAny(new URI(url).getPath(), path)) {
+ // 只允许访问图片
+ throw new MSPluginException("illegal path");
+ }
+ } catch (URISyntaxException e) {
+ PluginLogUtils.error(e);
+ throw new MSPluginException("illegal path");
+ }
+ }
+}
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/Platform.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/Platform.java
index c6dde0a4e5..c570791df1 100644
--- a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/Platform.java
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/api/Platform.java
@@ -1,147 +1,14 @@
package io.metersphere.plugin.platform.api;
-import io.metersphere.plugin.platform.dto.*;
-
-import java.util.List;
-
/**
- * 平台对接相关业务
+ * 平台对接相关业务接口
* @author jianxing.chen
*/
public interface Platform {
- /**
- * 获取平台相关需求
- * 功能用例关联需求时调用
- * @param projectConfig 项目设置表单值
- * @return 需求列表
- */
- List getDemands(String projectConfig);
-
- /**
- * 创建缺陷并封装 MS 返回
- * 创建缺陷时调用
- * @param bugsRequest bugRequest
- * @return MS 缺陷
- */
- MsBugDTO addBug(PlatformBugUpdateRequest bugsRequest);
-
- /**
- * 项目设置和缺陷表单中,调用接口获取下拉框选项
- * 配置文件的表单中选项值配置了 optionMethod ,则调用获取表单的选项值
- * @return 返回下拉列表
- */
- List getFormOptions(GetOptionRequest request);
-
- /**
- * 更新缺陷
- * 编辑缺陷时调用
- * @param request
- * @return MS 缺陷
- */
- MsBugDTO updateBug(PlatformBugUpdateRequest request);
-
- /**
- * 删除缺陷平台缺陷
- * 删除缺陷时调用
- * @param id 平台的缺陷 ID
- */
- void deleteBug(String id);
-
/**
* 校验服务集成配置
* 服务集成点击校验时调用
*/
void validateIntegrationConfig();
-
- /**
- * 校验项目配置
- * 项目设置成点击校验项目 key 时调用
- */
- void validateProjectConfig(String projectConfig);
-
- /**
- * 校验用户配置配置
- * 用户信息,校验第三方信息时调用
- */
- void validateUserConfig(String userConfig);
-
- /**
- * 支持附件上传
- * 编辑缺陷上传附件是会调用判断是否支持附件上传
- * 如果支持会调用 syncBugsAttachment 上传缺陷到第三方平台
- */
- boolean isAttachmentUploadSupport();
-
- /**
- * 同步缺陷最新变更
- * 开源用户点击同步缺陷时调用
- */
- SyncBugResult syncBugs(SyncBugRequest request);
-
- /**
- * 同步项目下所有的缺陷
- * 企业版用户会调用同步缺陷
- */
- void syncAllBugs(SyncAllBugRequest request);
-
- /**
- * 获取附件内容
- * 同步缺陷中,同步附件时会调用
- * @param fileKey 文件关键字
- */
- byte[] getAttachmentContent(String fileKey);
-
- /**
- * 获取第三方平台缺陷的自定义字段
- * 需要 PluginMetaInfo 的 isThirdPartTemplateSupport 返回 true
- * @return
- */
- List getThirdPartCustomField(String projectConfig);
-
- /**
- * Get请求的代理
- * 目前使用场景:富文本框中如果有图片是存储在第三方平台,MS 通过 url 访问
- * 这时如果第三方平台需要登入才能访问到静态资源,可以通过将富文本框图片内容构造如下格式访问
- * ![name](/resource/md/get/path?platform=Jira?project_id=&organization_id=&path=)
- * @param path
- * @return
- */
- Object proxyForGet(String path, Class responseEntityClazz);
-
- /**
- * 同步 MS 缺陷附件到第三方平台
- * isAttachmentUploadSupport 返回为 true 时,同步和创建缺陷时会调用
- */
- void syncBugsAttachment(SyncBugAttachmentRequest request);
-
- /**
- * 获取第三方平台的状态列表
- * 缺陷列表和编辑缺陷时会调用
- * @return
- */
- List getStatusList(String projectConfig);
-
- /**
- * 获取第三方平台的状态转移列表
- * 即编辑缺陷时的可选状态
- * 默认会调用 getStatusList,可重写覆盖
- * @param bugId
- * @return
- */
- List getTransitions(String projectConfig, String bugId);
-
- /**
- * 用例关联需求时调用
- * 可在第三方平台添加用例和需求的关联关系
- * @param request
- */
- void handleDemandUpdate(DemandUpdateRequest request);
-
- /**
- * 用例批量关联需求时调用
- * 可在第三方平台添加用例和需求的关联关系
- * @param request
- */
- void handleDemandUpdateBatch(DemandUpdateRequest request);
}
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/dto/PlatformRequest.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/dto/PlatformRequest.java
index bebd25c9be..1ec77aaddf 100644
--- a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/dto/PlatformRequest.java
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/dto/PlatformRequest.java
@@ -8,5 +8,6 @@ import lombok.Setter;
public class PlatformRequest {
private String integrationConfig;
private String organizationId;
- private String userPlatformInfo;
+ private String userAccountConfig;
+ private String pluginId;
}
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/EnvProxySelector.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/EnvProxySelector.java
new file mode 100644
index 0000000000..8d3c75b132
--- /dev/null
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/EnvProxySelector.java
@@ -0,0 +1,45 @@
+package io.metersphere.plugin.platform.utils;
+
+import io.metersphere.plugin.sdk.util.PluginLogUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.Arrays;
+import java.util.List;
+
+public class EnvProxySelector extends ProxySelector {
+ ProxySelector defaultProxySelector = ProxySelector.getDefault();
+
+ @Override
+ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+ PluginLogUtils.error("connectFailed : " + uri);
+ }
+
+ /**
+ * 获取环境变量http代理配置,
+ *
+ * @param uri
+ * @return
+ */
+ @Override
+ public List select(URI uri) {
+ String httpProxy = System.getenv("http_proxy");
+ String httpsProxy = System.getenv("https_proxy");
+ URI proxy = null;
+ try {
+ if (StringUtils.equalsIgnoreCase(uri.getScheme(), "https")
+ && StringUtils.isNotBlank(httpsProxy)) {
+ proxy = new URI(httpsProxy);
+ } else if (StringUtils.isNotBlank(httpProxy)) {
+ proxy = new URI(httpProxy);
+ }
+ if (proxy != null) {
+ return Arrays.asList(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxy.getHost(), proxy.getPort())));
+ }
+ } catch (URISyntaxException e) {
+ PluginLogUtils.error(e);
+ }
+ return defaultProxySelector.select(uri);
+ }
+}
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/PluginBeanUtils.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/PluginBeanUtils.java
new file mode 100644
index 0000000000..8d65a4710f
--- /dev/null
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/PluginBeanUtils.java
@@ -0,0 +1,69 @@
+package io.metersphere.plugin.platform.utils;
+
+import io.metersphere.plugin.sdk.util.PluginLogUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Method;
+
+public class PluginBeanUtils {
+
+ public static T copyBean(T target, Object source) {
+ try {
+ org.springframework.beans.BeanUtils.copyProperties(source, target);
+ return target;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to copy object: ", e);
+ }
+ }
+
+ public static T copyBean(T target, Object source, String... ignoreProperties) {
+ try {
+ org.springframework.beans.BeanUtils.copyProperties(source, target, ignoreProperties);
+ return target;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to copy object: ", e);
+ }
+ }
+
+ public static Object getFieldValueByName(String fieldName, Object bean) {
+ try {
+ if (StringUtils.isBlank(fieldName)) {
+ return null;
+ }
+ String firstLetter = fieldName.substring(0, 1).toUpperCase();
+ String getter = "get" + firstLetter + fieldName.substring(1);
+ Method method = bean.getClass().getMethod(getter);
+ return method.invoke(bean);
+ } catch (Exception e) {
+ PluginLogUtils.error("failed to getFieldValueByName. ", e);
+ return null;
+ }
+ }
+
+ public static void setFieldValueByName(Object bean, String fieldName, Object value, Class> type) {
+ try {
+ if (StringUtils.isBlank(fieldName)) {
+ return;
+ }
+ String firstLetter = fieldName.substring(0, 1).toUpperCase();
+ String setter = "set" + firstLetter + fieldName.substring(1);
+ Method method = bean.getClass().getMethod(setter, type);
+ method.invoke(bean, value);
+ } catch (Exception e) {
+ PluginLogUtils.error("failed to setFieldValueByName. ", e);
+ }
+ }
+
+ public static Method getMethod(Object bean, String fieldName, Class> type) {
+ try {
+ if (StringUtils.isBlank(fieldName)) {
+ return null;
+ }
+ String firstLetter = fieldName.substring(0, 1).toUpperCase();
+ String setter = "set" + firstLetter + fieldName.substring(1);
+ return bean.getClass().getMethod(setter, type);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/PluginCodingUtils.java b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/PluginCodingUtils.java
new file mode 100644
index 0000000000..1a1758a47d
--- /dev/null
+++ b/backend/framework/plugin/metersphere-platform-plugin-sdk/src/main/java/io/metersphere/plugin/platform/utils/PluginCodingUtils.java
@@ -0,0 +1,168 @@
+package io.metersphere.plugin.platform.utils;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.crypto.*;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+/**
+ * 加密解密工具
+ *
+ * @author kun.mo
+ */
+public class PluginCodingUtils {
+
+ private static final String UTF_8 = StandardCharsets.UTF_8.name();
+
+ private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ /**
+ * MD5加密
+ *
+ * @param src 要加密的串
+ * @return 加密后的字符串
+ */
+ public static String md5(String src) {
+ return md5(src, UTF_8);
+ }
+
+ /**
+ * MD5加密
+ *
+ * @param src 要加密的串
+ * @param charset 加密字符集
+ * @return 加密后的字符串
+ */
+ public static String md5(String src, String charset) {
+ try {
+ byte[] strTemp = StringUtils.isEmpty(charset) ? src.getBytes() : src.getBytes(charset);
+ MessageDigest mdTemp = MessageDigest.getInstance("MD5");
+ mdTemp.update(strTemp);
+
+ byte[] md = mdTemp.digest();
+ int j = md.length;
+ char[] str = new char[j * 2];
+ int k = 0;
+
+ for (byte byte0 : md) {
+ str[k++] = HEX_DIGITS[byte0 >>> 4 & 0xf];
+ str[k++] = HEX_DIGITS[byte0 & 0xf];
+ }
+
+ return new String(str);
+ } catch (Exception e) {
+ throw new RuntimeException("MD5 encrypt error:", e);
+ }
+ }
+
+ /**
+ * BASE64解密
+ *
+ * @param src 待解密的字符串
+ * @return 解密后的字符串
+ */
+ public static String base64Decoding(String src) {
+ byte[] b;
+ String result = null;
+ if (src != null) {
+ try {
+ b = Base64.decodeBase64(src);
+ result = new String(b, UTF_8);
+ } catch (Exception e) {
+ throw new RuntimeException("BASE64 decoding error:", e);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * BASE64加密
+ *
+ * @param src 待加密的字符串
+ * @return 加密后的字符串
+ */
+ public static String base64Encoding(String src) {
+ String result = null;
+ if (src != null) {
+ try {
+ result = Base64.encodeBase64String(src.getBytes(UTF_8));
+ } catch (Exception e) {
+ throw new RuntimeException("BASE64 encoding error:", e);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * AES加密
+ *
+ * @param src 待加密字符串
+ * @param secretKey 密钥
+ * @param iv 向量
+ * @return 加密后字符串
+ */
+ public static String aesEncrypt(String src, String secretKey, String iv) {
+ if (StringUtils.isBlank(secretKey)) {
+ throw new RuntimeException("secretKey is empty");
+ }
+
+ try {
+ byte[] raw = secretKey.getBytes(UTF_8);
+ SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
+ // "算法/模式/补码方式" ECB
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ IvParameterSpec iv1 = new IvParameterSpec(iv.getBytes());
+ cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv1);
+ byte[] encrypted = cipher.doFinal(src.getBytes(UTF_8));
+ return Base64.encodeBase64String(encrypted);
+ } catch (Exception e) {
+ throw new RuntimeException("AES encrypt error:", e);
+ }
+
+ }
+
+ /**
+ * AES 解密
+ *
+ * @param src 待解密字符串
+ * @param secretKey 密钥
+ * @param iv 向量
+ * @return 解密后字符串
+ */
+ public static String aesDecrypt(String src, String secretKey, String iv) {
+ if (StringUtils.isBlank(secretKey)) {
+ throw new RuntimeException("secretKey is empty");
+ }
+ try {
+ byte[] raw = secretKey.getBytes(UTF_8);
+ SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ IvParameterSpec iv1 = new IvParameterSpec(iv.getBytes());
+ cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv1);
+ byte[] encrypted1 = Base64.decodeBase64(src);
+ byte[] original = cipher.doFinal(encrypted1);
+ return new String(original, UTF_8);
+ } catch (BadPaddingException | IllegalBlockSizeException e) {
+ // 解密的原字符串为非加密字符串,则直接返回原字符串
+ return src;
+ } catch (Exception e) {
+ throw new RuntimeException("decrypt error,please check parameters", e);
+ }
+ }
+
+ public static String secretKey() {
+ try {
+ KeyGenerator keyGen = KeyGenerator.getInstance("AES");
+ keyGen.init(128);
+ SecretKey secretKey = keyGen.generateKey();
+ return Base64.encodeBase64String(secretKey.getEncoded());
+ } catch (Exception e) {
+ throw new RuntimeException("generate secretKey error", e);
+ }
+
+ }
+}
diff --git a/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/util/MSPluginException.java b/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/util/MSPluginException.java
new file mode 100644
index 0000000000..746979737c
--- /dev/null
+++ b/backend/framework/plugin/metersphere-plugin-sdk/src/main/java/io/metersphere/plugin/sdk/util/MSPluginException.java
@@ -0,0 +1,23 @@
+package io.metersphere.plugin.sdk.util;
+
+/**
+ * 插件异常类
+ * @author jianxing
+ */
+public class MSPluginException extends RuntimeException {
+ public MSPluginException() {
+ super();
+ }
+
+ public MSPluginException(String message) {
+ super(message);
+ }
+
+ public MSPluginException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public MSPluginException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/backend/framework/plugin/pom.xml b/backend/framework/plugin/pom.xml
index dd574d75a5..7d910b64d5 100644
--- a/backend/framework/plugin/pom.xml
+++ b/backend/framework/plugin/pom.xml
@@ -8,7 +8,6 @@
io.metersphere
framework
${revision}
-
io.metersphere
@@ -25,29 +24,4 @@
metersphere-api-plugin-sdk
metersphere-platform-plugin-sdk
-
-
-
- org.apache.jmeter
- ApacheJMeter_core
- ${jmeter.version}
-
-
- org.apache.jmeter
- jorphan
- ${jmeter.version}
-
-
- org.projectlombok
- lombok
-
-
- org.apache.commons
- commons-lang3
-
-
- com.fasterxml.jackson.core
- jackson-annotations
-
-
\ No newline at end of file
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/RestControllerExceptionHandler.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/RestControllerExceptionHandler.java
index 4f57231fdb..883079f6ce 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/RestControllerExceptionHandler.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/RestControllerExceptionHandler.java
@@ -3,6 +3,7 @@ package io.metersphere.sdk.controller.handler;
import io.metersphere.sdk.controller.handler.result.IResultCode;
import io.metersphere.sdk.controller.handler.result.MsHttpResultCode;
import io.metersphere.sdk.exception.MSException;
+import io.metersphere.sdk.util.Translator;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.shiro.ShiroException;
@@ -76,11 +77,11 @@ public class RestControllerExceptionHandler {
if (errorCode instanceof MsHttpResultCode) {
// 如果是 MsHttpResultCode,则设置响应的状态码,取状态码的后三位
return ResponseEntity.status(errorCode.getCode() % 1000)
- .body(ResultHolder.error(errorCode.getCode(), errorCode.getMessage()));
+ .body(ResultHolder.error(errorCode.getCode(), Translator.get(errorCode.getMessage(), errorCode.getMessage())));
} else {
// 响应码返回 500,设置业务状态码
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
- .body(ResultHolder.error(errorCode.getCode(), errorCode.getMessage(), e.getMessage()));
+ .body(ResultHolder.error(errorCode.getCode(), Translator.get(errorCode.getMessage(), errorCode.getMessage()), e.getMessage()));
}
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/result/CommonResultCode.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/result/CommonResultCode.java
index 94c4f99968..1cfcbbfea7 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/result/CommonResultCode.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/controller/handler/result/CommonResultCode.java
@@ -13,7 +13,10 @@ public enum CommonResultCode implements IResultCode {
* 调用获取全局用户组接口,如果操作的是内置的用户组,会返回该响应码
*/
INTERNAL_USER_ROLE_PERMISSION(101003, "internal_user_role_permission_error"),
- USER_ROLE_RELATION_REMOVE_ADMIN_USER_PERMISSION(100004, "user_role_relation_remove_admin_user_permission_error");
+ USER_ROLE_RELATION_REMOVE_ADMIN_USER_PERMISSION(100004, "user_role_relation_remove_admin_user_permission_error"),
+ FILE_NAME_ILLEGAL(100005, "file_name_illegal_error"),
+ PLUGIN_ENABLE(100006, "plugin_enable_error"),
+ PLUGIN_PERMISSION(100007, "plugin_permission_error");
private int code;
private String message;
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginManager.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginManager.java
index 7bd388c59b..5a8d2cc454 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginManager.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/plugin/loader/PluginManager.java
@@ -9,10 +9,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
/**
* @author jianxing.chen
@@ -24,6 +21,14 @@ public class PluginManager {
*/
protected Map classLoaderMap = new HashMap<>();
+ /**
+ * 缓存查找过的类
+ * 内层 map
+ * key 未接口的类
+ * value 为实现类
+ */
+ protected Map> implClassCache = new HashMap<>();
+
public PluginClassLoader getClassLoader(String pluginId) {
return classLoaderMap.get(pluginId);
}
@@ -48,6 +53,7 @@ public class PluginManager {
public void deletePlugin(String id) {
classLoaderMap.remove(id);
+ implClassCache.remove(id);
}
/**
@@ -70,40 +76,52 @@ public class PluginManager {
* 获取接口的单一实现类
*/
public Class getImplClass(String pluginId, Class superClazz) {
- PluginClassLoader classLoader = classLoaderMap.get(pluginId);
+ PluginClassLoader classLoader = getPluginClassLoader(pluginId);
+ Map classes = implClassCache.get(pluginId);
+ if (classes == null) {
+ classes = new HashMap<>();
+ implClassCache.put(pluginId, classes);
+ }
+ if (classes.get(superClazz) != null) {
+ return classes.get(superClazz);
+ }
LinkedHashSet> result = new LinkedHashSet<>();
Set clazzSet = classLoader.getClazzSet();
for (Class item : clazzSet) {
if (isImplClazz(superClazz, item) && !result.contains(item)) {
+ classes.put(superClazz, item);
return item;
}
}
return null;
}
+ private PluginClassLoader getPluginClassLoader(String pluginId) {
+ PluginClassLoader classLoader = classLoaderMap.get(pluginId);
+ if (classLoader == null) {
+ throw new MSException("插件未加载");
+ }
+ return classLoader;
+ }
+
/**
* 获取指定接口最后一次加载的实现类实例
*/
public T getImplInstance(String pluginId, Class superClazz) {
- try {
- Class clazz = getImplClass(pluginId, superClazz);
- if (clazz == null) {
- throw new MSException("未找到插件实现类");
- }
- return clazz.getConstructor().newInstance();
- } catch (InvocationTargetException e) {
- LogUtils.error(e);
- 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());
- }
+ return this.getImplInstance(pluginId, superClazz, null);
}
public T getImplInstance(String pluginId, Class superClazz, Object param) {
try {
Class clazz = getImplClass(pluginId, superClazz);
- return clazz.getConstructor(param.getClass()).newInstance(param);
+ 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());
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PlatformPluginService.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PlatformPluginService.java
new file mode 100644
index 0000000000..dfcfcd4834
--- /dev/null
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PlatformPluginService.java
@@ -0,0 +1,108 @@
+package io.metersphere.sdk.service;
+
+import io.metersphere.plugin.platform.api.Platform;
+import io.metersphere.plugin.platform.dto.PlatformRequest;
+import io.metersphere.sdk.constants.PluginScenarioType;
+import io.metersphere.sdk.exception.MSException;
+import io.metersphere.system.domain.*;
+import io.metersphere.system.mapper.PluginMapper;
+import io.metersphere.system.mapper.PluginOrganizationMapper;
+import io.metersphere.system.mapper.ServiceIntegrationMapper;
+import jakarta.annotation.Resource;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+import static io.metersphere.sdk.controller.handler.result.CommonResultCode.PLUGIN_ENABLE;
+import static io.metersphere.sdk.controller.handler.result.CommonResultCode.PLUGIN_PERMISSION;
+
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class PlatformPluginService {
+
+ @Resource
+ private PluginLoadService pluginLoadService;
+ @Resource
+ private ServiceIntegrationMapper serviceIntegrationMapper;
+ @Resource
+ private PluginMapper pluginMapper;
+ @Resource
+ private PluginOrganizationMapper pluginOrganizationMapper;
+
+ /**
+ * 获取平台实例
+ *
+ * @param pluginId
+ * @param orgId
+ * @param integrationConfig
+ * @return
+ */
+ public Platform getPlatform(String pluginId, String orgId, String integrationConfig) {
+ if (StringUtils.isNotBlank(orgId)) {
+ // 服务集成的测试链接,不需要校验插件开启和状态
+ Plugin plugin = pluginMapper.selectByPrimaryKey(pluginId);
+ checkPluginEnable(plugin);
+ checkPluginPermission(pluginId, orgId, plugin);
+ }
+ PlatformRequest pluginRequest = new PlatformRequest();
+ pluginRequest.setIntegrationConfig(integrationConfig);
+ pluginRequest.setOrganizationId(orgId);
+ return pluginLoadService.getImplInstance(pluginId, Platform.class, pluginRequest);
+ }
+
+ public Platform getPlatform(String pluginId, String orgId) {
+ // 这里会校验插件是否存在
+ pluginLoadService.getMsPluginInstance(pluginId);
+ ServiceIntegration serviceIntegration = getServiceIntegrationByPluginId(pluginId);
+ return getPlatform(pluginId, orgId, new String(serviceIntegration.getConfiguration()));
+ }
+
+ /**
+ * 校验该组织是否能访问插件
+ * @param pluginId
+ * @param orgId
+ * @param plugin
+ */
+ private void checkPluginPermission(String pluginId, String orgId, Plugin plugin) {
+ if (plugin.getGlobal()) {
+ return;
+ }
+ PluginOrganizationExample example = new PluginOrganizationExample();
+ example.createCriteria()
+ .andOrganizationIdEqualTo(orgId)
+ .andPluginIdEqualTo(pluginId);
+ List pluginOrganizations = pluginOrganizationMapper.selectByExample(example);
+ for (PluginOrganization pluginOrganization : pluginOrganizations) {
+ if (StringUtils.equals(pluginOrganization.getOrganizationId(), orgId)) {
+ return;
+ }
+ }
+ throw new MSException(PLUGIN_PERMISSION);
+ }
+
+ /**
+ * 校验插件是否启用
+ * @param plugin
+ */
+ private static void checkPluginEnable(Plugin plugin) {
+ if (BooleanUtils.isFalse(plugin.getEnable())) {
+ throw new MSException(PLUGIN_ENABLE);
+ }
+ }
+
+ private ServiceIntegration getServiceIntegrationByPluginId(String pluginId) {
+ ServiceIntegrationExample example = new ServiceIntegrationExample();
+ example.createCriteria().andPluginIdEqualTo(pluginId);
+ return serviceIntegrationMapper.selectByExampleWithBLOBs(example).get(0);
+ }
+
+ public List getPlatformPlugins() {
+ PluginExample example = new PluginExample();
+ example.createCriteria()
+ .andScenarioEqualTo(PluginScenarioType.PLATFORM.name());
+ return pluginMapper.selectByExample(example);
+ }
+}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PluginLoadService.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PluginLoadService.java
index 4ddd2cb06c..3fed1d8694 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PluginLoadService.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/service/PluginLoadService.java
@@ -1,15 +1,19 @@
package io.metersphere.sdk.service;
+import io.metersphere.plugin.platform.api.AbstractPlatformPlugin;
import io.metersphere.plugin.sdk.api.MsPlugin;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.plugin.loader.PluginClassLoader;
import io.metersphere.sdk.plugin.loader.PluginManager;
import io.metersphere.sdk.plugin.storage.MsStorageStrategy;
import io.metersphere.sdk.plugin.storage.StorageStrategy;
+import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.domain.Plugin;
import io.metersphere.system.domain.PluginExample;
+import io.metersphere.system.domain.PluginScript;
import io.metersphere.system.mapper.PluginMapper;
+import io.metersphere.system.mapper.PluginScriptMapper;
import jakarta.annotation.Resource;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
@@ -21,6 +25,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* @author jianxing
@@ -33,6 +39,8 @@ public class PluginLoadService {
@Resource
private PluginMapper pluginMapper;
+ @Resource
+ private PluginScriptMapper pluginScriptMapper;
/**
* 上传插件到 minio
@@ -178,6 +186,28 @@ public class PluginLoadService {
return pluginManager.getImplInstance(id, MsPlugin.class);
}
+ public T getImplInstance(String pluginId, Class superClazz, Object param) {
+ return pluginManager.getImplInstance(pluginId, superClazz, param);
+ }
+
+ public T getImplInstance(String pluginId, Class superClazz) {
+ return pluginManager.getImplInstance(pluginId, superClazz);
+ }
+
+ public List getPlatformPluginInstanceList() {
+ return getImplInstanceList(AbstractPlatformPlugin.class);
+ }
+
+ public AbstractPlatformPlugin getPlatformPluginInstance(String pluginId) {
+ return getImplInstance(pluginId, AbstractPlatformPlugin.class);
+ }
+
+ public List getImplInstanceList(Class clazz, Object... initArgs) {
+ return pluginManager.getClassLoaderMap().keySet().stream()
+ .map(pluginId -> pluginManager.getImplInstance(pluginId, clazz, initArgs)
+ ).collect(Collectors.toList());
+ }
+
public boolean hasPluginKey(String currentPluginId, String pluginKey) {
for (String pluginId : pluginManager.getClassLoaderMap().keySet()) {
MsPlugin msPlugin = getMsPluginInstance(pluginId);
@@ -187,4 +217,17 @@ public class PluginLoadService {
}
return false;
}
+
+ public InputStream getResourceAsStream(String pluginId, String name) {
+ return pluginManager.getClassLoaderMap().get(pluginId).getResourceAsStream(name);
+ }
+
+ public Map getPluginScriptConfig(String pluginId, String scriptId) {
+ PluginScript pluginScript = pluginScriptMapper.selectByPrimaryKey(pluginId, scriptId);
+ return JSON.parseMap(new String(pluginScript.getScript()));
+ }
+
+ public Object getPluginScriptContent(String pluginId, String scriptId) {
+ return getPluginScriptConfig(pluginId, scriptId).get("script");
+ }
}
diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java
index a5108bb443..5848c232cc 100644
--- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java
+++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/util/FilterChainUtils.java
@@ -55,7 +55,7 @@ public class FilterChainUtils {
filterChainDefinitionMap.put("/websocket/**", "csrf");
// 获取插件中的图片
- filterChainDefinitionMap.put("/platform/plugin/image/**", "anon");
+ filterChainDefinitionMap.put("/plugin/image/**", "anon");
return filterChainDefinitionMap;
}
diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties
index 12fa5b393d..42d84c033b 100644
--- a/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties
+++ b/backend/framework/sdk/src/main/resources/i18n/commons_en_US.properties
@@ -427,4 +427,8 @@ permission.add=Create
permission.edit=Update
permission.delete=Delete
permission.import=Import
-permission.recover=Recover
\ No newline at end of file
+permission.recover=Recover
+
+file_name_illegal_error=File name is invalid
+plugin_enable_error=Plugin is not enabled
+plugin_permission_error=No access to this plugin
\ No newline at end of file
diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties
index 52cde17e47..b5920f3158 100644
--- a/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties
+++ b/backend/framework/sdk/src/main/resources/i18n/commons_zh_CN.properties
@@ -426,4 +426,8 @@ permission.add=创建
permission.edit=修改
permission.delete=删除
permission.import=导入
-permission.recover=恢复
\ No newline at end of file
+permission.recover=恢复
+
+file_name_illegal_error=文件名不合法
+plugin_enable_error=插件未启用
+plugin_permission_error=没有该插件的访问权限
\ No newline at end of file
diff --git a/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties
index ddf132b0dd..1f71ba53a1 100644
--- a/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties
+++ b/backend/framework/sdk/src/main/resources/i18n/commons_zh_TW.properties
@@ -426,4 +426,8 @@ permission.add=創建
permission.edit=修改
permission.delete=刪除
permission.import=導入
-permission.recover=恢復
\ No newline at end of file
+permission.recover=恢復
+
+file_name_illegal_error=文件名不合法
+plugin_enable_error=插件未啟用
+plugin_permission_error=沒有該插件的訪問權限
\ No newline at end of file
diff --git a/backend/framework/sdk/src/main/resources/i18n/system_en_US.properties b/backend/framework/sdk/src/main/resources/i18n/system_en_US.properties
index 94e6ac80ff..119978b66f 100644
--- a/backend/framework/sdk/src/main/resources/i18n/system_en_US.properties
+++ b/backend/framework/sdk/src/main/resources/i18n/system_en_US.properties
@@ -175,6 +175,7 @@ service_integration.plugin_id.length_range=pluginId length must be between {min}
service_integration.organization_id.not_blank=organizationId cannot be empty
service_integration.organization_id.length_range=organizationId length must be between {min} and {max}
service_integration_exist_error=Service integration configuration already exists
+service_integration.configuration.not_blank=Service integration configuration cannot be empty
# permission
permission.system_plugin.name=Plugin
permission.system_organization_project.name=Organization Project
diff --git a/backend/framework/sdk/src/main/resources/i18n/system_zh_CN.properties b/backend/framework/sdk/src/main/resources/i18n/system_zh_CN.properties
index 3f6ea74d05..4453f4406a 100644
--- a/backend/framework/sdk/src/main/resources/i18n/system_zh_CN.properties
+++ b/backend/framework/sdk/src/main/resources/i18n/system_zh_CN.properties
@@ -160,20 +160,21 @@ plugin.file_name.not_blank=文件名不能为空
plugin.file_name.length_range=文件名长度必须在{min}和{max}之间
plugin.create_user.not_blank=创建人不能为空
plugin.create_user.length_range=创建人长度必须在{min}和{max}之间
-plugin.scenario.not_blank=插件使用场景PAI/PLATFORM不能为空
-plugin.scenario.length_range=插件使用场景PAI/PLATFORM长度必须在{min}和{max}之间
+plugin.scenario.not_blank=插件使用场景API/PLATFORM不能为空
+plugin.scenario.length_range=插件使用场景API/PLATFORM长度必须在{min}和{max}之间
plugin.exist=插件名称或文件名已存在
plugin.type.exist=插件类型已存在
plugin.script.exist=脚本id重复
plugin.script.format=脚本格式错误
# serviceIntegration
-service_integration.id.not_blank=ID不能為空
-service_integration.id.length_range=ID長度必須在{min}和{max}之间
-service_integration.plugin_id.not_blank=插件的ID不能為空
-service_integration.plugin_id.length_range=插件的ID長度必須在{min}和{max}之间
-service_integration.organization_id.not_blank=组织ID不能為空
-service_integration.organization_id.length_range=组织ID長度必須在{min}和{max}之间
+service_integration.id.not_blank=ID不能为空
+service_integration.id.length_range=ID长度必须在{min}和{max}之间
+service_integration.plugin_id.not_blank=插件的ID不能为空
+service_integration.plugin_id.length_range=插件的ID长度必须在{min}和{max}之间
+service_integration.organization_id.not_blank=组织ID不能为空
+service_integration.organization_id.length_range=组织ID长度必须在{min}和{max}之间
service_integration_exist_error=服务集成配置已存在
+service_integration.configuration.not_blank=服务集成配置不能為空
# permission
permission.system_plugin.name=插件
permission.system_organization_project.name=组织与项目
diff --git a/backend/framework/sdk/src/main/resources/i18n/system_zh_TW.properties b/backend/framework/sdk/src/main/resources/i18n/system_zh_TW.properties
index 58f439753b..b688ca669e 100644
--- a/backend/framework/sdk/src/main/resources/i18n/system_zh_TW.properties
+++ b/backend/framework/sdk/src/main/resources/i18n/system_zh_TW.properties
@@ -174,6 +174,7 @@ service_integration.plugin_id.length_range=插件的ID长度必须在{min}和{ma
service_integration.organization_id.not_blank=组织ID不能为空
service_integration.organization_id.length_range=组织ID长度必须在{min}和{max}之间
service_integration_exist_error=服務集成配置已存在
+service_integration.configuration.not_blank=服务集成配置不能為空
# permission
permission.system_plugin.name=插件
permission.system_organization_project.name=組織與項目
diff --git a/backend/framework/sdk/src/test/java/io/metersphere/sdk/base/BaseTest.java b/backend/framework/sdk/src/test/java/io/metersphere/sdk/base/BaseTest.java
index 8754f46f30..a19b4b6be8 100644
--- a/backend/framework/sdk/src/test/java/io/metersphere/sdk/base/BaseTest.java
+++ b/backend/framework/sdk/src/test/java/io/metersphere/sdk/base/BaseTest.java
@@ -40,6 +40,8 @@ import org.springframework.util.MultiValueMap;
import java.io.File;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.List;
@@ -232,20 +234,24 @@ public abstract class BaseTest {
}
protected T getResultData(MvcResult mvcResult, Class clazz) throws Exception {
- Object data = JSON.parseMap(mvcResult.getResponse().getContentAsString()).get("data");
+ Object data = parseResponse(mvcResult).get("data");
return JSON.parseObject(JSON.toJSONString(data), clazz);
}
protected T getResultMessageDetail(MvcResult mvcResult, Class clazz) throws Exception {
- Object data = JSON.parseMap(mvcResult.getResponse().getContentAsString()).get("messageDetail");
+ Object data = parseResponse(mvcResult).get("messageDetail");
return JSON.parseObject(JSON.toJSONString(data), clazz);
}
protected List getResultDataArray(MvcResult mvcResult, Class clazz) throws Exception {
- Object data = JSON.parseMap(mvcResult.getResponse().getContentAsString()).get("data");
+ Object data = parseResponse(mvcResult).get("data");
return JSON.parseArray(JSON.toJSONString(data), clazz);
}
+ private static Map parseResponse(MvcResult mvcResult) throws UnsupportedEncodingException {
+ return JSON.parseMap(mvcResult.getResponse().getContentAsString(Charset.defaultCharset()));
+ }
+
/**
* 校验错误响应码
*/
@@ -258,7 +264,7 @@ public abstract class BaseTest {
}
protected Pager> getPageResult(MvcResult mvcResult, Class clazz) throws Exception {
- Map pagerResult = (Map) JSON.parseMap(mvcResult.getResponse().getContentAsString()).get("data");
+ Map pagerResult = (Map) parseResponse(mvcResult).get("data");
List list = JSON.parseArray(JSON.toJSONString(pagerResult.get("list")), clazz);
Pager pager = new Pager();
pager.setPageSize(Long.valueOf(pagerResult.get("pageSize").toString()));
diff --git a/backend/framework/sdk/src/test/java/io/metersphere/sdk/base/param/NotEmptyParamGenerator.java b/backend/framework/sdk/src/test/java/io/metersphere/sdk/base/param/NotEmptyParamGenerator.java
index 1ffc1b1bd2..6fb01f3dec 100644
--- a/backend/framework/sdk/src/test/java/io/metersphere/sdk/base/param/NotEmptyParamGenerator.java
+++ b/backend/framework/sdk/src/test/java/io/metersphere/sdk/base/param/NotEmptyParamGenerator.java
@@ -2,7 +2,7 @@ package io.metersphere.sdk.base.param;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
-import java.util.ArrayList;
+import java.util.*;
/**
* @author jianxing
@@ -14,6 +14,15 @@ public class NotEmptyParamGenerator extends ParamGenerator {
*/
@Override
public Object invalidGenerate(Annotation annotation, Field field) {
- return new ArrayList<>(0);
+ if (field.getType().equals(List.class)) {
+ return new ArrayList<>(0);
+ }
+ if (field.getType().equals(Set.class)) {
+ return new HashSet<>(0);
+ }
+ if (field.getType().equals(Map.class)) {
+ return new HashMap<>(0);
+ }
+ throw new RuntimeException("不支持该类型");
}
}
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/PluginController.java b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/PluginController.java
index a8d75649cd..80b75b9150 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/PluginController.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/PluginController.java
@@ -22,6 +22,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import java.io.IOException;
import java.util.List;
/**
@@ -86,7 +87,7 @@ public class PluginController {
@Schema(description = "图片路径", requiredMode = Schema.RequiredMode.REQUIRED)
@RequestParam("imagePath")
String imagePath,
- HttpServletResponse response) {
- // todo
+ HttpServletResponse response) throws IOException {
+ pluginService.getPluginImg(pluginId, imagePath, response);
}
}
\ No newline at end of file
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/ServiceIntegrationController.java b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/ServiceIntegrationController.java
index cdccdc98a0..3e30f8d5d4 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/controller/ServiceIntegrationController.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/controller/ServiceIntegrationController.java
@@ -14,12 +14,13 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
+import jakarta.validation.constraints.NotEmpty;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
+import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* @author : jianxing
@@ -32,6 +33,7 @@ public class ServiceIntegrationController {
@Resource
private ServiceIntegrationService serviceIntegrationService;
+
@GetMapping("/list/{organizationId}")
@Operation(summary = "获取服务集成列表")
@RequiresPermissions(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_READ)
@@ -59,24 +61,27 @@ public class ServiceIntegrationController {
@Operation(summary = "删除服务集成")
@RequiresPermissions(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_DELETE)
@Log(type = OperationLogType.DELETE, expression = "#msClass.deleteLog(#id)", msClass = ServiceIntegrationLogService.class)
- public String delete(@PathVariable String id) {
- return serviceIntegrationService.delete(id);
+ public void delete(@PathVariable String id) {
+ serviceIntegrationService.delete(id);
}
- @PostMapping("/validate")
+ @PostMapping("/validate/{pluginId}")
@Operation(summary = "校验服务集成信息")
@RequiresPermissions(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_UPDATE)
- public boolean validate(@Validated({Updated.class}) @RequestBody
- @Schema(description = "配置的表单键值对", requiredMode = Schema.RequiredMode.REQUIRED)
- Map serviceIntegrationInfo) {
- return serviceIntegrationService.validate(serviceIntegrationInfo);
+ public void validate(@PathVariable String pluginId,
+ @Validated({Updated.class})
+ @RequestBody
+ @NotEmpty
+ @Schema(description = "配置的表单键值对", requiredMode = Schema.RequiredMode.REQUIRED)
+ HashMap serviceIntegrationInfo) {
+ serviceIntegrationService.validate(pluginId, serviceIntegrationInfo);
}
@GetMapping("/validate/{id}")
@Operation(summary = "校验服务集成信息")
@RequiresPermissions(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_UPDATE)
- public boolean validate(@PathVariable String id) {
- return serviceIntegrationService.validate(id);
+ public void validate(@PathVariable String id) {
+ serviceIntegrationService.validate(id);
}
@GetMapping("/script/{pluginId}")
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/ServiceIntegrationDTO.java b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/ServiceIntegrationDTO.java
index 8328f2786a..471887d44e 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/dto/ServiceIntegrationDTO.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/dto/ServiceIntegrationDTO.java
@@ -20,7 +20,7 @@ public class ServiceIntegrationDTO implements Serializable {
private String title;
@Schema(description = "插件描述")
- private String describe;
+ private String description;
@Schema(description = "是否启用")
private Boolean enable;
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/request/PluginUpdateRequest.java b/backend/services/system-setting/src/main/java/io/metersphere/system/request/PluginUpdateRequest.java
index 3dd9f5ba7b..38c1c8e9ae 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/request/PluginUpdateRequest.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/request/PluginUpdateRequest.java
@@ -28,7 +28,7 @@ public class PluginUpdateRequest {
private Boolean global;
@Schema(description = "插件描述")
- @Size(min = 1, max = 500, message = "{plugin.scenario.length_range}", groups = {Created.class, Updated.class})
+ @Size(max = 500, message = "{plugin.scenario.length_range}", groups = {Created.class, Updated.class})
private String description;
@Schema(hidden = true)
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/request/ServiceIntegrationUpdateRequest.java b/backend/services/system-setting/src/main/java/io/metersphere/system/request/ServiceIntegrationUpdateRequest.java
index af5f32c05b..58728b1af8 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/request/ServiceIntegrationUpdateRequest.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/request/ServiceIntegrationUpdateRequest.java
@@ -1,12 +1,14 @@
package io.metersphere.system.request;
+
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;
+
import java.io.Serializable;
import java.util.Map;
@@ -17,7 +19,7 @@ public class ServiceIntegrationUpdateRequest implements Serializable {
@Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{service_integration.id.not_blank}", groups = {Updated.class})
- @Size(min = 1, max = 50, message = "{service_integration.id.length_range}", groups = {Created.class, Updated.class})
+ @Size(min = 1, max = 50, message = "{service_integration.id.length_range}", groups = {Updated.class})
private String id;
@Schema(description = "插件的ID", requiredMode = Schema.RequiredMode.REQUIRED)
@@ -34,6 +36,6 @@ public class ServiceIntegrationUpdateRequest implements Serializable {
private String organizationId;
@Schema(description = "配置的表单键值对", requiredMode = Schema.RequiredMode.REQUIRED)
- @NotNull(message = "{service_integration.configuration.not_blank}", groups = {Created.class})
- private Map configuration;
+ @NotEmpty(message = "{service_integration.configuration.not_blank}", groups = {Created.class})
+ private Map configuration;
}
\ No newline at end of file
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java
index d2aa37c46c..0f5f4c96e2 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/PluginService.java
@@ -4,6 +4,7 @@ package io.metersphere.system.service;
import io.metersphere.plugin.sdk.api.MsPlugin;
import io.metersphere.sdk.constants.KafkaPluginTopicType;
import io.metersphere.sdk.constants.KafkaTopicConstants;
+import io.metersphere.sdk.controller.handler.result.CommonResultCode;
import io.metersphere.sdk.dto.OptionDTO;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.service.PluginLoadService;
@@ -15,6 +16,7 @@ import io.metersphere.system.mapper.ExtPluginMapper;
import io.metersphere.system.mapper.PluginMapper;
import io.metersphere.system.request.PluginUpdateRequest;
import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@@ -23,10 +25,10 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_EXIST;
import static io.metersphere.system.controller.result.SystemResultCode.PLUGIN_TYPE_EXIST;
@@ -138,8 +140,9 @@ public class PluginService {
}
/**
- * 通知其他节点加载插件
- * 这里需要传一下 fileName,事务未提交,查询不到文件名
+ * 通知其他节点加载插件
+ * 这里需要传一下 fileName,事务未提交,查询不到文件名
+ *
* @param pluginId
* @param fileName
*/
@@ -149,7 +152,8 @@ public class PluginService {
}
/**
- * 通知其他节点卸载插件
+ * 通知其他节点卸载插件
+ *
* @param pluginId
*/
public void notifiedPluginDelete(String pluginId) {
@@ -201,4 +205,34 @@ public class PluginService {
public String getScript(String pluginId, String scriptId) {
return pluginScriptService.get(pluginId, scriptId);
}
+
+ public void getPluginImg(String pluginId, String filePath, HttpServletResponse response) throws IOException {
+ validateImageFileName(filePath);
+ InputStream inputStream = pluginLoadService.getResourceAsStream(pluginId, filePath);
+ writeImage(filePath, inputStream, response);
+ }
+
+ public void validateImageFileName(String filename) {
+ Set imgSuffix = new HashSet<>() {{
+ add("jpg");
+ add("png");
+ add("gif");
+ add("jpeg");
+ }};
+ if (!imgSuffix.contains(StringUtils.substringAfterLast(filename, "."))) {
+ throw new MSException(CommonResultCode.FILE_NAME_ILLEGAL);
+ }
+ }
+
+ public void writeImage(String filePath, InputStream in, HttpServletResponse response) throws IOException {
+ response.setContentType("image/" + StringUtils.substringAfterLast(filePath, "."));
+ try (OutputStream out = response.getOutputStream()) {
+ byte[] buffer = new byte[1024];
+ int bytesRead;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ response.getOutputStream().write(buffer, 0, bytesRead);
+ }
+ out.flush();
+ }
+ }
}
\ No newline at end of file
diff --git a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java
index 13d83b956b..b5e2ba418e 100644
--- a/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java
+++ b/backend/services/system-setting/src/main/java/io/metersphere/system/service/ServiceIntegrationService.java
@@ -1,8 +1,13 @@
package io.metersphere.system.service;
+import io.metersphere.plugin.platform.api.AbstractPlatformPlugin;
+import io.metersphere.plugin.platform.api.Platform;
import io.metersphere.sdk.exception.MSException;
+import io.metersphere.sdk.service.PlatformPluginService;
+import io.metersphere.sdk.service.PluginLoadService;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.JSON;
+import io.metersphere.system.domain.Plugin;
import io.metersphere.system.domain.ServiceIntegration;
import io.metersphere.system.domain.ServiceIntegrationExample;
import io.metersphere.system.dto.ServiceIntegrationDTO;
@@ -10,13 +15,14 @@ import io.metersphere.system.mapper.ServiceIntegrationMapper;
import io.metersphere.system.request.ServiceIntegrationUpdateRequest;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.stream.Collectors;
import static io.metersphere.system.controller.result.SystemResultCode.SERVICE_INTEGRATION_EXIST;
@@ -27,12 +33,48 @@ import static io.metersphere.system.controller.result.SystemResultCode.SERVICE_I
@Service
@Transactional(rollbackFor = Exception.class)
public class ServiceIntegrationService {
-
@Resource
private ServiceIntegrationMapper serviceIntegrationMapper;
+ @Resource
+ private PluginLoadService pluginLoadService;
+ @Resource
+ private PlatformPluginService platformPluginService;
+
+ public static final String PLUGIN_IMAGE_GET_PATH = "/plugin/image/%s?imagePath=%s";
public List list(String organizationId) {
- return Arrays.asList(new ServiceIntegrationDTO());
+ // 查询服务集成已配置数据
+ Map serviceIntegrationMap = getServiceIntegrationByOrgId(organizationId).stream()
+ .collect(Collectors.toMap(ServiceIntegration::getPluginId, i -> i));
+
+ List plugins = platformPluginService.getPlatformPlugins();
+ return plugins.stream().map(plugin -> {
+ AbstractPlatformPlugin msPluginInstance = pluginLoadService.getPlatformPluginInstance(plugin.getId());
+ // 获取插件基础信息
+ ServiceIntegrationDTO serviceIntegrationDTO = new ServiceIntegrationDTO();
+ serviceIntegrationDTO.setTitle(msPluginInstance.getName());
+ serviceIntegrationDTO.setEnable(false);
+ serviceIntegrationDTO.setConfig(false);
+ serviceIntegrationDTO.setDescription(msPluginInstance.getDescription());
+ serviceIntegrationDTO.setLogo(String.format(PLUGIN_IMAGE_GET_PATH, plugin.getId(), msPluginInstance.getLogo()));
+ serviceIntegrationDTO.setPluginId(plugin.getId());
+ ServiceIntegration serviceIntegration = serviceIntegrationMap.get(plugin.getId());
+ if (serviceIntegration != null) {
+ serviceIntegrationDTO.setConfiguration(JSON.parseMap(new String(serviceIntegration.getConfiguration())));
+ serviceIntegrationDTO.setId(serviceIntegration.getId());
+ serviceIntegrationDTO.setEnable(serviceIntegration.getEnable());
+ serviceIntegrationDTO.setConfig(true);
+ serviceIntegrationDTO.setOrganizationId(serviceIntegration.getOrganizationId());
+ }
+ return serviceIntegrationDTO;
+ }).toList();
+ }
+
+ private List getServiceIntegrationByOrgId(String organizationId) {
+ ServiceIntegrationExample example = new ServiceIntegrationExample();
+ example.createCriteria()
+ .andOrganizationIdEqualTo(organizationId);
+ return serviceIntegrationMapper.selectByExampleWithBLOBs(example);
}
public ServiceIntegration add(ServiceIntegrationUpdateRequest request) {
@@ -47,6 +89,8 @@ public class ServiceIntegrationService {
public ServiceIntegration update(ServiceIntegrationUpdateRequest request) {
ServiceIntegration serviceIntegration = new ServiceIntegration();
+ // 组织不能修改
+ serviceIntegration.setOrganizationId(null);
BeanUtils.copyBean(serviceIntegration, request);
if (request.getConfiguration() != null) {
serviceIntegration.setConfiguration(JSON.toJSONBytes(request.getConfiguration()));
@@ -55,9 +99,8 @@ public class ServiceIntegrationService {
return serviceIntegration;
}
- public String delete(String id) {
+ public void delete(String id) {
serviceIntegrationMapper.deleteByPrimaryKey(id);
- return id;
}
private void checkAddExist(ServiceIntegration serviceIntegration) {
@@ -70,13 +113,15 @@ public class ServiceIntegrationService {
}
}
- public boolean validate(Map serviceIntegrationInfo) {
- return serviceIntegrationInfo == null;
+ public void validate(String pluginId, Map serviceIntegrationInfo) {
+ Platform platform = platformPluginService.getPlatform(pluginId, StringUtils.EMPTY, JSON.toJSONString(serviceIntegrationInfo));
+ platform.validateIntegrationConfig();
}
- public boolean validate(String id) {
+ public void validate(String id) {
ServiceIntegration serviceIntegration = serviceIntegrationMapper.selectByPrimaryKey(id);
- return serviceIntegration == null;
+ Platform platform = platformPluginService.getPlatform(serviceIntegration.getPluginId(), StringUtils.EMPTY);
+ platform.validateIntegrationConfig();
}
public ServiceIntegration get(String id) {
@@ -84,6 +129,7 @@ public class ServiceIntegrationService {
}
public Object getPluginScript(String pluginId) {
- return new ServiceIntegration();
+ AbstractPlatformPlugin platformPlugin = pluginLoadService.getImplInstance(pluginId, AbstractPlatformPlugin.class);
+ return pluginLoadService.getPluginScriptContent(pluginId, platformPlugin.getIntegrationScriptId());
}
}
\ No newline at end of file
diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java
index 91b2a026b5..b7872341d9 100644
--- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java
+++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/PluginControllerTests.java
@@ -29,6 +29,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import static io.metersphere.sdk.controller.handler.result.CommonResultCode.FILE_NAME_ILLEGAL;
import static io.metersphere.system.controller.result.SystemResultCode.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -260,8 +261,10 @@ public class PluginControllerTests extends BaseTest {
@Order(5)
public void getPluginImg() throws Exception {
// @@请求成功
- mockMvc.perform(getRequestBuilder(PLUGIN_IMAGE, "pluginId", "/static/jira.jpg"))
+ mockMvc.perform(getRequestBuilder(PLUGIN_IMAGE, anotherAddPlugin.getId(), "/static/jira.jpg"))
.andExpect(status().isOk());
+
+ assertErrorCode( this.requestGet(PLUGIN_IMAGE, anotherAddPlugin.getId(), "/static/jira.doc"), FILE_NAME_ILLEGAL);
}
@Test
@@ -274,6 +277,7 @@ public class PluginControllerTests extends BaseTest {
Assertions.assertNull(plugin);
Assertions.assertEquals(new ArrayList<>(0), getOrgIdsByPlugId(addPlugin.getId()));
Assertions.assertEquals(new ArrayList<>(0), getScriptIdsByPlugId(addPlugin.getId()));
+ this.requestGetWithOk(DEFAULT_DELETE, anotherAddPlugin.getId());
// @@校验日志
checkLog(addPlugin.getId(), OperationLogType.DELETE);
diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java
index c9c4f50db6..0fd5fb9a22 100644
--- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java
+++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/ServiceIntegrationControllerTests.java
@@ -1,22 +1,44 @@
package io.metersphere.system.controller;
+import io.metersphere.plugin.platform.api.AbstractPlatformPlugin;
+import io.metersphere.plugin.sdk.util.JSON;
import io.metersphere.sdk.base.BaseTest;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.log.constants.OperationLogType;
+import io.metersphere.sdk.service.PluginLoadService;
import io.metersphere.system.controller.param.ServiceIntegrationUpdateRequestDefinition;
+import io.metersphere.system.domain.Organization;
+import io.metersphere.system.domain.Plugin;
import io.metersphere.system.domain.ServiceIntegration;
+import io.metersphere.system.dto.ServiceIntegrationDTO;
import io.metersphere.system.mapper.ServiceIntegrationMapper;
+import io.metersphere.system.request.PluginUpdateRequest;
import io.metersphere.system.request.ServiceIntegrationUpdateRequest;
+import io.metersphere.system.service.OrganizationService;
+import io.metersphere.system.service.PluginService;
import jakarta.annotation.Resource;
-import org.junit.jupiter.api.MethodOrderer;
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestMethodOrder;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.*;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.model.Header;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MvcResult;
-import java.util.HashMap;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.List;
+import java.util.Map;
+
+import static io.metersphere.sdk.constants.InternalUserRole.ADMIN;
+import static io.metersphere.system.controller.result.SystemResultCode.SERVICE_INTEGRATION_EXIST;
+import static io.metersphere.system.service.ServiceIntegrationService.PLUGIN_IMAGE_GET_PATH;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
/**
* @author jianxing
@@ -29,41 +51,68 @@ public class ServiceIntegrationControllerTests extends BaseTest {
private static final String BASE_PATH = "/service/integration/";
private static final String LIST = "/list/{0}";
private static final String VALIDATE_GET = "/validate/{0}";
- private static final String VALIDATE_POST = "/validate";
+ private static final String VALIDATE_POST = "/validate/{0}";
private static final String SCRIPT_GET = "/script/{0}";
+ private static ServiceIntegration addServiceIntegration;
+ private static Plugin plugin;
+ private static Organization defaultOrg;
+
@Resource
private ServiceIntegrationMapper serviceIntegrationMapper;
- private static ServiceIntegration addServiceIntegration;
+ @Resource
+ private OrganizationService organizationService;
+ @Resource
+ private PluginLoadService pluginLoadService;
+ @Resource
+ private PluginService pluginService;
+ @Resource
+ private MockServerClient mockServerClient;
+ @Value("${embedded.mockserver.host}")
+ private String mockServerHost;
+ @Value("${embedded.mockserver.port}")
+ private int mockServerHostPort;
+
@Override
protected String getBasePath() {
return BASE_PATH;
}
@Test
- public void list() throws Exception {
- // @@请求成功
- this.requestGetWithOkAndReturn(LIST, "orgId");
-// List serviceIntegrationList = getResultDataArray(mvcResult, ServiceIntegration.class);
- // todo 校验数据是否正确
- // @@校验权限
- requestGetPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_READ, LIST,"orgId");
+ @Order(0)
+ public void listEmpty() throws Exception {
+ defaultOrg = organizationService.getDefault();
+ // @@请求成功, 校验空数据,请求是否正常
+ this.requestGetWithOkAndReturn(LIST, defaultOrg.getId());
}
+
@Test
- @Order(0)
+ @Order(1)
public void add() throws Exception {
+ plugin = addPlugin();
+
+ JiraIntegrationConfig integrationConfig = new JiraIntegrationConfig();
+ Map integrationConfigMap = JSON.parseMap(JSON.toJSONString(integrationConfig));
+
// @@请求成功
ServiceIntegrationUpdateRequest request = new ServiceIntegrationUpdateRequest();
request.setEnable(false);
- request.setPluginId("pluginId");
- request.setConfiguration(new HashMap<>());
- request.setOrganizationId("orgId");
- // todo 填充数据
+ request.setPluginId(plugin.getId());
+ request.setConfiguration(integrationConfigMap);
+ request.setOrganizationId(defaultOrg.getId());
MvcResult mvcResult = this.requestPostWithOkAndReturn(DEFAULT_ADD, request);
+ // 校验请求成功数据
ServiceIntegration resultData = getResultData(mvcResult, ServiceIntegration.class);
ServiceIntegration serviceIntegration = serviceIntegrationMapper.selectByPrimaryKey(resultData.getId());
- this.addServiceIntegration= serviceIntegration;
- // todo 校验请求成功数据
+ Assertions.assertEquals(JSON.parseMap(new String(serviceIntegration.getConfiguration())), integrationConfigMap);
+ Assertions.assertEquals(serviceIntegration.getEnable(), request.getEnable());
+ Assertions.assertEquals(serviceIntegration.getPluginId(), request.getPluginId());
+ Assertions.assertEquals(serviceIntegration.getOrganizationId(), request.getOrganizationId());
+ this.addServiceIntegration = serviceIntegration;
+
+ // @@重名校验异常
+ assertErrorCode(this.requestPost(DEFAULT_ADD, request), SERVICE_INTEGRATION_EXIST);
+
// @@校验日志
checkLog(this.addServiceIntegration.getId(), OperationLogType.ADD);
// @@异常参数校验
@@ -73,20 +122,32 @@ public class ServiceIntegrationControllerTests extends BaseTest {
}
@Test
- @Order(1)
+ @Order(2)
public void update() throws Exception {
+ JiraIntegrationConfig integrationConfig = new JiraIntegrationConfig();
+ integrationConfig.setAddress(String.format("http://%s:%s", mockServerHost, mockServerHostPort));
+ Map integrationConfigMap = JSON.parseMap(JSON.toJSONString(integrationConfig));
+
// @@请求成功
ServiceIntegrationUpdateRequest request = new ServiceIntegrationUpdateRequest();
request.setId(addServiceIntegration.getId());
request.setEnable(false);
- request.setPluginId("pluginId");
- request.setConfiguration(new HashMap<>());
- request.setOrganizationId("orgId");
- // todo 填充数据
+ request.setPluginId(plugin.getId());
+ request.setConfiguration(integrationConfigMap);
+ request.setOrganizationId(defaultOrg.getId());
this.requestPostWithOk(DEFAULT_UPDATE, request);
// 校验请求成功数据
-// ServiceIntegration serviceIntegration = serviceIntegrationMapper.selectByPrimaryKey(request.getId());
- // todo 校验请求成功数据
+ ServiceIntegration serviceIntegration = serviceIntegrationMapper.selectByPrimaryKey(request.getId());
+ Assertions.assertEquals(JSON.parseMap(new String(serviceIntegration.getConfiguration())), integrationConfigMap);
+ Assertions.assertEquals(serviceIntegration.getEnable(), request.getEnable());
+ Assertions.assertEquals(serviceIntegration.getPluginId(), request.getPluginId());
+ // 验证组织修改无效
+ Assertions.assertEquals(serviceIntegration.getOrganizationId(), defaultOrg.getId());
+
+ // 提升覆盖率
+ request.setConfiguration(null);
+ this.requestPostWithOk(DEFAULT_UPDATE, request);
+
// @@校验日志
checkLog(request.getId(), OperationLogType.UPDATE);
// @@异常参数校验
@@ -95,41 +156,132 @@ public class ServiceIntegrationControllerTests extends BaseTest {
requestPostPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_UPDATE, DEFAULT_UPDATE, request);
}
+ @Test
+ @Order(3)
+ public void list() throws Exception {
+ // @@请求成功
+ MvcResult mvcResult = this.requestGetWithOkAndReturn(LIST, defaultOrg.getId());
+ // 校验请求成功数据
+ List serviceIntegrationList = getResultDataArray(mvcResult, ServiceIntegrationDTO.class);
+ ServiceIntegrationDTO serviceIntegrationDTO = serviceIntegrationList.get(0);
+ ServiceIntegration serviceIntegration = serviceIntegrationMapper.selectByPrimaryKey(serviceIntegrationDTO.getId());
+ Assertions.assertEquals(JSON.parseMap(new String(serviceIntegration.getConfiguration())),
+ serviceIntegrationDTO.getConfiguration());
+ Assertions.assertEquals(serviceIntegration.getEnable(), serviceIntegrationDTO.getEnable());
+ Assertions.assertEquals(serviceIntegration.getPluginId(), serviceIntegrationDTO.getPluginId());
+ AbstractPlatformPlugin msPluginInstance = pluginLoadService.getPlatformPluginInstance(plugin.getId());
+ Assertions.assertEquals(serviceIntegrationDTO.getDescription(), msPluginInstance.getDescription());
+ Assertions.assertEquals(serviceIntegrationDTO.getOrganizationId(), defaultOrg.getId());
+ Assertions.assertEquals(serviceIntegrationDTO.getTitle(), msPluginInstance.getName());
+ Assertions.assertEquals(serviceIntegrationDTO.getConfig(), true);
+ Assertions.assertEquals(serviceIntegrationDTO.getLogo(),
+ String.format(PLUGIN_IMAGE_GET_PATH, plugin.getId(), msPluginInstance.getLogo()));
+
+ // @@校验权限
+ requestGetPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_READ, LIST, defaultOrg.getId());
+ }
+
+ @Test
+ @Order(4)
+ public void validateGet() throws Exception {
+ mockServerClient
+ .when(
+ request()
+ .withMethod("GET")
+ .withPath("/rest/api/2/myself"))
+ .respond(
+ response()
+ .withStatusCode(200)
+ .withHeaders(
+ new Header("Content-Type", "application/json; charset=utf-8"),
+ new Header("Cache-Control", "public, max-age=86400"))
+ .withBody("{\"self\"")
+ );
+
+ // @@请求成功
+ this.requestGetWithOk(VALIDATE_GET, addServiceIntegration.getId());
+ // @@校验权限
+ requestGetPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_UPDATE, VALIDATE_GET, addServiceIntegration.getId());
+ }
+
+ @Test
+ @Order(5)
+ public void validatePost() throws Exception {
+ JiraIntegrationConfig integrationConfig = new JiraIntegrationConfig();
+ integrationConfig.setAddress(String.format("http://%s:%s", mockServerHost, mockServerHostPort));
+ Map integrationConfigMap = JSON.parseMap(JSON.toJSONString(integrationConfig));
+ // @@请求成功
+ this.requestPostWithOk(VALIDATE_POST, integrationConfigMap, plugin.getId());
+ // @@校验权限
+ requestPostPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_UPDATE, VALIDATE_POST, integrationConfigMap, plugin.getId());
+ }
+
+ @Test
+ @Order(6)
+ public void getPluginScript() throws Exception {
+ // @@请求成功
+ MvcResult mvcResult = this.requestGetWithOkAndReturn(SCRIPT_GET, plugin.getId());
+ // 校验请求成功数据
+ Assertions.assertTrue(StringUtils.isNotBlank(mvcResult.getResponse().getContentAsString()));
+ // @@校验权限
+ requestGetPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_READ, SCRIPT_GET, plugin.getId());
+ }
+
@Test
public void delete() throws Exception {
// @@请求成功
this.requestGetWithOk(DEFAULT_DELETE, addServiceIntegration.getId());
- // todo 校验请求成功数据
+ // 校验请求成功数据
+ ServiceIntegration serviceIntegration = serviceIntegrationMapper.selectByPrimaryKey(addServiceIntegration.getId());
+ Assertions.assertNull(serviceIntegration);
+
+ // 清理插件
+ deletePlugin();
+
// @@校验日志
checkLog(addServiceIntegration.getId(), OperationLogType.DELETE);
// @@校验权限
requestGetPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_DELETE, DEFAULT_DELETE, addServiceIntegration.getId());
}
- @Test
- public void validateGet() throws Exception {
- // @@请求成功
- this.requestGetWithOk(VALIDATE_GET, addServiceIntegration.getId());
- // todo 校验请求成功数据
- // @@校验权限
- requestGetPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_UPDATE, VALIDATE_GET, addServiceIntegration.getId());
+
+ /**
+ * 添加插件,供测试使用
+ *
+ * @return
+ * @throws Exception
+ */
+ public Plugin addPlugin() throws Exception {
+ PluginUpdateRequest request = new PluginUpdateRequest();
+ File jarFile = new File(
+ this.getClass().getClassLoader().getResource("file/metersphere-jira-plugin-3.x.jar")
+ .getPath()
+ );
+ FileInputStream inputStream = new FileInputStream(jarFile);
+ MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), inputStream);
+ request.setName("测试插件");
+ request.setGlobal(true);
+ request.setEnable(true);
+ request.setCreateUser(ADMIN.name());
+ return pluginService.add(request, mockMultipartFile);
}
- @Test
- public void validatePost() throws Exception {
- // @@请求成功
- this.requestPostWithOk(VALIDATE_POST, new HashMap<>());
- // todo 校验请求成功数据
- // @@校验权限
- requestPostPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_UPDATE, VALIDATE_POST, new HashMap<>());
+ /**
+ * 删除插件
+ * @throws Exception
+ */
+ public void deletePlugin() throws Exception {
+ this.requestGetWithOk(DEFAULT_DELETE, plugin.getId());
}
- @Test
- public void getPluginScript() throws Exception {
- // @@请求成功
- this.requestGetWithOk(SCRIPT_GET, "pluginId");
- // todo 校验请求成功数据
- // @@校验权限
- requestGetPermissionTest(PermissionConstants.SYSTEM_SERVICE_INTEGRATION_READ, SCRIPT_GET, "pluginId");
+ @Getter
+ @Setter
+ public class JiraIntegrationConfig {
+ private String account;
+ private String password;
+ private String token;
+ private String authType;
+ private String address;
+ private String version;
}
}
\ No newline at end of file
diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/param/PluginUpdateRequestDefinition.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/param/PluginUpdateRequestDefinition.java
index f3e886bd79..d285ca31fb 100644
--- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/param/PluginUpdateRequestDefinition.java
+++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/param/PluginUpdateRequestDefinition.java
@@ -16,6 +16,6 @@ public class PluginUpdateRequestDefinition {
@Size(min = 1, max = 255, groups = {Created.class, Updated.class})
private String name;
- @Size(min = 1, max = 500, groups = {Created.class, Updated.class})
+ @Size(max = 500, groups = {Created.class, Updated.class})
private String description;
}
diff --git a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/param/ServiceIntegrationUpdateRequestDefinition.java b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/param/ServiceIntegrationUpdateRequestDefinition.java
index 162002c6c6..b381fe49a1 100644
--- a/backend/services/system-setting/src/test/java/io/metersphere/system/controller/param/ServiceIntegrationUpdateRequestDefinition.java
+++ b/backend/services/system-setting/src/test/java/io/metersphere/system/controller/param/ServiceIntegrationUpdateRequestDefinition.java
@@ -14,7 +14,7 @@ import java.util.Map;
public class ServiceIntegrationUpdateRequestDefinition {
@Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(groups = {Updated.class})
- @Size(min = 1, max = 50, groups = {Created.class, Updated.class})
+ @Size(min = 1, max = 50, groups = {Updated.class})
private String id;
@Schema(description = "插件的ID", requiredMode = Schema.RequiredMode.REQUIRED)
diff --git a/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar b/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar
index f8b4cb2f6f..c8a6ea2338 100644
Binary files a/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar and b/backend/services/system-setting/src/test/resources/file/metersphere-jira-plugin-3.x.jar differ