feat(系统设置): 服务集成接口实现
--task=1012656 --user=陈建星 系统设置-组织-服务集成-后台 https://www.tapd.cn/55049933/s/1401952
This commit is contained in:
parent
0120db4fc6
commit
8627f7b68f
|
@ -18,6 +18,20 @@
|
|||
<artifactId>metersphere-plugin-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 默认的http请求工具 restTemplate -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
|
|
|
@ -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> T getIntegrationConfig(String integrationConfig, Class<T> clazz) {
|
||||
if (StringUtils.isBlank(integrationConfig)) {
|
||||
throw new MSPluginException("服务集成配置为空");
|
||||
}
|
||||
return JSON.parseObject(integrationConfig, clazz);
|
||||
}
|
||||
|
||||
public String getPluginId() {
|
||||
return request.getPluginId();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> response) {
|
||||
return Arrays.asList(JSON.parseArray(getResult(response), clazz).toArray());
|
||||
}
|
||||
|
||||
protected Object getResultForObject(Class clazz, ResponseEntity<String> 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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<DemandDTO> getDemands(String projectConfig);
|
||||
|
||||
/**
|
||||
* 创建缺陷并封装 MS 返回
|
||||
* 创建缺陷时调用
|
||||
* @param bugsRequest bugRequest
|
||||
* @return MS 缺陷
|
||||
*/
|
||||
MsBugDTO addBug(PlatformBugUpdateRequest bugsRequest);
|
||||
|
||||
/**
|
||||
* 项目设置和缺陷表单中,调用接口获取下拉框选项
|
||||
* 配置文件的表单中选项值配置了 optionMethod ,则调用获取表单的选项值
|
||||
* @return 返回下拉列表
|
||||
*/
|
||||
List<SelectOption> 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<PlatformCustomFieldItemDTO> 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<PlatformStatusDTO> getStatusList(String projectConfig);
|
||||
|
||||
/**
|
||||
* 获取第三方平台的状态转移列表
|
||||
* 即编辑缺陷时的可选状态
|
||||
* 默认会调用 getStatusList,可重写覆盖
|
||||
* @param bugId
|
||||
* @return
|
||||
*/
|
||||
List<PlatformStatusDTO> getTransitions(String projectConfig, String bugId);
|
||||
|
||||
/**
|
||||
* 用例关联需求时调用
|
||||
* 可在第三方平台添加用例和需求的关联关系
|
||||
* @param request
|
||||
*/
|
||||
void handleDemandUpdate(DemandUpdateRequest request);
|
||||
|
||||
/**
|
||||
* 用例批量关联需求时调用
|
||||
* 可在第三方平台添加用例和需求的关联关系
|
||||
* @param request
|
||||
*/
|
||||
void handleDemandUpdateBatch(DemandUpdateRequest request);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<Proxy> 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);
|
||||
}
|
||||
}
|
|
@ -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> 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> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@
|
|||
<groupId>io.metersphere</groupId>
|
||||
<artifactId>framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
|
||||
</parent>
|
||||
|
||||
<groupId>io.metersphere</groupId>
|
||||
|
@ -25,29 +24,4 @@
|
|||
<module>metersphere-api-plugin-sdk</module>
|
||||
<module>metersphere-platform-plugin-sdk</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>ApacheJMeter_core</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.jmeter</groupId>
|
||||
<artifactId>jorphan</artifactId>
|
||||
<version>${jmeter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<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);
|
||||
}
|
||||
|
@ -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 <T> Class<T> getImplClass(String pluginId, Class<T> superClazz) {
|
||||
PluginClassLoader classLoader = classLoaderMap.get(pluginId);
|
||||
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) {
|
||||
try {
|
||||
Class<T> 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> 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());
|
||||
|
|
|
@ -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<PluginOrganization> 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<Plugin> getPlatformPlugins() {
|
||||
PluginExample example = new PluginExample();
|
||||
example.createCriteria()
|
||||
.andScenarioEqualTo(PluginScenarioType.PLATFORM.name());
|
||||
return pluginMapper.selectByExample(example);
|
||||
}
|
||||
}
|
|
@ -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> T getImplInstance(String pluginId, Class<T> superClazz, Object param) {
|
||||
return pluginManager.getImplInstance(pluginId, superClazz, param);
|
||||
}
|
||||
|
||||
public <T> T getImplInstance(String pluginId, Class<T> superClazz) {
|
||||
return pluginManager.getImplInstance(pluginId, superClazz);
|
||||
}
|
||||
|
||||
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 = 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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ public class FilterChainUtils {
|
|||
filterChainDefinitionMap.put("/websocket/**", "csrf");
|
||||
|
||||
// 获取插件中的图片
|
||||
filterChainDefinitionMap.put("/platform/plugin/image/**", "anon");
|
||||
filterChainDefinitionMap.put("/plugin/image/**", "anon");
|
||||
|
||||
return filterChainDefinitionMap;
|
||||
}
|
||||
|
|
|
@ -428,3 +428,7 @@ permission.edit=Update
|
|||
permission.delete=Delete
|
||||
permission.import=Import
|
||||
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
|
|
@ -427,3 +427,7 @@ permission.edit=修改
|
|||
permission.delete=删除
|
||||
permission.import=导入
|
||||
permission.recover=恢复
|
||||
|
||||
file_name_illegal_error=文件名不合法
|
||||
plugin_enable_error=插件未启用
|
||||
plugin_permission_error=没有该插件的访问权限
|
|
@ -427,3 +427,7 @@ permission.edit=修改
|
|||
permission.delete=刪除
|
||||
permission.import=導入
|
||||
permission.recover=恢復
|
||||
|
||||
file_name_illegal_error=文件名不合法
|
||||
plugin_enable_error=插件未啟用
|
||||
plugin_permission_error=沒有該插件的訪問權限
|
|
@ -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
|
||||
|
|
|
@ -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=组织与项目
|
||||
|
|
|
@ -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=組織與項目
|
||||
|
|
|
@ -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> T getResultData(MvcResult mvcResult, Class<T> 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> T getResultMessageDetail(MvcResult mvcResult, Class<T> 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 <T> List<T> getResultDataArray(MvcResult mvcResult, Class<T> 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 <T> Pager<List<T>> getPageResult(MvcResult mvcResult, Class<T> clazz) throws Exception {
|
||||
Map<String, Object> pagerResult = (Map<String, Object>) JSON.parseMap(mvcResult.getResponse().getContentAsString()).get("data");
|
||||
Map<String, Object> pagerResult = (Map<String, Object>) parseResponse(mvcResult).get("data");
|
||||
List<T> list = JSON.parseArray(JSON.toJSONString(pagerResult.get("list")), clazz);
|
||||
Pager pager = new Pager();
|
||||
pager.setPageSize(Long.valueOf(pagerResult.get("pageSize").toString()));
|
||||
|
|
|
@ -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) {
|
||||
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("不支持该类型");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
public void validate(@PathVariable String pluginId,
|
||||
@Validated({Updated.class})
|
||||
@RequestBody
|
||||
@NotEmpty
|
||||
@Schema(description = "配置的表单键值对", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
Map<String, String> serviceIntegrationInfo) {
|
||||
return serviceIntegrationService.validate(serviceIntegrationInfo);
|
||||
HashMap<String, String> 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}")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<String, String> configuration;
|
||||
@NotEmpty(message = "{service_integration.configuration.not_blank}", groups = {Created.class})
|
||||
private Map<String, Object> configuration;
|
||||
}
|
|
@ -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;
|
||||
|
@ -140,6 +142,7 @@ public class PluginService {
|
|||
/**
|
||||
* 通知其他节点加载插件
|
||||
* 这里需要传一下 fileName,事务未提交,查询不到文件名
|
||||
*
|
||||
* @param pluginId
|
||||
* @param fileName
|
||||
*/
|
||||
|
@ -150,6 +153,7 @@ 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<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ServiceIntegrationDTO> list(String organizationId) {
|
||||
return Arrays.asList(new ServiceIntegrationDTO());
|
||||
// 查询服务集成已配置数据
|
||||
Map<String, ServiceIntegration> serviceIntegrationMap = getServiceIntegrationByOrgId(organizationId).stream()
|
||||
.collect(Collectors.toMap(ServiceIntegration::getPluginId, i -> i));
|
||||
|
||||
List<Plugin> 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<ServiceIntegration> 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<String, String> serviceIntegrationInfo) {
|
||||
return serviceIntegrationInfo == null;
|
||||
public void validate(String pluginId, Map<String, String> 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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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<ServiceIntegration> 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<String, Object> 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());
|
||||
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;
|
||||
// todo 校验请求成功数据
|
||||
|
||||
// @@重名校验异常
|
||||
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<String, Object> 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<ServiceIntegrationDTO> 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<String, Object> 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue