refactor(接口测试): 支持跨项目场景可以走当前项目环境执行

Signed-off-by: fit2-zhao <yong.zhao@fit2cloud.com>
This commit is contained in:
fit2-zhao 2023-07-26 13:32:04 +08:00 committed by fit2-zhao
parent 9cb5163212
commit a85a59475f
65 changed files with 794 additions and 1947 deletions

View File

@ -8,7 +8,7 @@ import java.util.Map;
@Getter
@Setter
public class ApiScenarioEnvRequest {
private String projectId;
private String definition;
private String environmentType;
private String environmentGroupId;

View File

@ -8,7 +8,7 @@ import java.util.Set;
@Getter
@Setter
public class ScenarioEnv {
public class EnvironmentCheckDTO {
private Set<String> projectIds = new HashSet<>();
private Boolean fullUrl = true;

View File

@ -49,6 +49,7 @@ import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.CSVDataSet;
@ -85,7 +86,7 @@ public class ElementUtil {
private static final String BODY_FILE_DIR = FileUtils.BODY_FILE_DIR;
private static final String TEST_BEAN_GUI = "TestBeanGUI";
private final static String SCENARIO_REF = "SCENARIO-REF-STEP";
private final static String MS_DEFAULT = "ms-default-data-source";
public final static List<String> scriptList = new ArrayList<String>() {{
this.add(ElementConstants.JSR223);
this.add(ElementConstants.JSR223_PRE);
@ -349,7 +350,7 @@ public class ElementUtil {
}
}
public static void dataSetDomain(JSONArray hashTree, MsParameter msParameter) {
public static void dataSetDomain(JSONArray hashTree, ParameterConfig msParameter) {
try {
ApiScenarioMapper apiScenarioMapper = CommonBeanFactory.getBean(ApiScenarioMapper.class);
BaseEnvGroupProjectService environmentGroupProjectService = CommonBeanFactory.getBean(BaseEnvGroupProjectService.class);
@ -358,7 +359,7 @@ public class ElementUtil {
for (int i = 0; i < hashTree.length(); i++) {
JSONObject element = hashTree.optJSONObject(i);
boolean isScenarioEnv = false;
ParameterConfig config = new ParameterConfig();
ParameterConfig config = new ParameterConfig(msParameter.getCurrentProjectId(), false);
if (element != null && element.get(PropertyConstant.TYPE).toString().equals(ElementConstants.SCENARIO)) {
MsScenario scenario = JSON.parseObject(element.toString(), MsScenario.class);
if (scenario.isEnvironmentEnable()) {
@ -829,10 +830,10 @@ public class ElementUtil {
}
// 环境通用变量
if (config.isEffective(projectId)
&& config.getConfig().get(projectId).getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().get(projectId).getCommonConfig().getVariables())) {
&& config.get(projectId).getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.get(projectId).getCommonConfig().getVariables())) {
//常量
List<ScenarioVariable> constants = config.getConfig().get(projectId).getCommonConfig().getVariables().stream()
List<ScenarioVariable> constants = config.get(projectId).getCommonConfig().getVariables().stream()
.filter(ScenarioVariable::isConstantValid)
.filter(ScenarioVariable::isEnable)
.collect(Collectors.toList());
@ -844,7 +845,7 @@ public class ElementUtil {
? keyValue.getValue().replaceAll("[\r\n]", "")
: keyValue.getValue()), "="));
// List类型的变量
List<ScenarioVariable> variableList = config.getConfig().get(projectId).getCommonConfig().getVariables().stream()
List<ScenarioVariable> variableList = config.get(projectId).getCommonConfig().getVariables().stream()
.filter(ScenarioVariable::isListValid)
.filter(ScenarioVariable::isEnable)
.collect(Collectors.toList());
@ -855,8 +856,8 @@ public class ElementUtil {
}
});
// 清空变量防止重复添加
config.getConfig().get(projectId).getCommonConfig().getVariables().removeAll(constants);
config.getConfig().get(projectId).getCommonConfig().getVariables().removeAll(variableList);
config.get(projectId).getCommonConfig().getVariables().removeAll(constants);
config.get(projectId).getCommonConfig().getVariables().removeAll(variableList);
}
if (arguments.getArguments() != null && arguments.getArguments().size() > 0) {
@ -866,14 +867,14 @@ public class ElementUtil {
}
public static void addApiVariables(ParameterConfig config, HashTree httpSamplerTree, String projectId) {
if (config.isEffective(projectId) && config.getConfig().get(projectId).getCommonConfig() != null && CollectionUtils.isNotEmpty(config.getConfig().get(projectId).getCommonConfig().getVariables())) {
if (config.isEffective(projectId) && config.get(projectId).getCommonConfig() != null && CollectionUtils.isNotEmpty(config.get(projectId).getCommonConfig().getVariables())) {
ElementUtil.addApiCsvDataSet(httpSamplerTree,
config.getConfig().get(projectId).getCommonConfig().getVariables(),
config.get(projectId).getCommonConfig().getVariables(),
config, "shareMode.group");
ElementUtil.addCounter(httpSamplerTree,
config.getConfig().get(projectId).getCommonConfig().getVariables());
config.get(projectId).getCommonConfig().getVariables());
ElementUtil.addRandom(httpSamplerTree,
config.getConfig().get(projectId).getCommonConfig().getVariables());
config.get(projectId).getCommonConfig().getVariables());
}
}
@ -968,7 +969,7 @@ public class ElementUtil {
if (StringUtils.isEmpty(environmentId)) {
if (config.getConfig() != null) {
if (StringUtils.isNotBlank(projectId) && config.getConfig().containsKey(projectId)) {
return config.getConfig().get(projectId).getEnvironmentId();
return config.get(projectId).getEnvironmentId();
} else {
if (CollectionUtils.isNotEmpty(config.getConfig().values())) {
Optional<EnvironmentConfig> values = config.getConfig().entrySet().stream().findFirst().map(Map.Entry::getValue);
@ -1004,7 +1005,9 @@ public class ElementUtil {
jdbcProcessor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass(TEST_BEAN_GUI));
ElementUtil.setBaseParams(jdbcProcessor, vo.getParent(), config, vo.getId(), vo.getIndex());
jdbcProcessor.setDataSource(ElementUtil.getDataSourceName(vo.getDataSource().getName()));
if (ObjectUtils.isNotEmpty(vo.getDataSource())) {
jdbcProcessor.setDataSource(ElementUtil.getDataSourceName(vo.getDataSource().getName()));
}
jdbcProcessor.setProperty("dataSource", jdbcProcessor.getDataSource());
jdbcProcessor.setProperty("query", vo.getQuery());
jdbcProcessor.setProperty("queryTimeout", String.valueOf(vo.getQueryTimeout()));
@ -1015,6 +1018,9 @@ public class ElementUtil {
}
public static DataSourceElement jdbcDataSource(String sourceName, DatabaseConfig dataSource) {
if (dataSource == null) {
dataSource = new DatabaseConfig(MS_DEFAULT, MS_DEFAULT, 1, 1L, MS_DEFAULT, MS_DEFAULT, MS_DEFAULT, MS_DEFAULT);
}
DataSourceElement dataSourceElement = new DataSourceElement();
dataSourceElement.setEnabled(true);
dataSourceElement.setName(sourceName + " JDBCDataSource");
@ -1023,7 +1029,7 @@ public class ElementUtil {
dataSourceElement.setProperty("autocommit", true);
dataSourceElement.setProperty("keepAlive", true);
dataSourceElement.setProperty("preinit", false);
dataSourceElement.setProperty("dataSource", sourceName);
dataSourceElement.setProperty("dataSource", StringUtils.defaultIfBlank(sourceName, MS_DEFAULT));
dataSourceElement.setProperty("dbUrl", dataSource.getDbUrl());
dataSourceElement.setProperty("driver", dataSource.getDriver());
dataSourceElement.setProperty("username", dataSource.getUsername());
@ -1152,15 +1158,15 @@ public class ElementUtil {
return false;
}
public static DatabaseConfig selectDataSourceFromJDBCProcessor(String processorName, String environmentId, String dataSourceId, String projectId, ParameterConfig config) {
public static DatabaseConfig getDataSource(String processorName, String environmentId, String dataSourceId, String projectId, ParameterConfig config) {
if (config == null) {
return null;
}
DatabaseConfig dataSource = null;
// 自选了数据源
if (config.isEffective(projectId) && CollectionUtils.isNotEmpty(config.getConfig().get(projectId).getDatabaseConfigs())
&& isDataSource(dataSourceId, config.getConfig().get(projectId).getDatabaseConfigs())) {
EnvironmentConfig environmentConfig = config.getConfig().get(projectId);
if (config.isEffective(projectId) && CollectionUtils.isNotEmpty(config.get(projectId).getDatabaseConfigs())
&& isDataSource(dataSourceId, config.get(projectId).getDatabaseConfigs())) {
EnvironmentConfig environmentConfig = config.get(projectId);
if (environmentConfig.getDatabaseConfigs() != null && StringUtils.isNotEmpty(environmentConfig.getEnvironmentId())) {
environmentId = environmentConfig.getEnvironmentId();
}
@ -1170,14 +1176,14 @@ public class ElementUtil {
}
} else {
// 取当前环境下默认的一个数据源
if (config.isEffective(projectId) && CollectionUtils.isNotEmpty(config.getConfig().get(projectId).getDatabaseConfigs())) {
if (config.isEffective(projectId) && CollectionUtils.isNotEmpty(config.get(projectId).getDatabaseConfigs())) {
LoggerUtil.info(processorName + ":开始获取当前环境下默认数据源");
DatabaseConfig dataSourceOrg = ElementUtil.dataSource(projectId, dataSourceId, config.getConfig().get(projectId));
DatabaseConfig dataSourceOrg = ElementUtil.dataSource(projectId, dataSourceId, config.get(projectId));
if (dataSourceOrg != null) {
dataSource = dataSourceOrg;
} else {
LoggerUtil.info(processorName + ":获取当前环境下默认数据源结束!未查找到默认数据源");
dataSource = config.getConfig().get(projectId).getDatabaseConfigs().get(0);
dataSource = config.get(projectId).getDatabaseConfigs().get(0);
}
}
}

View File

@ -23,6 +23,7 @@ import io.metersphere.utils.LoggerUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
@ -74,11 +75,11 @@ public class MsScenario extends MsTestElement {
&& !this.setRefScenario(hashTree)) {
return;
}
// 设置共享cookie
config.setEnableCookieShare(enableCookieShare);
Map<String, EnvironmentConfig> envConfig = new HashMap<>(16);
if (config.getConfig() == null) {
if (MapUtils.isEmpty(config.getConfig())) {
// 兼容历史数据
if (this.environmentMap == null || this.environmentMap.isEmpty()) {
this.environmentMap = new HashMap<>(16);
@ -104,18 +105,18 @@ public class MsScenario extends MsTestElement {
}
HashTree scenarioTree = tree;
// 取出自身场景环境
ParameterConfig newConfig = new ParameterConfig();
ParameterConfig newConfig = new ParameterConfig(this.getProjectId(), false);
if (this.isEnvironmentEnable()) {
this.setNewConfig(envConfig, newConfig);
newConfig.setRetryNum(config.getRetryNum());
}
if (config != null && StringUtils.equals(this.getId(), config.getScenarioId())) {
if (StringUtils.equals(this.getId(), config.getScenarioId())) {
config.setTransferVariables(this.variables);
if (CollectionUtils.isNotEmpty(this.headers)) {
ElementUtil.setHeader(scenarioTree, this.headers, this.getName());
}
}
if (config != null && !config.getExcludeScenarioIds().contains(this.getId())) {
if (!config.getExcludeScenarioIds().contains(this.getId())) {
scenarioTree = MsCriticalSectionController.createHashTree(tree, this.getName(), this.isEnable());
}
// 启用当前场景变量优先选择
@ -130,7 +131,7 @@ public class MsScenario extends MsTestElement {
// 这里加入自定义变量解决ForEach循环控制器取值问题循环控制器无法从vars中取值
if (BooleanUtils.isTrue(this.variableEnable) || BooleanUtils.isTrue(this.mixEnable)) {
scenarioTree.add(ElementUtil.argumentsToUserParameters(valueSupposeMock));
} else if (config != null && (this.isAllEnable() || config.isApi())) {
} else if (this.isAllEnable() || config.isApi()) {
valueSupposeMock.setProperty(ElementConstants.COVER, true);
scenarioTree.add(valueSupposeMock);
}
@ -188,7 +189,7 @@ public class MsScenario extends MsTestElement {
if (envProcessor != null) {
BeanUtils.copyBean(processor, envProcessor);
}
if (processor != null && StringUtils.isNotEmpty(processor.getScript())) {
if (StringUtils.isNotEmpty(processor.getScript())) {
processor.setType(ElementConstants.JSR223);
processor.setClazzName(MsJSR223Processor.class.getCanonicalName());
boolean isConnScenarioPre = false;

View File

@ -22,6 +22,7 @@ import io.metersphere.service.definition.ApiTestCaseService;
import io.metersphere.service.plan.TestPlanApiCaseService;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
@ -30,15 +31,19 @@ import java.util.stream.Collectors;
@Data
public class ParameterConfig extends MsParameter {
public ParameterConfig(String currentProjectId, boolean isApi) {
this.currentProjectId = currentProjectId;
this.setApi(isApi);
if (MapUtils.isEmpty(this.config)) {
this.config = new HashMap<>();
}
}
/**
* 环境配置
*/
private Map<String, EnvironmentConfig> config;
/**
* UI 指令全局配置
*/
private Object commandConfig;
/**
* 缓存同一批请求的认证信息
*/
@ -61,10 +66,6 @@ public class ParameterConfig extends MsParameter {
* 公共Cookie
*/
private boolean enableCookieShare;
/**
* 是否停止继续
*/
private Boolean onSampleError;
/**
* 是否是导入/导出操作
@ -74,13 +75,12 @@ public class ParameterConfig extends MsParameter {
* 导入/导出操作时取样器的testName值
*/
private String operatingSampleTestName;
/**
* 项目ID支持单接口执行
*/
private String projectId;
private String scenarioId;
/**
* 当前项目id
*/
private String currentProjectId;
/**
* 报告 ID
*/
@ -90,7 +90,6 @@ public class ParameterConfig extends MsParameter {
private boolean runLocal;
private String browserLanguage;
private boolean isApi;
/**
* 失败重试次数
@ -104,7 +103,8 @@ public class ParameterConfig extends MsParameter {
private List<String> csvFilePaths = new ArrayList<>();
public boolean isEffective(String projectId) {
if (this.config != null && this.config.get(projectId) != null) {
if ((StringUtils.isNotBlank(projectId) && this.config != null && this.config.get(projectId) != null)
|| (StringUtils.isNotBlank(this.currentProjectId) && this.config != null && this.config.get(currentProjectId) != null)) {
return true;
}
return false;
@ -119,8 +119,7 @@ public class ParameterConfig extends MsParameter {
}
public HttpConfig matchConfig(MsHTTPSamplerProxy samplerProxy) {
HttpConfig httpConfig = this.getConfig().get(samplerProxy.getProjectId()).getHttpConfig();
public HttpConfig matchConfig(MsHTTPSamplerProxy samplerProxy, HttpConfig httpConfig) {
boolean isNext = true;
if (CollectionUtils.isNotEmpty(httpConfig.getConditions())) {
for (HttpConfigCondition item : httpConfig.getConditions()) {
@ -254,4 +253,17 @@ public class ParameterConfig extends MsParameter {
});
}
}
/**
* 获取项目环境配置如果没有则返回当前项目环境配置
*
* @param projectId 项目ID
*/
public EnvironmentConfig get(String projectId) {
EnvironmentConfig envConfig = this.getConfig().get(projectId);
if (envConfig == null && StringUtils.isNotEmpty(this.getCurrentProjectId())) {
return this.config.get(this.getCurrentProjectId());
}
return envConfig;
}
}

View File

@ -52,7 +52,7 @@ public class MsAuthManager extends MsTestElement {
} else {
if (config != null && config.isEffective(this.getProjectId())) {
if (config.isEffective(this.getProjectId())) {
String url = config.getConfig().get(this.getProjectId()).getHttpConfig().getProtocol() + "://" + config.getConfig().get(this.getProjectId()).getHttpConfig().getSocket();
String url = config.get(this.getProjectId()).getHttpConfig().getProtocol() + "://" + config.get(this.getProjectId()).getHttpConfig().getSocket();
auth.setURL(url);
}
}

View File

@ -6,7 +6,6 @@ import io.metersphere.api.dto.definition.request.ParameterConfig;
import io.metersphere.api.dto.scenario.DatabaseConfig;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.commons.constants.ElementConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.commons.utils.JSONUtil;
import io.metersphere.commons.vo.JDBCProcessorVO;
@ -16,6 +15,7 @@ import io.metersphere.utils.LoggerUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.protocol.jdbc.processor.JDBCPostProcessor;
import org.apache.jorphan.collections.HashTree;
@ -57,22 +57,20 @@ public class MsJDBCPostProcessor extends MsTestElement {
if (StringUtils.isBlank(this.getProjectId()) && this.getParent() != null) {
this.setProjectId(this.getParent().getProjectId());
}
if (config.getConfig() == null) {
if (MapUtils.isEmpty(config.getConfig()) && config.isApi()) {
// 单独接口执行
this.setProjectId(config.getProjectId());
this.setProjectId(config.getCurrentProjectId());
config.setConfig(ElementUtil.getEnvironmentConfig(StringUtils.isNotEmpty(useEnvironment) ? useEnvironment : environmentId, this.getProjectId()));
}
this.dataSource = ElementUtil.selectDataSourceFromJDBCProcessor(this.getName(), this.environmentId, this.dataSourceId, this.getProjectId(), config);
this.dataSource = ElementUtil.getDataSource(this.getName(), this.environmentId, this.dataSourceId, this.getProjectId(), config);
if (this.dataSource == null) {
// 用自身的数据
if (StringUtils.isNotEmpty(dataSourceId)) {
this.dataSource = ElementUtil.initDataSource(this.environmentId, this.dataSourceId);
}
if (this.dataSource == null) {
LoggerUtil.info(this.getName() + " 当前项目id", this.getProjectId() + " 当前环境配置信息", JSONUtil.toJSONString(config));
String message = "数据源为空请选择数据源";
MSException.throwException(StringUtils.isNotEmpty(this.getName()) ? this.getName() + "" + message : message);
LoggerUtil.error(this.getName() + ",未找到数据源", JSONUtil.toJSONString(config));
}
}
JDBCPostProcessor jdbcPostProcessor = jdbcPostProcessor(config);

View File

@ -17,6 +17,7 @@ import io.metersphere.utils.LoggerUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.protocol.jdbc.processor.JDBCPreProcessor;
import org.apache.jorphan.collections.HashTree;
@ -57,21 +58,19 @@ public class MsJDBCPreProcessor extends MsTestElement {
if (StringUtils.isBlank(this.getProjectId()) && this.getParent() != null) {
this.setProjectId(this.getParent().getProjectId());
}
if (config.getConfig() == null) {
if (MapUtils.isEmpty(config.getConfig()) && config.isApi()) {
// 单独接口执行
this.setProjectId(config.getProjectId());
this.setProjectId(config.getCurrentProjectId());
config.setConfig(ElementUtil.getEnvironmentConfig(StringUtils.isNotEmpty(useEnvironment) ? useEnvironment : environmentId, this.getProjectId()));
}
this.dataSource = ElementUtil.selectDataSourceFromJDBCProcessor(this.getName(), this.environmentId, this.dataSourceId, this.getProjectId(), config);
this.dataSource = ElementUtil.getDataSource(this.getName(), this.environmentId, this.dataSourceId, this.getProjectId(), config);
if (this.dataSource == null) {
// 用自身的数据
if (StringUtils.isNotEmpty(dataSourceId)) {
this.dataSource = ElementUtil.initDataSource(this.environmentId, this.dataSourceId);
}
if (this.dataSource == null) {
LoggerUtil.info(this.getName() + " 当前项目id", this.getProjectId() + " 当前环境配置信息", JSONUtil.toJSONString(config));
String message = "数据源为空请选择数据源";
MSException.throwException(StringUtils.isNotEmpty(this.getName()) ? this.getName() + "" + message : message);
LoggerUtil.error(this.getName() + ",未找到数据源", JSONUtil.toJSONString(config));
}
}

View File

@ -90,7 +90,7 @@ public class MsDubboSampler extends MsTestElement {
//添加全局前后置脚本
EnvironmentConfig envConfig = null;
if (config.getConfig() != null) {
envConfig = config.getConfig().get(this.getProjectId());
envConfig = config.get(this.getProjectId());
}
//处理全局前后置脚本(步骤内)
String environmentId = this.getEnvironmentId();

View File

@ -31,13 +31,16 @@ import io.metersphere.jmeter.utils.ScriptEngineUtils;
import io.metersphere.plugin.core.MsParameter;
import io.metersphere.plugin.core.MsTestElement;
import io.metersphere.service.definition.ApiTestCaseService;
import io.metersphere.utils.LoggerUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.KeystoreConfig;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.protocol.http.util.HTTPArgument;
import org.apache.jmeter.protocol.http.util.HTTPConstants;
@ -134,10 +137,10 @@ public class MsHTTPSamplerProxy extends MsTestElement {
sampler.setImplementation(this.getImplementation());
sampler.setUseKeepAlive(true);
sampler.setDoMultipart(this.isDoMultipartPost());
if (config.getConfig() == null) {
if (MapUtils.isEmpty(config.getConfig()) && config.isApi()) {
// 单独接口执行
if (StringUtils.isNotEmpty(config.getProjectId())) {
this.setProjectId(config.getProjectId());
if (StringUtils.isNotEmpty(config.getCurrentProjectId())) {
this.setProjectId(config.getCurrentProjectId());
}
String projectId = this.getProjectId();
config.setConfig(ElementUtil.getEnvironmentConfig(this.useEnvironment, projectId));
@ -166,7 +169,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
if (CollectionUtils.isNotEmpty(bodyParams)) {
Arguments arguments = httpArguments(bodyParams);
if (arguments != null && !arguments.getArguments().isEmpty()) {
if (!arguments.getArguments().isEmpty()) {
sampler.setArguments(arguments);
}
}
@ -205,7 +208,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
}
// 场景头
if (config != null && CollectionUtils.isNotEmpty(config.getHeaders())) {
if (CollectionUtils.isNotEmpty(config.getHeaders())) {
ElementUtil.setHeader(httpSamplerTree, config.getHeaders(), this.getName());
}
// 环境通用请求头
@ -216,10 +219,10 @@ public class MsHTTPSamplerProxy extends MsTestElement {
//添加csv
ElementUtil.addApiVariables(config, httpSamplerTree, this.getProjectId());
//判断是否要开启DNS
if (config.isEffective(this.getProjectId()) && config.getConfig().get(this.getProjectId()).getCommonConfig() != null
&& config.getConfig().get(this.getProjectId()).getCommonConfig().isEnableHost()) {
MsDNSCacheManager.addEnvironmentVariables(httpSamplerTree, this.getName(), config.getConfig().get(this.getProjectId()));
MsDNSCacheManager.addEnvironmentDNS(httpSamplerTree, this.getName(), config.getConfig().get(this.getProjectId()), httpConfig);
if (config.isEffective(this.getProjectId()) && config.get(this.getProjectId()).getCommonConfig() != null
&& config.get(this.getProjectId()).getCommonConfig().isEnableHost()) {
MsDNSCacheManager.addEnvironmentVariables(httpSamplerTree, this.getName(), config.get(this.getProjectId()));
MsDNSCacheManager.addEnvironmentDNS(httpSamplerTree, this.getName(), config.get(this.getProjectId()), httpConfig);
}
if (this.authManager != null && MsAuthManager.mechanismMap.containsKey(this.authManager.getVerification())) {
this.authManager.setAuth(httpSamplerTree, this.authManager, sampler);
@ -295,11 +298,11 @@ public class MsHTTPSamplerProxy extends MsTestElement {
private void initConnectAndResponseTimeout(ParameterConfig config) {
if (config.isEffective(this.getProjectId())) {
String useEvnId = config.getConfig().get(this.getProjectId()).getEnvironmentId();
String useEvnId = config.get(this.getProjectId()).getEnvironmentId();
if (StringUtils.isNotEmpty(useEvnId) && !StringUtils.equals(useEvnId, this.getEnvironmentId())) {
this.setEnvironmentId(useEvnId);
}
CommonConfig commonConfig = config.getConfig().get(this.getProjectId()).getCommonConfig();
CommonConfig commonConfig = config.get(this.getProjectId()).getCommonConfig();
if (commonConfig != null) {
if (this.getConnectTimeout() == null || StringUtils.equals(this.getConnectTimeout(), DEF_TIME_OUT)) {
if (commonConfig.getRequestTimeout() != 0) {
@ -316,46 +319,47 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
private EnvironmentConfig getEnvironmentConfig(ParameterConfig config) {
return config.getConfig().get(this.getProjectId());
return config.get(this.getProjectId());
}
private HttpConfig getHttpConfig(ParameterConfig config) {
if (config.isEffective(this.getProjectId())) {
EnvironmentConfig environmentConfig = config.getConfig().get(this.getProjectId());
if (environmentConfig != null) {
String useEvnId = environmentConfig.getEnvironmentId();
if (this.authManager == null && environmentConfig.getAuthManager() != null && environmentConfig.getAuthManager().getAuthManager() != null) {
if (config.isEffective(this.getProjectId()) || config.isEffective(config.getCurrentProjectId())) {
EnvironmentConfig envConfig = config.get(this.getProjectId());
if (envConfig != null) {
String useEnvId = envConfig.getEnvironmentId();
if (this.authManager == null && envConfig.getAuthManager() != null
&& envConfig.getAuthManager().getAuthManager() != null) {
MsAuthManager authManager = new MsAuthManager();
BeanUtils.copyBean(authManager, environmentConfig.getAuthManager().getAuthManager());
BeanUtils.copyBean(authManager, envConfig.getAuthManager().getAuthManager());
this.authManager = authManager;
}
if (StringUtils.isNotEmpty(useEvnId) && !StringUtils.equals(useEvnId, this.getEnvironmentId())) {
this.setEnvironmentId(useEvnId);
if (StringUtils.isNotEmpty(useEnvId) && !StringUtils.equals(useEnvId, this.getEnvironmentId())) {
this.setEnvironmentId(useEnvId);
}
HttpConfig httpConfig = config.matchConfig(this);
if (environmentConfig.getPreProcessor() != null) {
HttpConfig httpConfig = config.matchConfig(this, envConfig.getHttpConfig());
if (envConfig.getPreProcessor() != null) {
MsJSR223PreProcessor msJSR223PreProcessor = new MsJSR223PreProcessor();
if (environmentConfig.getPreProcessor() != null) {
BeanUtils.copyBean(msJSR223PreProcessor, environmentConfig.getPreProcessor());
if (envConfig.getPreProcessor() != null) {
BeanUtils.copyBean(msJSR223PreProcessor, envConfig.getPreProcessor());
httpConfig.setPreProcessor(msJSR223PreProcessor);
}
}
if (environmentConfig.getPostProcessor() != null) {
if (envConfig.getPostProcessor() != null) {
MsJSR223PostProcessor postProcessor = new MsJSR223PostProcessor();
if (environmentConfig.getPostProcessor() != null) {
BeanUtils.copyBean(postProcessor, environmentConfig.getPostProcessor());
if (envConfig.getPostProcessor() != null) {
BeanUtils.copyBean(postProcessor, envConfig.getPostProcessor());
httpConfig.setPostProcessor(postProcessor);
}
}
httpConfig.setGlobalScriptConfig(environmentConfig.getGlobalScriptConfig());
if (CollectionUtils.isNotEmpty(environmentConfig.getAssertions())) {
httpConfig.setAssertions(ElementUtil.copyAssertion(environmentConfig.getAssertions()));
httpConfig.setGlobalScriptConfig(envConfig.getGlobalScriptConfig());
if (CollectionUtils.isNotEmpty(envConfig.getAssertions())) {
httpConfig.setAssertions(ElementUtil.copyAssertion(envConfig.getAssertions()));
}
if ((!this.isCustomizeReq() || this.isRefEnvironment) && environmentConfig.isUseErrorCode()) {
if ((!this.isCustomizeReq() || this.isRefEnvironment) && envConfig.isUseErrorCode()) {
FakeError fakeError = new FakeError();
fakeError.setHigherThanError(environmentConfig.isHigherThanError());
fakeError.setHigherThanError(envConfig.isHigherThanError());
fakeError.setProjectId(this.getProjectId());
fakeError.setHigherThanSuccess(environmentConfig.isHigherThanSuccess());
fakeError.setHigherThanSuccess(envConfig.isHigherThanSuccess());
httpConfig.setFakeError(fakeError);
}
return httpConfig;
@ -367,14 +371,8 @@ public class MsHTTPSamplerProxy extends MsTestElement {
private void setSamplerPath(ParameterConfig config, HttpConfig httpConfig, HTTPSamplerProxy sampler) {
try {
if (config.isEffective(this.getProjectId())) {
if (httpConfig == null && !ElementUtil.isURL(this.getUrl())) {
MSException.throwException("未匹配到环境,请检查环境配置");
}
if (StringUtils.isEmpty(httpConfig.getProtocol())) {
MSException.throwException(this.getName() + "接口,对应的环境无协议,请完善环境信息");
}
if (StringUtils.isEmpty(this.useEnvironment)) {
this.useEnvironment = config.getConfig().get(this.getProjectId()).getEnvironmentId();
this.useEnvironment = config.get(this.getProjectId()).getEnvironmentId();
}
String url = httpConfig.getProtocol() + "://" + httpConfig.getSocket();
if (isUrl()) {
@ -386,7 +384,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
String envPath = sampler.getPath();
if (CollectionUtils.isNotEmpty(this.getRest()) && this.isRest()) {
envPath = getRestParameters(envPath);
sampler.setProperty("HTTPSampler.path", envPath);
sampler.setProperty(HTTPSamplerBase.PATH, envPath);
}
if (CollectionUtils.isNotEmpty(this.getArguments())) {
String path = postQueryParameters(envPath);
@ -395,34 +393,30 @@ public class MsHTTPSamplerProxy extends MsTestElement {
path = "/" + path;
}
}
sampler.setProperty("HTTPSampler.path", path);
sampler.setProperty(HTTPSamplerBase.PATH, path);
}
} else {
String url = this.getUrl();
if (StringUtils.isEmpty(url)) {
MSException.throwException("当前步骤:" + this.getName() + " 环境为空,请重新选择环境");
}
String envPath = "";
String envPath;
try {
URL urlObject = new URL(url);
if (url.contains("${")) {
envPath = url;
} else {
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), StandardCharsets.UTF_8.name()));
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), StandardCharsets.UTF_8));
envPath = urlObject.getPath();
sampler.setPort(urlObject.getPort());
}
sampler.setProtocol(urlObject.getProtocol());
} catch (Exception e) {
envPath = url;
envPath = url == null ? "" : url;
}
sampler.setProperty("HTTPSampler.path", envPath, StandardCharsets.UTF_8.name());
sampler.setProperty(HTTPSamplerBase.PATH, envPath, StandardCharsets.UTF_8.name());
if (CollectionUtils.isNotEmpty(this.getRest()) && this.isRest()) {
envPath = getRestParameters(URLDecoder.decode(URLEncoder.encode(envPath, StandardCharsets.UTF_8.name()), StandardCharsets.UTF_8.name()));
sampler.setProperty("HTTPSampler.path", envPath);
envPath = getRestParameters(URLDecoder.decode(URLEncoder.encode(envPath, StandardCharsets.UTF_8), StandardCharsets.UTF_8));
sampler.setProperty(HTTPSamplerBase.PATH, envPath);
}
if (CollectionUtils.isNotEmpty(this.getArguments())) {
sampler.setProperty("HTTPSampler.path", postQueryParameters(URLDecoder.decode(URLEncoder.encode(envPath, StandardCharsets.UTF_8.name()), StandardCharsets.UTF_8.name())));
sampler.setProperty(HTTPSamplerBase.PATH, postQueryParameters(URLDecoder.decode(URLEncoder.encode(envPath, StandardCharsets.UTF_8), StandardCharsets.UTF_8)));
}
}
} catch (Exception e) {
@ -451,7 +445,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
} else {
envPath = StringUtils.join(urlObject.getPath(), envPath);
}
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), StandardCharsets.UTF_8.name()));
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), StandardCharsets.UTF_8));
sampler.setProtocol(httpConfig.getProtocol());
sampler.setPort(urlObject.getPort());
} else {
@ -465,30 +459,30 @@ public class MsHTTPSamplerProxy extends MsTestElement {
} else {
URL urlObject = new URL(this.path);
envPath = StringUtils.equals(urlObject.getPath(), "/") ? "" : urlObject.getFile();
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), StandardCharsets.UTF_8.name()));
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), StandardCharsets.UTF_8));
sampler.setProtocol(urlObject.getProtocol());
}
sampler.setProperty("HTTPSampler.path", URLDecoder.decode(URLEncoder.encode(envPath, StandardCharsets.UTF_8.name()), StandardCharsets.UTF_8.name()));
sampler.setProperty(HTTPSamplerBase.PATH, URLDecoder.decode(URLEncoder.encode(envPath, StandardCharsets.UTF_8), StandardCharsets.UTF_8));
}
}
private void fullPath(HTTPSamplerProxy sampler, String url) {
if (this.isCustomizeReq() && StringUtils.isNotEmpty(this.getUrl())) {
url = this.getUrl();
sampler.setProperty("HTTPSampler.path", url);
}
if (StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
url = url.replace(this.getPort(), "10990");
}
try {
if (this.isCustomizeReq() && StringUtils.isNotEmpty(this.getUrl())) {
url = this.getUrl();
sampler.setProperty(HTTPSamplerBase.PATH, url);
}
if (StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
url = url.replace(this.getPort(), "10990");
}
URL urlObject = new URL(url);
String envPath;
if (url.contains("${")) {
envPath = url;
} else {
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), StandardCharsets.UTF_8.name()));
if (urlObject.getPort() > 0 && urlObject.getPort() == 10990 && StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
sampler.setProperty("HTTPSampler.port", this.getPort());
sampler.setDomain(URLDecoder.decode(urlObject.getHost(), StandardCharsets.UTF_8));
if (urlObject.getPort() == 10990 && StringUtils.isNotEmpty(this.getPort()) && this.getPort().startsWith("${")) {
sampler.setProperty(HTTPSamplerBase.PORT, this.getPort());
} else if (urlObject.getPort() != -1) {
sampler.setPort(urlObject.getPort());
}
@ -496,19 +490,15 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
sampler.setProtocol(urlObject.getProtocol());
sampler.setProperty("HTTPSampler.path", URLDecoder.decode(envPath, StandardCharsets.UTF_8.name()), StandardCharsets.UTF_8.name());
sampler.setProperty(HTTPSamplerBase.PATH, URLDecoder.decode(envPath, StandardCharsets.UTF_8), StandardCharsets.UTF_8.name());
} catch (Exception e) {
sampler.setProperty("HTTPSampler.path", url);
sampler.setProperty(HTTPSamplerBase.PATH, url);
LogUtil.error(e.getMessage(), e);
}
}
/**
* 加载SSL认证
*
* @param config
* @param httpSamplerTree
* @return
*/
private void addCertificate(ParameterConfig config, HashTree httpSamplerTree) {
if (config != null && config.isEffective(this.getProjectId()) && getEnvironmentConfig(config).getSslConfig() != null) {
@ -561,24 +551,15 @@ public class MsHTTPSamplerProxy extends MsTestElement {
/**
* 自定义请求如果是完整url时不拼接mock信息
*
* @param url
* @return
*/
private boolean isCustomizeReqCompleteUrl(String url) {
if (isCustomizeReq() && StringUtils.isNotEmpty(url) && (url.startsWith("http://") || url.startsWith("https://"))) {
return true;
}
return false;
return isCustomizeReq() && StringUtils.isNotEmpty(url) && (url.startsWith("http://") || url.startsWith("https://"));
}
private boolean isUrl() {
// 自定义字段没有引用环境则非url
if (this.isCustomizeReq()) {
if (this.isRefEnvironment) {
return false;
}
return true;
return !this.isRefEnvironment;
}
return false;
}
@ -624,7 +605,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
}
} catch (Exception ex) {
ex.printStackTrace();
LoggerUtil.error(this.getName(), ex);
}
return path;
}
@ -632,7 +613,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
private String postQueryParameters(String path) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(path);
if (path.indexOf("?") != -1) {
if (path.contains("?")) {
stringBuffer.append("&");
} else {
stringBuffer.append("?");

View File

@ -15,7 +15,6 @@ import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.constants.CommonConstants;
import io.metersphere.commons.constants.ElementConstants;
import io.metersphere.commons.constants.MsTestElementConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.commons.vo.JDBCProcessorVO;
import io.metersphere.environment.service.BaseEnvironmentService;
@ -67,7 +66,7 @@ public class MsJDBCSampler extends MsTestElement {
} else if (config.isOperating() && StringUtils.isNotEmpty(config.getOperatingSampleTestName())) {
this.setName(config.getOperatingSampleTestName());
}
if (this.getReferenced() != null && MsTestElementConstants.REF.name().equals(this.getReferenced())) {
boolean ref = this.setRefElement();
if (!ref) {
@ -76,9 +75,9 @@ public class MsJDBCSampler extends MsTestElement {
}
hashTree = this.getHashTree();
}
if (config != null && config.getConfig() == null) {
if (MapUtils.isEmpty(config.getConfig()) && config.isApi()) {
// 单独接口执行
this.setProjectId(config.getProjectId());
this.setProjectId(config.getCurrentProjectId());
config.setConfig(ElementUtil.getEnvironmentConfig(StringUtils.isNotEmpty(useEnvironment) ? useEnvironment : environmentId, this.getProjectId()));
}
@ -88,9 +87,9 @@ public class MsJDBCSampler extends MsTestElement {
this.dataSource = null;
envConfig = this.initDataSource();
} else {
if (config.isEffective(this.getProjectId()) && CollectionUtils.isNotEmpty(config.getConfig().get(this.getProjectId()).getDatabaseConfigs())
&& isDataSource(config.getConfig().get(this.getProjectId()).getDatabaseConfigs())) {
EnvironmentConfig environmentConfig = config.getConfig().get(this.getProjectId());
if (config.isEffective(this.getProjectId()) && CollectionUtils.isNotEmpty(config.get(this.getProjectId()).getDatabaseConfigs())
&& isDataSource(config.get(this.getProjectId()).getDatabaseConfigs())) {
EnvironmentConfig environmentConfig = config.get(this.getProjectId());
if (environmentConfig.getDatabaseConfigs() != null && StringUtils.isNotEmpty(environmentConfig.getEnvironmentId())) {
this.environmentId = environmentConfig.getEnvironmentId();
}
@ -102,8 +101,8 @@ public class MsJDBCSampler extends MsTestElement {
} else {
// 取当前环境下默认的一个数据源
if (config.isEffective(this.getProjectId())) {
if (config.getConfig().get(this.getProjectId()) != null) {
envConfig = config.getConfig().get(this.getProjectId());
if (config.get(this.getProjectId()) != null) {
envConfig = config.get(this.getProjectId());
if (CollectionUtils.isNotEmpty(envConfig.getDatabaseConfigs())) {
LoggerUtil.info(this.getName() + ":开始获取当前环境下默认数据源");
DatabaseConfig dataSourceOrg = ElementUtil.dataSource(getProjectId(), dataSourceId, envConfig);
@ -119,9 +118,7 @@ public class MsJDBCSampler extends MsTestElement {
}
}
if (this.dataSource == null) {
LoggerUtil.info(this.getName() + " 当前项目id", this.getProjectId() + " 当前环境配置信息", JSONUtil.toJSONString(config));
String message = "数据源为空请选择数据源";
MSException.throwException(StringUtils.isNotEmpty(this.getName()) ? this.getName() + "" + message : message);
LoggerUtil.error(this.getName() + ",未找到数据源", JSONUtil.toJSONString(config));
}
JDBCSampler jdbcSampler = jdbcSampler(config);
// 失败重试

View File

@ -91,7 +91,7 @@ public class MsTCPSampler extends MsTestElement {
} else if (config.isOperating() && StringUtils.isNotEmpty(config.getOperatingSampleTestName())) {
this.setName(config.getOperatingSampleTestName());
}
if (this.getReferenced() != null && MsTestElementConstants.REF.name().equals(this.getReferenced())) {
boolean ref = this.setRefElement();
if (!ref) {
@ -122,16 +122,16 @@ public class MsTCPSampler extends MsTestElement {
break;
}
}
if (config.getConfig() == null) {
if (MapUtils.isEmpty(config.getConfig()) && config.isApi()) {
// 单独接口执行
if (StringUtils.isNotEmpty(config.getProjectId())) {
this.setProjectId(config.getProjectId());
if (StringUtils.isNotEmpty(config.getCurrentProjectId())) {
this.setProjectId(config.getCurrentProjectId());
}
config.setConfig(ElementUtil.getEnvironmentConfig(StringUtils.isNotEmpty(this.getEnvironmentId()) ? this.getEnvironmentId() : useEnvironment, this.getProjectId()));
}
EnvironmentConfig envConfig = null;
if (config.getConfig() != null) {
envConfig = config.getConfig().get(this.projectId);
envConfig = config.get(this.projectId);
parseEnvironment(envConfig);
}
// 添加环境中的公共变量
@ -303,17 +303,17 @@ public class MsTCPSampler extends MsTestElement {
List<StringProperty> threadValues = new ArrayList<>();
if (CollectionUtils.isNotEmpty(this.parameters)) {
this.parameters.forEach(item -> {
names.add(new StringProperty(new Integer(new Random().nextInt(1000000)).toString(), item.getName()));
names.add(new StringProperty(String.valueOf(new Random().nextInt(1000000)), item.getName()));
String value = item.getValue();
if (StringUtils.isNotEmpty(value)) {
value = this.formatMockValue(value);
threadValues.add(new StringProperty(new Integer(new Random().nextInt(1000000)).toString(), value));
threadValues.add(new StringProperty(String.valueOf(new Random().nextInt(1000000)), value));
}
});
}
userParameters.setNames(new CollectionProperty(UserParameters.NAMES, names));
List<CollectionProperty> collectionPropertyList = new ArrayList<>();
collectionPropertyList.add(new CollectionProperty(new Integer(new Random().nextInt(1000000)).toString(), threadValues));
collectionPropertyList.add(new CollectionProperty(String.valueOf(new Random().nextInt(1000000)), threadValues));
userParameters.setThreadLists(new CollectionProperty(UserParameters.THREAD_VALUES, collectionPropertyList));
tree.add(userParameters);
}

View File

@ -1,8 +1,12 @@
package io.metersphere.api.dto.scenario;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DatabaseConfig {
private String id;

View File

@ -75,8 +75,6 @@ public class ApiCaseExecuteService {
/**
* 测试计划case执行
*
* @param request
* @return
*/
public List<MsExecResponseDTO> run(BatchRunDefinitionRequest request) {
List<MsExecResponseDTO> responseDTOS = new LinkedList<>();

View File

@ -136,7 +136,7 @@ public class ApiCaseSerialService {
parse(element, testId, envId, caseWithBLOBs.getProjectId());
group.getHashTree().add(JSONUtil.parseObject(element.toString(), MsTestElement.class));
testPlan.getHashTree().add(group);
ParameterConfig config = new ParameterConfig();
ParameterConfig config = new ParameterConfig(projectId, true);
if (runRequest.isRetryEnable() && runRequest.getRetryNum() > 0) {
config.setRetryNum(runRequest.getRetryNum());
}

View File

@ -196,18 +196,17 @@ public class ApiExecuteService {
this.add(request.getProjectId());
}}, new BooleanPool()).keySet().stream().toList());
threadGroup.getHashTree().add(request);
ParameterConfig config = new ParameterConfig();
config.setProjectId(request.getProjectId());
ParameterConfig config = new ParameterConfig(request.getProjectId(), true);
config.setCurrentProjectId(request.getProjectId());
return testPlan.generateHashTree(config);
}
private JmeterRunRequestDTO initRunRequest(RunDefinitionRequest request, List<MultipartFile> bodyFiles) {
ParameterConfig config = new ParameterConfig();
config.setProjectId(request.getProjectId());
ParameterConfig config = new ParameterConfig(request.getProjectId(), true);
config.setApi(true);
Map<String, EnvironmentConfig> envConfig = new HashMap<>();
Map<String, String> map = request.getEnvironmentMap();
if (map != null && map.size() > 0) {
if (MapUtils.isNotEmpty(map)) {
for (String key : map.keySet()) {
ApiTestEnvironmentWithBLOBs environment = apiTestEnvironmentService.get(map.get(key));
if (environment != null) {
@ -317,8 +316,7 @@ public class ApiExecuteService {
BaseEnvironmentService apiTestEnvironmentService = CommonBeanFactory.getBean(BaseEnvironmentService.class);
ApiTestEnvironmentWithBLOBs environment = apiTestEnvironmentService.get(request.getEnvironmentId());
ParameterConfig parameterConfig = new ParameterConfig();
parameterConfig.setApi(true);
ParameterConfig parameterConfig = new ParameterConfig(projectId, true);
Map<String, EnvironmentConfig> envConfig = new HashMap<>(16);
if (environment != null && environment.getConfig() != null) {
EnvironmentConfig environmentConfig = JSONUtil.parseObject(environment.getConfig(), EnvironmentConfig.class);

View File

@ -3,7 +3,6 @@ package io.metersphere.api.exec.engine;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.ExecListener;
import io.fabric8.kubernetes.client.dsl.ExecWatch;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.JSON;
@ -32,9 +31,9 @@ public class KubernetesApiExec {
return nodePods.get(new Random().nextInt(nodePods.size()));
}
public static ExecWatch newExecWatch(KubernetesClient client, String namespace, String podName, String command, JmeterRunRequestDTO runRequest) {
public static void newExecWatch(KubernetesClient client, String namespace, String podName, String command, JmeterRunRequestDTO runRequest) {
LoggerUtil.info("CURL 命令:【 " + command + "");
return client.pods().inNamespace(namespace).withName(podName)
client.pods().inNamespace(namespace).withName(podName)
.readingInput(System.in)
.writingOutput(System.out)
.writingError(System.err)
@ -43,35 +42,30 @@ public class KubernetesApiExec {
.exec("sh", "-c", command);
}
private static class SimpleListener implements ExecListener {
private JmeterRunRequestDTO runRequest;
SimpleListener(JmeterRunRequestDTO runRequest) {
this.runRequest = runRequest;
}
private record SimpleListener(JmeterRunRequestDTO runRequest) implements ExecListener {
@Override
public void onOpen() {
LoggerUtil.info("K8s命令执行监听 onOpen ", runRequest.getReportId());
}
@Override
public void onFailure(Throwable t, Response response) {
LoggerUtil.info("进入K8s onFailure处理");
if (runRequest != null) {
LoggerUtil.info("请求参数:", JSON.toJSONString(runRequest));
RemakeReportService apiScenarioReportService = CommonBeanFactory.getBean(RemakeReportService.class);
apiScenarioReportService.testEnded(runRequest, StringUtils.join("K8s执行异常", t.getMessage()));
} else {
MSException.throwException("K8S 节点执行错误:" + t.getMessage());
public void onOpen() {
LoggerUtil.info("K8s命令执行监听 onOpen ", runRequest.getReportId());
}
LoggerUtil.error("K8S 节点执行错误:", t.getMessage());
}
@Override
public void onClose(int code, String reason) {
LoggerUtil.info(code + "_" + reason, runRequest.getReportId());
LoggerUtil.info("K8s命令执行监听 onClose ", runRequest.getReportId());
@Override
public void onFailure(Throwable t, Response response) {
LoggerUtil.info("进入K8s onFailure处理");
if (runRequest != null) {
LoggerUtil.info("请求参数:", JSON.toJSONString(runRequest));
RemakeReportService apiScenarioReportService = CommonBeanFactory.getBean(RemakeReportService.class);
apiScenarioReportService.testEnded(runRequest, StringUtils.join("K8s执行异常", t.getMessage()));
} else {
MSException.throwException("K8S 节点执行错误:" + t.getMessage());
}
LoggerUtil.error("K8S 节点执行错误:", t.getMessage());
}
@Override
public void onClose(int code, String reason) {
LoggerUtil.info(code + "_" + reason, runRequest.getReportId());
LoggerUtil.info("K8s命令执行监听 onClose ", runRequest.getReportId());
}
}
}
}

View File

@ -1,21 +0,0 @@
package io.metersphere.api.exec.queue;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.utils.LoggerUtil;
public class ExecTask implements Runnable {
private JmeterRunRequestDTO request;
public ExecTask(JmeterRunRequestDTO request) {
this.request = request;
}
public JmeterRunRequestDTO getRequest() {
return this.request;
}
@Override
public void run() {
LoggerUtil.info("任务执行超时", request.getReportId());
}
}

View File

@ -1,183 +0,0 @@
package io.metersphere.api.exec.queue;
import io.metersphere.api.jmeter.ApiLocalRunner;
import io.metersphere.commons.utils.NamedThreadFactory;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Queue;
import java.util.concurrent.*;
@Service
public class ExecThreadPoolExecutor {
// 线程池维护线程的最少数量
private final static int CORE_POOL_SIZE = 10;
// 线程池维护线程的最大数量
private final static int MAX_POOL_SIZE = 10;
// 线程池维护线程所允许的空闲时间
private final static int KEEP_ALIVE_TIME = 1;
// 线程池所使用的缓冲队列大小
private final static int WORK_QUEUE_SIZE = 10000;
private MsRejectedExecutionHandler msRejectedExecutionHandler = new MsRejectedExecutionHandler();
/**
* 创建线程池
*/
private final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue(WORK_QUEUE_SIZE),
new NamedThreadFactory("MS-JMETER-RUN-TASK"),
msRejectedExecutionHandler);
/**
* 缓冲区调度线程池
*/
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new NamedThreadFactory("MS-BUFFER-SCHEDULED"));
public void addTask(JmeterRunRequestDTO requestDTO) {
ExecTask task = new ExecTask(requestDTO);
threadPool.execute(task);
outApiThreadPoolExecutorLogger("报告:[" + requestDTO.getReportId() + "] 资源:[" + requestDTO.getTestId() + "] 加入执行队列");
}
/**
* 调度线程池检查缓冲区
*/
final ScheduledFuture scheduledFuture = scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//判断缓冲队列是否存在记录
if (CollectionUtils.isNotEmpty(msRejectedExecutionHandler.getBufferQueue())) {
//当线程池的队列容量少于WORK_QUEUE_SIZE则开始把缓冲队列的任务 加入到 线程池
if (threadPool.getQueue().size() < WORK_QUEUE_SIZE) {
JmeterRunRequestDTO requestDTO = msRejectedExecutionHandler.getBufferQueue().poll();
ExecTask task = new ExecTask(requestDTO);
threadPool.submit(task);
LoggerUtil.info("把缓冲区任务重新添加到线程池报告ID" + requestDTO.getReportId());
}
}
}
}, 0, 2, TimeUnit.SECONDS);
/**
* 终止线程池和调度线程池
*/
public void shutdown() {
//true表示如果定时任务在执行立即中止false则等待任务结束后再停止
LoggerUtil.info("终止执行线程池和调度线程池:" + scheduledFuture.cancel(true));
scheduler.shutdown();
threadPool.shutdown();
}
/**
* 保留两位小数
*/
private String divide(int num1, int num2) {
return String.format("%1.2f%%", Double.parseDouble(num1 + StringUtils.EMPTY) / Double.parseDouble(num2 + StringUtils.EMPTY) * 100);
}
public void outApiThreadPoolExecutorLogger(String message) {
ArrayBlockingQueue queue = (ArrayBlockingQueue) threadPool.getQueue();
StringBuffer buffer = new StringBuffer(StringUtils.LF + message);
buffer.append(StringUtils.LF).append("线程池详情:").append(StringUtils.LF);
buffer.append(" 核心线程数:" + threadPool.getCorePoolSize()).append(StringUtils.LF);
buffer.append(" 活动线程数:" + threadPool.getActiveCount()).append(" (略有波动非精确数据)").append(StringUtils.LF);
buffer.append(" 最大线程数:" + threadPool.getMaximumPoolSize()).append(StringUtils.LF);
buffer.append(" 线程池活跃度:" + divide(threadPool.getActiveCount(), threadPool.getMaximumPoolSize())).append(StringUtils.LF);
buffer.append(" 最大队列数:" + (queue.size() + queue.remainingCapacity())).append(StringUtils.LF);
buffer.append(" 当前排队线程数:" + (msRejectedExecutionHandler.getBufferQueue().size() + queue.size())).append(StringUtils.LF);
buffer.append(" 执行中队列大小:" + PoolExecBlockingQueueUtil.queue.size()).append(StringUtils.LF);
buffer.append(" 队列使用度:" + divide(queue.size(), queue.size() + queue.remainingCapacity()));
LoggerUtil.info(buffer.toString());
if (queue.size() > 0 && LoggerUtil.getLogger().isDebugEnabled()) {
LoggerUtil.debug(this.getWorkerQueue());
}
}
public void setCorePoolSize(int maximumPoolSize) {
try {
if (maximumPoolSize != threadPool.getMaximumPoolSize()) {
threadPool.setMaximumPoolSize(maximumPoolSize);
int corePoolSize = maximumPoolSize > 500 ? 500 : maximumPoolSize;
if (corePoolSize > CORE_POOL_SIZE) {
threadPool.setCorePoolSize(corePoolSize);
}
threadPool.allowCoreThreadTimeOut(true);
LoggerUtil.info("AllCoreThreads: " + threadPool.prestartAllCoreThreads());
}
} catch (Exception e) {
LoggerUtil.error("设置线程参数异常:", e);
}
}
public void removeQueue(String reportId) {
ApiLocalRunner.stop(reportId);
// 检查缓冲区
Queue<JmeterRunRequestDTO> bufferQueue = msRejectedExecutionHandler.getBufferQueue();
if (CollectionUtils.isNotEmpty(bufferQueue)) {
bufferQueue.forEach(item -> {
if (item != null && StringUtils.equals(item.getReportId(), reportId)) {
bufferQueue.remove(item);
}
});
}
// 检查等待队列
BlockingQueue workerQueue = threadPool.getQueue();
workerQueue.forEach(item -> {
ExecTask task = (ExecTask) item;
if (task != null && task.getRequest() != null && StringUtils.equals(task.getRequest().getReportId(), reportId)) {
workerQueue.remove(item);
}
});
}
public void removeAllQueue() {
// 检查缓冲区
msRejectedExecutionHandler.getBufferQueue().clear();
// 检查等待队列
threadPool.getQueue().clear();
}
public boolean check(String reportId) {
// 检查缓冲区
Queue<JmeterRunRequestDTO> bufferQueue = msRejectedExecutionHandler.getBufferQueue();
if (CollectionUtils.isNotEmpty(bufferQueue)) {
return bufferQueue.stream().filter(task -> StringUtils.equals(task.getReportId(), reportId)).count() > 0;
}
// 检查等待队列
BlockingQueue workerQueue = threadPool.getQueue();
return workerQueue.stream().filter(task -> StringUtils.equals(((ExecTask) task).getRequest().getReportId(), reportId)).count() > 0;
}
public boolean checkPlanReport(String planReportId) {
// 检查缓冲区
Queue<JmeterRunRequestDTO> bufferQueue = msRejectedExecutionHandler.getBufferQueue();
if (CollectionUtils.isNotEmpty(bufferQueue)) {
return bufferQueue.stream().filter(task -> StringUtils.equals(task.getTestPlanReportId(), planReportId)).count() > 0;
}
// 检查等待队列
BlockingQueue workerQueue = threadPool.getQueue();
return workerQueue.stream().filter(task -> StringUtils.equals(((ExecTask) task).getRequest().getTestPlanReportId(), planReportId)).count() > 0;
}
public String getWorkerQueue() {
StringBuffer buffer = new StringBuffer();
BlockingQueue workerQueue = threadPool.getQueue();
workerQueue.forEach(item -> {
ExecTask task = (ExecTask) item;
if (task.getRequest() != null) {
buffer.append("等待队列报告:【 " + task.getRequest().getReportId() + "】资源:【 " + task.getRequest().getTestId() + "").append(StringUtils.LF);
}
});
return buffer.toString();
}
}

View File

@ -1,28 +0,0 @@
package io.metersphere.api.exec.queue;
import io.metersphere.dto.JmeterRunRequestDTO;
import io.metersphere.utils.LoggerUtil;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
public class MsRejectedExecutionHandler implements RejectedExecutionHandler {
/**
* 执行任务缓冲队列,当线程池满了则将任务存入到此缓冲队列
* 这里是否搞个redis/写到磁盘
*/
private Queue<JmeterRunRequestDTO> bufferQueue = new LinkedBlockingQueue<>();
public Queue<JmeterRunRequestDTO> getBufferQueue() {
return bufferQueue;
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//任务加入到缓冲队列
bufferQueue.offer(((ExecTask) r).getRequest());
LoggerUtil.info("执行任务过多,任务加入缓冲区:" + ((ExecTask) r).getRequest().getReportId());
}
}

View File

@ -1,50 +0,0 @@
package io.metersphere.api.exec.queue;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class PoolExecBlockingQueueUtil {
// 系统级队列控制整体并发数量
public static Map<String, BlockingQueue<Object>> queue = new ConcurrentHashMap<>();
private static final String END_SIGN = "RUN-END";
private static final int QUEUE_SIZE = 1;
public static void offer(String key) {
if (StringUtils.isNotEmpty(key) && queue.containsKey(key)) {
try {
queue.get(key).offer(END_SIGN);
} catch (Exception e) {
LogUtil.error(e);
} finally {
queue.remove(key);
}
}
}
public static Object take(String key) {
try {
if (StringUtils.isNotEmpty(key) && !queue.containsKey(key)) {
BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(QUEUE_SIZE);
queue.put(key, blockingQueue);
return blockingQueue.poll(10, TimeUnit.MINUTES);
}
} catch (Exception e) {
LogUtil.error("初始化队列失败:" + e.getMessage());
}
return null;
}
public static void remove(String key) {
if (StringUtils.isNotEmpty(key) && queue.containsKey(key)) {
queue.get(key).offer(END_SIGN);
queue.remove(key);
}
}
}

View File

@ -88,7 +88,7 @@ public class ApiEnvironmentRunningParamService {
itemObj.put(NAME, entry.getKey());
itemObj.put(VALUE, entry.getValue());
itemObj.put(ENABLE, true);
if (variables.length() > 0 && StringUtils.isEmpty(variables.optJSONObject(variables.length() - 1).optString(NAME))) {
if (!variables.isEmpty() && StringUtils.isEmpty(variables.optJSONObject(variables.length() - 1).optString(NAME))) {
variables.put(variables.length() - 1, itemObj);
} else {
variables.put(itemObj);

View File

@ -1,8 +1,8 @@
package io.metersphere.api.exec.scenario;
import io.metersphere.api.dto.EnvironmentCheckDTO;
import io.metersphere.api.dto.EnvironmentType;
import io.metersphere.api.dto.RunModeConfigWithEnvironmentDTO;
import io.metersphere.api.dto.ScenarioEnv;
import io.metersphere.api.dto.automation.ApiScenarioDTO;
import io.metersphere.api.dto.automation.RunScenarioRequest;
import io.metersphere.api.dto.definition.request.ElementUtil;
@ -35,6 +35,7 @@ import io.metersphere.service.definition.ApiDefinitionService;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.springframework.stereotype.Service;
@ -61,36 +62,32 @@ public class ApiScenarioEnvService {
@Resource
private BaseEnvironmentService apiTestEnvironmentService;
public ScenarioEnv getApiScenarioEnv(String definition) {
ScenarioEnv env = new ScenarioEnv();
public EnvironmentCheckDTO getApiScenarioEnv(String definition) {
EnvironmentCheckDTO env = new EnvironmentCheckDTO();
if (StringUtils.isEmpty(definition)) {
return env;
}
List<MsTestElement> hashTree = GenerateHashTreeUtil.getScenarioHashTree(definition);
if (CollectionUtils.isNotEmpty(hashTree)) {
// 过滤掉禁用的步骤
hashTree = hashTree.stream().filter(item -> item.isEnable()).collect(Collectors.toList());
}
List<Boolean> hasFullUrlList = new ArrayList<>();
for (MsTestElement testElement : hashTree) {
this.formatElement(testElement, env, hasFullUrlList);
if (CollectionUtils.isNotEmpty(testElement.getHashTree())) {
getHashTree(testElement.getHashTree(), env, hasFullUrlList);
}
}
env.setFullUrl(!hasFullUrlList.contains(false));
List<Boolean> fullUrls = new ArrayList<>();
getHashTree(hashTree, env, fullUrls);
env.setFullUrl(!fullUrls.contains(false));
return env;
}
private void getHashTree(List<MsTestElement> tree, ScenarioEnv env, List<Boolean> hasFullUrlList) {
private void getHashTree(List<MsTestElement> tree, EnvironmentCheckDTO env, List<Boolean> fullUrls) {
try {
// 过滤掉禁用的步骤
tree = tree.stream().filter(item -> item.isEnable()).collect(Collectors.toList());
tree = tree.stream().filter(MsTestElement::isEnable).collect(Collectors.toList());
for (MsTestElement element : tree) {
this.formatElement(element, env, hasFullUrlList);
if (element instanceof MsScenario
&& BooleanUtils.isTrue(((MsScenario) element).isEnvironmentEnable())) {
fullUrls.add(true);
continue;
}
this.formatElement(element, env, fullUrls);
if (CollectionUtils.isNotEmpty(element.getHashTree())) {
getHashTree(element.getHashTree(), env, hasFullUrlList);
getHashTree(element.getHashTree(), env, fullUrls);
}
}
} catch (Exception e) {
@ -98,7 +95,7 @@ public class ApiScenarioEnvService {
}
}
private void formatElement(MsTestElement testElement, ScenarioEnv env, List<Boolean> hasFullUrlList) {
private void formatElement(MsTestElement testElement, EnvironmentCheckDTO env, List<Boolean> fullUrls) {
if (StringUtils.equals(MsTestElementConstants.REF.name(), testElement.getReferenced())) {
if (StringUtils.equals(testElement.getType(), ElementConstants.HTTP_SAMPLER)) {
MsHTTPSamplerProxy http = (MsHTTPSamplerProxy) testElement;
@ -109,7 +106,7 @@ public class ApiScenarioEnvService {
if (!StringUtils.equalsIgnoreCase(http.getReferenced(), ElementConstants.STEP_CREATED)
|| (http.getIsRefEnvironment() != null && http.getIsRefEnvironment())) {
env.getProjectIds().add(http.getProjectId());
hasFullUrlList.add(false);
fullUrls.add(false);
}
} else if (StringUtils.equals(testElement.getType(), ElementConstants.JDBC_SAMPLER)
|| StringUtils.equals(testElement.getType(), ElementConstants.TCP_SAMPLER)) {
@ -118,18 +115,18 @@ public class ApiScenarioEnvService {
ApiTestCase testCase = apiTestCaseMapper.selectByPrimaryKey(testElement.getId());
if (testCase != null) {
env.getProjectIds().add(testCase.getProjectId());
hasFullUrlList.add(false);
fullUrls.add(false);
}
} else {
ApiDefinition apiDefinition = apiDefinitionService.get(testElement.getId());
if (apiDefinition != null) {
env.getProjectIds().add(apiDefinition.getProjectId());
hasFullUrlList.add(false);
fullUrls.add(false);
}
}
} else {
env.getProjectIds().add(testElement.getProjectId());
hasFullUrlList.add(false);
fullUrls.add(false);
}
} else if (StringUtils.equals(testElement.getType(), ElementConstants.SCENARIO) && StringUtils.isEmpty(testElement.getProjectId())) {
ApiScenarioWithBLOBs apiScenario = apiScenarioMapper.selectByPrimaryKey(testElement.getId());
@ -145,14 +142,14 @@ public class ApiScenarioEnvService {
if (StringUtils.equals(testElement.getType(), ElementConstants.HTTP_SAMPLER)) {
// 校验是否是全路径
MsHTTPSamplerProxy httpSamplerProxy = (MsHTTPSamplerProxy) testElement;
checkCustomEnv(env, httpSamplerProxy.isCustomizeReq(), httpSamplerProxy.getProjectId(), httpSamplerProxy.getIsRefEnvironment(), httpSamplerProxy.getReferenced(), hasFullUrlList);
checkCustomEnv(env, httpSamplerProxy.isCustomizeReq(), httpSamplerProxy.getProjectId(), httpSamplerProxy.getIsRefEnvironment(), httpSamplerProxy.getReferenced(), fullUrls);
} else if (StringUtils.equals(testElement.getType(), ElementConstants.TCP_SAMPLER)) {
MsTCPSampler tcpSampler = (MsTCPSampler) testElement;
checkCustomEnv(env, tcpSampler.isCustomizeReq(), tcpSampler.getProjectId(), tcpSampler.getIsRefEnvironment(), tcpSampler.getReferenced(), hasFullUrlList);
checkCustomEnv(env, tcpSampler.isCustomizeReq(), tcpSampler.getProjectId(), tcpSampler.getIsRefEnvironment(), tcpSampler.getReferenced(), fullUrls);
} else if (StringUtils.equals(testElement.getType(), ElementConstants.JDBC_SAMPLER)) {
MsJDBCSampler jdbcSampler = (MsJDBCSampler) testElement;
checkCustomEnv(env, jdbcSampler.isCustomizeReq(), jdbcSampler.getProjectId(), jdbcSampler.getIsRefEnvironment(), jdbcSampler.getReferenced(), hasFullUrlList);
checkCustomEnv(env, jdbcSampler.isCustomizeReq(), jdbcSampler.getProjectId(), jdbcSampler.getIsRefEnvironment(), jdbcSampler.getReferenced(), fullUrls);
}
}
if (StringUtils.equals(testElement.getType(), ElementConstants.SCENARIO)
@ -161,10 +158,10 @@ public class ApiScenarioEnvService {
}
}
private void checkCustomEnv(ScenarioEnv env, boolean customizeReq, String projectId, Boolean isRefEnvironment, String referenced, List<Boolean> hasFullUrlList) {
private void checkCustomEnv(EnvironmentCheckDTO env, boolean customizeReq, String projectId, Boolean isRefEnvironment, String referenced, List<Boolean> hasFullUrlList) {
if (customizeReq) {
env.getProjectIds().add(projectId);
hasFullUrlList.add(isRefEnvironment == null ? true : !isRefEnvironment);
hasFullUrlList.add(isRefEnvironment == null || !isRefEnvironment);
} else if (!StringUtils.equalsIgnoreCase(referenced, ElementConstants.STEP_CREATED)
|| (isRefEnvironment != null && isRefEnvironment)) {
env.getProjectIds().add(projectId);
@ -174,8 +171,6 @@ public class ApiScenarioEnvService {
/**
* 设置场景的运行环境 环境组/环境JSON
*
* @param apiScenarioWithBLOBs
*/
public void setScenarioEnv(ApiScenarioWithBLOBs apiScenarioWithBLOBs, RunScenarioRequest request) {
if (apiScenarioWithBLOBs == null) {
@ -210,60 +205,22 @@ public class ApiScenarioEnvService {
String definition = apiScenarioWithBLOBs.getScenarioDefinition();
MsScenarioEnv scenario = JSON.parseObject(definition, MsScenarioEnv.class);
Map<String, String> envMap = scenario.getEnvironmentMap();
return this.check(definition, envMap, scenario.getEnvironmentId(), apiScenarioWithBLOBs.getProjectId());
return this.check(definition, envMap, apiScenarioWithBLOBs.getProjectId());
}
return true;
}
private boolean check(String definition, Map<String, String> envMap, String envId, String projectId) {
boolean isEnv = true;
ScenarioEnv apiScenarioEnv = getApiScenarioEnv(definition);
// 所有请求非全路径检查环境
private boolean check(String definition, Map<String, String> envMap, String projectId) {
EnvironmentCheckDTO apiScenarioEnv = getApiScenarioEnv(definition);
// 当前项目是否选择了环境
if (MapUtils.isNotEmpty(envMap) && envMap.containsKey(projectId)) {
return true;
}
// 所有请求是否都是自定义请求且没有引用环境
if (!apiScenarioEnv.getFullUrl()) {
try {
if (envMap == null || envMap.isEmpty()) {
isEnv = false;
} else {
Set<String> projectIds = apiScenarioEnv.getProjectIds();
projectIds.remove(null);
if (CollectionUtils.isNotEmpty(envMap.keySet())) {
for (String id : projectIds) {
Project project = projectMapper.selectByPrimaryKey(id);
String s = envMap.get(id);
if (project == null) {
s = envMap.get(projectId);
}
if (StringUtils.isBlank(s)) {
isEnv = false;
break;
} else {
ApiTestEnvironmentWithBLOBs env = apiTestEnvironmentMapper.selectByPrimaryKey(s);
if (env == null) {
isEnv = false;
break;
}
}
}
} else {
isEnv = false;
}
}
} catch (Exception e) {
isEnv = false;
LogUtil.error(e.getMessage(), e);
}
return false;
}
// 1.8 之前环境是 environmentId
if (!isEnv) {
if (StringUtils.isNotBlank(envId)) {
ApiTestEnvironmentWithBLOBs env = apiTestEnvironmentMapper.selectByPrimaryKey(envId);
if (env != null) {
isEnv = true;
}
}
}
return isEnv;
return true;
}
public boolean verifyPlanScenarioEnv(ApiScenarioWithBLOBs apiScenarioWithBLOBs, TestPlanApiScenarioInfoDTO testPlanApiScenarios) {
@ -283,20 +240,15 @@ public class ApiScenarioEnvService {
envMap = new HashMap<>();
}
}
return this.check(definition, envMap, scenario.getEnvironmentId(), apiScenarioWithBLOBs.getProjectId());
return this.check(definition, envMap, apiScenarioWithBLOBs.getProjectId());
}
return true;
}
/**
* 检查是否存在运行环境若不存在则报错存在的话返回所存在的运行环境
*
* @param request
* @param apiScenarios
* @return <projectId,envIds>
*/
public Map<String, List<String>> checkEnv(RunScenarioRequest request, List<ApiScenarioWithBLOBs> apiScenarios) {
Map<String, List<String>> projectEnvMap = new HashMap<>();
public void checkEnv(RunScenarioRequest request, List<ApiScenarioWithBLOBs> apiScenarios) {
if (StringUtils.equals(request.getRequestOriginator(), CommonConstants.TEST_PLAN)) {
this.checkPlanScenarioEnv(request);
} else if (StringUtils.isNotBlank(request.getRunMode()) && StringUtils.equalsAny(request.getRunMode(), ApiRunMode.SCENARIO.name(), ApiRunMode.SCENARIO_PLAN.name(), ApiRunMode.JENKINS_SCENARIO_PLAN.name())) {
@ -312,7 +264,7 @@ public class ApiScenarioEnvService {
MSException.throwException("场景:" + apiScenarioWithBLOBs.getName() + ",步骤解析错误,检查是否包含插件步骤!");
}
}
if (builder.length() > 0) {
if (!builder.isEmpty()) {
MSException.throwException("场景:" + builder + "运行环境未配置,请检查!");
}
} else if (StringUtils.equals(request.getRunMode(), ApiRunMode.SCHEDULE_SCENARIO.name())) {
@ -324,10 +276,11 @@ public class ApiScenarioEnvService {
}
}
}
return projectEnvMap;
}
//检查测试计划场景的环境没有环境的场景从执行队列中移除
/**
* 检查测试计划场景的环境没有环境的场景从执行队列中移除
*/
public void checkPlanScenarioEnv(RunScenarioRequest request) {
if (request.getProcessVO() != null &&
MapUtils.isNotEmpty(request.getProcessVO().getTestPlanScenarioMap())
@ -354,12 +307,12 @@ public class ApiScenarioEnvService {
public Map<String, List<String>> selectApiScenarioEnv(List<? extends ApiScenarioWithBLOBs> list) {
Map<String, List<String>> projectEnvMap = new LinkedHashMap<>();
for (int i = 0; i < list.size(); i++) {
for (ApiScenarioWithBLOBs apiScenarioWithBLOBs : list) {
try {
Map<String, String> map = new HashMap<>();
String environmentType = list.get(i).getEnvironmentType();
String environmentGroupId = list.get(i).getEnvironmentGroupId();
String env = list.get(i).getEnvironmentJson();
String environmentType = apiScenarioWithBLOBs.getEnvironmentType();
String environmentGroupId = apiScenarioWithBLOBs.getEnvironmentGroupId();
String env = apiScenarioWithBLOBs.getEnvironmentJson();
if (StringUtils.equals(environmentType, EnvironmentType.JSON.name())) {
// 环境属性为空 跳过
if (StringUtils.isBlank(env)) {
@ -394,7 +347,7 @@ public class ApiScenarioEnvService {
}
}
} catch (Exception e) {
LogUtil.error("api scenario environment map incorrect parsing. api scenario id:" + list.get(i).getId());
LogUtil.error("api scenario environment map incorrect parsing. api scenario id:" + apiScenarioWithBLOBs.getId());
}
}
return projectEnvMap;
@ -594,9 +547,9 @@ public class ApiScenarioEnvService {
private boolean isProjectEnvMapNotContainsEnv(LinkedHashMap<String, List<String>> returnMap, String projectName, String envName) {
if (MapUtils.isNotEmpty(returnMap)) {
if (returnMap.containsKey(projectName) && CollectionUtils.isNotEmpty(returnMap.get(projectName)) && returnMap.get(projectName).contains(envName)) {
return false;
}
return !returnMap.containsKey(projectName)
|| !CollectionUtils.isNotEmpty(returnMap.get(projectName))
|| !returnMap.get(projectName).contains(envName);
}
return true;
}

View File

@ -122,9 +122,6 @@ public class ApiScenarioExecuteService {
}
// 检查执行内容合规性
PerformInspectionUtil.scenarioInspection(apiScenarios);
// 环境检查
LoggerUtil.info("Scenario run-执行脚本装载-开始针对所有执行场景进行环境检查");
apiScenarioEnvService.checkEnv(request, apiScenarios);
// 集合报告设置
if (!request.isRerun() && GenerateHashTreeUtil.isSetReport(request.getConfig())) {
if (isSerial(request)) {
@ -412,7 +409,7 @@ public class ApiScenarioExecuteService {
if (StringUtils.equals(request.getEnvironmentType(), EnvironmentType.GROUP.toString())) {
request.setEnvironmentMap(environmentGroupProjectService.getEnvMap(request.getEnvironmentGroupId()));
}
ParameterConfig config = new ParameterConfig();
ParameterConfig config = new ParameterConfig(request.getProjectId(), false);
config.setScenarioId(request.getScenarioId());
if (MapUtils.isNotEmpty(request.getEnvironmentMap())) {
apiScenarioEnvService.setEnvConfig(request.getEnvironmentMap(), config);
@ -445,7 +442,7 @@ public class ApiScenarioExecuteService {
if (testElement != null) {
apiScenario.setName(testElement.getName());
apiScenario.setScenarioDefinition(JSON.toJSONString(testElement));
List<String> projectIdLists =ElementUtil. getProjectIds(apiScenario.getScenarioDefinition());
List<String> projectIdLists = ElementUtil.getProjectIds(apiScenario.getScenarioDefinition());
Map<String, String> envMap = ElementUtil.getProjectEnvMap(projectIdLists, request.getEnvironmentMap());
request.getConfig().setEnvMap(envMap);
report.setEnvConfig(JSON.toJSONString(request.getConfig()));

View File

@ -15,7 +15,7 @@ public class JMeterLoggerAppender extends UnsynchronizedAppenderBase<ILoggingEve
public void append(ILoggingEvent event) {
try {
if (!event.getLevel().levelStr.equals(LogUtil.DEBUG) && StringUtils.isNotEmpty(event.getThreadName())) {
StringBuffer message = new StringBuffer();
StringBuilder message = new StringBuilder();
String threadName = StringUtils.substringBeforeLast(event.getThreadName(), THREAD_SPLIT);
message.append(DateUtils.getTimeStr(event.getTimeStamp())).append(StringUtils.SPACE)
.append(event.getLevel()).append(StringUtils.SPACE)
@ -31,8 +31,7 @@ public class JMeterLoggerAppender extends UnsynchronizedAppenderBase<ILoggingEve
}
}
}
if (message != null && !message.toString().contains("java.net.UnknownHostException")
&& FixedCapacityUtil.containsKey(threadName)) {
if (!message.toString().contains("java.net.UnknownHostException") && FixedCapacityUtil.containsKey(threadName)) {
FixedCapacityUtil.get(threadName).append(message);
}
}

View File

@ -4,7 +4,6 @@ package io.metersphere.api.jmeter;
import io.metersphere.api.dto.definition.request.ElementUtil;
import io.metersphere.api.dto.definition.request.MsTestPlan;
import io.metersphere.api.exec.engine.EngineFactory;
import io.metersphere.api.exec.queue.ExecThreadPoolExecutor;
import io.metersphere.api.jmeter.utils.JmxFileUtil;
import io.metersphere.api.jmeter.utils.ServerConfig;
import io.metersphere.api.jmeter.utils.SmoothWeighted;
@ -28,6 +27,7 @@ import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.util.JMeterUtils;
import org.springframework.context.i18n.LocaleContextHolder;
@ -53,8 +53,6 @@ public class JMeterService {
@Resource
private RemakeReportService remakeReportService;
@Resource
private ExecThreadPoolExecutor execThreadPoolExecutor;
@Resource
private ApiPoolDebugService apiPoolDebugService;
@Resource
private PluginService pluginService;
@ -163,7 +161,7 @@ public class JMeterService {
request.setEnable(config.isEnable());
LoggerUtil.info("开始发送请求【 " + request.getTestId() + " 】到 " + config.getUrl() + " 节点执行", request.getReportId());
ResponseEntity<String> result = restTemplate.postForEntity(config.getUrl(), request, String.class);
if (result == null || !StringUtils.equals("SUCCESS", result.getBody())) {
if (!StringUtils.equals("SUCCESS", result.getBody())) {
LoggerUtil.error("发送请求[ " + request.getTestId() + " ] 到" + config.getUrl() + " 节点执行失败", request.getReportId());
}
} catch (Exception e) {
@ -182,7 +180,6 @@ public class JMeterService {
ElementUtil.coverArguments(request.getHashTree());
//解析hashTree是否含有文件库文件
HashTreeUtil.initRepositoryFiles(request);
execThreadPoolExecutor.addTask(request);
}
}
@ -200,7 +197,7 @@ public class JMeterService {
Integer port = node.getPort();
String uri = String.format(BASE_URL + "/jmeter/get/running/queue/" + reportId, nodeIp, port);
ResponseEntity<Boolean> result = restTemplate.getForEntity(uri, Boolean.class);
if (result != null && result.getBody()) {
if (BooleanUtils.isTrue(result.getBody())) {
isRunning = true;
break;
}

View File

@ -1,52 +0,0 @@
package io.metersphere.api.jmeter;
import io.metersphere.api.exec.queue.ExecThreadPoolExecutor;
import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
public class JMeterThreadUtils {
public static String stop(String name) {
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
int noThreads = currentGroup.activeCount();
Thread[] lstThreads = new Thread[noThreads];
currentGroup.enumerate(lstThreads);
StringBuilder threadNames = new StringBuilder();
for (int i = 0; i < noThreads; i++) {
if (lstThreads[i] != null && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(name)) {
LogUtil.error("异常强制处理线程编号:" + i + " = " + lstThreads[i].getName());
threadNames.append(lstThreads[i].getName()).append("");
lstThreads[i].interrupt();
}
}
return threadNames.toString();
}
public static boolean isRunning(String reportId, String testId) {
if (StringUtils.isEmpty(reportId)) {
return false;
}
if (PoolExecBlockingQueueUtil.queue.containsKey(reportId)) {
return true;
}
if (CommonBeanFactory.getBean(ExecThreadPoolExecutor.class).check(reportId)) {
return true;
}
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
int noThreads = currentGroup.activeCount();
Thread[] lstThreads = new Thread[noThreads];
currentGroup.enumerate(lstThreads);
for (int i = 0; i < noThreads; i++) {
if (lstThreads[i] != null) {
if (StringUtils.isNotEmpty(reportId) && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(reportId)) {
return true;
} else if (StringUtils.isNotEmpty(testId) && StringUtils.isNotEmpty(lstThreads[i].getName()) && lstThreads[i].getName().startsWith(testId)) {
return true;
}
}
}
return false;
}
}

View File

@ -65,13 +65,9 @@ public class KafkaListenerTask implements Runnable {
(MapUtils.isNotEmpty(dto.getArbitraryData()) &&
dto.getArbitraryData().containsKey(ENV))) {
String key = RUN_MODE_MAP.get(dto.getRunMode());
if (assortMap.containsKey(key)) {
assortMap.get(key).add(dto);
} else {
assortMap.put(key, new LinkedList<>() {{
this.add(dto);
}});
}
assortMap.put(key, new LinkedList<>() {{
this.add(dto);
}});
}
if (MapUtils.isNotEmpty(assortMap)) {
testResultService.batchSaveResults(assortMap);

View File

@ -1,228 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.metersphere.api.jmeter;
import io.metersphere.api.dto.MsgDTO;
import io.metersphere.api.dto.RequestResultExpandDTO;
import io.metersphere.api.dto.RunningParamKeys;
import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.constants.CommonConstants;
import io.metersphere.commons.utils.*;
import io.metersphere.dto.MsRegexDTO;
import io.metersphere.dto.RequestResult;
import io.metersphere.jmeter.JMeterBase;
import io.metersphere.service.definition.ApiDefinitionEnvService;
import io.metersphere.utils.JMeterVars;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.engine.util.NoThreadClone;
import org.apache.jmeter.reporters.AbstractListenerElement;
import org.apache.jmeter.samplers.*;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.testelement.property.BooleanProperty;
import org.apache.jmeter.threads.JMeterVariables;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 实时结果监听
*/
public class MsDebugListener extends AbstractListenerElement implements SampleListener, Clearable, Serializable,
TestStateListener, Remoteable, NoThreadClone {
private static final String ERROR_LOGGING = "MsResultCollector.error_logging"; // $NON-NLS-1$
private static final String SUCCESS_ONLY_LOGGING = "MsResultCollector.success_only_logging"; // $NON-NLS-1$
private static final String TEST_IS_LOCAL = "*local*"; // $NON-NLS-1$
public static final String TEST_END = "MS_TEST_END";
private String runMode;
private ApiDefinitionEnvService apiDefinitionEnvService;
private Map<String, List<MsRegexDTO>> fakeErrorMap;
@Override
public Object clone() {
MsDebugListener clone = (MsDebugListener) super.clone();
return clone;
}
public void setFakeErrorMap(Map<String, List<MsRegexDTO>> fakeErrorMap) {
this.fakeErrorMap = fakeErrorMap;
}
public boolean isErrorLogging() {
return getPropertyAsBoolean(ERROR_LOGGING);
}
public void setRunMode(String runMode) {
this.runMode = runMode;
}
public final void setSuccessOnlyLogging(boolean value) {
if (value) {
setProperty(new BooleanProperty(SUCCESS_ONLY_LOGGING, true));
} else {
removeProperty(SUCCESS_ONLY_LOGGING);
}
}
/**
* Get the state of successful only logging
*
* @return Flag whether only successful samples should be logged
*/
public boolean isSuccessOnlyLogging() {
return getPropertyAsBoolean(SUCCESS_ONLY_LOGGING, false);
}
public boolean isSampleWanted(boolean success, SampleResult result) {
boolean errorOnly = isErrorLogging();
boolean successOnly = isSuccessOnlyLogging();
return isSampleWanted(success, errorOnly, successOnly) && !StringUtils.containsIgnoreCase(result.getSampleLabel(), "MS_CLEAR_LOOPS_VAR_");
}
public static boolean isSampleWanted(boolean success, boolean errorOnly,
boolean successOnly) {
return (!errorOnly && !successOnly) ||
(success && successOnly) ||
(!success && errorOnly);
}
@Override
public void testEnded(String host) {
LoggerUtil.info("Debug TestEnded " + this.getName());
MsgDTO dto = new MsgDTO();
dto.setExecEnd(false);
dto.setContent(TEST_END);
dto.setReportId("send." + this.getName());
dto.setToReport(this.getName());
LoggerUtil.debug("send. " + this.getName());
WebSocketUtil.sendMessageSingle(dto);
PoolExecBlockingQueueUtil.offer(this.getName());
//删除可能出现的临时文件
FileUtils.deleteBodyTmpFiles(this.getName());
JvmUtil.memoryInfo();
ApiLocalRunner.clearCache(this.getName());
}
@Override
public void testStarted(String host) {
LogUtil.debug("TestStarted " + this.getName());
if (apiDefinitionEnvService == null) {
apiDefinitionEnvService = CommonBeanFactory.getBean(ApiDefinitionEnvService.class);
}
}
@Override
public void testEnded() {
testEnded(TEST_IS_LOCAL);
}
@Override
public void testStarted() {
testStarted(TEST_IS_LOCAL);
}
@Override
public void sampleStarted(SampleEvent e) {
try {
MsgDTO dto = new MsgDTO();
dto.setContent(e.getThreadGroup());
dto.setReportId("send." + this.getName());
dto.setToReport(this.getName());
LoggerUtil.debug("send. " + this.getName());
WebSocketUtil.sendMessageSingle(dto);
} catch (Exception ex) {
LoggerUtil.error("消息推送失败:", ex);
}
}
@Override
public void sampleStopped(SampleEvent e) {
}
@Override
public void sampleOccurred(SampleEvent event) {
SampleResult result = event.getResult();
this.setVars(result);
if (isSampleWanted(result.isSuccessful(), result) && !StringUtils.equals(result.getSampleLabel(), RunningParamKeys.RUNNING_DEBUG_SAMPLER_NAME)) {
RequestResult requestResult = JMeterBase.getRequestResult(result, fakeErrorMap);
if (requestResult != null && ResultParseUtil.isNotAutoGenerateSampler(requestResult)) {
MsgDTO dto = new MsgDTO();
dto.setExecEnd(false);
dto.setReportId("send." + this.getName());
dto.setToReport(this.getName());
dto.setRunMode(runMode);
String console = FixedCapacityUtil.getJmeterLogger(this.getName(), false);
if (StringUtils.isNotEmpty(requestResult.getName())
&& requestResult.getName().startsWith(CommonConstants.PRE_TRANSACTION)) {
requestResult.getSubRequestResults().forEach(transactionResult -> {
this.sendResult(transactionResult, console, dto);
});
} else {
this.sendResult(requestResult, console, dto);
}
LoggerUtil.debug("send. " + this.getName());
}
}
}
private void sendResult(RequestResult requestResult, String console, MsgDTO dto) {
requestResult.getResponseResult().setConsole(console);
//对响应内容进行进一步解析和处理
RequestResultExpandDTO expandDTO = ResponseUtil.parseByRequestResult(requestResult);
if (StringUtils.equalsAnyIgnoreCase(dto.getRunMode(), ApiRunMode.DEFINITION.name(), ApiRunMode.API_PLAN.name())) {
apiDefinitionEnvService.setEnvAndPoolName(requestResult, expandDTO);
}
dto.setContent("result_" + JSON.toJSONString(expandDTO));
WebSocketUtil.sendMessageSingle(dto);
}
private void setVars(SampleResult result) {
if (StringUtils.isNotEmpty(result.getSampleLabel())
&& result.getSampleLabel().startsWith(CommonConstants.PRE_TRANSACTION)) {
for (int i = 0; i < result.getSubResults().length; i++) {
SampleResult subResult = result.getSubResults()[i];
this.setVars(subResult);
}
}
JMeterVariables variables = JMeterVars.get(result.getResourceId());
if (variables != null && CollectionUtils.isNotEmpty(variables.entrySet())) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, Object> entry : variables.entrySet()) {
builder.append(entry.getKey()).append("").append(entry.getValue()).append(StringUtils.LF);
}
if (StringUtils.isNotEmpty(builder)) {
result.setExtVars(builder.toString());
}
}
}
@Override
public void clearData() {
}
}

View File

@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit;
public class MsKafkaListener {
public static final String CONSUME_ID = "ms-api-exec-consume";
public static final String DEBUG_CONSUME_ID = "ms-api-debug-consume";
private static final String PRE_RESULT = "result_";
@Resource
private ApiExecutionQueueService apiExecutionQueueService;
@Resource
@ -52,7 +53,7 @@ public class MsKafkaListener {
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue(WORK_QUEUE_SIZE),
new ArrayBlockingQueue<>(WORK_QUEUE_SIZE),
new NamedThreadFactory("MS-KAFKA-LISTENER-TASK"));
@KafkaListener(id = CONSUME_ID, topics = KafkaTopicConstants.API_REPORT_TOPIC, groupId = "${spring.kafka.consumer.group-id}", containerFactory = "batchFactory")
@ -80,15 +81,18 @@ public class MsKafkaListener {
LoggerUtil.info("接收到执行结果:", record.key());
if (ObjectUtils.isNotEmpty(record.value()) && WebSocketUtil.has(record.key().toString())) {
MsgDTO dto = JSONUtil.parseObject(record.value(), MsgDTO.class);
if (StringUtils.isNotBlank(dto.getContent()) && dto.getContent().startsWith("result_")) {
if (StringUtils.isNotBlank(dto.getContent()) && dto.getContent().startsWith(PRE_RESULT)) {
String content = dto.getContent().substring(7);
if (StringUtils.isNotBlank(content)) {
RequestResult baseResult = JSONUtil.parseObject(content, RequestResult.class);
if (ObjectUtils.isNotEmpty(baseResult)) {
//解析是否含有误报库信息
RequestResultExpandDTO expandDTO = ResponseUtil.parseByRequestResult(baseResult);
dto.setContent(StringUtils.join("result_", JSON.toJSONString(expandDTO)));
if (StringUtils.equalsAnyIgnoreCase(dto.getRunMode(), ApiRunMode.DEFINITION.name(), ApiRunMode.API_PLAN.name()) && dto.getContent().startsWith("result_")) {
dto.setContent(StringUtils.join(PRE_RESULT, JSON.toJSONString(expandDTO)));
if (StringUtils.equalsAnyIgnoreCase(dto.getRunMode(),
ApiRunMode.DEFINITION.name(),
ApiRunMode.API_PLAN.name())
&& dto.getContent().startsWith(PRE_RESULT)) {
apiDefinitionEnvService.setEnvAndPoolName(dto);
}
}

View File

@ -28,12 +28,11 @@ public class ResourcePoolCalculation {
example.createCriteria().andStatusEqualTo("VALID").andTypeEqualTo("NODE").andIdEqualTo(resourcePoolId);
List<TestResourcePool> pools = testResourcePoolMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(pools)) {
List<String> poolIds = pools.stream().map(pool -> pool.getId()).collect(Collectors.toList());
List<String> poolIds = pools.stream().map(TestResourcePool::getId).collect(Collectors.toList());
TestResourceExample resourceExample = new TestResourceExample();
resourceExample.createCriteria().andTestResourcePoolIdIn(poolIds);
resourceExample.setOrderByClause("create_time");
List<TestResource> testResources = testResourceMapper.selectByExampleWithBLOBs(resourceExample);
return testResources;
return testResourceMapper.selectByExampleWithBLOBs(resourceExample);
}
return new ArrayList<>();
}

View File

@ -178,8 +178,7 @@ public class DataFormattingUtil {
public static JmxInfoDTO getJmxInfoDTO(RunDefinitionRequest runRequest, List<MultipartFile> bodyFiles) {
BaseEnvironmentService fileMetadataService = CommonBeanFactory.getBean(BaseEnvironmentService.class);
ParameterConfig config = new ParameterConfig();
config.setProjectId(runRequest.getProjectId());
ParameterConfig config = new ParameterConfig(runRequest.getProjectId(), true);
config.setOperating(true);
config.setOperatingSampleTestName(runRequest.getName());

View File

@ -49,10 +49,10 @@ public class GenerateHashTreeUtil {
JSONObject element = JSONUtil.parseObject(scenarioDefinition);
ElementUtil.dataFormatting(element);
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
if (element != null && element.has(ElementConstants.HASH_TREE)) {
if (element.has(ElementConstants.HASH_TREE)) {
scenario.setHashTree(JSONUtil.readValue(element.optJSONArray(ElementConstants.HASH_TREE).toString()));
}
if (element != null && StringUtils.isNotEmpty(element.optString("variables"))) {
if (StringUtils.isNotEmpty(element.optString("variables"))) {
scenario.setVariables(JSONUtil.parseArray(element.optString("variables"), ScenarioVariable.class));
}
} catch (Exception e) {
@ -62,7 +62,7 @@ public class GenerateHashTreeUtil {
public static LinkedList<MsTestElement> getScenarioHashTree(String definition) {
JSONObject element = JSONUtil.parseObject(definition);
if (element != null && element.has(ElementConstants.HASH_TREE)) {
if (element.has(ElementConstants.HASH_TREE)) {
ElementUtil.dataFormatting(element);
return JSONUtil.readValue(element.optJSONArray(ElementConstants.HASH_TREE).toString());
}
@ -153,7 +153,7 @@ public class GenerateHashTreeUtil {
group.setHashTree(scenarios);
testPlan.getHashTree().add(group);
ParameterConfig config = new ParameterConfig();
ParameterConfig config = new ParameterConfig(item.getProjectId(), false);
config.setScenarioId(item.getId());
config.setReportType(runRequest.getReportType());
if (runRequest.isRetryEnable() && runRequest.getRetryNum() > 0) {

View File

@ -1,7 +1,6 @@
package io.metersphere.controller;
import io.metersphere.api.dto.BodyFileRequest;
import io.metersphere.api.jmeter.JMeterThreadUtils;
import io.metersphere.api.jmeter.utils.JmxFileUtil;
import io.metersphere.dto.ProjectJarConfig;
import io.metersphere.service.ApiJMeterFileService;
@ -28,7 +27,7 @@ public class ApiJMeterFileController {
@GetMapping("stop/{name}")
public String stop(@PathVariable String name) {
return JMeterThreadUtils.stop(name);
return name;
}
@PostMapping("download/jar")

View File

@ -1,21 +0,0 @@
package io.metersphere.controller;
import io.metersphere.api.exec.queue.ExecThreadPoolExecutor;
import io.metersphere.commons.utils.CommonBeanFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
@RestController
@RequestMapping("/exec/thread/pool")
public class ExecThreadPoolController {
@GetMapping("/set-core-size/{size}")
public void setExecThreadPoolCoreSize(@PathVariable int size) {
Objects.requireNonNull(CommonBeanFactory.getBean(ExecThreadPoolExecutor.class)).setCorePoolSize(size);
}
}

View File

@ -11,9 +11,7 @@ import io.metersphere.api.dto.definition.request.assertions.document.DocumentEle
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.swaggerurl.SwaggerTaskResult;
import io.metersphere.api.dto.swaggerurl.SwaggerUrlRequest;
import io.metersphere.api.exec.api.ApiExecuteService;
import io.metersphere.api.exec.generator.JSONSchemaParser;
import io.metersphere.api.exec.queue.ExecThreadPoolExecutor;
import io.metersphere.api.parse.api.ApiDefinitionImport;
import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
@ -53,10 +51,6 @@ public class ApiDefinitionController {
@Resource
private BaseEnvironmentService apiTestEnvironmentService;
@Resource
private ExecThreadPoolExecutor execThreadPoolExecutor;
@Resource
private ApiExecuteService apiExecuteService;
@Resource
private FunctionRunService functionRunService;
@PostMapping("/list/{goPage}/{pageSize}")
@ -376,11 +370,6 @@ public class ApiDefinitionController {
apiDefinitionService.deleteFollows(definitionIds);
}
@GetMapping("/getWorkerQueue")
public String getWorkerQueue() {
return execThreadPoolExecutor.getWorkerQueue();
}
@GetMapping("versions/{definitionId}")
public List<ApiDefinitionResult> getApiDefinitionVersions(@PathVariable String definitionId) {
return apiDefinitionService.getApiDefinitionVersions(definitionId);

View File

@ -196,13 +196,13 @@ public class ApiScenarioController {
return apiAutomationService.getNewApiScenario(id);
}
@PostMapping("/scenario-env")
public ScenarioEnv getScenarioDefinition(@RequestBody byte[] request) {
return apiAutomationService.getApiScenarioEnv(request);
@PostMapping("/project-valid")
public EnvironmentCheckDTO getScenarioDefinition(@RequestBody List<String> projectIds) {
return apiAutomationService.getApiScenarioEnv(projectIds);
}
@GetMapping("/env-project-ids/{id}")
public ScenarioEnv getApiScenarioProjectId(@PathVariable String id) {
public EnvironmentCheckDTO getApiScenarioProjectId(@PathVariable String id) {
return apiAutomationService.getApiScenarioProjectId(id);
}

View File

@ -1,17 +1,14 @@
package io.metersphere.listener;
import com.mchange.lang.IntegerUtils;
import io.metersphere.api.dto.shell.filter.ScriptFilter;
import io.metersphere.api.exec.queue.ExecThreadPoolExecutor;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.jmeter.ProjectClassLoader;
import io.metersphere.service.*;
import io.metersphere.service.definition.ApiModuleService;
import io.metersphere.service.scenario.ApiScenarioModuleService;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.python.core.Options;
import org.python.util.PythonInterpreter;
@ -20,8 +17,6 @@ import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
@Component
public class ApiAppStartListener implements ApplicationRunner {
@Resource
@ -74,12 +69,6 @@ public class ApiAppStartListener implements ApplicationRunner {
LogUtil.info("初始化默认项目场景模块");
apiScenarioModuleService.initDefaultModule();
BaseSystemConfigDTO dto = systemParameterService.getBaseInfo();
LogUtil.info("设置并发队列核心数", dto.getConcurrency());
if (StringUtils.isNotEmpty(dto.getConcurrency())) {
CommonBeanFactory.getBean(ExecThreadPoolExecutor.class).setCorePoolSize(IntegerUtils.parseInt(dto.getConcurrency(), 10));
}
LogUtil.info("导入内置python包处理");
initPythonEnv();

View File

@ -14,10 +14,12 @@ import io.metersphere.dto.RunModeConfigDTO;
import io.metersphere.service.ApiPoolDebugService;
import io.metersphere.service.scenario.ApiScenarioService;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@ -40,6 +42,7 @@ public class ApiScenarioTestJob extends MsScheduleJob {
public ApiScenarioTestJob() {
apiAutomationService = CommonBeanFactory.getBean(ApiScenarioService.class);
apiPoolDebugService = CommonBeanFactory.getBean(ApiPoolDebugService.class);
}
@Override
@ -77,6 +80,9 @@ public class ApiScenarioTestJob extends MsScheduleJob {
String config = jobDataMap.getString("config");
if (StringUtils.isNotBlank(config)) {
RunModeConfigDTO runModeConfig = JSON.parseObject(config, RunModeConfigDTO.class);
if (BooleanUtils.isTrue(runModeConfig.getDefaultEnv())) {
runModeConfig.setEnvMap(new HashMap<>());
}
request.setConfig(runModeConfig);
} else {
RunModeConfigDTO runModeConfigDTO = new RunModeConfigDTO();

View File

@ -5,7 +5,6 @@ import io.metersphere.api.exec.api.ApiCaseSerialService;
import io.metersphere.api.exec.queue.DBTestQueue;
import io.metersphere.api.exec.scenario.ApiScenarioSerialService;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.JMeterThreadUtils;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.BaseApiExecutionQueueMapper;
@ -33,7 +32,6 @@ import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.services.FileServer;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
@ -388,10 +386,6 @@ public class ApiExecutionQueueService {
if (StringUtils.isNotEmpty(queue.getPoolId()) && jMeterService.getRunningQueue(queue.getPoolId(), item.getReportId())) {
continue;
}
// 检查执行报告是否还在等待队列中或执行线程中
if (JMeterThreadUtils.isRunning(item.getReportId(), item.getTestId())) {
continue;
}
// 检查是否已经超时
ResultDTO dto = new ResultDTO();
dto.setQueueId(item.getQueueId());
@ -446,9 +440,6 @@ public class ApiExecutionQueueService {
TestPlanReportStatus.RUNNING.name(), ApiReportStatus.PENDING.name()) && (report.getUpdateTime() < timeout)) {
report.setStatus(ApiReportStatus.ERROR.name());
apiScenarioReportMapper.updateByPrimaryKeySelective(report);
if (FileServer.getFileServer() != null) {
FileServer.getFileServer().closeCsv(item.getReportId());
}
}
});
}
@ -474,9 +465,6 @@ public class ApiExecutionQueueService {
}
public void stop(String reportId) {
if (FileServer.getFileServer() != null) {
FileServer.getFileServer().closeCsv(reportId);
}
ApiExecutionQueueDetailExample example = new ApiExecutionQueueDetailExample();
example.createCriteria().andReportIdEqualTo(reportId);
List<ApiExecutionQueueDetail> details = executionQueueDetailMapper.selectByExample(example);
@ -500,12 +488,6 @@ public class ApiExecutionQueueService {
if (CollectionUtils.isEmpty(reportIds)) {
return;
}
// 清理CSV
reportIds.forEach(item -> {
if (FileServer.getFileServer() != null) {
FileServer.getFileServer().closeCsv(item);
}
});
ApiExecutionQueueDetailExample example = new ApiExecutionQueueDetailExample();
example.createCriteria().andReportIdIn(reportIds);
List<ApiExecutionQueueDetail> details = executionQueueDetailMapper.selectByExample(example);

View File

@ -1,6 +1,5 @@
package io.metersphere.service;
import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil;
import io.metersphere.api.jmeter.ApiLocalRunner;
import io.metersphere.base.domain.ApiDefinitionExecResultWithBLOBs;
import io.metersphere.base.domain.ApiExecutionQueueDetail;
@ -47,8 +46,6 @@ public class RemakeReportService {
dto.setTestId(request.getTestId());
dto.setErrorEnded(true);
LoggerUtil.info("进入异常结果处理:" + dto.getRunMode() + " 整体处理完成", dto.getReportId());
// 全局并发队列
PoolExecBlockingQueueUtil.offer(dto.getReportId());
LoggerUtil.error("执行异常处理:" + errorMsg, request.getReportId());
if (StringUtils.isNotEmpty(dto.getQueueId())) {
queueService.queueNext(dto);

View File

@ -1,9 +1,6 @@
package io.metersphere.service.ext;
import io.metersphere.api.exec.queue.ExecThreadPoolExecutor;
import io.metersphere.api.exec.queue.PoolExecBlockingQueueUtil;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.api.jmeter.JMeterThreadUtils;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ApiScenarioReportMapper;
@ -59,8 +56,6 @@ public class ExtApiTaskService extends TaskService {
@Resource
private ExtApiScenarioReportMapper extApiScenarioReportMapper;
@Resource
private ExecThreadPoolExecutor execThreadPoolExecutor;
@Resource
private ApiExecutionQueueService apiExecutionQueueService;
public List<TaskCenterDTO> getCases(String id) {
@ -98,7 +93,7 @@ public class ExtApiTaskService extends TaskService {
}
}
public String apiStop(List<TaskRequestDTO> taskRequests) {
public void apiStop(List<TaskRequestDTO> taskRequests) {
if (CollectionUtils.isNotEmpty(taskRequests)) {
List<TaskRequestDTO> stopTasks = taskRequests.stream().filter(s -> StringUtils.isNotEmpty(s.getReportId())).collect(Collectors.toList());
// 聚类同一批资源池的一批发送
@ -107,9 +102,7 @@ public class ExtApiTaskService extends TaskService {
if (CollectionUtils.isNotEmpty(stopTasks) && stopTasks.size() == 1) {
// 从队列移除
TaskRequestDTO request = stopTasks.get(0);
execThreadPoolExecutor.removeQueue(request.getReportId());
apiExecutionQueueService.stop(request.getReportId());
PoolExecBlockingQueueUtil.offer(request.getReportId());
if (StringUtils.equals(request.getType(), "API")) {
ApiDefinitionExecResultWithBLOBs result = apiDefinitionExecResultMapper.selectByPrimaryKey(request.getReportId());
if (result != null) {
@ -137,7 +130,6 @@ public class ExtApiTaskService extends TaskService {
thread.start();
}
}
return "SUCCESS";
}
private void batchStop(List<TaskRequestDTO> taskRequests) {
@ -152,19 +144,12 @@ public class ExtApiTaskService extends TaskService {
// 结束掉未分发完成的任务
LoggerUtil.info("结束正在进行中的计划任务队列");
JMeterThreadUtils.stop("PLAN-CASE");
JMeterThreadUtils.stop("API-CASE-RUN");
JMeterThreadUtils.stop("SCENARIO-PARALLEL-THREAD");
if (taskRequestMap.containsKey("API")) {
List<TaskResultVO> results = extApiDefinitionExecResultMapper.findByProjectIds(taskCenterRequest);
LoggerUtil.info("查询API进行中的报告" + results.size());
if (CollectionUtils.isNotEmpty(results)) {
for (TaskResultVO item : results) {
extracted(poolMap, item.getId(), item.getActuator());
// 从队列移除
execThreadPoolExecutor.removeQueue(item.getId());
PoolExecBlockingQueueUtil.offer(item.getId());
}
LoggerUtil.info("结束API进行中的报告");
baseTaskMapper.stopApi(taskCenterRequest);
@ -181,9 +166,6 @@ public class ExtApiTaskService extends TaskService {
for (TaskResultVO report : reports) {
extracted(poolMap, report.getId(), report.getActuator());
// 从队列移除
execThreadPoolExecutor.removeQueue(report.getId());
PoolExecBlockingQueueUtil.offer(report.getId());
}
// 清理队列并停止测试计划报告
@ -213,8 +195,6 @@ public class ExtApiTaskService extends TaskService {
this.add(reportId);
}});
}
} else {
JMeterThreadUtils.stop(reportId);
}
}
@ -223,7 +203,7 @@ public class ExtApiTaskService extends TaskService {
example.createCriteria().andStatusEqualTo("VALID").andTypeEqualTo("NODE").andIdEqualTo(poolId);
List<TestResourcePool> pools = testResourcePoolMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(pools)) {
List<String> poolIds = pools.stream().map(pool -> pool.getId()).collect(Collectors.toList());
List<String> poolIds = pools.stream().map(TestResourcePool::getId).collect(Collectors.toList());
TestResourceExample resourceExample = new TestResourceExample();
resourceExample.createCriteria().andTestResourcePoolIdIn(poolIds);
resourceExample.setOrderByClause("create_time");

View File

@ -5,7 +5,7 @@ import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.ApiCaseRelevanceRequest;
import io.metersphere.api.dto.EnvironmentType;
import io.metersphere.api.dto.RelevanceScenarioRequest;
import io.metersphere.api.dto.ScenarioEnv;
import io.metersphere.api.dto.EnvironmentCheckDTO;
import io.metersphere.api.dto.automation.*;
import io.metersphere.api.dto.plan.*;
import io.metersphere.api.exec.scenario.ApiScenarioEnvService;
@ -237,7 +237,7 @@ public class TestPlanScenarioCaseService {
Map<String, String> newEnvMap = new HashMap<>(16);
List<String> list = mapping.get(id);
if (CollectionUtils.isEmpty(list)) {
ScenarioEnv scenarioEnv = apiAutomationService.getApiScenarioProjectId(id);
EnvironmentCheckDTO scenarioEnv = apiAutomationService.getApiScenarioProjectId(id);
list = new ArrayList<>(scenarioEnv.getProjectIds());
}
list.forEach(l -> newEnvMap.put(l, envMap == null ? StringUtils.EMPTY : envMap.getOrDefault(l, StringUtils.EMPTY)));

View File

@ -168,6 +168,8 @@ public class ApiScenarioService {
private ApiAutomationRelationshipEdgeService apiAutomationRelationshipEdgeService;
@Resource
private ApiTestCaseService apiTestCaseService;
@Resource
private BaseProjectService baseProjectService;
private ThreadLocal<Long> currentScenarioOrder = new ThreadLocal<>();
@ -492,7 +494,7 @@ public class ApiScenarioService {
.flatMap(nv -> Optional.ofNullable(nv.getFiles()).orElse(List.of()).stream())
.map(BodyFile::getId)
.toList();
oldVariables.forEach(item ->{
oldVariables.forEach(item -> {
if (CollectionUtils.isNotEmpty(item.getFiles()) &&
StringUtils.isNotBlank(item.getFiles().get(0).getId()) &&
!ids.contains(item.getFiles().get(0).getId())) {
@ -501,7 +503,7 @@ public class ApiScenarioService {
});
}
if (CollectionUtils.isEmpty(newVariables) && CollectionUtils.isNotEmpty(oldVariables)) {
oldVariables.forEach(item ->{
oldVariables.forEach(item -> {
if (CollectionUtils.isNotEmpty(item.getFiles()) &&
StringUtils.isNotBlank(item.getFiles().get(0).getId())) {
ApiFileUtil.deleteBodyFiles(item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName());
@ -840,8 +842,8 @@ public class ApiScenarioService {
public ParameterConfig getConfig(ApiScenarioDTO scenario) {
try {
ParameterConfig config = new ParameterConfig();
Map<String, String> environmentMap = new HashMap<>();
ParameterConfig config = new ParameterConfig(scenario.getProjectId(), false);
Map<String, String> environmentMap;
String environmentType = scenario.getEnvironmentType();
String environmentGroupId = scenario.getEnvironmentGroupId();
String environmentJson = scenario.getEnvironmentJson();
@ -881,12 +883,12 @@ public class ApiScenarioService {
String scenarioId = request.getId();
ApiScenarioDTO scenario = getNewApiScenario(scenarioId);
if (scenario != null) {
String referenced = element.optString("referenced");
if (StringUtils.equalsIgnoreCase("REF", referenced)) {
String referenced = element.optString(MsHashTreeConstants.REFERENCED);
if (StringUtils.equalsIgnoreCase(MsHashTreeConstants.REF, referenced)) {
JSONObject source = JSONUtil.parseObject(scenario.getScenarioDefinition());
element = jsonMerge(source, element);
}
element.put("referenced", referenced);
element.put(MsHashTreeConstants.REFERENCED, referenced);
String environmentType = scenario.getEnvironmentType();
String environmentGroupId = scenario.getEnvironmentGroupId();
String environmentJson = scenario.getEnvironmentJson();
@ -897,8 +899,11 @@ public class ApiScenarioService {
}
}
}
String projectId = StringUtils.isNotBlank(request.getProjectId())
? request.getProjectId()
: element.optString(PropertyConstant.PROJECT_ID);
ParameterConfig config = new ParameterConfig();
ParameterConfig config = new ParameterConfig(projectId, false);
apiScenarioEnvService.setEnvConfig(environmentMap, config);
if (config.getConfig() != null && !config.getConfig().isEmpty()) {
ElementUtil.dataSetDomain(element.optJSONArray(ElementConstants.HASH_TREE), config);
@ -933,6 +938,7 @@ public class ApiScenarioService {
}
if (StringUtils.isNotBlank(dto.getEnvironmentJson())) {
ApiScenarioEnvRequest request = new ApiScenarioEnvRequest();
request.setProjectId(dto.getProjectId());
request.setEnvironmentEnable(false);
request.setDefinition(dto.getScenarioDefinition());
request.setEnvironmentMap(JSON.parseObject(dto.getEnvironmentJson(), Map.class));
@ -975,7 +981,7 @@ public class ApiScenarioService {
projectIds.add(projectId);
testPlan.setName(apiScenario.getName());
testPlan.setHashTree(new LinkedList<>());
ParameterConfig config = new ParameterConfig();
ParameterConfig config = new ParameterConfig(apiScenario.getProjectId(), false);
config.setOperating(true);
config.getExcludeScenarioIds().add(apiScenario.getId());
config.setScenarioId(apiScenario.getId());
@ -1735,7 +1741,7 @@ public class ApiScenarioService {
public byte[] exportZip(ApiScenarioBatchRequest request) {
List<ApiScenarioWithBLOBs> scenarios = getExportResult(request);
//环境检查
checkExportEnv(scenarios);
// checkExportEnv(scenarios);
// 生成jmx
Map<String, byte[]> files = new LinkedHashMap<>();
scenarios.forEach(item -> {
@ -1901,9 +1907,9 @@ public class ApiScenarioService {
return apiIdList;
}
public ScenarioEnv getApiScenarioProjectId(String id) {
public EnvironmentCheckDTO getApiScenarioProjectId(String id) {
ApiScenarioWithBLOBs scenario = apiScenarioMapper.selectByPrimaryKey(id);
ScenarioEnv scenarioEnv = new ScenarioEnv();
EnvironmentCheckDTO scenarioEnv = new EnvironmentCheckDTO();
if (scenario == null) {
return scenarioEnv;
}
@ -1931,7 +1937,7 @@ public class ApiScenarioService {
example.createCriteria().andIdIn(request.getIds());
List<ApiScenarioWithBLOBs> scenarioList = apiScenarioMapper.selectByExampleWithBLOBs(example);
for (ApiScenarioWithBLOBs scenario : scenarioList) {
ScenarioEnv scenarioEnv = new ScenarioEnv();
EnvironmentCheckDTO scenarioEnv = new EnvironmentCheckDTO();
if (scenario == null) {
continue;
}
@ -2141,9 +2147,11 @@ public class ApiScenarioService {
}
public boolean verifyScenarioEnv(String scenarioId) {
ApiScenarioWithBLOBs apiScenarioWithBLOBs = apiScenarioMapper.selectByPrimaryKey(scenarioId);
return true;
// 暂时去除环境校验
/*ApiScenarioWithBLOBs apiScenarioWithBLOBs = apiScenarioMapper.selectByPrimaryKey(scenarioId);
apiScenarioEnvService.setScenarioEnv(apiScenarioWithBLOBs, null);
return apiScenarioEnvService.verifyScenarioEnv(apiScenarioWithBLOBs);
return apiScenarioEnvService.verifyScenarioEnv(apiScenarioWithBLOBs);*/
}
public List<String> getFollows(String scenarioId) {
@ -2157,9 +2165,12 @@ public class ApiScenarioService {
return follows.stream().map(ApiScenarioFollow::getFollowId).distinct().collect(Collectors.toList());
}
public ScenarioEnv getApiScenarioEnv(byte[] request) {
String definition = new String(request, StandardCharsets.UTF_8);
return apiScenarioEnvService.getApiScenarioEnv(definition);
public EnvironmentCheckDTO getApiScenarioEnv(List<String> projectIds) {
List<Project> projects = baseProjectService.getProjectByIds(projectIds);
projectIds.removeIf(id -> !projects.stream().map(Project::getId).collect(Collectors.toSet()).contains(id));
EnvironmentCheckDTO checkDTO = new EnvironmentCheckDTO();
checkDTO.setProjectIds(new HashSet<>(projectIds));
return checkDTO;
}
public List<MsExecResponseDTO> run(RunScenarioRequest request) {
@ -2208,7 +2219,7 @@ public class ApiScenarioService {
List<String> strings = new LinkedList<>();
apiScenarios.forEach(item -> {
if (StringUtils.isNotEmpty(item.getScenarioDefinition())) {
ScenarioEnv env = apiScenarioEnvService.getApiScenarioEnv(item.getScenarioDefinition());
EnvironmentCheckDTO env = apiScenarioEnvService.getApiScenarioEnv(item.getScenarioDefinition());
if (!strings.contains(item.getProjectId())) {
strings.add(item.getProjectId());
}
@ -2436,7 +2447,7 @@ public class ApiScenarioService {
example.createCriteria().andIdIn(scenarioIdList);
List<ApiScenarioWithBLOBs> scenarioWithBLOBsList = apiScenarioMapper.selectByExampleWithBLOBs(example);
for (ApiScenarioWithBLOBs scenario : scenarioWithBLOBsList) {
ScenarioEnv scenarioEnv = apiScenarioEnvService.getApiScenarioEnv(scenario.getScenarioDefinition());
EnvironmentCheckDTO scenarioEnv = apiScenarioEnvService.getApiScenarioEnv(scenario.getScenarioDefinition());
if (CollectionUtils.isNotEmpty(scenarioEnv.getProjectIds())) {
scenarioEnv.getProjectIds().forEach(projectId -> {
if (!returnDTO.getProjectIdList().contains(projectId)) {

View File

@ -27,10 +27,6 @@ export function getScenarioByProjectId(projectId) {
return get('/api/automation/env-project-ids/' + projectId);
}
export function checkScenarioEnv(scenarioId) {
return get('/api/automation/env-valid/' + scenarioId);
}
export function execStop(reportId) {
return get('/api/automation/stop/' + reportId);
}
@ -109,14 +105,13 @@ export function getUploadConfig(url, formData) {
url: url,
data: formData,
headers: {
'Content-Type': "application/octet-stream",
'Content-Type': 'application/octet-stream',
},
};
}
export function getApiScenarioEnv(params) {
let reqParams = getUploadConfig('/api/automation/scenario-env', params);
return request( reqParams);
return post('/api/automation/project-valid', params);
}
export function batchEditScenario(params) {

View File

@ -1,154 +0,0 @@
<template>
<el-dialog
title="环境选择"
:visible.sync="dialogVisible"
width="30%"
:destroy-on-close="true"
:before-close="handleClose">
<div v-loading="result">
<div v-for="pe in data" :key="pe.id" style="margin-left: 20px">
{{ getProjectName(pe.id) }}
<el-select
v-model="pe['selectEnv']"
placeholder="请选择环境"
style="margin-left: 10px; margin-top: 10px"
size="small">
<el-option
v-for="(environment, index) in pe.envs"
:key="index"
:label="environment.name"
:value="environment.id" />
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)">
{{ $t('api_test.environment.environment_config') }}
</el-button>
<template v-slot:empty>
<div class="empty-environment">
<el-button class="ms-scenario-button" size="mini" type="primary" @click="openEnvironmentConfig(pe.id)">
{{ $t('api_test.environment.environment_config') }}
</el-button>
</div>
</template>
</el-select>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false" size="small"> </el-button>
<el-button type="primary" @click="handleConfirm" size="small"> </el-button>
</span>
<!-- 环境配置 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose" />
</el-dialog>
</template>
<script>
import { parseEnvironment } from '@/business/environment/model/EnvironmentModel';
import ApiEnvironmentConfig from 'metersphere-frontend/src/components/environment/ApiEnvironmentConfig';
import { getEnvironmentByProjectId } from 'metersphere-frontend/src/api/environment';
export default {
name: 'ApiScenarioEnv',
components: { ApiEnvironmentConfig },
props: {
envMap: Map,
projectIds: Set,
projectList: Array,
},
data() {
return {
data: [],
result: false,
projects: [],
environmentId: '',
environments: [],
dialogVisible: false,
};
},
methods: {
handleClose() {
this.dialogVisible = false;
},
init() {
this.projectIds.forEach((id) => {
let item = { id: id, envs: [], selectEnv: '' };
this.data.push(item);
this.result = getEnvironmentByProjectId(id).then((res) => {
let envs = res.data;
envs.forEach((environment) => {
parseEnvironment(environment);
});
//
let temp = this.data.find((dt) => dt.id === id);
temp.envs = envs;
temp.selectEnv = this.envMap.get(id);
});
});
},
open() {
this.data = [];
this.dialogVisible = true;
if (this.projectIds.size > 0) {
this.init();
}
},
getProjectName(id) {
const project = this.projectList.find((p) => p.id === id);
return project ? project.name : '';
},
openEnvironmentConfig(projectId) {
if (!projectId) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs.environmentConfig.open(projectId);
},
handleConfirm() {
let map = new Map();
let sign = true;
this.data.forEach((dt) => {
if (!dt.selectEnv) {
sign = false;
return;
}
map.set(dt.id, dt.selectEnv);
});
if (!sign) {
this.$warning('请为当前场景选择一个运行环境!');
return;
}
this.$emit('setProjectEnvMap', map);
this.dialogVisible = false;
},
checkEnv() {
let sign = true;
if (this.data.length > 0) {
this.data.forEach((dt) => {
if (!dt.selectEnv) {
sign = false;
return false;
}
});
} else {
sign = false;
}
if (!sign) {
this.$warning('请为当前场景选择一个运行环境!');
return false;
}
return true;
},
environmentConfigClose() {
this.data = [];
this.init();
},
},
};
</script>
<style scoped>
.ms-scenario-button {
margin-left: 20px;
}
</style>

View File

@ -402,7 +402,6 @@ import {
batchEditScenario,
batchGenPerformanceTestJmx,
checkBeforeDelete,
checkScenarioEnv,
delByScenarioId,
delByScenarioIdAndRefId,
deleteBatchByCondition,
@ -1370,16 +1369,9 @@ export default {
}
this.environmentType = this.currentScenario.environmentType;
this.envGroupId = this.currentScenario.environmentGroupId;
checkScenarioEnv(this.currentScenario.id).then((res) => {
let data = res.data;
if (!data) {
this.$warning(this.$t('workspace.env_group.please_select_env_for_current_scenario'));
return false;
}
this.reportId = getUUID().substring(0, 8);
this.runVisible = true;
this.$set(row, 'isStop', true);
});
this.reportId = getUUID().substring(0, 8);
this.runVisible = true;
this.$set(row, 'isStop', true);
}
});
},

View File

@ -424,9 +424,6 @@
:is-across-space="true"
ref="scenarioRelevance" />
<!-- 环境 -->
<api-environment-config v-if="type !== 'detail'" ref="environmentConfig" @close="environmentConfigClose" />
<!--执行组件-->
<ms-run
:debug="true"
@ -564,7 +561,6 @@ import {
} from '@/api/scenario';
import { API_STATUS, PRIORITY } from '../../definition/model/JsonData';
import { buttons, setComponent } from './menu/Menu';
import { parseEnvironment } from '@/business/environment/model/EnvironmentModel';
import { ELEMENT_TYPE, STEP, TYPE_TO_C } from './Setting';
import { KeyValue } from '@/business/definition/model/ApiTestModel';
import { getCurrentProjectID, getCurrentUser } from 'metersphere-frontend/src/utils/token';
@ -581,7 +577,6 @@ import {
import MsComponentConfig from './component/ComponentConfig';
import { ENV_TYPE } from 'metersphere-frontend/src/utils/constants';
import { mergeRequestDocumentData } from '@/business/definition/api-definition';
import { getEnvironmentByProjectId } from 'metersphere-frontend/src/api/environment';
import { useApiStore } from '@/store';
import { getDefaultVersion, setLatestVersionById } from 'metersphere-frontend/src/api/version';
@ -722,7 +717,6 @@ export default {
projectEnvMap: new Map(),
projectList: [],
drawer: false,
isFullUrl: true,
expandedStatus: false,
stepEnable: true,
envResult: {
@ -809,7 +803,6 @@ export default {
this.getWsProjects();
this.getMaintainerOptions();
this.getApiScenario();
this.getEnvironments();
this.buttonData = buttons(this);
this.getPlugins().then(() => {
this.initPlugins();
@ -1329,13 +1322,6 @@ export default {
this.debugLoading = true;
let definition = JSON.parse(JSON.stringify(this.currentScenario));
definition.hashTree = this.scenarioDefinition;
await this.getEnv(JSON.stringify(definition));
await this.$refs.envPopover.initEnv();
const sign = await this.$refs.envPopover.checkEnv(this.isFullUrl);
if (!sign) {
this.debugLoading = false;
return;
}
this.initParameter();
this.debugData = {
id: this.currentScenario.id,
@ -1799,15 +1785,6 @@ export default {
/*触发执行操作*/
this.$refs.currentScenario.validate(async (valid) => {
if (valid) {
let definition = JSON.parse(JSON.stringify(this.currentScenario));
definition.hashTree = this.scenarioDefinition;
await this.getEnv(JSON.stringify(definition));
await this.$refs.envPopover.initEnv();
const sign = await this.$refs.envPopover.checkEnv(this.isFullUrl);
if (!sign) {
this.debugLoading = false;
return;
}
let scenario = undefined;
if (runScenario && runScenario.type === 'scenario') {
scenario = runScenario;
@ -1847,16 +1824,6 @@ export default {
}
});
},
getEnvironments() {
if (this.projectId) {
getEnvironmentByProjectId(this.projectId).then((response) => {
this.environments = response.data;
this.environments.forEach((environment) => {
parseEnvironment(environment);
});
});
}
},
checkDataIsCopy() {
//
if (this.currentScenario.copy) {
@ -1864,16 +1831,6 @@ export default {
}
},
openEnvironmentConfig() {
if (!this.projectId) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs.environmentConfig.open(this.projectId);
},
environmentConfigClose() {
this.getEnvironments();
},
allowDrag(node) {
if (node.data && node.data.disabled && node.parent.data && node.parent.data.disabled) {
return false;
@ -1999,16 +1956,21 @@ export default {
},
getEnv(definition) {
return new Promise((resolve) => {
const encoder = new TextEncoder();
const bytes = encoder.encode(definition, 'utf-8');
getApiScenarioEnv(bytes).then((res) => {
if (res.data && res.data.data) {
this.projectIds = new Set(res.data.data.projectIds);
this.projectIds.add(this.projectId);
this.isFullUrl = res.data.data.fullUrl;
}
this.projectIds = new Set();
const regex = /"projectId"\s*:\s*"([^"]+)"/g;
let match;
while ((match = regex.exec(definition)) !== null) {
this.projectIds.add(match[1]);
}
this.projectIds.add(this.projectId);
if (this.projectIds.size > 1) {
getApiScenarioEnv(Array.from(this.projectIds)).then((res) => {
this.projectIds = new Set(res.data.projectIds);
resolve();
});
} else {
resolve();
});
}
});
},
getApiScenario(isRefresh) {

View File

@ -1,42 +1,101 @@
<template>
<div v-loading="result.loading">
<div v-for="pe in data" :key="pe.id" style="margin-left: 20px">
<el-select
v-model="pe['selectEnv']"
filterable
:placeholder="$t('workspace.env_group.please_select_env')"
style="margin-top: 8px; width: 200px"
size="small">
<el-option
v-for="(environment, index) in pe.envs"
:key="index"
:label="environment.name"
:value="environment.id" />
<el-button
class="ms-scenario-button"
v-if="isShowConfirmButton(pe.id)"
size="mini"
type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
</el-button>
<template v-slot:empty>
<!--这里只做没有可搜索内容时使用否则如果没有符合搜索条件的也会显示该项与上面的btn重复显示 -->
<div v-if="isShowConfirmButton(pe.id) && pe.envs.length === 0" class="empty-environment">
<el-button
class="ms-scenario-button"
size="mini"
type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
</el-button>
</div>
</template>
</el-select>
<span class="project-name" :title="getProjectName(pe.id)">
{{ getProjectName(pe.id) }}
<div v-for="pe in currentProjectEnv" :key="pe.id" style="margin-left: 20px; margin-bottom: 20px">
<span :v-show="pe.id === currentProjectID">
<span :title="getProjectName(pe.id)" style="margin-left: 30px; margin-right: 10px">
{{ getProjectName(pe.id) }}
</span>
<el-select
v-model="pe['selectEnv']"
filterable
:placeholder="$t('workspace.env_group.please_select_env')"
style="margin-top: 8px; width: 250px"
size="small">
<el-option
v-for="(environment, index) in pe.envs"
:key="index"
:label="environment.name"
:value="environment.id" />
<el-button
class="ms-scenario-button"
v-if="isShowConfirmButton(pe.id)"
size="mini"
type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
</el-button>
<template v-slot:empty>
<div v-if="isShowConfirmButton(pe.id) && pe.envs.length === 0" class="empty-environment">
<el-button
class="ms-scenario-button"
size="mini"
type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
</el-button>
</div>
</template>
</el-select>
</span>
</div>
<div v-show="data.length > 0">
<span @click="active" @click.stop>
<el-tooltip>
<i class="header-icon el-icon-info" />
<div slot="content">
<div style="width: 560px">{{ $t('automation.project_env_info_tips') }}</div>
</div>
</el-tooltip>
{{ $t('automation.project_env_info') }}
<i class="icon el-icon-arrow-right" :class="{ 'is-active': isActive }" @click="active" @click.stop />
</span>
<el-collapse-transition>
<div v-if="isActive">
<div v-for="pe in data" :key="pe.id" style="margin-left: 20px">
<span :v-if="pe.id !== currentProjectID">
<span class="project-name" :title="getProjectName(pe.id)">
{{ getProjectName(pe.id) }}
<span style="color: red" :v-show="pe.id === currentProjectID">* </span>
</span>
<el-select
v-model="pe['selectEnv']"
filterable
:placeholder="$t('workspace.env_group.please_select_env')"
style="margin-top: 8px; width: 250px"
clearable
size="small">
<el-option
v-for="(environment, index) in pe.envs"
:key="index"
:label="environment.name"
:value="environment.id" />
<el-button
class="ms-scenario-button"
v-if="isShowConfirmButton(pe.id)"
size="mini"
type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
</el-button>
<template v-slot:empty>
<!--这里只做没有可搜索内容时使用否则如果没有符合搜索条件的也会显示该项与上面的btn重复显示 -->
<div v-if="isShowConfirmButton(pe.id) && pe.envs.length === 0" class="empty-environment">
<el-button
class="ms-scenario-button"
size="mini"
type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
</el-button>
</div>
</template>
</el-select>
</span>
</div>
</div>
</el-collapse-transition>
</div>
<el-button type="primary" @click="handleConfirm" size="small" :style="btnStyle" class="env-confirm">
{{ $t('workspace.env_group.confirm') }}
@ -50,8 +109,9 @@
<script>
import { parseEnvironment } from '@/business/environment/model/EnvironmentModel';
import ApiEnvironmentConfig from 'metersphere-frontend/src/components/environment/ApiEnvironmentConfig';
import { getEnvironmentByProjectId } from 'metersphere-frontend/src/api/environment';
import { getEnvironmentByProjectId, getEnvironmentByProjectIds } from 'metersphere-frontend/src/api/environment';
import { getOwnerProjectIds } from '@/api/project';
import { getCurrentProjectID } from 'metersphere-frontend/src/utils/token';
export default {
name: 'EnvironmentSelect',
@ -87,9 +147,17 @@ export default {
permissionProjectIds: [],
dialogVisible: false,
isFullUrl: true,
currentProjectEnv: [],
isActive: false,
};
},
methods: {
active() {
this.isActive = !this.isActive;
},
currentProjectID() {
return getCurrentProjectID();
},
isShowConfirmButton(projectId) {
if (this.showConfigButtonWithOutPermission === true) {
return true;
@ -112,32 +180,44 @@ export default {
}
let arr = [];
this.currentProjectEnv = [];
this.projectIds.forEach((id) => {
const project = this.projectList.find((p) => p.id === id);
if (project) {
let item = { id: id, envs: [], selectEnv: '' };
this.data.push(item);
let p = new Promise((resolve) => {
getEnvironmentByProjectId(id).then((res) => {
let envs = res.data;
envs.forEach((environment) => {
parseEnvironment(environment);
});
//
let temp = this.data.find((dt) => dt.id === id);
temp.envs = envs;
let envId = undefined;
if (this.envMap) {
envId = this.envMap.get(id);
}
//
temp.selectEnv = envs.filter((e) => e.id === envId).length === 0 ? null : envId;
resolve();
});
});
arr.push(p);
if (this.currentProjectID() === project.id) {
this.currentProjectEnv.push(item);
} else {
this.data.push(item);
}
}
});
let p = new Promise((resolve) => {
getEnvironmentByProjectIds(Array.from(this.projectIds)).then((res) => {
let envMap = new Map();
res.data.forEach((environment) => {
envMap.has(environment.projectId)
? envMap.set(environment.projectId, envMap.get(environment.projectId).concat(environment))
: envMap.set(environment.projectId, [environment]);
});
envMap.forEach((value, key) => {
//
let temp =
this.currentProjectID() === key
? this.currentProjectEnv.find((dt) => dt.id === key)
: this.data.find((dt) => dt.id === key);
temp.envs = envMap.get(key);
let envId = undefined;
if (this.envMap) {
envId = this.envMap.get(key);
}
//
temp.selectEnv = envMap.get(key).filter((e) => e.id === envId).length === 0 ? null : envId;
});
resolve();
});
});
arr.push(p);
return arr;
},
getUserPermissionProjectIds() {
@ -156,6 +236,9 @@ export default {
return Promise.all(this.init());
},
getProjectName(id) {
if (id === getCurrentProjectID()) {
return this.$t('commons.current_project');
}
const project = this.projectList.find((p) => p.id === id);
return project ? project.name : '';
},
@ -168,31 +251,30 @@ export default {
},
handleConfirm() {
let map = new Map();
let sign = true;
this.data.forEach((dt) => {
this.currentProjectEnv.forEach((dt) => {
if (!dt.selectEnv) {
return;
}
map.set(dt.id, dt.selectEnv);
});
//
this.data.forEach((dt) => {
if (!dt.selectEnv) {
sign = false;
return;
}
map.set(dt.id, dt.selectEnv);
});
if (!sign) {
this.$warning(this.$t('workspace.env_group.please_select_env_for_current_scenario'));
return;
}
this.$emit('setProjectEnvMap', map);
this.$emit('close');
},
checkEnv(data) {
let sign = true;
this.isFullUrl = true;
if (data) {
return true;
}
if (this.data.length > 0) {
this.data.forEach((dt) => {
if (this.currentProjectEnv.length > 0) {
this.currentProjectEnv.forEach((dt) => {
if (!dt.selectEnv) {
sign = false;
return false;
}
});
@ -201,17 +283,11 @@ export default {
if (this.envMap && this.envMap.size > 0) {
this.projectIds.forEach((id) => {
if (!this.envMap.get(id)) {
sign = false;
return false;
}
});
}
}
if (!sign) {
this.$warning(this.$t('workspace.env_group.please_select_env_for_current_scenario'));
return false;
}
return true;
},
environmentConfigClose() {
@ -232,13 +308,24 @@ export default {
margin-top: 10px;
}
:deep(.el-collapse-item__arrow) {
margin: 0 0px 0px 8px;
}
.project-name {
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 150px;
margin-left: 8px;
width: 100px;
vertical-align: middle;
}
.icon.is-active {
transform: rotate(90deg);
}
.el-icon-arrow-right {
margin-right: 3px;
}
</style>

View File

@ -210,7 +210,6 @@ export default {
this.basisData.method = this.basisData.protocol;
this.$emit('saveApi', this.basisData);
},
runTest() {},
itselfEnvironment(environmentId) {
let id = this.request.projectId ? this.request.projectId : this.projectId;
this.result = getEnvironmentByProjectId(id).then((response) => {
@ -240,9 +239,22 @@ export default {
this.initDataSource(undefined, undefined, targetDataSourceName);
});
},
getEnvironments(environmentId, isCreated) {
let envId = '';
//
async selectProjectId(environmentId) {
let id = this.request.projectId ? this.request.projectId : this.projectId;
//
if (environmentId) {
return id;
}
let scenarioEnvId = this.scenarioId !== '' ? this.scenarioId + '_' + id : id;
if (store.scenarioEnvMap && store.scenarioEnvMap instanceof Map && store.scenarioEnvMap.has(scenarioEnvId)) {
return id;
}
return this.projectId;
},
async getEnvironments(environmentId, isCreated) {
let envId = '';
let id = await this.selectProjectId(environmentId);
let scenarioEnvId = this.scenarioId !== '' ? this.scenarioId + '_' + id : id;
if (store.scenarioEnvMap && store.scenarioEnvMap instanceof Map && store.scenarioEnvMap.has(scenarioEnvId)) {
envId = store.scenarioEnvMap.get(scenarioEnvId);
@ -298,7 +310,7 @@ export default {
}
});
if (!hasEnvironment) {
this.request.environmentId = "";
this.request.environmentId = '';
}
this.initDataSource(envId, currentEnvironment, targetDataSourceName);
});
@ -402,14 +414,6 @@ export default {
margin-left: 60px;
}
.ms-left-cell {
margin-top: 40px;
}
.ms-left-buttion {
margin: 6px 0px 8px 30px;
}
.environment-button {
margin-left: 20px;
padding: 7px;

View File

@ -112,7 +112,7 @@ import { getCurrentProjectID, getCurrentWorkspaceId } from 'metersphere-frontend
import { getUUID, strMapToObj } from 'metersphere-frontend/src/utils';
import { STEP } from '@/business/automation/scenario/Setting';
import { getOwnerProjectIds, getProject } from '@/api/project';
import { checkScenarioEnv, getScenarioById, setScenarioDomain } from '@/api/scenario';
import { getScenarioById, setScenarioDomain } from '@/api/scenario';
export default {
name: 'ApiScenarioComponent',
@ -228,14 +228,7 @@ export default {
this.reload();
},
checkEnv(val) {
checkScenarioEnv(this.scenario.id).then((res) => {
if (this.scenario.environmentEnable && !res.data) {
this.scenario.environmentEnable = false;
this.$warning(this.$t('commons.scenario_warning'));
return;
}
this.setDomain(val);
});
this.setDomain(val);
},
setDomain(val) {
let param = {

View File

@ -70,7 +70,7 @@ import MsAddBasisApi from '../api/AddBasisApi';
import MsAddApiCase from '../api/AddApiCase';
import { getUUID, strMapToObj } from 'metersphere-frontend/src/utils';
import { getCurrentProjectID } from 'metersphere-frontend/src/utils/token';
import { checkScenarioEnv, getScenarioWithBLOBsById, setScenarioDomain } from '@/api/scenario';
import { getScenarioWithBLOBsById, setScenarioDomain } from '@/api/scenario';
import { hasPermission } from 'metersphere-frontend/src/utils/permission';
export default {
@ -176,14 +176,7 @@ export default {
});
},
checkEnv(val) {
checkScenarioEnv(this.data.id).then((res) => {
if (this.data.environmentEnable && !res.data) {
this.data.environmentEnable = false;
this.$warning(this.$t('commons.scenario_warning'));
return;
}
this.setDomain(val);
});
this.setDomain(val);
},
setDomain(val) {
let param = {

View File

@ -46,7 +46,9 @@
</div>
</el-col>
<el-col :span="12">
<el-checkbox v-model="cookieShare" @change="setCookieShare">{{ $t('api_test.scenario.share_cookie') }}</el-checkbox>
<el-checkbox v-model="cookieShare" @change="setCookieShare">{{
$t('api_test.scenario.share_cookie')
}}</el-checkbox>
<el-checkbox v-model="sampleError" @change="setOnSampleError" style="margin-right: 10px">
{{ $t('commons.failure_continues') }}
</el-checkbox>
@ -89,7 +91,12 @@
<!-- 场景步骤-->
<ms-container :class="{ 'maximize-container': !asideHidden }">
<ms-aside-container :draggable="false" @setAsideHidden="setAsideHidden" style="padding: 0px; overflow: hidden" width="50%" @click.native="handleMainClick">
<ms-aside-container
:draggable="false"
@setAsideHidden="setAsideHidden"
style="padding: 0px; overflow: hidden"
width="35%"
@click.native="handleMainClick">
<div class="ms-debug-result" v-if="reqTotal > 0">
<span style="float: right">
<span class="ms-message-right"> {{ reqTotalTime }} ms </span>
@ -116,8 +123,7 @@
@node-drag-end="allowDrag"
@node-click="nodeClick"
class="ms-max-tree"
ref="maxStepTree"
>
ref="maxStepTree">
<el-row
class="custom-tree-node"
:gutter="18"
@ -224,8 +230,7 @@
@openScenario="openScenario"
@runScenario="runScenario"
@stopScenario="stopScenario"
v-if="selectedTreeNode && selectedNode"
/>
v-if="selectedTreeNode && selectedNode" />
<!-- 请求下还有的子步骤-->
<div v-if="selectedTreeNode && selectedTreeNode.hashTree && showNode(selectedTreeNode)">
<div v-for="item in selectedTreeNode.hashTree" :key="item.id" class="ms-col-one">
@ -274,9 +279,6 @@
<!--场景导入 -->
<scenario-relevance v-if="type !== 'detail'" @save="addScenario" ref="scenarioRelevance" />
<!-- 环境 -->
<api-environment-config v-if="type !== 'detail'" ref="environmentConfig" @close="environmentConfigClose" />
<!--执行组件-->
<ms-run
:debug="true"
@ -318,21 +320,16 @@
<script>
import { getApiScenarioEnv } from '@/api/scenario';
import { API_STATUS, PRIORITY } from '../../../definition/model/JsonData';
import { parseEnvironment } from '@/business/environment/model/EnvironmentModel';
import { STEP } from '../Setting';
import { getUUID, strMapToObj } from 'metersphere-frontend/src/utils';
import { getCurrentProjectID } from 'metersphere-frontend/src/utils/token';
import { hasLicense } from 'metersphere-frontend/src/utils/permission';
import OutsideClick from '../common/outside-click';
import { copyScenarioRow, saveScenario, scenarioSort, handleCtrlSEvent } from '@/business/automation/api-automation';
import { buttons, setComponent } from '../menu/Menu';
import MsContainer from 'metersphere-frontend/src/components/MsContainer';
import MsMainContainer from 'metersphere-frontend/src/components/MsMainContainer';
import MsAsideContainer from 'metersphere-frontend/src/components/MsAsideContainer';
// import html2canvas from 'html2canvas';
import { getEnvironmentByProjectId } from 'metersphere-frontend/src/api/environment';
import { useApiStore } from '@/store';
import { getPluginList } from '@/api/plugin';
const store = useApiStore();
let jsonPath = require('jsonpath');
@ -535,19 +532,19 @@ export default {
},
},
methods: {
handleHeaderClick(e){
if(e.target.tagName === 'DIV'){
this.outsideClick(e)
handleHeaderClick(e) {
if (e.target.tagName === 'DIV') {
this.outsideClick(e);
}
},
handleMainClick(e) {
this.outsideClick(e)
this.outsideClick(e);
},
handleComponentClick(e) {
e.stopPropagation();
},
initPlugins() {
if(this.pluginList && this.pluginList.length > 0){
if (this.pluginList && this.pluginList.length > 0) {
this.pluginList.forEach((item) => {
let plugin = {
title: item.name,
@ -813,56 +810,12 @@ export default {
});
},
debugScenario() {
if(this.scenarioDefinition.length < 1){
if (this.scenarioDefinition.length < 1) {
return;
}
this.debugLoading = true;
this.$emit('runDebug');
},
runDebug() {
/*触发执行操作*/
let sign = this.$refs.envPopover.checkEnv();
if (!sign) {
this.errorRefresh();
return;
}
if (this.$refs['currentScenario']) {
this.$refs['currentScenario'].validate((valid) => {
if (valid) {
Promise.all([this.editScenario()]).then((val) => {
if (val) {
this.debugData = {
id: this.currentScenario.id,
name: this.currentScenario.name,
type: 'scenario',
variables: this.currentScenario.variables,
referenced: 'Created',
enableCookieShare: this.enableCookieShare,
headers: this.currentScenario.headers,
environmentMap: this.projectEnvMap,
hashTree: this.scenarioDefinition,
};
this.reportId = getUUID().substring(0, 8);
}
});
} else {
this.errorRefresh();
}
});
}
},
getEnvironments() {
if (this.projectId) {
getEnvironmentByProjectId(this.projectId).then((response) => {
this.environments = response.data;
this.environments.forEach((environment) => {
parseEnvironment(environment);
});
//
this.checkDataIsCopy();
});
}
},
checkDataIsCopy() {
//
@ -871,16 +824,6 @@ export default {
}
},
openEnvironmentConfig() {
if (!this.projectId) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs.environmentConfig.open(this.projectId);
},
environmentConfigClose() {
this.getEnvironments();
},
allowDrop(draggingNode, dropNode, dropType) {
if (draggingNode.data.type === 'Assertions' || dropNode.data.type === 'Assertions') {
return false;
@ -1324,16 +1267,21 @@ export default {
},
getEnv(definition) {
return new Promise((resolve) => {
const encoder = new TextEncoder();
const bytes = encoder.encode(definition, 'utf-8');
getApiScenarioEnv(bytes).then((res) => {
if (res.data && res.data.data) {
res.data.data.projectIds.push(this.projectId);
this.$emit('update:projectIds', new Set(res.data.data.projectIds));
this.$emit('update:isFullUrl', res.data.data.fullUrl);
}
this.projectIds = new Set();
const regex = /"projectId"\s*:\s*"([^"]+)"/g;
let match;
while ((match = regex.exec(definition)) !== null) {
this.projectIds.add(match[1]);
}
this.projectIds.add(this.projectId);
if (this.projectIds.size > 1) {
getApiScenarioEnv(Array.from(this.projectIds)).then((res) => {
this.$emit('update:projectIds', new Set(res.data.projectIds));
resolve();
});
} else {
resolve();
});
}
});
},
},
@ -1516,5 +1464,4 @@ export default {
white-space: nowrap;
width: 120px;
}
</style>

View File

@ -52,6 +52,10 @@
<span>{{ $t('load_test.runtime_config') }}</span>
<div style="padding-top: 10px">
<span class="ms-mode-span">{{ $t('commons.environment') }}</span>
<el-radio-group v-model="runConfig.defaultEnv" style="margin-right: 20px">
<el-radio :label="true">{{ $t('automation.default_environment') }}</el-radio>
<el-radio :label="false">{{ $t('automation.select_new_environment') }}</el-radio>
</el-radio-group>
<env-popover
:project-ids="projectIds"
:placement="'bottom-start'"
@ -64,15 +68,13 @@
@setProjectEnvMap="setProjectEnvMap"
@showPopover="showPopover"
ref="envPopover"
class="env-popover" />
class="env-popover"
v-show="this.runConfig.defaultEnv === false" />
</div>
<div class="ms-mode-div">
<span class="ms-mode-span">{{ $t('run_mode.other_config') }}</span>
<span>{{ $t('run_mode.run_with_resource_pool') }}:</span>
<el-select
style="margin-left: 10px"
v-model="runConfig.resourcePoolId"
size="mini">
<el-select style="margin-left: 10px" v-model="runConfig.resourcePoolId" size="mini">
<el-option
v-for="item in resourcePools"
:key="item.id"
@ -87,9 +89,8 @@
<crontab @hide="showCron = false" @fill="crontabFill" :expression="schedule.value" ref="crontab" />
</el-dialog>
</el-tab-pane>
<el-tab-pane :label="$t('schedule.task_notification')" name="second"
v-permission="['PROJECT_MESSAGE:READ']">
<ms-schedule-notification :test-id="testId" :schedule-receiver-options="scheduleReceiverOptions"/>
<el-tab-pane :label="$t('schedule.task_notification')" name="second" v-permission="['PROJECT_MESSAGE:READ']">
<ms-schedule-notification :test-id="testId" :schedule-receiver-options="scheduleReceiverOptions" />
</el-tab-pane>
</el-tabs>
</div>
@ -188,6 +189,7 @@ export default {
envMap: {},
environmentGroupId: '',
environmentType: ENV_TYPE.JSON,
defaultEnv: true,
},
projectList: [],
projectIds: new Set(),
@ -195,9 +197,9 @@ export default {
};
},
methods: {
async checkPool(){
async checkPool() {
let hasPool = false;
this.resourcePools.forEach(item => {
this.resourcePools.forEach((item) => {
if (item.id === this.runConfig.resourcePoolId) {
hasPool = true;
}
@ -208,7 +210,7 @@ export default {
let hasPool = await this.checkPool();
if (!hasPool) {
this.runConfig.resourcePoolId = null;
getProjectConfig(getCurrentProjectID(), "").then(async (res) => {
getProjectConfig(getCurrentProjectID(), '').then(async (res) => {
if (res.data && res.data.poolEnable && res.data.resourcePoolId) {
this.runConfig.resourcePoolId = res.data.resourcePoolId;
}
@ -219,9 +221,6 @@ export default {
});
}
},
currentUser: () => {
return getCurrentUser();
},
intervalValidate() {
if (this.getIntervalTime() < 1 * 60 * 1000) {
return false;
@ -322,6 +321,10 @@ export default {
this.schedule = response.data;
if (response.data.config) {
this.runConfig = JSON.parse(response.data.config);
//
if (this.runConfig.defaultEnv === null) {
this.runConfig.defaultEnv = false;
}
if (this.runConfig.envMap) {
this.projectEnvListMap = objToStrMap(this.runConfig.envMap);
} else {
@ -384,16 +387,15 @@ export default {
}
if (this.schedule.enable) {
if (
(this.runConfig.environmentType === 'JSON' && Object.keys(this.runConfig.envMap).length === 0) ||
(!this.runConfig.defaultEnv &&
this.runConfig.environmentType === 'JSON' &&
Object.keys(this.runConfig.envMap).length === 0) ||
(this.runConfig.environmentType === 'GROUP' && !this.runConfig.environmentGroupId)
) {
this.$warning(this.$t('workspace.env_group.please_select_env_for_current_scenario'));
return;
}
if (this.runConfig.resourcePoolId == null) {
this.$warning(this.$t('workspace.env_group.please_select_run_within_resource_pool'));
return;
}
)
if (this.runConfig.resourcePoolId == null) {
this.$warning(this.$t('workspace.env_group.please_select_run_within_resource_pool'));
return;
}
}
param.config = JSON.stringify(this.runConfig);
param.scheduleFrom = 'scenario';

View File

@ -132,13 +132,19 @@ const message = {
automation: {
project_no_permission: 'The current person does not have the operation permission for this step',
document_validity_msg: 'The file has been modified, please re-upload',
scenario_step_ref_message:
'The current may cause page loading exceptions, whether to continue?',
scenario_step_ref_message: 'The current may cause page loading exceptions, whether to continue?',
case_message: 'Please select a case',
scenario_message: 'Please select a scene',
scenario_plugin_debug_warning: 'The scenario contains plugin steps, and the corresponding scenario has been deleted and cannot be debugged ',
scenario_plugin_save_warning: 'The scene contains plugin steps, and the corresponding scene has been deleted and cannot be edited ',
scenario_plugin_run_warning: 'The scenario contains plugin steps, and the corresponding scenario has been deleted and cannot be executed',
scenario_plugin_debug_warning:
'The scenario contains plugin steps, and the corresponding scenario has been deleted and cannot be debugged ',
scenario_plugin_save_warning:
'The scene contains plugin steps, and the corresponding scene has been deleted and cannot be edited ',
scenario_plugin_run_warning:
'The scenario contains plugin steps, and the corresponding scenario has been deleted and cannot be executed',
project_env_info: 'More project environment',
project_env_info_tips:
'1. Steps involving cross-projects in the current scenario can be executed in the environment of the project to which they belong. If not specified, the current project environment will be used by default.\n' +
' 2. If the cross-project steps are configured to use the original scene environment in the scene settings, the original scene environment will be used by default to execute.',
},
};
export default {

View File

@ -134,6 +134,11 @@ const message = {
scenario_plugin_debug_warning: '场景包含插件步骤,对应场景已经删除不能调试!',
scenario_plugin_save_warning: '场景包含插件步骤,对应场景已经删除不能编辑!',
scenario_plugin_run_warning: '场景包含插件步骤,对应场景已经删除不能执行!',
project_env_info: '更多项目环境',
project_env_info_tips:
'1.当前场景涉及到跨项目的步骤可以指定其所属项目的环境执行,不指定则默认使用当前项目环境执行。\n 2.如跨项目的步骤在场景设置中配置了使用原场景环境,则默认使用原场景环境执行。',
default_environment: '默认环境',
select_new_environment: '指定新环境',
},
};

View File

@ -131,9 +131,12 @@ const message = {
scenario_step_ref_message: '當前操作可能導致頁面加載異常,是否繼續',
case_message: '請選擇案例',
scenario_message: '請選擇場景',
scenario_plugin_debug_warning:'場景包含挿件步驟,對應場景已經刪除不能調試! ',
scenario_plugin_debug_warning: '場景包含挿件步驟,對應場景已經刪除不能調試! ',
scenario_plugin_save_warning: '場景包含挿件步驟,對應場景已經刪除不能編輯! ',
scenario_plugin_run_warning: '場景包含挿件步驟,對應場景已經刪除不能運行! '
scenario_plugin_run_warning: '場景包含挿件步驟,對應場景已經刪除不能運行! ',
project_env_info: '更多項目環境',
project_env_info_tips:
'1.當前場景涉及到跨項目的步驟可以指定其所屬項目的環境執行,不指定則默認使用當前項目環境執行。 \n 2.如跨項目的步驟在場景設置中配置了使用原場景環境,則默認使用原場景環境執行。 ',
},
};

View File

@ -1,6 +1,6 @@
import {get, post, request} from "../plugins/request"
import { get, post, request } from '../plugins/request';
// 获取使用当前js模块的package.json不要修改引入路径
import packageInfo from '@/../package.json'
import packageInfo from '@/../package.json';
const currentModuleName = packageInfo.name;
export function getEnvironmentMapByGroupId(id) {
@ -10,6 +10,10 @@ export function getEnvironmentMapByGroupId(id) {
export function getEnvironmentByProjectId(projectId) {
return get('/environment/list/' + projectId);
}
// 不含环境的blob数据
export function getEnvironmentByProjectIds(projectIds) {
return post('/environment/project-env', projectIds);
}
export function getEnvironmentById(environmentId) {
return get('/environment/get/' + environmentId);
@ -84,23 +88,26 @@ export function databaseValidate(params) {
export function getUploadConfig(url, formData) {
return {
method: 'POST', url: url, data: formData, headers: {
'Content-Type': undefined
}
method: 'POST',
url: url,
data: formData,
headers: {
'Content-Type': undefined,
},
};
}
export function fileUpload(url, file, files, param) {
let formData = new FormData();
if (file) {
formData.append("file", file);
formData.append('file', file);
}
if (files) {
files.forEach(f => {
formData.append("files", f);
files.forEach((f) => {
formData.append('files', f);
});
}
formData.append('request', new Blob([JSON.stringify(param)], {type: "application/json"}));
formData.append('request', new Blob([JSON.stringify(param)], { type: 'application/json' }));
let config = getUploadConfig(url, formData);
return request(config);
}
@ -150,7 +157,7 @@ export function getModuleByUrl(url) {
}
export function getCaseRelateModuleByCondition(url, params) {
return post('/environment/relate' + url, params)
return post('/environment/relate' + url, params);
}
export function getCodeSnippetPages(goPage, pageSize, params) {

View File

@ -14,6 +14,7 @@ public class RunModeConfigDTO {
private String testId;
private String amassReport;
private boolean onSampleError;
private Boolean defaultEnv;
// 失败重试
private boolean retryEnable;
// 失败重试次数

View File

@ -1,353 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jmeter.config;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.engine.event.LoopIterationListener;
import org.apache.jmeter.engine.util.NoConfigMerge;
import org.apache.jmeter.gui.GUIMenuSortOrder;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.save.CSVSaveService;
import org.apache.jmeter.services.FileServer;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer;
import org.apache.jmeter.testelement.property.JMeterProperty;
import org.apache.jmeter.testelement.property.StringProperty;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.util.JMeterStopThreadException;
import org.apache.jorphan.util.JOrphanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.io.IOException;
import java.util.ResourceBundle;
/**
* Read lines from a file and split int variables.
*
* The iterationStart() method is used to set up each set of values.
*
* By default, the same file is shared between all threads
* (and other thread groups, if they use the same file name).
*
* The shareMode can be set to:
* <ul>
* <li>All threads - default, as described above</li>
* <li>Current thread group</li>
* <li>Current thread</li>
* <li>Identifier - all threads sharing the same identifier</li>
* </ul>
*
* The class uses the FileServer alias mechanism to provide the different share modes.
* For all threads, the file alias is set to the file name.
* Otherwise, a suffix is appended to the filename to make it unique within the required context.
* For current thread group, the thread group identityHashcode is used;
* for individual threads, the thread hashcode is used as the suffix.
* Or the user can provide their own suffix, in which case the file is shared between all
* threads with the same suffix.
*
*/
@GUIMenuSortOrder(1)
@TestElementMetadata(labelResource = "displayName")
public class CSVDataSet extends ConfigTestElement
implements TestBean, LoopIterationListener, NoConfigMerge {
private static final Logger log = LoggerFactory.getLogger(CSVDataSet.class);
private static final long serialVersionUID = 233L;
private static final String EOFVALUE = // value to return at EOF
JMeterUtils.getPropDefault("csvdataset.eofstring", "<EOF>"); //$NON-NLS-1$ //$NON-NLS-2$
private transient String filename;
private transient String fileEncoding;
private transient String variableNames;
private transient String delimiter;
private transient boolean quoted;
private transient boolean recycle = true;
private transient boolean stopThread;
private transient String[] vars;
private transient String alias;
private transient String shareMode;
private boolean firstLineIsNames = false;
private boolean ignoreFirstLine = false;
private final static String THREAD_SPLIT = " ";
private Object readResolve() {
recycle = true;
return this;
}
/**
* Override the setProperty method in order to convert
* the original String shareMode property.
* This used the locale-dependent display value, so caused
* problems when the language was changed.
* If the "shareMode" value matches a resource value then it is converted
* into the resource key.
* To reduce the need to look up resources, we only attempt to
* convert values with spaces in them, as these are almost certainly
* not variables (and they are definitely not resource keys).
*/
@Override
public void setProperty(JMeterProperty property) {
if (!(property instanceof StringProperty)) {
super.setProperty(property);
return;
}
final String propName = property.getName();
if (!"shareMode".equals(propName)) {
super.setProperty(property);
return;
}
final String propValue = property.getStringValue();
if (propValue.contains(" ")) { // variables are unlikely to contain spaces, so most likely a translation
try {
final BeanInfo beanInfo = Introspector.getBeanInfo(this.getClass());
final ResourceBundle rb = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE);
for (String resKey : CSVDataSetBeanInfo.getShareTags()) {
if (propValue.equals(rb.getString(resKey))) {
if (log.isDebugEnabled()) {
log.debug("Converted {}={} to {} using Locale: {}", propName, propValue, resKey, rb.getLocale());
}
((StringProperty) property).setValue(resKey); // reset the value
super.setProperty(property);
return;
}
}
// This could perhaps be a variable name
log.warn("Could not translate {}={} using Locale: {}", propName, propValue, rb.getLocale());
} catch (IntrospectionException e) {
LoggerUtil.error("Could not find BeanInfo; cannot translate shareMode entries", e);
}
}
super.setProperty(property);
}
@Override
public void iterationStart(LoopIterationEvent iterEvent) {
FileServer server = FileServer.getFileServer();
final JMeterContext context = getThreadContext();
String delim = getDelimiter();
if ("\\t".equals(delim)) { // $NON-NLS-1$
delim = "\t";// Make it easier to enter a Tab // $NON-NLS-1$
} else if (delim.isEmpty()) {
log.debug("Empty delimiter, will use ','");
delim = ",";
}
if (vars == null) {
initVars(server, context, delim);
}
// TODO: fetch this once as per vars above?
JMeterVariables threadVars = context.getVariables();
String[] lineValues = {};
try {
if (getQuotedData()) {
lineValues = server.getParsedLine(alias, recycle,
firstLineIsNames || ignoreFirstLine, delim.charAt(0));
} else {
String line = server.readLine(alias, recycle,
firstLineIsNames || ignoreFirstLine);
lineValues = JOrphanUtils.split(line, delim, false);
}
for (int a = 0; a < vars.length && a < lineValues.length; a++) {
threadVars.put(vars[a], lineValues[a]);
}
} catch (IOException e) { // treat the same as EOF
LoggerUtil.error(e.toString());
}
if (lineValues.length == 0) {// i.e. EOF
if (getStopThread()) {
throw new JMeterStopThreadException("End of file:" + getFilename() + " detected for CSV DataSet:"
+ getName() + " configured with stopThread:" + getStopThread() + ", recycle:" + getRecycle());
}
for (String var : vars) {
threadVars.put(var, EOFVALUE);
}
}
}
private void initVars(FileServer server, final JMeterContext context, String delim) {
String fileName = getFilename().trim();
LoggerUtil.info("初始化csv内容" + fileName);
setAlias(context, fileName);
final String names = getVariableNames();
if (StringUtils.isEmpty(names)) {
String header = server.reserveFile(fileName, getFileEncoding(), alias, true);
try {
vars = CSVSaveService.csvSplitString(header, delim.charAt(0));
firstLineIsNames = true;
} catch (IOException e) {
throw new IllegalArgumentException("Could not split CSV header line from file:" + fileName, e);
}
} else {
server.reserveFile(fileName, getFileEncoding(), alias, ignoreFirstLine);
vars = JOrphanUtils.split(names, ","); // $NON-NLS-1$
}
trimVarNames(vars);
}
private void setAlias(final JMeterContext context, String alias) {
String mode = getShareMode();
int modeInt = CSVDataSetBeanInfo.getShareModeAsInt(mode);
String threadName = StringUtils.substringBeforeLast(context.getThread().getThreadName(), THREAD_SPLIT);
switch (modeInt) {
case CSVDataSetBeanInfo.SHARE_ALL:
this.alias = alias;
break;
case CSVDataSetBeanInfo.SHARE_GROUP:
this.alias = alias + "@" + "GROUP_" + threadName + "@" + System.identityHashCode(context.getThreadGroup());
break;
case CSVDataSetBeanInfo.SHARE_THREAD:
this.alias = alias + "@" + threadName + "@" + System.identityHashCode(context.getThread());
break;
default:
this.alias = alias + "@" + mode; // user-specified key
break;
}
}
/**
* trim content of array varNames
* @param varsNames
*/
private void trimVarNames(String[] varsNames) {
for (int i = 0; i < varsNames.length; i++) {
varsNames[i] = varsNames[i].trim();
}
}
/**
* @return Returns the filename.
*/
public String getFilename() {
return filename;
}
/**
* @param filename The filename to set.
*/
public void setFilename(String filename) {
this.filename = filename;
}
/**
* @return Returns the file encoding.
*/
public String getFileEncoding() {
return fileEncoding;
}
/**
* @param fileEncoding
* The fileEncoding to set.
*/
public void setFileEncoding(String fileEncoding) {
this.fileEncoding = fileEncoding;
}
/**
* @return Returns the variableNames.
*/
public String getVariableNames() {
return variableNames;
}
/**
* @param variableNames
* The variableNames to set.
*/
public void setVariableNames(String variableNames) {
this.variableNames = variableNames;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public boolean getQuotedData() {
return quoted;
}
public void setQuotedData(boolean quoted) {
this.quoted = quoted;
}
public boolean getRecycle() {
return recycle;
}
public void setRecycle(boolean recycle) {
this.recycle = recycle;
}
public boolean getStopThread() {
return stopThread;
}
public void setStopThread(boolean value) {
this.stopThread = value;
}
public String getShareMode() {
return shareMode;
}
public void setShareMode(String value) {
this.shareMode = value;
}
/**
* @return the ignoreFirstLine
*/
public boolean isIgnoreFirstLine() {
return ignoreFirstLine;
}
/**
* @param ignoreFirstLine the ignoreFirstLine to set
*/
public void setIgnoreFirstLine(boolean ignoreFirstLine) {
this.ignoreFirstLine = ignoreFirstLine;
}
}

View File

@ -18,11 +18,11 @@
package org.apache.jmeter.services;
import io.metersphere.utils.LoggerUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.gui.JMeterFileFilter;
import org.apache.jmeter.save.CSVSaveService;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.util.JOrphanUtils;
import org.slf4j.Logger;
@ -30,7 +30,10 @@ import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
/**
@ -121,7 +124,7 @@ public class FileServer {
* from its parent.
*
* @param scriptPath the path of the script file; must be not be {@code null}
* @throws IllegalStateException if files are still open
* @throws IllegalStateException if files are still open
* @throws IllegalArgumentException if scriptPath parameter is null
*/
public synchronized void setBaseForScript(File scriptPath) {
@ -137,7 +140,7 @@ public class FileServer {
* Sets the current base directory for relative file names.
*
* @param jmxBase the path of the script file base directory, cannot be null
* @throws IllegalStateException if files are still open
* @throws IllegalStateException if files are still open
* @throws IllegalArgumentException if {@code basepath} is null
*/
public synchronized void setBase(File jmxBase) {
@ -207,14 +210,14 @@ public class FileServer {
* @param filename - relative (to base) or absolute file name (must not be null)
*/
public void reserveFile(String filename) {
reserveFile(filename,null);
reserveFile(filename, null);
}
/**
* Creates an association between a filename and a File inputOutputObject,
* and stores it for later use - unless it is already stored.
*
* @param filename - relative (to base) or absolute file name (must not be null)
* @param filename - relative (to base) or absolute file name (must not be null)
* @param charsetName - the character set encoding to use for the file (may be null)
*/
public void reserveFile(String filename, String charsetName) {
@ -225,9 +228,9 @@ public class FileServer {
* Creates an association between a filename and a File inputOutputObject,
* and stores it for later use - unless it is already stored.
*
* @param filename - relative (to base) or absolute file name (must not be null)
* @param filename - relative (to base) or absolute file name (must not be null)
* @param charsetName - the character set encoding to use for the file (may be null)
* @param alias - the name to be used to access the object (must not be null)
* @param alias - the name to be used to access the object (must not be null)
*/
public void reserveFile(String filename, String charsetName, String alias) {
reserveFile(filename, charsetName, alias, false);
@ -237,10 +240,10 @@ public class FileServer {
* Creates an association between a filename and a File inputOutputObject,
* and stores it for later use - unless it is already stored.
*
* @param filename - relative (to base) or absolute file name (must not be null or empty)
* @param filename - relative (to base) or absolute file name (must not be null or empty)
* @param charsetName - the character set encoding to use for the file (may be null)
* @param alias - the name to be used to access the object (must not be null)
* @param hasHeader true if the file has a header line describing the contents
* @param alias - the name to be used to access the object (must not be null)
* @param hasHeader true if the file has a header line describing the contents
* @return the header line; may be null
* @throws IllegalArgumentException if header could not be read or filename is null or empty
*/
@ -251,6 +254,16 @@ public class FileServer {
if (alias == null) {
throw new IllegalArgumentException("Alias must not be null");
}
// todo 这里更改了原始代码
if(!new File(filename).exists()){
log.error("file does not exist [ "+ filename+" ]");
return "";
}
String threadName = JMeterContextService.getContext().getThread().getThreadName();
if (!StringUtils.contains(alias, threadName)) {
alias = StringUtils.join(threadName, alias);
}
FileEntry fileEntry = files.get(alias);
if (fileEntry == null) {
fileEntry = new FileEntry(resolveFileFromPath(filename), null, charsetName);
@ -282,6 +295,7 @@ public class FileServer {
* Resolves file name into {@link File} instance.
* When filename is not absolute and not found from current working dir,
* it tries to find it under current base directory
*
* @param filename original file name
* @return {@link File} instance
*/
@ -309,7 +323,7 @@ public class FileServer {
* Get the next line of the named file, first line is name to false
*
* @param filename the filename or alias that was used to reserve the file
* @param recycle - should file be restarted at EOF?
* @param recycle - should file be restarted at EOF?
* @return String containing the next line in the file (null if EOF reached and not recycle)
* @throws IOException when reading of the file fails, or the file was not reserved properly
*/
@ -320,14 +334,19 @@ public class FileServer {
/**
* Get the next line of the named file
*
* @param filename the filename or alias that was used to reserve the file
* @param recycle - should file be restarted at EOF?
* @param filename the filename or alias that was used to reserve the file
* @param recycle - should file be restarted at EOF?
* @param ignoreFirstLine - Ignore first line
* @return String containing the next line in the file (null if EOF reached and not recycle)
* @throws IOException when reading of the file fails, or the file was not reserved properly
*/
public synchronized String readLine(String filename, boolean recycle,
boolean ignoreFirstLine) throws IOException {
String threadName = JMeterContextService.getContext().getThread().getThreadName();
if (!StringUtils.contains(filename, threadName)) {
filename = StringUtils.join(threadName, filename);
}
FileEntry fileEntry = files.get(filename);
if (fileEntry != null) {
if (fileEntry.inputOutputObject == null) {
@ -350,15 +369,17 @@ public class FileServer {
log.debug("Read:{}", line);
return line;
}
throw new IOException("File never reserved: "+filename);
// todo 这里更改了原始代码
//throw new IOException("File never reserved: " + filename);
log.error("File never reserved: " + filename);
return "";
}
/**
*
* @param alias the file name or alias
* @param recycle whether the file should be re-started on EOF
* @param alias the file name or alias
* @param recycle whether the file should be re-started on EOF
* @param ignoreFirstLine whether the file contains a file header which will be ignored
* @param delim the delimiter to use for parsing
* @param delim the delimiter to use for parsing
* @return the parsed line, will be empty if the file is at EOF
* @throws IOException when reading of the aliased file fails, or the file was not reserved properly
*/
@ -377,6 +398,11 @@ public class FileServer {
* @return {@link BufferedReader}
*/
private BufferedReader getReader(String alias, boolean recycle, boolean ignoreFirstLine) throws IOException {
String threadName = JMeterContextService.getContext().getThread().getThreadName();
if (!StringUtils.contains(alias, threadName)) {
alias = StringUtils.join(threadName, alias);
}
FileEntry fileEntry = files.get(alias);
if (fileEntry != null) {
BufferedReader reader;
@ -409,7 +435,7 @@ public class FileServer {
}
return reader;
} else {
throw new IOException("File never reserved: "+alias);
throw new IOException("File never reserved: " + alias);
}
}
@ -433,6 +459,10 @@ public class FileServer {
}
public synchronized void write(String filename, String value) throws IOException {
String threadName = JMeterContextService.getContext().getThread().getThreadName();
if (!StringUtils.contains(filename, threadName)) {
filename = StringUtils.join(threadName, filename);
}
FileEntry fileEntry = files.get(filename);
if (fileEntry != null) {
if (fileEntry.inputOutputObject == null) {
@ -444,7 +474,7 @@ public class FileServer {
log.debug("Write:{}", value);
writer.write(value);
} else {
throw new IOException("File never reserved: "+filename);
throw new IOException("File never reserved: " + filename);
}
}
@ -464,49 +494,36 @@ public class FileServer {
public synchronized void closeFiles() throws IOException {
for (Map.Entry<String, FileEntry> me : files.entrySet()) {
closeFile(me.getKey(), me.getValue() );
closeFile(me.getKey(), me.getValue());
}
files.clear();
}
/**
* 根据线程名字清理掉当前线程缓存的csv
*
* @param name 线程组名称
*/
public synchronized void closeCsv(String name) {
public synchronized void closeFiles(String threadName) {
try {
if (StringUtils.isNotEmpty(name)) {
List<String> list = new ArrayList<>();
for (Iterator<String> iterator = files.keySet().iterator(); iterator.hasNext(); ) {
String key = iterator.next();
if (key.contains(name)) {
FileEntry fileEntry = files.get(key);
closeFile(name, fileEntry);
list.add(key);
}
}
if (CollectionUtils.isNotEmpty(list)) {
for (String key : list) {
files.remove(key);
}
for (Iterator<Map.Entry<String, FileEntry>> it = files.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, FileEntry> entry = it.next();
if (entry.getKey().startsWith(threadName)) {
closeFile(entry.getKey(), entry.getValue());
it.remove();
}
}
LoggerUtil.info("在处理中的CSV数量" + files.size());
LoggerUtil.info("清除当前线程组占用文件后剩余文件数:" + files.size() + "", threadName);
} catch (Exception e) {
LoggerUtil.error("关闭CSV异常" + name, e);
log.error("close files exception", e);
}
}
public int fileSize() {
return files.size();
}
/**
* @param name the name or alias of the file to be closed
* @throws IOException when closing of the aliased file fails
*/
public synchronized void closeFile(String name) throws IOException {
String threadName = JMeterContextService.getContext().getThread().getThreadName();
if (!StringUtils.contains(name, threadName)) {
name = StringUtils.join(threadName, name);
}
FileEntry fileEntry = files.get(name);
closeFile(name, fileEntry);
}
@ -582,9 +599,8 @@ public class FileServer {
* "jmeter.save.saveservice.base_prefix" - default "~/" - then the name is
* assumed to be relative to the basename.
*
* @param relativeName
* filename that should be checked for
* <code>jmeter.save.saveservice.base_prefix</code>
* @param relativeName filename that should be checked for
* <code>jmeter.save.saveservice.base_prefix</code>
* @return the updated filename
*/
public static String resolveBaseRelativeName(String relativeName) {
@ -610,4 +626,4 @@ public class FileServer {
public void setScriptName(String scriptName) {
this.scriptName = scriptName;
}
}
}

View File

@ -32,6 +32,7 @@ import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.processor.PostProcessor;
import org.apache.jmeter.processor.PreProcessor;
import org.apache.jmeter.samplers.*;
import org.apache.jmeter.services.FileServer;
import org.apache.jmeter.testbeans.TestBeanHelper;
import org.apache.jmeter.testelement.*;
import org.apache.jmeter.threads.JMeterContext.TestLogicalAction;
@ -334,6 +335,7 @@ public class JMeterThread implements Runnable, Interruptible {
threadFinished(iterationListener);
monitor.threadFinished(this); // Tell the monitor we are done
JMeterContextService.removeContext(); // Remove the ThreadLocal entry
FileServer.getFileServer().closeFiles(threadName);
} finally {
interruptLock.unlock(); // Allow any pending interrupt to complete (OK because currentSampler == null)
}

View File

@ -2,6 +2,7 @@ package io.metersphere.environment.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.ApiTestEnvironment;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.base.domain.EnvironmentGroup;
import io.metersphere.commons.constants.OperLogConstants;
@ -45,6 +46,11 @@ public class TestEnvironmentController {
return baseEnvironmentService.list(projectId);
}
@PostMapping("/project-env")
public List<ApiTestEnvironment> projectEnv(@RequestBody List<String> projectIds) {
return baseEnvironmentService.selectList(projectIds);
}
/**
* 查询指定项目和指定名称的环境
*

View File

@ -370,6 +370,16 @@ public class BaseEnvironmentService extends NodeTreeService<ApiModuleDTO> {
return apiTestEnvironmentMapper.selectByExampleWithBLOBs(example);
}
public List<ApiTestEnvironment> selectList(List<String> projectIds) {
if(CollectionUtils.isEmpty(projectIds)){
return new ArrayList<>();
}
ApiTestEnvironmentExample example = new ApiTestEnvironmentExample();
example.createCriteria().andProjectIdIn(projectIds);
return apiTestEnvironmentMapper.selectByExample(example);
}
public ApiTestEnvironmentWithBLOBs get(String id) {
return apiTestEnvironmentMapper.selectByPrimaryKey(id);
}

View File

@ -1,21 +1,41 @@
<template>
<div v-loading="result.loading">
<div v-for="pe in data" :key="pe.id" style="margin-left: 20px;">
<el-select v-model="pe['selectEnv']" filterable :placeholder="$t('workspace.env_group.please_select_env')"
style="margin-top: 8px;width: 200px;" size="small">
<el-option v-for="(environment, index) in pe.envs" :key="index"
:label="environment.name"
:value="environment.id"/>
<el-button class="ms-scenario-button" v-if="isShowConfirmButton(pe.id)" size="mini" type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
<div v-for="pe in data" :key="pe.id" style="margin-left: 20px">
<el-select
v-model="pe['selectEnv']"
filterable
:placeholder="$t('workspace.env_group.please_select_env')"
style="margin-top: 8px; width: 200px"
size="small"
>
<el-option
v-for="(environment, index) in pe.envs"
:key="index"
:label="environment.name"
:value="environment.id"
/>
<el-button
class="ms-scenario-button"
v-if="isShowConfirmButton(pe.id)"
size="mini"
type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])"
>
{{ $t("api_test.environment.environment_config") }}
</el-button>
<template v-slot:empty>
<!--这里只做没有可搜索内容时使用否则如果没有符合搜索条件的也会显示该项与上面的btn重复显示 -->
<div v-if="isShowConfirmButton(pe.id) && pe.envs.length===0" class="empty-environment">
<el-button class="ms-scenario-button" size="mini" type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])">
{{ $t('api_test.environment.environment_config') }}
<div
v-if="isShowConfirmButton(pe.id) && pe.envs.length === 0"
class="empty-environment"
>
<el-button
class="ms-scenario-button"
size="mini"
type="primary"
@click="openEnvironmentConfig(pe.id, pe['selectEnv'])"
>
{{ $t("api_test.environment.environment_config") }}
</el-button>
</div>
</template>
@ -25,104 +45,114 @@
</span>
</div>
<el-button type="primary" @click="handleConfirm" size="small" class="env-confirm">{{$t('workspace.env_group.confirm')}}</el-button>
<el-button
type="primary"
@click="handleConfirm"
size="small"
class="env-confirm"
>{{ $t("workspace.env_group.confirm") }}</el-button
>
<!-- 环境配置 -->
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
<api-environment-config
ref="environmentConfig"
@close="environmentConfigClose"
/>
</div>
</template>
<script>
import {parseEnvironment} from "metersphere-frontend/src/model/EnvironmentModel";
import { parseEnvironment } from "metersphere-frontend/src/model/EnvironmentModel";
import ApiEnvironmentConfig from "@/business/module/api/ApiEnvironmentConfig";
import {getEnvironmentByProjectId} from "metersphere-frontend/src/api/environment";
import {getOwnerProjectIds} from "@/api/project";
import { getEnvironmentByProjectId } from "metersphere-frontend/src/api/environment";
import { getOwnerProjectIds } from "@/api/project";
export default {
name: "EnvironmentSelect",
components: {ApiEnvironmentConfig},
components: { ApiEnvironmentConfig },
props: {
envMap: Map,
projectIds: Set,
projectList: Array,
showConfigButtonWithOutPermission:{
showConfigButtonWithOutPermission: {
type: Boolean,
default() {
return true;
}
},
},
result: {
type: Object,
default() {
return {loading: false}
}
}
return { loading: false };
},
},
},
data() {
return {
data: [],
projects: [],
environments: [],
permissionProjectIds:[],
permissionProjectIds: [],
dialogVisible: false,
isFullUrl: true,
}
};
},
methods: {
isShowConfirmButton(projectId){
if(this.showConfigButtonWithOutPermission === true){
isShowConfirmButton(projectId) {
if (this.showConfigButtonWithOutPermission === true) {
return true;
}else{
if(this.permissionProjectIds){
if(this.permissionProjectIds.indexOf(projectId)<0){
} else {
if (this.permissionProjectIds) {
if (this.permissionProjectIds.indexOf(projectId) < 0) {
return false;
}else {
} else {
return true;
}
}else{
} else {
return false;
}
}
},
init() {
//ID
if(this.permissionProjectIds.length === 0){
if (this.permissionProjectIds.length === 0) {
this.getUserPermissionProjectIds();
}
let arr = [];
this.projectIds.forEach(id => {
const project = this.projectList.find(p => p.id === id);
this.projectIds.forEach((id) => {
const project = this.projectList.find((p) => p.id === id);
if (project) {
let item = {id: id, envs: [], selectEnv: ""};
let item = { id: id, envs: [], selectEnv: "" };
this.data.push(item);
let p = new Promise(resolve => {
getEnvironmentByProjectId(id).then(res => {
let p = new Promise((resolve) => {
getEnvironmentByProjectId(id).then((res) => {
let envs = res.data;
envs.forEach(environment => {
envs.forEach((environment) => {
parseEnvironment(environment);
});
//
let temp = this.data.find(dt => dt.id === id);
let temp = this.data.find((dt) => dt.id === id);
temp.envs = envs;
let envId = undefined;
if (this.envMap) {
envId = this.envMap.get(id);
}
//
temp.selectEnv = envs.filter(e => e.id === envId).length === 0 ? null : envId;
temp.selectEnv =
envs.filter((e) => e.id === envId).length === 0 ? null : envId;
resolve();
})
})
});
});
arr.push(p);
}
})
});
return arr;
},
getUserPermissionProjectIds(){
getOwnerProjectIds().then(res => {
getUserPermissionProjectIds() {
getOwnerProjectIds().then((res) => {
this.permissionProjectIds = res.data;
})
});
},
open() {
this.data = [];
@ -135,12 +165,12 @@ export default {
return Promise.all(this.init());
},
getProjectName(id) {
const project = this.projectList.find(p => p.id === id);
const project = this.projectList.find((p) => p.id === id);
return project ? project.name : "";
},
openEnvironmentConfig(projectId, envId) {
if (!projectId) {
this.$error(this.$t('api_test.select_project'));
this.$error(this.$t("api_test.select_project"));
return;
}
this.$refs.environmentConfig.open(projectId, envId);
@ -148,56 +178,47 @@ export default {
handleConfirm() {
let map = new Map();
let sign = true;
this.data.forEach(dt => {
this.data.forEach((dt) => {
if (!dt.selectEnv) {
sign = false;
return;
}
map.set(dt.id, dt.selectEnv);
})
if (!sign) {
this.$warning(this.$t('workspace.env_group.please_select_env_for_current_scenario'));
return;
}
this.$emit('setProjectEnvMap', map);
this.$emit('close');
});
this.$emit("setProjectEnvMap", map);
this.$emit("close");
},
checkEnv(data) {
let sign = true;
this.isFullUrl = true;
if(data){
if (data) {
return true;
}
if (this.data.length > 0) {
this.data.forEach(dt => {
this.data.forEach((dt) => {
if (!dt.selectEnv) {
sign = false;
return false;
}
})
});
} else {
//
if (this.envMap && this.envMap.size > 0) {
this.projectIds.forEach(id => {
this.projectIds.forEach((id) => {
if (!this.envMap.get(id)) {
sign = false;
return false;
}
})
});
}
}
if (!sign) {
this.$warning(this.$t('workspace.env_group.please_select_env_for_current_scenario'));
return false;
}
return true;
},
environmentConfigClose() {
// todo
}
}
}
},
},
};
</script>
<style scoped>