feat(接口测试): 场景csv功能

This commit is contained in:
AgAngle 2024-05-15 18:10:34 +08:00 committed by Craftsman
parent 21fbfee0b4
commit b74eef8a6c
30 changed files with 545 additions and 307 deletions

View File

@ -25,12 +25,18 @@ public class ApiScenarioCsvStep implements Serializable {
@Size(min = 1, max = 50, message = "{api_scenario_csv_step.step_id.length_range}", groups = {Created.class, Updated.class})
private String stepId;
@Schema(description = "场景ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_scenario_csv_step.scenario_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{api_scenario_csv_step.scenario_id.length_range}", groups = {Created.class, Updated.class})
private String scenarioId;
private static final long serialVersionUID = 1L;
public enum Column {
id("id", "id", "VARCHAR", false),
fileId("file_id", "fileId", "VARCHAR", false),
stepId("step_id", "stepId", "VARCHAR", false);
stepId("step_id", "stepId", "VARCHAR", false),
scenarioId("scenario_id", "scenarioId", "VARCHAR", false);
private static final String BEGINNING_DELIMITER = "`";

View File

@ -313,6 +313,76 @@ public class ApiScenarioCsvStepExample {
addCriterion("step_id not between", value1, value2, "stepId");
return (Criteria) this;
}
public Criteria andScenarioIdIsNull() {
addCriterion("scenario_id is null");
return (Criteria) this;
}
public Criteria andScenarioIdIsNotNull() {
addCriterion("scenario_id is not null");
return (Criteria) this;
}
public Criteria andScenarioIdEqualTo(String value) {
addCriterion("scenario_id =", value, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdNotEqualTo(String value) {
addCriterion("scenario_id <>", value, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdGreaterThan(String value) {
addCriterion("scenario_id >", value, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdGreaterThanOrEqualTo(String value) {
addCriterion("scenario_id >=", value, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdLessThan(String value) {
addCriterion("scenario_id <", value, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdLessThanOrEqualTo(String value) {
addCriterion("scenario_id <=", value, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdLike(String value) {
addCriterion("scenario_id like", value, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdNotLike(String value) {
addCriterion("scenario_id not like", value, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdIn(List<String> values) {
addCriterion("scenario_id in", values, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdNotIn(List<String> values) {
addCriterion("scenario_id not in", values, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdBetween(String value1, String value2) {
addCriterion("scenario_id between", value1, value2, "scenarioId");
return (Criteria) this;
}
public Criteria andScenarioIdNotBetween(String value1, String value2) {
addCriterion("scenario_id not between", value1, value2, "scenarioId");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -5,6 +5,7 @@
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="file_id" jdbcType="VARCHAR" property="fileId" />
<result column="step_id" jdbcType="VARCHAR" property="stepId" />
<result column="scenario_id" jdbcType="VARCHAR" property="scenarioId" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -65,7 +66,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, file_id, step_id
id, file_id, step_id, scenario_id
</sql>
<select id="selectByExample" parameterType="io.metersphere.api.domain.ApiScenarioCsvStepExample" resultMap="BaseResultMap">
select
@ -98,10 +99,10 @@
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.api.domain.ApiScenarioCsvStep">
insert into api_scenario_csv_step (id, file_id, step_id
)
values (#{id,jdbcType=VARCHAR}, #{fileId,jdbcType=VARCHAR}, #{stepId,jdbcType=VARCHAR}
)
insert into api_scenario_csv_step (id, file_id, step_id,
scenario_id)
values (#{id,jdbcType=VARCHAR}, #{fileId,jdbcType=VARCHAR}, #{stepId,jdbcType=VARCHAR},
#{scenarioId,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.api.domain.ApiScenarioCsvStep">
insert into api_scenario_csv_step
@ -115,6 +116,9 @@
<if test="stepId != null">
step_id,
</if>
<if test="scenarioId != null">
scenario_id,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -126,6 +130,9 @@
<if test="stepId != null">
#{stepId,jdbcType=VARCHAR},
</if>
<if test="scenarioId != null">
#{scenarioId,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.api.domain.ApiScenarioCsvStepExample" resultType="java.lang.Long">
@ -146,6 +153,9 @@
<if test="record.stepId != null">
step_id = #{record.stepId,jdbcType=VARCHAR},
</if>
<if test="record.scenarioId != null">
scenario_id = #{record.scenarioId,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -155,7 +165,8 @@
update api_scenario_csv_step
set id = #{record.id,jdbcType=VARCHAR},
file_id = #{record.fileId,jdbcType=VARCHAR},
step_id = #{record.stepId,jdbcType=VARCHAR}
step_id = #{record.stepId,jdbcType=VARCHAR},
scenario_id = #{record.scenarioId,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -169,22 +180,26 @@
<if test="stepId != null">
step_id = #{stepId,jdbcType=VARCHAR},
</if>
<if test="scenarioId != null">
scenario_id = #{scenarioId,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.api.domain.ApiScenarioCsvStep">
update api_scenario_csv_step
set file_id = #{fileId,jdbcType=VARCHAR},
step_id = #{stepId,jdbcType=VARCHAR}
step_id = #{stepId,jdbcType=VARCHAR},
scenario_id = #{scenarioId,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
insert into api_scenario_csv_step
(id, file_id, step_id)
(id, file_id, step_id, scenario_id)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.fileId,jdbcType=VARCHAR}, #{item.stepId,jdbcType=VARCHAR}
)
(#{item.id,jdbcType=VARCHAR}, #{item.fileId,jdbcType=VARCHAR}, #{item.stepId,jdbcType=VARCHAR},
#{item.scenarioId,jdbcType=VARCHAR})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -206,6 +221,9 @@
<if test="'step_id'.toString() == column.value">
#{item.stepId,jdbcType=VARCHAR}
</if>
<if test="'scenario_id'.toString() == column.value">
#{item.scenarioId,jdbcType=VARCHAR}
</if>
</foreach>
)
</foreach>

View File

@ -127,6 +127,10 @@ CREATE TABLE IF NOT EXISTS test_plan_report_bug(
CREATE UNIQUE INDEX idx_test_plan_report_id ON test_plan_report_bug(test_plan_report_id);
-- 场景步骤 csv 表增加场景ID字段
ALTER TABLE api_scenario_csv_step ADD scenario_id varchar(50) NOT NULL COMMENT '场景ID';
CREATE INDEX idx_scenario_id USING BTREE ON api_scenario_csv_step (scenario_id);
-- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT;

View File

@ -9,6 +9,8 @@ import org.apache.jorphan.collections.HashTree;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
/**
@ -22,10 +24,25 @@ public abstract class AbstractJmeterElementConverter<T extends MsTestElement> im
/**
* 获取转换器的函数
* 主应用在实例化转换器的时候会设置
*/
@Setter
private Function<Class<? extends MsTestElement>, AbstractJmeterElementConverter> getConverterFunc;
private static Function<Class<? extends MsTestElement>, AbstractJmeterElementConverter> getConverterFunc;
/**
* 解析子步骤前的前置处理函数
*/
private static List<AbstractJmeterElementConverter> childPreConverters = new ArrayList<>();
/**
* 解析子步骤前的前置处理函数
*/
private static List<AbstractJmeterElementConverter> childPostConverters = new ArrayList<>();
public static void registerChildPreConverters(AbstractJmeterElementConverter converter) {
childPreConverters.add(converter);
}
public static void registerChildPostConverters(AbstractJmeterElementConverter converter) {
childPostConverters.add(converter);
}
public AbstractJmeterElementConverter() {
Type genericSuperclass = getClass().getGenericSuperclass();
@ -45,10 +62,14 @@ public abstract class AbstractJmeterElementConverter<T extends MsTestElement> im
*/
public void parseChild(HashTree tree, AbstractMsTestElement element, ParameterConfig config) {
if (element != null && element.getChildren() != null) {
// 解析子步骤前的前置处理函数
childPreConverters.forEach(processor -> processor.toHashTree(tree, element, config));
element.getChildren().forEach(child -> {
child.setParent(element);
getConverterFunc.apply(child.getClass()).toHashTree(tree, child, config);
});
// 解析子步骤后的后置处理函数
childPostConverters.forEach(processor -> processor.toHashTree(tree, element, config));
}
}

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
import java.util.LinkedList;
import java.util.List;
/**
*
@ -50,4 +51,8 @@ public abstract class AbstractMsTestElement implements MsTestElement {
* 父组件
*/
private AbstractMsTestElement parent;
/**
* 关联的 csv ID
*/
private List<String> csvIds;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.api.dto;
import io.metersphere.api.dto.scenario.CsvVariable;
import io.metersphere.plugin.api.dto.ParameterConfig;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.dto.environment.EnvironmentInfoDTO;
@ -80,4 +81,13 @@ public class ApiParamConfig extends ParameterConfig {
public EnvironmentInfoDTO getEnvConfig(String projectId) {
return getEnvConfig();
}
/**
* 获取 csv 信息
* @param csvId
* @return
*/
public CsvVariable getCsvVariable(String csvId) {
return null;
}
}

View File

@ -1,11 +1,13 @@
package io.metersphere.api.dto;
import io.metersphere.api.dto.scenario.CsvVariable;
import io.metersphere.api.dto.scenario.ScenarioConfig;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.dto.environment.EnvironmentInfoDTO;
import lombok.Data;
import org.apache.commons.lang3.BooleanUtils;
import java.util.HashMap;
import java.util.Map;
/**
@ -31,6 +33,13 @@ public class ApiScenarioParamConfig extends ApiParamConfig {
*/
private Boolean grouped;
/**
* csv Map
* key csv ID
* value csv 的信息
*/
private Map<String, CsvVariable> csvMap = new HashMap<>(0);
@Override
public Map<String, Object> getProtocolEnvConfig(AbstractMsTestElement msTestElement) {
if (BooleanUtils.isTrue(grouped)) {
@ -53,4 +62,14 @@ public class ApiScenarioParamConfig extends ApiParamConfig {
return getEnvConfig();
}
}
/**
* 获取 csv 信息
* @param csvId
* @return
*/
@Override
public CsvVariable getCsvVariable(String csvId) {
return csvMap.get(csvId);
}
}

View File

@ -59,6 +59,7 @@ public class ApiScenarioAddRequest {
private String environmentId;
@Schema(description = "场景的通用配置")
@Valid
private ScenarioConfig scenarioConfig = new ScenarioConfig();
@Schema(description = "步骤集合")
@ -80,10 +81,4 @@ public class ApiScenarioAddRequest {
*/
@Schema(description = "步骤文件操作相关参数")
private Map<String, ResourceAddFileParam> stepFileParam;
/**
* 步骤文件操作相关参数
*/
@Schema(description = "场景文件操作相关参数")
private ResourceAddFileParam fileParam;
}

View File

@ -11,7 +11,7 @@ import java.util.List;
@EqualsAndHashCode(callSuper = false)
public class ApiScenarioDetail extends ApiScenario {
@Schema(description = "场景的通用配置")
private ScenarioConfig scenarioConfig;
private ScenarioConfig scenarioConfig = new ScenarioConfig();
@Schema(description = "步骤")
private List<ApiScenarioStepDTO> steps;
}

View File

@ -56,8 +56,8 @@ public class ApiScenarioStepCommonDTO<T extends ApiScenarioStepCommonDTO> {
@Schema(description = "循环等组件基础数据")
private Object config;
@Schema(description = "csv文件id集合")
private List<String> csvFileIds;
@Schema(description = "csv id集合")
private List<String> csvIds;
@Schema(description = "项目fk")
@NotBlank

View File

@ -83,10 +83,4 @@ public class ApiScenarioUpdateRequest {
*/
@Schema(description = "步骤文件操作相关参数")
private Map<String, ResourceUpdateFileParam> stepFileParam;
/**
* 步骤文件操作相关参数
*/
@Schema(description = "场景文件操作相关参数")
private ResourceUpdateFileParam fileParam;
}

View File

@ -1,5 +1,7 @@
package io.metersphere.api.dto.scenario;
import io.metersphere.sdk.constants.ValueEnum;
import io.metersphere.system.valid.EnumValue;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@ -14,50 +16,51 @@ import lombok.Data;
public class CsvVariable {
@Schema(description = "id")
@NotBlank
@Size(max = 50)
private String id;
@Schema(description = "文件id/引用文件id", requiredMode = Schema.RequiredMode.REQUIRED)
@Size(min = 1, max = 50, message = "{api_scenario_csv.file_id.length_range}")
@Size(max = 50, message = "{api_scenario_csv.file_id.length_range}")
@NotBlank
private String fileId;
@Schema(description = "场景id", requiredMode = Schema.RequiredMode.REQUIRED)
@Size(min = 1, max = 50, message = "{api_scenario_csv.scenario_id.length_range}")
private String scenarioId;
@Schema(description = "csv变量名称", requiredMode = Schema.RequiredMode.REQUIRED)
@Size(min = 1, max = 255, message = "{api_scenario_csv.name.length_range}")
private String name;
@Schema(description = "文件名称")
@NotBlank
private String fileName;
@Schema(description = "作用域 SCENARIO/STEP")
@Schema(description = "是否是关联文件")
private Boolean association = false;
@Schema(description = "csv变量名称", requiredMode = Schema.RequiredMode.REQUIRED)
@Size(max = 255, message = "{api_scenario_csv.name.length_range}")
private String name;
/**
* @see CsvVariableScope
*/
@Schema(description = "作用域 SCENARIO/STEP")
@NotBlank(message = "{api_scenario_csv.scope.not_blank}")
@Size(min = 1, max = 50, message = "{api_scenario_csv.scope.length_range}")
private String scope;
@EnumValue(enumClass = CsvVariableScope.class)
private String scope = CsvVariableScope.SCENARIO.name();
@Schema(description = "启用/禁用")
private Boolean enable = true;
@Schema(description = "是否引用")
private Boolean association = false;
@Schema(description = "文件编码")
/**
* 文件编码
*
* @see CsvEncodingType
*/
@Schema(description = "文件编码 UTF-8/UTF-16/ISO-8859-15/US-ASCII")
@Size(max = 50, message = "{api_scenario_csv.encoding.length_range}")
private String encoding;
@EnumValue(enumClass = CsvEncodingType.class)
private String encoding = CsvEncodingType.UTF8.getValue();
@Schema(description = "是否随机")
private Boolean random = false;
@Schema(description = "变量名称(西文逗号间隔)")
@Schema(description = "变量名称(文逗号间隔)")
@Size(max = 255, message = "{api_scenario_csv.variable_names.length_range}")
private String variableNames;
@ -78,7 +81,7 @@ public class CsvVariable {
private Boolean stopThreadOnEof = false;
public enum CsvEncodingType {
public enum CsvEncodingType implements ValueEnum {
UTF8("UTF-8"), UFT16("UTF-16"), ISO885915("ISO-8859-15"), US_ASCII("US-ASCII");
private String value;
@ -86,6 +89,7 @@ public class CsvVariable {
this.value = value;
}
@Override
public String getValue() {
return value;
}

View File

@ -2,6 +2,7 @@ package io.metersphere.api.dto.scenario;
import io.metersphere.api.dto.assertion.MsScenarioAssertionConfig;
import io.metersphere.api.dto.request.processors.MsProcessorConfig;
import jakarta.validation.Valid;
import lombok.Data;
/**
@ -13,6 +14,7 @@ public class ScenarioConfig {
/**
* 场景变量
*/
@Valid
private ScenarioVariable variable = new ScenarioVariable();
/**
* 前置处理器配置

View File

@ -1,6 +1,7 @@
package io.metersphere.api.dto.scenario;
import io.metersphere.project.dto.environment.variables.CommonVariables;
import jakarta.validation.Valid;
import lombok.Data;
import java.util.ArrayList;
@ -15,9 +16,11 @@ public class ScenarioVariable {
/**
* 普通变量
*/
@Valid
private List<CommonVariables> commonVariables = new ArrayList<>(0);
/**
* csv变量
*/
@Valid
private List<CsvVariable> csvVariables = new ArrayList<>(0);
}

View File

@ -18,7 +18,7 @@ public interface ExtApiScenarioStepMapper {
List<CsvVariable> getCsvVariableByScenarioId(@Param("id") String id);
List<ApiScenarioCsvStep> getCsvStepByStepIds(@Param("ids") List<String> stepIds);
List<ApiScenarioCsvStep> getCsvStepByScenarioId(@Param("scenarioId") String scenarioId);
/**
* 查询有步骤详情的请求类型的步骤

View File

@ -18,13 +18,10 @@
<include refid="io.metersphere.api.mapper.ApiScenarioCsvMapper.Base_Column_List"/>
from api_scenario_csv where scenario_id = #{id}
</select>
<select id="getCsvStepByStepIds" resultType="io.metersphere.api.domain.ApiScenarioCsvStep">
<select id="getCsvStepByScenarioId" resultType="io.metersphere.api.domain.ApiScenarioCsvStep">
select
<include refid="io.metersphere.api.mapper.ApiScenarioCsvStepMapper.Base_Column_List"/>
from api_scenario_csv_step where step_id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
from api_scenario_csv_step where scenario_id = #{scenarioId}
</select>
<select id="getHasBlobRequestStepIds" resultType="java.lang.String">
select id

View File

@ -5,10 +5,13 @@ import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.dto.ApiScenarioParamConfig;
import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.request.processors.MsProcessorConfig;
import io.metersphere.api.dto.scenario.CsvVariable;
import io.metersphere.api.dto.scenario.ScenarioConfig;
import io.metersphere.api.dto.scenario.ScenarioStepConfig;
import io.metersphere.api.dto.scenario.ScenarioVariable;
import io.metersphere.api.parser.jmeter.child.MsCsvChildPreConverter;
import io.metersphere.api.parser.jmeter.constants.JmeterAlias;
import io.metersphere.api.parser.jmeter.constants.JmeterProperty;
import io.metersphere.api.parser.jmeter.processor.MsProcessorConverter;
import io.metersphere.api.parser.jmeter.processor.MsProcessorConverterFactory;
import io.metersphere.api.parser.jmeter.processor.assertion.AssertionConverterFactory;
@ -65,6 +68,9 @@ public class MsScenarioConverter extends AbstractJmeterElementConverter<MsScenar
tree.add(getCookieManager());
}
// 添加 csv 数据集
addCsvDataSet(tree, msScenario.getScenarioConfig());
// 添加场景和环境变量
addUserParameters(tree, msScenario, envInfo, config);
@ -85,8 +91,17 @@ public class MsScenarioConverter extends AbstractJmeterElementConverter<MsScenar
addScenarioAssertions(tree, msScenario, config);
}
private void addCsvDataSet(HashTree tree, ScenarioConfig scenarioConfig) {
if (scenarioConfig == null || scenarioConfig.getVariable() == null || CollectionUtils.isEmpty(scenarioConfig.getVariable().getCsvVariables())) {
return;
}
List<CsvVariable> csvVariables = scenarioConfig.getVariable().getCsvVariables();
MsCsvChildPreConverter.addCsvDataSet(tree, JmeterProperty.CSVDataSetProperty.SHARE_MODE_GROUP, csvVariables);
}
/**
* 添加临界控制器解决变量冲突
*
* @param tree
* @param msScenario
* @return
@ -270,9 +285,9 @@ public class MsScenarioConverter extends AbstractJmeterElementConverter<MsScenar
envScenarioProcessors.stream()
.filter(MsProcessor::getEnable)
.forEach(processor -> {
processor.setProjectId(msScenario.getProjectId());
getConverterFunc.apply(processor.getClass()).parse(tree, processor, config);
});
processor.setProjectId(msScenario.getProjectId());
getConverterFunc.apply(processor.getClass()).parse(tree, processor, config);
});
}
/**
@ -319,9 +334,9 @@ public class MsScenarioConverter extends AbstractJmeterElementConverter<MsScenar
scenarioPreProcessors.stream()
.filter(MsProcessor::getEnable)
.forEach(processor -> {
processor.setProjectId(msScenario.getProjectId());
getConverterFunc.apply(processor.getClass()).parse(tree, processor, config);
});
processor.setProjectId(msScenario.getProjectId());
getConverterFunc.apply(processor.getClass()).parse(tree, processor, config);
});
}
private boolean isRef(String refType) {
@ -347,6 +362,9 @@ public class MsScenarioConverter extends AbstractJmeterElementConverter<MsScenar
private ApiScenarioParamConfig getEnableConfig(MsScenario msScenario, ApiScenarioParamConfig config) {
ApiScenarioParamConfig enableConfig = config;
if (!isRef(msScenario.getRefType())) {
if (isRootScenario(msScenario.getRefType())) {
setScenarioConfig(msScenario, enableConfig);
}
// 非引用的场景使用当前环境参数
return enableConfig;
}
@ -369,13 +387,24 @@ public class MsScenarioConverter extends AbstractJmeterElementConverter<MsScenar
}
}
setScenarioConfig(msScenario, enableConfig);
return enableConfig;
}
private void setScenarioConfig(MsScenario msScenario, ApiScenarioParamConfig enableConfig) {
ScenarioConfig scenarioConfig = msScenario.getScenarioConfig();
if (scenarioConfig != null) {
// 设置是否使用全局cookie
enableConfig.setEnableGlobalCookie(scenarioConfig.getOtherConfig().getEnableCookieShare());
}
return enableConfig;
ScenarioVariable variable = scenarioConfig.getVariable();
if (variable != null && variable.getCsvVariables() != null) {
for (CsvVariable csvVariable : variable.getCsvVariables()) {
enableConfig.getCsvMap().put(csvVariable.getId(), csvVariable);
}
}
}
}
private CookieManager getCookieManager() {

View File

@ -0,0 +1,72 @@
package io.metersphere.api.parser.jmeter.child;
import io.metersphere.api.dto.ApiParamConfig;
import io.metersphere.api.dto.scenario.CsvVariable;
import io.metersphere.api.parser.jmeter.constants.JmeterAlias;
import io.metersphere.api.parser.jmeter.constants.JmeterProperty;
import io.metersphere.plugin.api.dto.ParameterConfig;
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.constants.LocalRepositoryDir;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.CSVDataSet;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.collections.HashTree;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* 解析 csv 文件
* 在解析子步骤前先添加 csv 配置
* @Author: jianxing
* @CreateTime: 2024-05-15 11:32
*/
public class MsCsvChildPreConverter extends AbstractJmeterElementConverter<AbstractMsTestElement> {
@Override
public void toHashTree(HashTree tree, AbstractMsTestElement element, ParameterConfig config) {
List<String> csvIds = element.getCsvIds();
ApiParamConfig apiParamConfig = (ApiParamConfig) config;
if (CollectionUtils.isEmpty(csvIds)) {
return;
}
csvIds.forEach(csvId -> {
CsvVariable csvVariable = apiParamConfig.getCsvVariable(csvId);
if (csvVariable != null) {
String shareMode = StringUtils.equals(csvVariable.getScope(), CsvVariable.CsvVariableScope.SCENARIO.name()) ?
JmeterProperty.CSVDataSetProperty.SHARE_MODE_GROUP : JmeterProperty.CSVDataSetProperty.SHARE_MODE_THREAD;
addCsvDataSet(tree, shareMode, csvVariable);
}
});
}
public static void addCsvDataSet(HashTree tree, String shareMode, List<CsvVariable> list) {
list.forEach(item -> addCsvDataSet(tree, shareMode, item));
}
private static void addCsvDataSet(HashTree tree, String shareMode, CsvVariable csvVariable) {
// 执行机执行文件存放的缓存目录
String path = LocalRepositoryDir.getSystemCacheDir() + "/" + csvVariable.getFileId() + "/" + csvVariable.getFileName();
if (!StringUtils.equals(File.separator, "/")) {
// windows 系统下运行 / 转换为 \否则jmeter报错
path = path.replace("/", File.separator);
}
CSVDataSet csvDataSet = new CSVDataSet();
csvDataSet.setEnabled(true);
csvDataSet.setIgnoreFirstLine(false);
csvDataSet.setProperty(TestElement.TEST_CLASS, CSVDataSet.class.getName());
csvDataSet.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass(JmeterAlias.TEST_BEAN_GUI));
csvDataSet.setName(StringUtils.isEmpty(csvVariable.getName()) ? CSVDataSet.class.getSimpleName() : csvVariable.getName());
csvDataSet.setProperty(JmeterProperty.FILE_ENCODING, StringUtils.isEmpty(csvVariable.getEncoding()) ? StandardCharsets.UTF_8.name() : csvVariable.getEncoding());
csvDataSet.setProperty(JmeterProperty.CSVDataSetProperty.FILE_NAME, path);
csvDataSet.setProperty(JmeterProperty.CSVDataSetProperty.SHARE_MODE, shareMode);
csvDataSet.setProperty(JmeterProperty.CSVDataSetProperty.RECYCLE, true);
csvDataSet.setProperty(JmeterProperty.CSVDataSetProperty.DELIMITER, csvVariable.getDelimiter());
csvDataSet.setProperty(JmeterProperty.CSVDataSetProperty.QUOTED_DATA, csvVariable.getAllowQuotedData());
tree.add(csvDataSet);
}
}

View File

@ -11,4 +11,16 @@ public class JmeterProperty {
public final static String ASS_OPTION = "ASS_OPTION";
public final static String BEAN_SHELL_ASSERTION_QUERY = "BeanShellAssertion.query";
public final static String BEAN_SHELL_SAMPLER_QUERY = "BeanShellSampler.query";
public final static String FILE_ENCODING = "fileEncoding";
public class CSVDataSetProperty {
public static final String FILE_NAME = "filename";
public static final String SHARE_MODE = "shareMode";
public static final String RECYCLE = "recycle";
public static final String DELIMITER = "delimiter";
public static final String QUOTED_DATA = "quotedData";
public static final String SHARE_MODE_GROUP = "shareMode.group";
public static final String SHARE_MODE_THREAD = "shareMode.thread";
}
}

View File

@ -1,4 +1,4 @@
package io.metersphere.api.parser.jmeter;
package io.metersphere.api.parser.jmeter.controller;
import io.metersphere.api.dto.request.controller.MsConstantTimerController;
import io.metersphere.api.parser.jmeter.processor.ScenarioTimeWaitingProcessorConverter;

View File

@ -1,4 +1,4 @@
package io.metersphere.api.parser.jmeter;
package io.metersphere.api.parser.jmeter.controller;
import io.metersphere.api.dto.request.controller.MsIfController;
import io.metersphere.plugin.api.dto.ParameterConfig;
@ -21,12 +21,9 @@ public class MsIfControllerConverter extends AbstractJmeterElementConverter<MsIf
LogUtils.info("MsIfController is disabled");
return;
}
//TODO 这里需要处理csv文件
HashTree groupTree = tree.add(ifController(element));
parseChild(groupTree, element, config);
}
private IfController ifController(MsIfController element) {

View File

@ -1,4 +1,4 @@
package io.metersphere.api.parser.jmeter;
package io.metersphere.api.parser.jmeter.controller;
import io.metersphere.api.dto.request.controller.LoopType;
import io.metersphere.api.dto.request.controller.MsLoopController;
@ -27,7 +27,6 @@ public class MsLoopControllerConverter extends AbstractJmeterElementConverter<Ms
return;
}
final HashTree groupTree = controller(tree, element);
//TODO 这里需要处理csv文件 场景变量
if (groupTree == null) {
return;

View File

@ -1,4 +1,4 @@
package io.metersphere.api.parser.jmeter;
package io.metersphere.api.parser.jmeter.controller;
import io.metersphere.api.dto.request.controller.MsOnceOnlyController;
import io.metersphere.plugin.api.dto.ParameterConfig;
@ -20,7 +20,6 @@ public class MsOnceOnlyControllerConverter extends AbstractJmeterElementConverte
LogUtils.info("MsOnceOnlyController is disabled");
return;
}
//TODO 这里需要处理csv文件
HashTree groupTree = tree.add(onceOnlyController(element));
parseChild(groupTree, element, config);

View File

@ -237,8 +237,8 @@ public class ApiFileResourceService {
/**
* 删除资源下所有的文件或者关联关系
*
* @param dirPrefix 本地上传文件目录前缀
* @param resourceIds 资源ID
* @param dirPrefix 本地上传文件目录前缀
* @param resourceIds 资源ID
* @param projectId 项目ID
* @param operator 操作人
*/
@ -402,18 +402,27 @@ public class ApiFileResourceService {
fileRequest.setFileName(fileName);
fileRequest.setStorage(StorageType.MINIO.name());
byte[] bytes;
try {
fileId = fileMetadataService.transferFile(request.getFileName(), request.getOriginalName(), request.getProjectId(), request.getModuleId(), currentUser, fileService.download(fileRequest));
if (CollectionUtils.isEmpty(apiFileResources)) {
// 删除临时文件
FileRequest deleteRequest = new FileRequest();
deleteRequest.setFolder(folder);
FileCenter.getDefaultRepository().deleteFolder(deleteRequest);
}
bytes = fileService.download(fileRequest);
} catch (Exception e) {
LogUtils.error(e);
throw new MSException(Translator.get("file.transfer.failed"));
throw new MSException(Translator.get("file.transfer.failed"), e);
}
fileId = fileMetadataService.transferFile(request.getFileName(), request.getOriginalName(), request.getProjectId(), request.getModuleId(), currentUser, bytes);
if (CollectionUtils.isEmpty(apiFileResources)) {
// 删除临时文件
FileRequest deleteRequest = new FileRequest();
deleteRequest.setFolder(folder);
try {
FileCenter.getDefaultRepository().deleteFolder(deleteRequest);
} catch (Exception e) {
LogUtils.error(e);
throw new MSException(Translator.get("file.transfer.failed"), e);
}
}
return fileId;
}

View File

@ -1,6 +1,5 @@
package io.metersphere.api.service.scenario;
import io.metersphere.sdk.constants.ApiFileResourceType;
import io.metersphere.api.constants.ApiResourceType;
import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.constants.ApiScenarioStepType;
@ -51,9 +50,7 @@ import io.metersphere.sdk.domain.EnvironmentGroupExample;
import io.metersphere.sdk.dto.api.task.ApiRunModeConfigDTO;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileCopyRequest;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.MinioRepository;
import io.metersphere.sdk.mapper.EnvironmentGroupMapper;
import io.metersphere.sdk.mapper.EnvironmentMapper;
@ -152,10 +149,6 @@ public class ApiScenarioService extends MoveNodeService {
@Resource
private ApiTestCaseService apiTestCaseService;
@Resource
private FileAssociationService fileAssociationService;
@Resource
private FileMetadataService fileMetadataService;
@Resource
private ApiScenarioCsvMapper apiScenarioCsvMapper;
@Resource
private ApiScenarioCsvStepMapper apiScenarioCsvStepMapper;
@ -442,6 +435,18 @@ public class ApiScenarioService extends MoveNodeService {
apiScenarioBlob.setConfig(JSON.toJSONString(request.getScenarioConfig()).getBytes());
apiScenarioBlobMapper.insert(apiScenarioBlob);
// 处理csv文件
handCsvFilesAdd(request, creator, scenario);
// 处理添加的步骤
handleStepAdd(request, scenario);
// 处理步骤文件
handleStepFilesAdd(request, creator, scenario);
return scenario;
}
private void handleStepAdd(ApiScenarioAddRequest request, ApiScenario scenario) {
// 插入步骤
if (CollectionUtils.isNotEmpty(request.getSteps())) {
checkCircularRef(scenario.getId(), request.getSteps());
@ -462,33 +467,25 @@ public class ApiScenarioService extends MoveNodeService {
apiScenarioStepBlobMapper.batchInsert(apiScenarioStepBlobs);
}
saveStepCsv(steps, csvSteps);
csvSteps.forEach(step -> step.setScenarioId(scenario.getId()));
saveStepCsv(scenario.getId(), steps, csvSteps);
}
// 处理步骤文件
handleStepFiles(request, creator, scenario);
// 处理场景文件csv等
handScenarioFiles(request, creator, scenario);
return scenario;
}
private void handScenarioFiles(ApiScenarioAddRequest request, String creator, ApiScenario scenario) {
ResourceAddFileParam fileParam = request.getFileParam();
if (fileParam == null) {
private void handCsvFilesAdd(ApiScenarioAddRequest request, String creator, ApiScenario scenario) {
if (request.getScenarioConfig() == null || request.getScenarioConfig().getVariable() == null || request.getScenarioConfig().getVariable().getCsvVariables() == null) {
return;
}
ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(scenario.getId(), scenario.getProjectId(), creator);
resourceUpdateRequest.setUploadFileIds(fileParam.getUploadFileIds());
resourceUpdateRequest.setLinkFileIds(fileParam.getLinkFileIds());
apiFileResourceService.addFileResource(resourceUpdateRequest);
if (request.getScenarioConfig() != null
&& request.getScenarioConfig().getVariable() != null) {
saveCsv(request.getScenarioConfig().getVariable().getCsvVariables(), resourceUpdateRequest);
}
List<CsvVariable> csvVariables = request.getScenarioConfig().getVariable().getCsvVariables();
// 处理 csv 相关数据表
handleCsvDataUpdate(csvVariables, scenario, List.of());
// 处理文件的上传
handleCsvFileUpdate(csvVariables, List.of(), scenario, creator);
}
private void handleStepFiles(ApiScenarioAddRequest request, String creator, ApiScenario scenario) {
private void handleStepFilesAdd(ApiScenarioAddRequest request, String creator, ApiScenario scenario) {
Map<String, ResourceAddFileParam> stepFileParam = request.getStepFileParam();
if (MapUtils.isNotEmpty(stepFileParam)) {
stepFileParam.forEach((stepId, fileParam) -> {
@ -499,7 +496,7 @@ public class ApiScenarioService extends MoveNodeService {
}
}
private void handleStepFiles(ApiScenarioUpdateRequest request, String updater, ApiScenario scenario) {
private void handleStepFilesUpdate(ApiScenarioUpdateRequest request, String updater, ApiScenario scenario) {
Map<String, ResourceUpdateFileParam> stepFileParam = request.getStepFileParam();
if (MapUtils.isNotEmpty(stepFileParam)) {
stepFileParam.forEach((stepId, fileParam) -> {
@ -520,156 +517,126 @@ public class ApiScenarioService extends MoveNodeService {
return resourceUpdateRequest;
}
private void saveStepCsv(List<ApiScenarioStep> steps, List<ApiScenarioCsvStep> csvSteps) {
//获取所有的步骤id 然后删掉历史的关联关系
List<String> stepIds = steps.stream().map(ApiScenarioStep::getId).toList();
SubListUtils.dealForSubList(stepIds, 500, subList -> {
ApiScenarioCsvStepExample csvStepExample = new ApiScenarioCsvStepExample();
csvStepExample.createCriteria().andStepIdIn(subList);
apiScenarioCsvStepMapper.deleteByExample(csvStepExample);
});
//插入csv步骤
private void saveStepCsv(String scenarioId, List<ApiScenarioStep> steps, List<ApiScenarioCsvStep> csvSteps) {
// 先删除
ApiScenarioCsvStepExample csvStepExample = new ApiScenarioCsvStepExample();
csvStepExample.createCriteria().andScenarioIdEqualTo(scenarioId);
apiScenarioCsvStepMapper.deleteByExample(csvStepExample);
// 再添加
if (CollectionUtils.isNotEmpty(csvSteps)) {
SubListUtils.dealForSubList(csvSteps, 100, subList -> apiScenarioCsvStepMapper.batchInsert(subList));
}
}
private void saveCsv(List<CsvVariable> csvVariables, ApiFileResourceUpdateRequest resourceUpdateRequest) {
//进行比较一下 哪些是已存在的 那些是新上传的
//查询已经存在的fileId 这里直接过滤所有的数据 拿到新上传的本地文件id 和已经存在的文件id 关联的文件id 关于新的 需要删除的 已经存在的
ApiScenarioCsvExample apiScenarioCsvExample = new ApiScenarioCsvExample();
apiScenarioCsvExample.createCriteria().andScenarioIdEqualTo(resourceUpdateRequest.getResourceId());
List<ApiScenarioCsv> dbFileIds = apiScenarioCsvMapper.selectByExample(apiScenarioCsvExample);
//取出所有的fileId Association为false的数据
List<String> dbLocalFileIds = dbFileIds.stream().filter(c -> BooleanUtils.isFalse(c.getAssociation())).map(ApiScenarioCsv::getFileId).toList();
//取出所有的fileId Association为true的数据
List<String> dbRefFileIds = dbFileIds.stream().filter(c -> BooleanUtils.isTrue(c.getAssociation())).map(ApiScenarioCsv::getFileId).toList();
private void handleCsvUpdate(ScenarioConfig scenarioConfig, ApiScenario scenario, String userId) {
if (scenarioConfig == null || scenarioConfig.getVariable() == null || scenarioConfig.getVariable().getCsvVariables() == null) {
return;
}
List<CsvVariable> csvVariables = scenarioConfig.getVariable().getCsvVariables();
List<ApiScenarioCsv> dbCsv = getApiScenarioCsv(scenario.getId());
//获取传的所有Association为false的数据
List<String> localFileIds = csvVariables.stream().filter(c -> BooleanUtils.isFalse(c.getAssociation())).map(CsvVariable::getFileId).toList();
//获取传的所有Association为true的数据
List<String> refFileIds = csvVariables.stream().filter(c -> BooleanUtils.isTrue(c.getAssociation())).map(CsvVariable::getFileId).toList();
// 更新 csv 相关数据表
handleCsvDataUpdate(csvVariables, scenario, dbCsv);
//取交集 交集数据是已存在的 不需要重新上传 和处理关联关系 但是需要更新apiScenarioCsv表的数据
List<String> intersectionLocal = ListUtils.intersection(dbLocalFileIds, localFileIds);
//取差集 dbFileIds和 intersection的差集是需要删除的数据 本地数据需要删除的数据
List<String> deleteLocals = ListUtils.subtract(dbLocalFileIds, intersectionLocal);
resourceUpdateRequest.setDeleteFileIds(deleteLocals);
List<String> addLocal = ListUtils.subtract(localFileIds, intersectionLocal);
resourceUpdateRequest.setUploadFileIds(addLocal);
//获取 关联文件的交集
List<String> intersectionRef = ListUtils.intersection(dbRefFileIds, refFileIds);
//获取删除的
List<String> deleteRefs = ListUtils.subtract(dbRefFileIds, intersectionRef);
List<String> addRef = ListUtils.subtract(refFileIds, intersectionRef);
resourceUpdateRequest.setLinkFileIds(addRef);
resourceUpdateRequest.setUnLinkFileIds(deleteRefs);
//删除不存在的数据
deleteCsvResource(resourceUpdateRequest);
addCsvResource(resourceUpdateRequest, csvVariables);
// 处理文件的上传和删除
handleCsvFileUpdate(csvVariables, dbCsv, scenario, userId);
}
private void addCsvResource(ApiFileResourceUpdateRequest resourceUpdateRequest,
List<CsvVariable> csvVariables) {
List<ApiScenarioCsv> addData = new ArrayList<>();
List<ApiScenarioCsv> updateData = new ArrayList<>();
List<String> addFileIds = resourceUpdateRequest.getUploadFileIds();
List<String> linkFileIds = resourceUpdateRequest.getLinkFileIds();
Map<String, String> refFilesMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(linkFileIds)) {
//根据fileId查询文件名
List<FileMetadata> fileList = fileMetadataService.selectByList(linkFileIds);
if (CollectionUtils.isNotEmpty(fileList)) {
//生成map key为文件id 值为文件名称 文件名称为name.类型
refFilesMap = fileList.stream().collect(Collectors.toMap(FileMetadata::getId, f -> f.getName() + "." + f.getType()));
}
fileAssociationService.association(resourceUpdateRequest.getResourceId(), FileAssociationSourceUtil.SOURCE_TYPE_API_SCENARIO, linkFileIds,
apiFileResourceService.createFileLogRecord(resourceUpdateRequest.getOperator(), resourceUpdateRequest.getProjectId(), OperationLogModule.API_SCENARIO_MANAGEMENT_SCENARIO));
}
Map<String, String> finalRefFilesMap = refFilesMap;
// 添加文件与接口的关联关系
Map<String, String> addFileMap = new HashMap<>();
csvVariables.forEach(item -> {
private void handleCsvDataUpdate(List<CsvVariable> csvVariables, ApiScenario scenario, List<ApiScenarioCsv> dbCsv) {
List<String> dbCsvIds = dbCsv.stream()
.map(ApiScenarioCsv::getId)
.toList();
List<String> csvIds = csvVariables.stream()
.map(CsvVariable::getId)
.toList();
List<String> deleteCsvIds = ListUtils.subtract(dbCsvIds, csvIds);
//删除不存在的数据
deleteCsvResource(deleteCsvIds);
Set<String> dbCsvIdSet = dbCsvIds.stream().collect(Collectors.toSet());
List<ApiScenarioCsv> addCsvList = new ArrayList<>();
csvVariables.stream().forEach(item -> {
ApiScenarioCsv scenarioCsv = new ApiScenarioCsv();
BeanUtils.copyBean(scenarioCsv, item);
scenarioCsv.setScenarioId(resourceUpdateRequest.getResourceId());
scenarioCsv.setProjectId(resourceUpdateRequest.getProjectId());
// uploadFileIds里包含的数据 全部需要上传到minio上或者需要重新建立关联关系
if (BooleanUtils.isFalse(item.getAssociation())
&& CollectionUtils.isNotEmpty(addFileIds)
&& addFileIds.contains(item.getFileId())) {
scenarioCsv.setFileName(apiFileResourceService.getTempFileNameByFileId(item.getFileId()));
addFileMap.put(item.getFileId(), scenarioCsv.getId());
} else if (BooleanUtils.isTrue(item.getAssociation())
&& finalRefFilesMap.containsKey(item.getFileId())
&& CollectionUtils.isNotEmpty(linkFileIds)
&& linkFileIds.contains(item.getFileId())) {
scenarioCsv.setFileName(finalRefFilesMap.get(item.getFileId()));
}
if (StringUtils.isBlank(item.getId())) {
scenarioCsv.setId(IDGenerator.nextStr());
addData.add(scenarioCsv);
scenarioCsv.setScenarioId(scenario.getId());
scenarioCsv.setProjectId(scenario.getProjectId());
if (!dbCsvIdSet.contains(item.getId())) {
addCsvList.add(scenarioCsv);
} else {
updateData.add(scenarioCsv);
apiScenarioCsvMapper.updateByPrimaryKey(scenarioCsv);
}
});
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ApiScenarioCsvMapper mapper = sqlSession.getMapper(ApiScenarioCsvMapper.class);
if (CollectionUtils.isNotEmpty(updateData)) {
//更新apiScenarioCsv表
updateData.forEach(mapper::updateByPrimaryKeySelective);
}
if (CollectionUtils.isNotEmpty(addData)) {
//插入apiScenarioCsv表
addData.forEach(mapper::insertSelective);
}
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
if (MapUtils.isNotEmpty(addFileMap)) {
// 上传文件到对象存储
apiFileResourceService.uploadFileResource(resourceUpdateRequest.getFolder(), addFileMap);
if (CollectionUtils.isNotEmpty(addCsvList)) {
apiScenarioCsvMapper.batchInsert(addCsvList);
}
}
private void deleteCsvResource(ApiFileResourceUpdateRequest resourceUpdateRequest) {
// 处理本地上传文件
List<String> deleteFileIds = resourceUpdateRequest.getDeleteFileIds();
ApiScenarioCsvExample example = new ApiScenarioCsvExample();
ApiScenarioCsvStepExample stepExample = new ApiScenarioCsvStepExample();
if (CollectionUtils.isNotEmpty(deleteFileIds)) {
// 删除关联关系
deleteFileIds.forEach(fileId -> {
FileRequest request = new FileRequest();
// 删除文件所在目录
request.setFolder(resourceUpdateRequest.getFolder() + "/" + fileId);
try {
FileCenter.getDefaultRepository().deleteFolder(request);
} catch (Exception e) {
LogUtils.error(e);
}
});
private void handleCsvFileUpdate(List<CsvVariable> csvVariables, List<ApiScenarioCsv> dbCsv, ApiScenario scenario, String userId) {
ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(scenario.getId(), scenario.getProjectId(), userId);
// 设置本地文件相关参数
setCsvLocalFileParam(csvVariables, dbCsv, resourceUpdateRequest);
// 设置关联文件相关参数
setCsvLinkFileParam(csvVariables, dbCsv, resourceUpdateRequest);
apiFileResourceService.addFileResource(resourceUpdateRequest);
}
example.createCriteria()
.andScenarioIdEqualTo(resourceUpdateRequest.getResourceId())
.andFileIdIn(deleteFileIds);
apiScenarioCsvMapper.deleteByExample(example);
stepExample.createCriteria().andFileIdIn(deleteFileIds);
apiScenarioCsvStepMapper.deleteByExample(stepExample);
private void setCsvLinkFileParam(List<CsvVariable> csvVariables, List<ApiScenarioCsv> dbCsv, ApiFileResourceUpdateRequest resourceUpdateRequest) {
// 获取数据库中关联的文件id
List<String> dbRefFileIds = dbCsv.stream()
.filter(c -> BooleanUtils.isTrue(c.getAssociation()))
.map(ApiScenarioCsv::getFileId)
.toList();
}
List<String> unLinkFileIds = resourceUpdateRequest.getUnLinkFileIds();
// 处理关联文件
if (CollectionUtils.isNotEmpty(unLinkFileIds)) {
fileAssociationService.deleteBySourceIdAndFileIds(resourceUpdateRequest.getResourceId(), unLinkFileIds,
apiFileResourceService.createFileLogRecord(resourceUpdateRequest.getOperator(), resourceUpdateRequest.getProjectId(), resourceUpdateRequest.getLogModule()));
example.clear();
example.createCriteria()
.andScenarioIdEqualTo(resourceUpdateRequest.getResourceId())
.andFileIdIn(unLinkFileIds);
// 获取请求中关联的文件id
List<String> refFileIds = csvVariables.stream()
.filter(c -> BooleanUtils.isTrue(c.getAssociation())).map(CsvVariable::getFileId).toList();
List<String> unlinkFileIds = ListUtils.subtract(dbRefFileIds, refFileIds);
resourceUpdateRequest.setUnLinkFileIds(unlinkFileIds);
List<String> linkFileIds = ListUtils.subtract(refFileIds, dbRefFileIds);
resourceUpdateRequest.setLinkFileIds(linkFileIds);
}
private void setCsvLocalFileParam(List<CsvVariable> csvVariables, List<ApiScenarioCsv> dbCsv, ApiFileResourceUpdateRequest resourceUpdateRequest) {
// 获取数据库中的本地文件
List<String> dbLocalFileIds = dbCsv.stream()
.filter(c -> BooleanUtils.isFalse(c.getAssociation()))
.map(ApiScenarioCsv::getFileId)
.toList();
// 获取请求中的本地文件
List<String> localFileIds = csvVariables.stream()
.filter(c -> BooleanUtils.isFalse(c.getAssociation()))
.map(CsvVariable::getFileId).toList();
// 待删除文件
List<String> deleteLocals = ListUtils.subtract(dbLocalFileIds, localFileIds);
resourceUpdateRequest.setDeleteFileIds(deleteLocals);
// 新上传文件
List<String> addLocal = ListUtils.subtract(localFileIds, dbLocalFileIds);
resourceUpdateRequest.setUploadFileIds(addLocal);
}
private List<ApiScenarioCsv> getApiScenarioCsv(String scenarioId) {
ApiScenarioCsvExample apiScenarioCsvExample = new ApiScenarioCsvExample();
apiScenarioCsvExample.createCriteria().andScenarioIdEqualTo(scenarioId);
return apiScenarioCsvMapper.selectByExample(apiScenarioCsvExample);
}
private void deleteCsvResource(List<String> deleteCsvIds) {
if (CollectionUtils.isNotEmpty(deleteCsvIds)) {
ApiScenarioCsvExample example = new ApiScenarioCsvExample();
example.createCriteria().andIdIn(deleteCsvIds);
apiScenarioCsvMapper.deleteByExample(example);
stepExample.clear();
stepExample.createCriteria().andFileIdIn(deleteFileIds);
ApiScenarioCsvStepExample stepExample = new ApiScenarioCsvStepExample();
stepExample.createCriteria().andIdIn(deleteCsvIds);
apiScenarioCsvStepMapper.deleteByExample(stepExample);
}
}
@ -719,32 +686,14 @@ public class ApiScenarioService extends MoveNodeService {
updateApiScenarioStep(request, originScenario, updater);
// 处理步骤文件
handleStepFiles(request, updater, originScenario);
handleStepFilesUpdate(request, updater, originScenario);
// 处理场景文件
handleScenarioFiles(request, updater, originScenario);
// 处理 csv 文件
handleCsvUpdate(request.getScenarioConfig(), scenario, updater);
return scenario;
}
private void handleScenarioFiles(ApiScenarioUpdateRequest request, String updater, ApiScenario scenario) {
// 处理场景文件
ApiFileResourceUpdateRequest resourceUpdateRequest = getApiFileResourceUpdateRequest(scenario.getId(), scenario.getProjectId(), updater);
ResourceAddFileParam fileParam = request.getFileParam();
if (fileParam != null) {
resourceUpdateRequest = BeanUtils.copyBean(resourceUpdateRequest, fileParam);
apiFileResourceService.updateFileResource(resourceUpdateRequest);
}
//处理csv变量
if (request.getScenarioConfig() != null
&& request.getScenarioConfig().getVariable() != null) {
saveCsv(request.getScenarioConfig().getVariable().getCsvVariables(), resourceUpdateRequest);
} else {
saveCsv(new ArrayList<>(), resourceUpdateRequest);
}
}
/**
* 更新场景步骤
*/
@ -764,8 +713,9 @@ public class ApiScenarioService extends MoveNodeService {
// 获取待更新的步骤
List<ApiScenarioStep> apiScenarioSteps = getApiScenarioSteps(null, steps, scenarioCsvSteps);
apiScenarioSteps.forEach(step -> step.setScenarioId(scenario.getId()));
scenarioCsvSteps.forEach(step -> step.setScenarioId(scenario.getId()));
saveStepCsv(apiScenarioSteps, scenarioCsvSteps);
saveStepCsv(scenario.getId(), apiScenarioSteps, scenarioCsvSteps);
// 获取待更新的步骤详情
addSpecialStepDetails(steps, request.getStepDetails());
List<ApiScenarioStepBlob> updateStepBlobs = getUpdateStepBlobs(apiScenarioSteps, request.getStepDetails());
@ -941,9 +891,9 @@ public class ApiScenarioService extends MoveNodeService {
continue;
}
if (CollectionUtils.isNotEmpty(step.getCsvFileIds())) {
if (CollectionUtils.isNotEmpty(step.getCsvIds())) {
//如果是csv文件 需要保存到apiScenarioCsvStep表中
step.getCsvFileIds().forEach(fileId -> {
step.getCsvIds().forEach(fileId -> {
ApiScenarioCsvStep csvStep = new ApiScenarioCsvStep();
csvStep.setId(IDGenerator.nextStr());
csvStep.setStepId(apiScenarioStep.getId());
@ -1183,16 +1133,11 @@ public class ApiScenarioService extends MoveNodeService {
private void deleteCsvByScenarioId(String id) {
ApiScenarioCsvExample example = new ApiScenarioCsvExample();
example.createCriteria().andScenarioIdEqualTo(id);
List<ApiScenarioCsv> apiScenarioCsv = apiScenarioCsvMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(apiScenarioCsv)) {
List<String> fileIds = apiScenarioCsv.stream().map(ApiScenarioCsv::getFileId).toList();
//删除关联关系
ApiScenarioCsvStepExample stepExample = new ApiScenarioCsvStepExample();
stepExample.createCriteria().andFileIdIn(fileIds);
apiScenarioCsvStepMapper.deleteByExample(stepExample);
}
apiScenarioCsvMapper.deleteByExample(example);
ApiScenarioCsvStepExample stepExample = new ApiScenarioCsvStepExample();
stepExample.createCriteria().andScenarioIdEqualTo(id);
apiScenarioCsvStepMapper.deleteByExample(stepExample);
}
//级联删除
@ -1810,6 +1755,7 @@ public class ApiScenarioService extends MoveNodeService {
msTestElement.setName(step.getName());
// 步骤ID设置为唯一ID
msTestElement.setStepId(step.getUniqueId());
msTestElement.setCsvIds(step.getCsvIds());
// 记录引用的资源ID和项目ID下载执行文件时需要使用
parseParam.getRefProjectIds().add(step.getProjectId());
@ -2152,15 +2098,14 @@ public class ApiScenarioService extends MoveNodeService {
ApiScenarioDetail apiScenarioDetail = BeanUtils.copyBean(new ApiScenarioDetail(), apiScenario);
apiScenarioDetail.setSteps(List.of());
ApiScenarioBlob apiScenarioBlob = apiScenarioBlobMapper.selectByPrimaryKey(scenarioId);
if (apiScenarioBlob != null) {
apiScenarioDetail.setScenarioConfig(JSON.parseObject(new String(apiScenarioBlob.getConfig()), ScenarioConfig.class));
}
//存放csv变量
List<CsvVariable> csvVariables = extApiScenarioStepMapper.getCsvVariableByScenarioId(scenarioId);
if (CollectionUtils.isNotEmpty(csvVariables) && apiScenarioDetail.getScenarioConfig() != null
&& apiScenarioDetail.getScenarioConfig().getVariable() != null) {
apiScenarioDetail.getScenarioConfig().getVariable().setCsvVariables(csvVariables);
}
apiScenarioDetail.getScenarioConfig().getVariable().setCsvVariables(csvVariables);
// 获取所有步骤
List<ApiScenarioStepDTO> allSteps = getAllStepsByScenarioIds(List.of(scenarioId))
@ -2169,16 +2114,13 @@ public class ApiScenarioService extends MoveNodeService {
.collect(Collectors.toList());
//获取所有步骤的csv的关联关系
List<String> stepIds = allSteps.stream().map(ApiScenarioStepDTO::getId).toList();
if (CollectionUtils.isNotEmpty(stepIds)) {
List<ApiScenarioCsvStep> csvSteps = extApiScenarioStepMapper.getCsvStepByStepIds(stepIds);
// 构造 mapkey 为步骤IDvalue 为csv文件ID列表
Map<String, List<String>> stepsCsvMap = csvSteps.stream()
.collect(Collectors.groupingBy(ApiScenarioCsvStep::getStepId, Collectors.mapping(ApiScenarioCsvStep::getFileId, Collectors.toList())));
//将stepsCsvMap根据步骤id放入到allSteps中
if (CollectionUtils.isNotEmpty(allSteps)) {
allSteps.forEach(step -> step.setCsvFileIds(stepsCsvMap.get(step.getId())));
}
List<ApiScenarioCsvStep> csvSteps = extApiScenarioStepMapper.getCsvStepByScenarioId(scenarioId);
// 构造 mapkey 为步骤IDvalue 为csv文件ID列表
Map<String, List<String>> stepsCsvMap = csvSteps.stream()
.collect(Collectors.groupingBy(ApiScenarioCsvStep::getStepId, Collectors.mapping(ApiScenarioCsvStep::getFileId, Collectors.toList())));
//将stepsCsvMap根据步骤id放入到allSteps中
if (CollectionUtils.isNotEmpty(allSteps)) {
allSteps.forEach(step -> step.setCsvIds(stepsCsvMap.get(step.getId())));
}
// 构造 mapkey 为场景IDvalue 为步骤列表

View File

@ -1,6 +1,11 @@
package io.metersphere.api.utils;
import io.metersphere.api.parser.jmeter.*;
import io.metersphere.api.parser.jmeter.child.MsCsvChildPreConverter;
import io.metersphere.api.parser.jmeter.controller.MsConstantTimerControllerConverter;
import io.metersphere.api.parser.jmeter.controller.MsIfControllerConverter;
import io.metersphere.api.parser.jmeter.controller.MsLoopControllerConverter;
import io.metersphere.api.parser.jmeter.controller.MsOnceOnlyControllerConverter;
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
import io.metersphere.plugin.api.spi.MsTestElement;
import io.metersphere.plugin.sdk.util.PluginLogUtils;
@ -24,7 +29,11 @@ public class JmeterElementConverterRegister {
private static final Map<Class<? extends MsTestElement>, AbstractJmeterElementConverter<? extends MsTestElement>> parserMap = new HashMap<>();
static {
// 注册默认的转换器 todo 注册插件的转换器
// 设置获取转换器的方法
AbstractJmeterElementConverter.setGetConverterFunc(JmeterElementConverterRegister::getConverter);
// 注册子步骤前置解析器
AbstractJmeterElementConverter.registerChildPreConverters(new MsCsvChildPreConverter());
// 注册默认的转换器
register(MsHTTPElementConverter.class);
register(MsCommonElementConverter.class);
register(MsScriptElementConverter.class);
@ -43,8 +52,6 @@ public class JmeterElementConverterRegister {
public static void register(Class<? extends AbstractJmeterElementConverter<? extends MsTestElement>> elementConverterClass) {
try {
AbstractJmeterElementConverter<? extends MsTestElement> elementConverter = elementConverterClass.getDeclaredConstructor().newInstance();
// 设置获取转换器的方法
elementConverter.setGetConverterFunc(JmeterElementConverterRegister::getConverter);
// 注册到解析器集合中
parserMap.put(elementConverter.testElementClass, elementConverter);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |

View File

@ -5,7 +5,6 @@ import io.metersphere.api.domain.*;
import io.metersphere.api.dto.ApiFile;
import io.metersphere.api.dto.ApiRunModeRequest;
import io.metersphere.api.dto.ReferenceRequest;
import io.metersphere.api.dto.ResourceAddFileParam;
import io.metersphere.api.dto.assertion.MsAssertionConfig;
import io.metersphere.api.dto.debug.ModuleCreateRequest;
import io.metersphere.api.dto.definition.*;
@ -106,6 +105,9 @@ public class ApiScenarioControllerTests extends BaseTest {
private static final String UPDATE_STATUS = "update-status";
private static final String UPDATE_PRIORITY = "update-priority";
private static final String BATCH_RUN = "batch-operation/run";
private static final String TRANSFER_OPTIONS = "transfer/options/{0}";
private static final String TRANSFER = "transfer";
private static final String STEP_TRANSFER = "step/transfer";
private static final Map<String, String> BATCH_OPERATION_SCENARIO_MODULE_MAP = new HashMap<>();
private static final List<String> BATCH_OPERATION_SCENARIO_ID = new ArrayList<>();
@ -355,9 +357,6 @@ public class ApiScenarioControllerTests extends BaseTest {
request.setSteps(steps);
request.setStepDetails(steptDetailMap);
request.setScenarioConfig(getScenarioConfig());
String fileId = doUploadTempFile(getMockMultipartFile());
request.setFileParam(new ResourceAddFileParam());
request.getFileParam().setUploadFileIds(List.of(fileId));
MvcResult mvcResult = this.requestPostWithOkAndReturn(DEFAULT_ADD, request);
ApiScenario resultData = getResultData(mvcResult, ApiScenario.class);
this.addApiScenario = apiScenarioMapper.selectByPrimaryKey(resultData.getId());
@ -365,7 +364,6 @@ public class ApiScenarioControllerTests extends BaseTest {
assertUpdateSteps(steps, steptDetailMap);
request.setName("anOther name");
request.getFileParam().setUploadFileIds(List.of());
request.setGrouped(true);
request.setEnvironmentId(envGroupId);
ApiScenarioStepRequest stepRequest = new ApiScenarioStepRequest();
@ -387,6 +385,7 @@ public class ApiScenarioControllerTests extends BaseTest {
stepRequest2.setProjectId(DEFAULT_PROJECT_ID);
steps = List.of(stepRequest, stepRequest2);
request.setSteps(steps);
request.getScenarioConfig().getVariable().getCsvVariables().forEach(csvVariable -> csvVariable.setId(UUID.randomUUID().toString()));
mvcResult = this.requestPostWithOkAndReturn(DEFAULT_ADD, request);
this.anOtherAddApiScenario = apiScenarioMapper.selectByPrimaryKey(getResultData(mvcResult, ApiScenario.class).getId());
assertUpdateApiScenario(request, request.getScenarioConfig(), anOtherAddApiScenario.getId());
@ -444,7 +443,7 @@ public class ApiScenarioControllerTests extends BaseTest {
}
}
private ScenarioConfig getScenarioConfig() throws Exception {
private ScenarioConfig getScenarioConfig() {
ScenarioConfig scenarioConfig = new ScenarioConfig();
MsAssertionConfig msAssertionConfig = new MsAssertionConfig();
MsScriptAssertion scriptAssertion = new MsScriptAssertion();
@ -478,16 +477,20 @@ public class ApiScenarioControllerTests extends BaseTest {
fileMetadataId = fileMetadataService.upload(fileUploadRequest, "admin", file);
}
public List<CsvVariable> getCsvVariables() throws Exception {
public List<CsvVariable> getCsvVariables() {
List<CsvVariable> csvVariables = new ArrayList<>();
CsvVariable csvVariable = new CsvVariable();
csvVariable.setId(UUID.randomUUID().toString());
csvVariable.setFileId(localFileId);
csvVariable.setName("csv变量");
csvVariable.setFileName("test.jbc");
csvVariable.setScope(CsvVariable.CsvVariableScope.SCENARIO.name());
csvVariables.add(csvVariable);
csvVariable = new CsvVariable();
csvVariable.setId(UUID.randomUUID().toString());
csvVariable.setFileId(fileMetadataId);
csvVariable.setName("csv-关联的");
csvVariable.setFileName("test.jbc");
csvVariable.setScope(CsvVariable.CsvVariableScope.SCENARIO.name());
csvVariable.setAssociation(true);
csvVariables.add(csvVariable);
@ -506,7 +509,7 @@ public class ApiScenarioControllerTests extends BaseTest {
stepRequest.setStepType(ApiScenarioStepType.API_CASE.name());
stepRequest.setProjectId(DEFAULT_PROJECT_ID);
stepRequest.setConfig(new HashMap<>());
stepRequest.setCsvFileIds(List.of(fileMetadataId));
stepRequest.setCsvIds(List.of(fileMetadataId));
ApiScenarioStepRequest stepRequest2 = new ApiScenarioStepRequest();
stepRequest2.setId(IDGenerator.nextStr());
@ -518,7 +521,7 @@ public class ApiScenarioControllerTests extends BaseTest {
stepRequest2.setStepType(ApiScenarioStepType.API_CASE.name());
stepRequest2.setRefType(ApiScenarioStepRefType.COPY.name());
stepRequest2.setProjectId(DEFAULT_PROJECT_ID);
stepRequest2.setCsvFileIds(List.of(fileMetadataId));
stepRequest2.setCsvIds(List.of(fileMetadataId));
ApiScenarioStepRequest stepRequest3 = new ApiScenarioStepRequest();
stepRequest3.setId(IDGenerator.nextStr());
@ -809,7 +812,7 @@ public class ApiScenarioControllerTests extends BaseTest {
@Test
@Order(7)
public void testTransfer() throws Exception {
this.requestGetWithOk("transfer/options/" + "/" + DEFAULT_PROJECT_ID);
this.requestGetWithOk(TRANSFER_OPTIONS, DEFAULT_PROJECT_ID);
ApiTransferRequest apiTransferRequest = new ApiTransferRequest();
apiTransferRequest.setSourceId(addApiScenario.getId());
apiTransferRequest.setProjectId(DEFAULT_PROJECT_ID);
@ -819,10 +822,10 @@ public class ApiScenarioControllerTests extends BaseTest {
apiTransferRequest.setOriginalName("test-scenario-file.txt");
String uploadFileId = doUploadTempFile(getMockMultipartFile());
apiTransferRequest.setFileId(uploadFileId);
this.requestPost("transfer", apiTransferRequest).andExpect(status().isOk());
this.requestPost(TRANSFER, apiTransferRequest).andExpect(status().isOk());
//文件不存在
apiTransferRequest.setFileId("111");
this.requestPost("transfer", apiTransferRequest).andExpect(status().is5xxServerError());
this.requestPost(TRANSFER, apiTransferRequest).andExpect(status().is5xxServerError());
//文件已经上传
ApiFileResourceExample apiFileResourceExample = new ApiFileResourceExample();
apiFileResourceExample.createCriteria().andResourceIdEqualTo(addApiScenario.getId());
@ -831,8 +834,26 @@ public class ApiScenarioControllerTests extends BaseTest {
apiTransferRequest.setFileId(apiFileResources.get(0).getFileId());
apiTransferRequest.setFileName("test-scenario-file-1");
apiTransferRequest.setOriginalName("test-scenario-file-1.txt");
this.requestPost("transfer", apiTransferRequest).andExpect(status().isOk());
this.requestPost(TRANSFER, apiTransferRequest).andExpect(status().isOk());
}
@Test
@Order(7)
public void testStepTransfer() throws Exception {
this.requestGetWithOk(TRANSFER_OPTIONS, DEFAULT_PROJECT_ID);
ApiTransferRequest apiTransferRequest = new ApiTransferRequest();
apiTransferRequest.setSourceId(addApiScenarioSteps.get(0).getId());
apiTransferRequest.setProjectId(DEFAULT_PROJECT_ID);
apiTransferRequest.setModuleId("root");
apiTransferRequest.setLocal(true);
apiTransferRequest.setFileName("test-scenario-step-file");
apiTransferRequest.setOriginalName("test-scenario-step-file.txt");
String uploadFileId = doUploadTempFile(getMockMultipartFile());
apiTransferRequest.setFileId(uploadFileId);
this.requestPost(STEP_TRANSFER, apiTransferRequest).andExpect(status().isOk());
//文件不存在
apiTransferRequest.setFileId("111");
this.requestPost(STEP_TRANSFER, apiTransferRequest).andExpect(status().is5xxServerError());
}
@Test

View File

@ -316,7 +316,7 @@ public class FileAssociationService {
* @return
* @throws Exception
*/
public String transferAndAssociation(@Validated FileAssociationDTO fileAssociationDTO) throws Exception {
public String transferAndAssociation(@Validated FileAssociationDTO fileAssociationDTO) {
FileAssociationSource source = extFileAssociationMapper.selectNameBySourceTableAndId(FileAssociationSourceUtil.getQuerySql(fileAssociationDTO.getSourceType()), fileAssociationDTO.getSourceId());
this.validateSourceName(source);
String fileId = fileMetadataService.transferFile(

View File

@ -22,10 +22,7 @@ import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileRepository;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.MinioRepository;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.GitRepositoryUtil;
import io.metersphere.sdk.util.TempFileUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.sdk.util.*;
import io.metersphere.system.mapper.BaseUserMapper;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.PageUtils;
@ -214,7 +211,7 @@ public class FileMetadataService {
* @return
* @throws Exception
*/
public String transferFile(String fileName, String originFileName, String projectId, String moduleId, String operator, byte[] fileBytes) throws Exception {
public String transferFile(String fileName, String originFileName, String projectId, String moduleId, String operator, byte[] fileBytes) {
if (StringUtils.isBlank(originFileName)) {
throw new MSException(Translator.get("file.name.cannot.be.empty"));
}
@ -228,7 +225,13 @@ public class FileMetadataService {
uploadFileRequest.setStorage(StorageType.MINIO.name());
FileRepository minio = CommonBeanFactory.getBean(MinioRepository.class);
String filePath = minio.saveFile(fileBytes, uploadFileRequest);
String filePath;
try {
filePath = minio.saveFile(fileBytes, uploadFileRequest);
} catch (Exception e) {
LogUtils.error(e);
throw new MSException(Translator.get("file.transfer.failed"), e);
}
fileMetadata.setPath(filePath);
fileMetadata.setFileVersion(fileMetadata.getId());
fileMetadataMapper.insert(fileMetadata);