fix(接口测试): 复制的步骤,复制步骤中的文件

--task=1016924 --user=陈建星 复制步骤文件接口-场景未保存时操作-调试等 https://www.tapd.cn/55049933/s/1624392
This commit is contained in:
AgAngle 2024-12-04 15:11:01 +08:00 committed by Craftsman
parent aeede658ab
commit e1c14e77c7
19 changed files with 525 additions and 44 deletions

View File

@ -21,6 +21,7 @@ import io.metersphere.api.service.scenario.ApiScenarioService;
import io.metersphere.project.service.FileModuleService; import io.metersphere.project.service.FileModuleService;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO; import io.metersphere.sdk.dto.api.task.TaskRequestDTO;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.dto.OperationHistoryDTO; import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest; import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.sdk.BaseTreeNode; import io.metersphere.system.dto.sdk.BaseTreeNode;
@ -154,12 +155,20 @@ public class ApiScenarioController {
return apiScenarioService.getApiScenarioDetailDTO(scenarioId, SessionUtils.getUserId()); return apiScenarioService.getApiScenarioDetailDTO(scenarioId, SessionUtils.getUserId());
} }
@PostMapping("/step/get/un-save")
@Operation(summary = "接口测试-接口场景管理-获取未保存的场景步骤详情")
@RequiresPermissions(value = {PermissionConstants.PROJECT_API_SCENARIO_UPDATE, PermissionConstants.PROJECT_API_SCENARIO_ADD},
logical = Logical.OR)
public ApiScenarioUnsavedStepDetailDTO getUnsavedStepDetail(@Validated @RequestBody ApiScenarioStepDetailRequest request) {
return apiScenarioService.getUnsavedStepDetail(request);
}
@GetMapping("/step/get/{stepId}") @GetMapping("/step/get/{stepId}")
@Operation(summary = "接口测试-接口场景管理-获取场景步骤详情") @Operation(summary = "接口测试-接口场景管理-获取场景步骤详情")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_READ) @RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_READ)
@CheckOwner(resourceId = "#stepId", resourceType = "api_scenario_step") @CheckOwner(resourceId = "#stepId", resourceType = "api_scenario_step")
public Object getStepDetail(@PathVariable String stepId) { public Object getStepDetail(@PathVariable String stepId) {
return apiScenarioService.getStepDetail(stepId); return JSON.parseObject(JSON.toJSONString(apiScenarioService.getStepDetail(stepId)));
} }
@GetMapping("/step/resource-info/{resourceId}") @GetMapping("/step/resource-info/{resourceId}")

View File

@ -0,0 +1,26 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
import java.util.Map;
/**
* @Author: jianxing
* @CreateTime: 2024-01-10 11:24
*/
@Data
public class ApiScenarioCopyStepMap {
/**
* key stepIdvalue copyFrom 的步骤ID
*/
private Map<String, String> copyFromStepIdMap;
/**
* key stepIdvalue copyFrom 的接口ID
*/
private Map<String, String> isNewApiResourceMap;
/**
* key stepIdvalue copyFrom 的接口用例ID
*/
private Map<String, String> isNewApiCaseResourceMap;
}

View File

@ -0,0 +1,49 @@
package io.metersphere.api.dto.scenario;
import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.sdk.valid.EnumValue;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ApiScenarioStepDetailRequest {
@Schema(description = "步骤id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@Size(max = 50, message = "{api_scenario_step.id.length_range}")
private String id;
/**
* 记录是从哪个步骤复制来的
* 如果没有传步骤详情
* 保存时需要根据这个字段查询原步骤详情保存
*/
@Schema(description = "复制的目标步骤ID")
@NotBlank
private String copyFromStepId;
@Schema(description = "资源id")
private String resourceId;
/**
* {@link ApiScenarioStepType}
*/
@Schema(description = "步骤类型/API/CASE等")
@NotBlank
@EnumValue(enumClass = ApiScenarioStepType.class)
private String stepType;
/**
* 引用模式默认完全引用
* - 完全引用步骤状态不可调整
* - 部分引用步骤状态可调整
*
* @see ApiScenarioStepRefType
*/
@Schema(description = "引用/复制/自定义")
@EnumValue(enumClass = ApiScenarioStepRefType.class)
@NotBlank
private String refType;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.api.dto.scenario; package io.metersphere.api.dto.scenario;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@Data @Data
@ -9,9 +10,11 @@ public class ApiScenarioStepRequest extends ApiScenarioStepCommonDTO<ApiScenario
* 如果没有传步骤详情 * 如果没有传步骤详情
* 保存时需要根据这个字段查询原步骤详情保存 * 保存时需要根据这个字段查询原步骤详情保存
*/ */
@Schema(description = "复制的目标步骤ID")
private String copyFromStepId; private String copyFromStepId;
/** /**
* 记录当前步骤是否是新增的步骤 * 记录当前步骤是否是新增的步骤
*/ */
@Schema(description = "当前步骤是否是新增的步骤")
private Boolean isNew; private Boolean isNew;
} }

View File

@ -0,0 +1,16 @@
package io.metersphere.api.dto.scenario;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class ApiScenarioUnsavedStepDetailDTO {
@Schema(description = "步骤详情")
private Object detail;
@Schema(description = "复制的步骤中的本地文件ID集合")
private List<String> uploadIds;
}

View File

@ -1,8 +1,8 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.domain.ApiTestCaseBlob; import io.metersphere.api.domain.ApiTestCaseBlob;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.api.mapper.ApiTestCaseBlobMapper; import io.metersphere.api.mapper.ApiTestCaseBlobMapper;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -27,7 +27,7 @@ public class ApiCaseStepParser extends StepParser {
} }
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
if (isRef(step.getRefType())) { if (isRef(step.getRefType())) {
ApiTestCaseBlobMapper apiTestCaseBlobMapper = CommonBeanFactory.getBean(ApiTestCaseBlobMapper.class); ApiTestCaseBlobMapper apiTestCaseBlobMapper = CommonBeanFactory.getBean(ApiTestCaseBlobMapper.class);
ApiTestCaseBlob apiTestCaseBlob = apiTestCaseBlobMapper.selectByPrimaryKey(step.getResourceId()); ApiTestCaseBlob apiTestCaseBlob = apiTestCaseBlobMapper.selectByPrimaryKey(step.getResourceId());

View File

@ -1,9 +1,9 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioBlob; import io.metersphere.api.domain.ApiScenarioBlob;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.request.MsScenario; import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.api.dto.scenario.ScenarioConfig; import io.metersphere.api.dto.scenario.ScenarioConfig;
import io.metersphere.api.mapper.ApiScenarioBlobMapper; import io.metersphere.api.mapper.ApiScenarioBlobMapper;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -38,7 +38,7 @@ public class ApiScenarioStepParser extends StepParser {
* @return * @return
*/ */
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
if (isRef(step.getRefType())) { if (isRef(step.getRefType())) {
ApiScenarioBlobMapper apiScenarioBlobMapper = CommonBeanFactory.getBean(ApiScenarioBlobMapper.class); ApiScenarioBlobMapper apiScenarioBlobMapper = CommonBeanFactory.getBean(ApiScenarioBlobMapper.class);
ApiScenarioBlob apiScenarioBlob = apiScenarioBlobMapper.selectByPrimaryKey(step.getResourceId()); ApiScenarioBlob apiScenarioBlob = apiScenarioBlobMapper.selectByPrimaryKey(step.getResourceId());

View File

@ -1,11 +1,11 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiDefinitionBlob; import io.metersphere.api.domain.ApiDefinitionBlob;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.body.Body; import io.metersphere.api.dto.request.http.body.Body;
import io.metersphere.api.dto.request.http.body.FormDataKV; import io.metersphere.api.dto.request.http.body.FormDataKV;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.api.mapper.ApiDefinitionBlobMapper; import io.metersphere.api.mapper.ApiDefinitionBlobMapper;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -52,7 +52,7 @@ public class ApiStepParser extends StepParser {
} }
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
if (isRef(step.getRefType())) { if (isRef(step.getRefType())) {
ApiDefinitionBlobMapper apiDefinitionBlobMapper = CommonBeanFactory.getBean(ApiDefinitionBlobMapper.class); ApiDefinitionBlobMapper apiDefinitionBlobMapper = CommonBeanFactory.getBean(ApiDefinitionBlobMapper.class);
ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(step.getResourceId()); ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(step.getResourceId());

View File

@ -1,8 +1,8 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.request.controller.MsConstantTimerController; import io.metersphere.api.dto.request.controller.MsConstantTimerController;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
public class ConstantTimerStepParser extends StepParser { public class ConstantTimerStepParser extends StepParser {
@ -12,7 +12,7 @@ public class ConstantTimerStepParser extends StepParser {
} }
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
return null; return null;
} }
} }

View File

@ -1,7 +1,7 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
/** /**
@ -18,7 +18,7 @@ public class DefaultStepParser extends StepParser {
} }
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
return parse2MsTestElement(getStepBlobString(step.getId())); return parse2MsTestElement(getStepBlobString(step.getId()));
} }
} }

View File

@ -1,8 +1,8 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.request.controller.MsIfController; import io.metersphere.api.dto.request.controller.MsIfController;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
public class IfControllerStepParser extends StepParser { public class IfControllerStepParser extends StepParser {
@ -12,7 +12,7 @@ public class IfControllerStepParser extends StepParser {
} }
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
return null; return null;
} }
} }

View File

@ -1,8 +1,8 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.request.MsJMeterComponent; import io.metersphere.api.dto.request.MsJMeterComponent;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -13,7 +13,7 @@ public class JMeterComponentStepParser extends StepParser {
} }
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
return null; return null;
} }
} }

View File

@ -1,8 +1,8 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.request.controller.MsLoopController; import io.metersphere.api.dto.request.controller.MsLoopController;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
public class LoopControllerStepParser extends StepParser { public class LoopControllerStepParser extends StepParser {
@ -13,7 +13,7 @@ public class LoopControllerStepParser extends StepParser {
} }
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
return null; return null;
} }
} }

View File

@ -1,8 +1,8 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.request.controller.MsOnceOnlyController; import io.metersphere.api.dto.request.controller.MsOnceOnlyController;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.BeanUtils;
@ -15,7 +15,7 @@ public class OnceOnlyControllerStepParser extends StepParser {
} }
@Override @Override
public Object parseDetail(ApiScenarioStep step) { public Object parseDetail(ApiScenarioStepDetailRequest step) {
return null; return null;
} }
} }

View File

@ -1,9 +1,9 @@
package io.metersphere.api.parser.step; package io.metersphere.api.parser.step;
import io.metersphere.api.constants.ApiScenarioStepRefType; import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.domain.ApiScenarioStepBlob; import io.metersphere.api.domain.ApiScenarioStepBlob;
import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepCommonDTO;
import io.metersphere.api.dto.scenario.ApiScenarioStepDetailRequest;
import io.metersphere.api.mapper.ApiScenarioStepBlobMapper; import io.metersphere.api.mapper.ApiScenarioStepBlobMapper;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -37,7 +37,7 @@ public abstract class StepParser {
* @param step * @param step
* @return * @return
*/ */
public abstract Object parseDetail(ApiScenarioStep step); public abstract Object parseDetail(ApiScenarioStepDetailRequest step);
protected boolean isRef(String refType) { protected boolean isRef(String refType) {

View File

@ -28,10 +28,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* @Author: jianxing * @Author: jianxing
@ -400,4 +397,9 @@ public class ApiFileResourceService {
return fileId; return fileId;
} }
public List<ApiFileResource> selectByResourceIds(Collection<String> copyFromStepIds) {
ApiFileResourceExample example = new ApiFileResourceExample();
example.createCriteria().andResourceIdIn(new ArrayList<>(copyFromStepIds));
return apiFileResourceMapper.selectByExample(example);
}
} }

View File

@ -443,7 +443,7 @@ public class ApiScenarioDataTransferService {
scenarioCsvSteps = apiScenarioService.filterNotExistCsv(request.getScenarioConfig(), scenarioCsvSteps); scenarioCsvSteps = apiScenarioService.filterNotExistCsv(request.getScenarioConfig(), scenarioCsvSteps);
this.saveStepCsv(scenarioId, scenarioCsvSteps, csvStepBatchMapper); this.saveStepCsv(scenarioId, scenarioCsvSteps, csvStepBatchMapper);
// apiScenarioService.
// 获取待更新的步骤详情 // 获取待更新的步骤详情
apiScenarioService.addSpecialStepDetails(steps, request.getStepDetails()); apiScenarioService.addSpecialStepDetails(steps, request.getStepDetails());
List<ApiScenarioStepBlob> updateStepBlobs = apiScenarioService.getUpdateStepBlobs(apiScenarioSteps, request.getStepDetails()); List<ApiScenarioStepBlob> updateStepBlobs = apiScenarioService.getUpdateStepBlobs(apiScenarioSteps, request.getStepDetails());

View File

@ -125,7 +125,9 @@ public class ApiScenarioRunService {
apiScenarioService.handleRefUpgradeFile(csvVariables, dbCsv); apiScenarioService.handleRefUpgradeFile(csvVariables, dbCsv);
// 处理特殊的步骤详情 // 处理特殊的步骤详情
apiScenarioService.addSpecialStepDetails(request.getSteps(), request.getStepDetails()); ApiScenarioCopyStepMap apiScenarioCopyStepMap = apiScenarioService.addSpecialStepDetails(request.getSteps(), request.getStepDetails());
// 处理copy的步骤文件
apiScenarioService.handleRunCopyStepFiles(request, apiScenarioCopyStepMap, request.getStepDetails());
ApiResourceRunRequest runRequest = new ApiResourceRunRequest(); ApiResourceRunRequest runRequest = new ApiResourceRunRequest();
runRequest = setFileParam(request, runRequest); runRequest = setFileParam(request, runRequest);
@ -420,7 +422,9 @@ public class ApiScenarioRunService {
} }
// 处理特殊的步骤详情 // 处理特殊的步骤详情
apiScenarioService.addSpecialStepDetails(request.getSteps(), request.getStepDetails()); ApiScenarioCopyStepMap apiScenarioCopyStepMap = apiScenarioService.addSpecialStepDetails(request.getSteps(), request.getStepDetails());
// 处理copy的步骤文件
apiScenarioService.handleRunCopyStepFiles(request, apiScenarioCopyStepMap, request.getStepDetails());
ApiScenarioParseTmpParam tmpParam = parse(msScenario, request.getSteps(), request); ApiScenarioParseTmpParam tmpParam = parse(msScenario, request.getSteps(), request);
@ -983,7 +987,7 @@ public class ApiScenarioRunService {
// 记录引用的资源ID和项目ID下载执行文件时需要使用 // 记录引用的资源ID和项目ID下载执行文件时需要使用
parseParam.getRefProjectIds().add(step.getProjectId()); parseParam.getRefProjectIds().add(step.getProjectId());
if (apiScenarioService.isRefOrPartialRef(step.getRefType())) { if (apiScenarioService.isRefOrPartialRef(step.getRefType()) && !apiScenarioService.isRefApi(step.getStepType(), step.getRefType())) {
// 引用的步骤记录引用的资源ID // 引用的步骤记录引用的资源ID
parseParam.getFileResourceIds().add(step.getResourceId()); parseParam.getFileResourceIds().add(step.getResourceId());
} else if (msTestElement instanceof MsHTTPElement) { } else if (msTestElement instanceof MsHTTPElement) {

View File

@ -178,8 +178,6 @@ public class ApiScenarioService extends MoveNodeService {
public static final String SCHEDULE = "Schedule"; public static final String SCHEDULE = "Schedule";
private static final String SCENARIO_TABLE = "api_scenario"; private static final String SCENARIO_TABLE = "api_scenario";
private static final String SCENARIO = "SCENARIO"; private static final String SCENARIO = "SCENARIO";
@Resource
private ApiReportRelateTaskMapper apiReportRelateTaskMapper;
public List<ApiScenarioDTO> getScenarioPage(ApiScenarioPageRequest request, boolean isRepeat, String testPlanId) { public List<ApiScenarioDTO> getScenarioPage(ApiScenarioPageRequest request, boolean isRepeat, String testPlanId) {
@ -468,7 +466,10 @@ public class ApiScenarioService extends MoveNodeService {
List<ApiScenarioStep> steps = getApiScenarioSteps(null, request.getSteps(), csvSteps); List<ApiScenarioStep> steps = getApiScenarioSteps(null, request.getSteps(), csvSteps);
steps.forEach(step -> step.setScenarioId(scenario.getId())); steps.forEach(step -> step.setScenarioId(scenario.getId()));
// 处理特殊的步骤详情 // 处理特殊的步骤详情
addSpecialStepDetails(request.getSteps(), request.getStepDetails()); ApiScenarioCopyStepMap apiScenarioCopyStepMap = addSpecialStepDetails(request.getSteps(), request.getStepDetails());
// 处理copy的步骤文件
handleSaveCopyStepFiles(apiScenarioCopyStepMap, request.getStepDetails(), scenario);
List<ApiScenarioStepBlob> apiScenarioStepBlobs = getUpdateStepBlobs(steps, request.getStepDetails()); List<ApiScenarioStepBlob> apiScenarioStepBlobs = getUpdateStepBlobs(steps, request.getStepDetails());
apiScenarioStepBlobs.forEach(step -> step.setScenarioId(scenario.getId())); apiScenarioStepBlobs.forEach(step -> step.setScenarioId(scenario.getId()));
@ -844,8 +845,12 @@ public class ApiScenarioService extends MoveNodeService {
scenarioCsvSteps = filterNotExistCsv(request.getScenarioConfig(), scenarioCsvSteps); scenarioCsvSteps = filterNotExistCsv(request.getScenarioConfig(), scenarioCsvSteps);
saveStepCsv(scenario.getId(), scenarioCsvSteps); saveStepCsv(scenario.getId(), scenarioCsvSteps);
// 获取待更新的步骤详情 // 获取待更新的步骤详情
addSpecialStepDetails(steps, request.getStepDetails()); ApiScenarioCopyStepMap apiScenarioCopyStepMap = addSpecialStepDetails(steps, request.getStepDetails());
// 处理copy的步骤文件
handleSaveCopyStepFiles(apiScenarioCopyStepMap, request.getStepDetails(), scenario);
List<ApiScenarioStepBlob> updateStepBlobs = getUpdateStepBlobs(apiScenarioSteps, request.getStepDetails()); List<ApiScenarioStepBlob> updateStepBlobs = getUpdateStepBlobs(apiScenarioSteps, request.getStepDetails());
updateStepBlobs.forEach(step -> step.setScenarioId(scenario.getId())); updateStepBlobs.forEach(step -> step.setScenarioId(scenario.getId()));
@ -1041,9 +1046,9 @@ public class ApiScenarioService extends MoveNodeService {
* copyFromStepId detail * copyFromStepId detail
* isNew 的资源的 detail * isNew 的资源的 detail
*/ */
public void addSpecialStepDetails(List<ApiScenarioStepRequest> steps, Map<String, Object> stepDetails) { public ApiScenarioCopyStepMap addSpecialStepDetails(List<ApiScenarioStepRequest> steps, Map<String, Object> stepDetails) {
if (CollectionUtils.isEmpty(steps)) { if (CollectionUtils.isEmpty(steps)) {
return; return null;
} }
// key stepIdvalue copyFrom 的步骤ID // key stepIdvalue copyFrom 的步骤ID
@ -1107,6 +1112,287 @@ public class ApiScenarioService extends MoveNodeService {
apiScenarioStepBlobMapper.selectByExampleWithBLOBs(example) apiScenarioStepBlobMapper.selectByExampleWithBLOBs(example)
.forEach(scenarioStepBlob -> copyFromBlobMap.put(scenarioStepBlob.getId(), scenarioStepBlob.getContent())); .forEach(scenarioStepBlob -> copyFromBlobMap.put(scenarioStepBlob.getId(), scenarioStepBlob.getContent()));
}); });
ApiScenarioCopyStepMap apiScenarioCopyStepMap = new ApiScenarioCopyStepMap();
apiScenarioCopyStepMap.setCopyFromStepIdMap(copyFromStepIdMap);
apiScenarioCopyStepMap.setIsNewApiResourceMap(isNewApiResourceMap);
apiScenarioCopyStepMap.setIsNewApiCaseResourceMap(isNewApiCaseResourceMap);
return apiScenarioCopyStepMap;
}
/**
* 处理保存时copy的步骤中文件
* @param apiScenarioCopyStepMap
* @param stepDetails
*/
public void handleSaveCopyStepFiles(ApiScenarioCopyStepMap apiScenarioCopyStepMap, Map<String, Object> stepDetails, ApiScenario scenario) {
try {
List<ApiFileResource> apiFileResources = new ArrayList<>();
apiFileResources.addAll(handleCopyFromStepFiles(stepDetails, apiScenarioCopyStepMap.getCopyFromStepIdMap(), scenario));
apiFileResources.addAll(handleCopyApiFiles(stepDetails, apiScenarioCopyStepMap.getIsNewApiResourceMap(), scenario));
apiFileResources.addAll(handleCopyApiCaseFiles(stepDetails, apiScenarioCopyStepMap.getIsNewApiCaseResourceMap(), scenario));
if (CollectionUtils.isNotEmpty(apiFileResources)) {
// 插入步骤和文件的关联关系
apiFileResources.forEach(apiFileResource -> apiFileResource.setProjectId(scenario.getProjectId()));
apiFileResourceMapper.batchInsert(apiFileResources);
}
} catch (Exception e) {
LogUtils.error(e);
}
}
/**
* 处理调试执行时copy的步骤中文件
* @param debugRequest
* @param apiScenarioCopyStepMap
* @param stepDetails
*/
public void handleRunCopyStepFiles(ApiScenarioDebugRequest debugRequest, ApiScenarioCopyStepMap apiScenarioCopyStepMap, Map<String, Object> stepDetails) {
try {
List<ApiFileResource> apiFileResources = new ArrayList<>();
apiFileResources.addAll(handleCopyFromStepFiles(stepDetails, apiScenarioCopyStepMap.getCopyFromStepIdMap(), null));
apiFileResources.addAll(handleCopyApiFiles(stepDetails, apiScenarioCopyStepMap.getIsNewApiResourceMap(), null));
apiFileResources.addAll(handleCopyApiCaseFiles(stepDetails, apiScenarioCopyStepMap.getIsNewApiCaseResourceMap(), null));
if (debugRequest.getStepFileParam() == null) {
debugRequest.setStepFileParam(new HashMap<>());
}
// 将copy的步骤中的文件设置为新上传的临时文件执行时从临时目录获取
Map<String, ResourceAddFileParam> stepFileParam = debugRequest.getStepFileParam();
for (ApiFileResource apiFileResource : apiFileResources) {
stepFileParam.putIfAbsent(apiFileResource.getResourceId(), new ResourceAddFileParam());
ResourceAddFileParam resourceAddFileParam = stepFileParam.get(apiFileResource.getResourceId());
if (resourceAddFileParam.getUploadFileIds() == null) {
resourceAddFileParam.setUploadFileIds(new ArrayList<>());
}
resourceAddFileParam.getUploadFileIds().add(apiFileResource.getFileId());
}
} catch (Exception e) {
LogUtils.error(e);
}
}
/**
* 处理 copy 的步骤中的文件
* 复制文件
* 创建关联关系
* 替换文件ID
* @param stepDetails
* @param copyFromStepIdMap
*/
private List<ApiFileResource> handleCopyFromStepFiles(Map<String, Object> stepDetails, Map<String, String> copyFromStepIdMap, ApiScenario scenario) {
if (copyFromStepIdMap.isEmpty()) {
return List.of();
}
// 查询 copyFrom 的步骤所关联的文件
Collection<String> copyFromStepIds = copyFromStepIdMap.values();
List<ApiFileResource> apiFileResources = apiFileResourceService.selectByResourceIds(copyFromStepIds);
if (apiFileResources.isEmpty()) {
return List.of();
}
Map<String, List<ApiFileResource>> stepFileMap = apiFileResources.stream().collect(Collectors.groupingBy(ApiFileResource::getResourceId));
// 查询 copyFrom 步骤的场景ID Map
List<String> hasFileCopyFromStepIds = stepFileMap.keySet().stream().toList();
Map<String, String> copyFromStepScenarioMap = getApiScenarioStepByIds(hasFileCopyFromStepIds).stream()
.collect(Collectors.toMap(ApiScenarioStep::getId, ApiScenarioStep::getScenarioId));
Map<String, String> fileIdMap = new HashMap<>();
List<ApiFileResource> newApiFileResources = new ArrayList<>();
for (String stepId : copyFromStepIdMap.keySet()) {
List<ApiFileResource> originApiFileResources = stepFileMap.get(copyFromStepIdMap.get(stepId));
if (CollectionUtils.isEmpty(originApiFileResources)) {
continue;
}
boolean isSave = scenario != null;
String newFileId = IDGenerator.nextStr();
for (ApiFileResource originApiFileResource : originApiFileResources) {
String sourceDir = DefaultRepositoryDir.getApiScenarioStepDir(originApiFileResource.getProjectId(),
copyFromStepScenarioMap.get(originApiFileResource.getResourceId()), originApiFileResource.getResourceId());
// 如果是保存则copy到正式目录如果是执行则copy到临时目录
String targetDir = isSave ? DefaultRepositoryDir.getApiScenarioStepDir(scenario.getProjectId(), scenario.getId(), stepId)
: DefaultRepositoryDir.getSystemTempDir();
// 复制文件
apiFileResourceService.copyFile(sourceDir + "/" + originApiFileResource.getFileId(),
targetDir + "/" + newFileId,
originApiFileResource.getFileName());
// 记录步骤和文件信息
ApiFileResource newApiFileResource = getStepApiFileResource(stepId, newFileId, originApiFileResource.getFileName());
newApiFileResources.add(newApiFileResource);
// 记录文件ID映射
fileIdMap.put(originApiFileResource.getFileId(), newFileId);
}
// 替换详情中的文件ID
replaceCopyStepFileId(stepDetails, fileIdMap, stepId);
}
return newApiFileResources;
}
/**
* 处理复制的接口定义步骤中的文件
* 复制文件
* 创建关联关系
* 替换文件ID
* @param stepDetails
*/
private List<ApiFileResource> handleCopyApiFiles(Map<String, Object> stepDetails, Map<String, String> copyApiIdMap, ApiScenario scenario) {
if (copyApiIdMap.isEmpty()) {
return List.of();
}
// 查询 copy 的接口定义所关联的文件
Collection<String> copyApiIds = copyApiIdMap.values();
List<ApiFileResource> apiFileResources = apiFileResourceService.selectByResourceIds(copyApiIds);
if (apiFileResources.isEmpty()) {
return List.of();
}
Map<String, List<ApiFileResource>> stepFileMap = apiFileResources.stream().collect(Collectors.groupingBy(ApiFileResource::getResourceId));
Map<String, String> fileIdMap = new HashMap<>();
List<ApiFileResource> newApiFileResources = new ArrayList<>();
for (String stepId : copyApiIdMap.keySet()) {
List<ApiFileResource> originApiFileResources = stepFileMap.get(copyApiIdMap.get(stepId));
if (CollectionUtils.isEmpty(originApiFileResources)) {
continue;
}
String newFileId = IDGenerator.nextStr();
for (ApiFileResource originApiFileResource : originApiFileResources) {
String sourceDir = DefaultRepositoryDir.getApiDefinitionDir(originApiFileResource.getProjectId(), originApiFileResource.getResourceId());
boolean isSave = scenario != null;
// 如果是保存则copy到正式目录如果是执行则copy到临时目录
String targetDir = isSave ? DefaultRepositoryDir.getApiScenarioStepDir(scenario.getProjectId(), scenario.getId(), stepId)
: DefaultRepositoryDir.getSystemTempDir();
// 复制文件
apiFileResourceService.copyFile(sourceDir + "/" + originApiFileResource.getFileId(),
targetDir + "/" + newFileId,
originApiFileResource.getFileName());
// 记录步骤和文件信息
ApiFileResource newApiFileResource = getStepApiFileResource(stepId, newFileId, originApiFileResource.getFileName());
newApiFileResources.add(newApiFileResource);
// 记录文件ID映射
fileIdMap.put(originApiFileResource.getFileId(), newFileId);
}
// 替换详情中的文件ID
replaceCopyStepFileId(stepDetails, fileIdMap, stepId);
}
return newApiFileResources;
}
/**
* 处理复制的接口用例步骤中的文件
* 复制文件
* 创建关联关系
* 替换文件ID
* @param stepDetails
*/
private List<ApiFileResource> handleCopyApiCaseFiles(Map<String, Object> stepDetails, Map<String, String> copyApiCaseIdMap, ApiScenario scenario) {
if (copyApiCaseIdMap.isEmpty()) {
return List.of();
}
// 查询 copy 的接口定义所关联的文件
Collection<String> copyApiIds = copyApiCaseIdMap.values();
List<ApiFileResource> apiFileResources = apiFileResourceService.selectByResourceIds(copyApiIds);
if (apiFileResources.isEmpty()) {
return List.of();
}
Map<String, List<ApiFileResource>> stepFileMap = apiFileResources.stream().collect(Collectors.groupingBy(ApiFileResource::getResourceId));
boolean isSave = scenario != null;
Map<String, String> fileIdMap = new HashMap<>();
List<ApiFileResource> newApiFileResources = new ArrayList<>();
for (String stepId : copyApiCaseIdMap.keySet()) {
List<ApiFileResource> originApiFileResources = stepFileMap.get(copyApiCaseIdMap.get(stepId));
if (CollectionUtils.isEmpty(originApiFileResources)) {
continue;
}
String newFileId = IDGenerator.nextStr();
for (ApiFileResource originApiFileResource : originApiFileResources) {
String sourceDir = DefaultRepositoryDir.getApiCaseDir(originApiFileResource.getProjectId(), originApiFileResource.getResourceId());
// 如果是保存则copy到正式目录如果是执行则copy到临时目录
String targetDir = isSave ? DefaultRepositoryDir.getApiScenarioStepDir(scenario.getProjectId(), scenario.getId(), stepId)
: DefaultRepositoryDir.getSystemTempDir();
// 复制文件
apiFileResourceService.copyFile(sourceDir + "/" + originApiFileResource.getFileId(),
targetDir + "/" + newFileId,
originApiFileResource.getFileName());
// 记录步骤和文件信息
ApiFileResource newApiFileResource = getStepApiFileResource(stepId, newFileId, originApiFileResource.getFileName());
newApiFileResources.add(newApiFileResource);
// 记录文件ID映射
fileIdMap.put(originApiFileResource.getFileId(), newFileId);
}
// 替换详情中的文件ID
replaceCopyStepFileId(stepDetails, fileIdMap, stepId);
}
return newApiFileResources;
}
/**
* 替换复制的步骤中详情的文件ID
* @param stepDetails
* @param fileIdMap
* @param stepId
*/
private void replaceCopyStepFileId(Map<String, Object> stepDetails, Map<String, String> fileIdMap, String stepId) {
Object stepDetail = stepDetails.get(stepId);
if (stepDetail != null) {
// 替换详情中的文件ID
if (stepDetail instanceof byte[] detailBytes) {
AbstractMsTestElement msTestElement = getAbstractMsTestElement(detailBytes);
for (ApiFile apiFile : apiCommonService.getApiFiles(msTestElement)) {
if (fileIdMap.get(apiFile.getFileId()) != null) {
apiFile.setFileId(fileIdMap.get(apiFile.getFileId()));
}
}
stepDetails.put(stepId, msTestElement);
} else if (stepDetail instanceof AbstractMsTestElement msTestElement) {
for (ApiFile apiFile : apiCommonService.getApiFiles(msTestElement)) {
if (fileIdMap.get(apiFile.getFileId()) != null) {
apiFile.setFileId(fileIdMap.get(apiFile.getFileId()));
}
}
}
}
}
private ApiFileResource getStepApiFileResource(String stepId, String fileId, String fileName) {
ApiFileResource apiFileResource = new ApiFileResource();
apiFileResource.setFileId(fileId);
apiFileResource.setResourceId(stepId);
apiFileResource.setResourceType(ApiFileResourceType.API_SCENARIO_STEP.name());
apiFileResource.setCreateTime(System.currentTimeMillis());
apiFileResource.setFileName(fileName);
return apiFileResource;
}
private List<ApiScenarioStep> getApiScenarioStepByIds(List<String> stepIds) {
if (CollectionUtils.isEmpty(stepIds)) {
List.of();
}
ApiScenarioStepExample example = new ApiScenarioStepExample();
example.createCriteria().andIdIn(stepIds);
return apiScenarioStepMapper.selectByExample(example);
} }
/** /**
@ -1698,7 +1984,7 @@ public class ApiScenarioService extends MoveNodeService {
* 判断步骤是否是引用的接口定义 * 判断步骤是否是引用的接口定义
* 引用的接口定义允许修改参数值需要特殊处理 * 引用的接口定义允许修改参数值需要特殊处理
*/ */
private boolean isRefApi(String stepType, String refType) { public boolean isRefApi(String stepType, String refType) {
return isApi(stepType) && StringUtils.equals(refType, ApiScenarioStepRefType.REF.name()); return isApi(stepType) && StringUtils.equals(refType, ApiScenarioStepRefType.REF.name());
} }
@ -1881,6 +2167,10 @@ public class ApiScenarioService extends MoveNodeService {
public Object getStepDetail(String stepId) { public Object getStepDetail(String stepId) {
ApiScenarioStep step = apiScenarioStepMapper.selectByPrimaryKey(stepId); ApiScenarioStep step = apiScenarioStepMapper.selectByPrimaryKey(stepId);
return getStepDetail(BeanUtils.copyBean(new ApiScenarioStepDetailRequest(), step));
}
private Object getStepDetail(ApiScenarioStepDetailRequest step) {
StepParser stepParser = StepParserFactory.getStepParser(step.getStepType()); StepParser stepParser = StepParserFactory.getStepParser(step.getStepType());
Object stepDetail = stepParser.parseDetail(step); Object stepDetail = stepParser.parseDetail(step);
if (stepDetail instanceof MsScriptElement msScriptElement) { if (stepDetail instanceof MsScriptElement msScriptElement) {
@ -1900,7 +2190,7 @@ public class ApiScenarioService extends MoveNodeService {
apiCommonService.setLinkFileInfo(step.getId(), msTestElement); apiCommonService.setLinkFileInfo(step.getId(), msTestElement);
apiCommonService.setEnableCommonScriptProcessorInfo(msTestElement); apiCommonService.setEnableCommonScriptProcessorInfo(msTestElement);
} }
return JSON.parseObject(JSON.toJSONString(stepDetail)); return stepDetail;
} }
private void checkTargetModule(String targetModuleId, String projectId) { private void checkTargetModule(String targetModuleId, String projectId) {
@ -2387,13 +2677,7 @@ public class ApiScenarioService extends MoveNodeService {
return; return;
} }
AbstractMsTestElement msTestElement = null; AbstractMsTestElement msTestElement = getAbstractMsTestElement(apiScenarioStepBlob.getContent());
try {
msTestElement = ApiDataUtils.parseObject(new String(apiScenarioStepBlob.getContent()), AbstractMsTestElement.class);
// 如果插件删除会转换异常
} catch (Exception e) {
LogUtils.error(e);
}
if (msTestElement != null && msTestElement instanceof MsHTTPElement msHTTPElement) { if (msTestElement != null && msTestElement instanceof MsHTTPElement msHTTPElement) {
List<ApiFile> updateFiles = apiCommonService.getApiFilesByFileId(originFileAssociation.getFileId(), msHTTPElement); List<ApiFile> updateFiles = apiCommonService.getApiFilesByFileId(originFileAssociation.getFileId(), msHTTPElement);
// 替换文件的Id和name // 替换文件的Id和name
@ -2406,6 +2690,20 @@ public class ApiScenarioService extends MoveNodeService {
} }
} }
private AbstractMsTestElement getAbstractMsTestElement(byte[] msTestElementByte) {
return getAbstractMsTestElement(new String(msTestElementByte));
}
private AbstractMsTestElement getAbstractMsTestElement(String msTestElementStr) {
try {
return ApiDataUtils.parseObject(msTestElementStr, AbstractMsTestElement.class);
// 如果插件删除会转换异常
} catch (Exception e) {
LogUtils.error(e);
}
return null;
}
public void handleScenarioFileAssociationUpgrade(FileAssociation originFileAssociation, FileMetadata newFileMetadata) { public void handleScenarioFileAssociationUpgrade(FileAssociation originFileAssociation, FileMetadata newFileMetadata) {
String scenarioId = originFileAssociation.getSourceId(); String scenarioId = originFileAssociation.getSourceId();
// 查询步骤详情 // 查询步骤详情
@ -2680,4 +2978,78 @@ public class ApiScenarioService extends MoveNodeService {
.toList(); .toList();
} }
/**
* 获取未保存过的步骤的详情
* @param request
* @return
*/
public ApiScenarioUnsavedStepDetailDTO getUnsavedStepDetail(ApiScenarioStepDetailRequest request) {
ApiScenarioStep step = apiScenarioStepMapper.selectByPrimaryKey(request.getId());
if (step == null) {
ApiScenarioUnsavedStepDetailDTO result = new ApiScenarioUnsavedStepDetailDTO();
ApiScenarioStepDetailRequest getDetailRequest = BeanUtils.copyBean(new ApiScenarioStepDetailRequest(), request);
if (StringUtils.isNotBlank(request.getCopyFromStepId())) {
// 从已有步骤复制则获取复制步骤的详情
ApiScenarioStep copyFromStep = apiScenarioStepMapper.selectByPrimaryKey(request.getCopyFromStepId());
getDetailRequest = BeanUtils.copyBean(new ApiScenarioStepDetailRequest(), copyFromStep);
}
Object stepDetail = getStepDetail(getDetailRequest);
List<String> uploadFileIds = uploadCopyStepLocalFiles(request, stepDetail);
result.setDetail(JSON.parseObject(JSON.toJSONString(stepDetail)));
result.setUploadIds(uploadFileIds);
return result;
}
throw new MSException("步骤已存在,请调用 /api/scenario/step/get/{stepId} 接口获取详情");
}
/**
* 复制的步骤将步骤中的文件上传
* 并替换文件ID
* @param request
* @param stepDetail
* @return
*/
private List<String> uploadCopyStepLocalFiles(ApiScenarioStepDetailRequest request, Object stepDetail) {
if (stepDetail instanceof AbstractMsTestElement stepMsTestElement) {
List<ApiFile> localFiles = apiCommonService.getApiFiles(stepMsTestElement)
.stream()
.filter(file -> BooleanUtils.isTrue(file.getLocal()))
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(localFiles)) {
List<String> uploadFileIds = new ArrayList<>();
String sourceDir = null;
String stepType = request.getStepType();
String resourceId = request.getResourceId();
if (StringUtils.isNotBlank(request.getCopyFromStepId())) {
// 从已有步骤复制则获取复制步骤的文件
ApiScenarioStep copyFromStep = apiScenarioStepMapper.selectByPrimaryKey(request.getCopyFromStepId());
sourceDir = DefaultRepositoryDir.getApiScenarioStepDir(copyFromStep.getProjectId(),
copyFromStep.getScenarioId(), copyFromStep.getId());
} else if (StringUtils.equals(stepType, ApiScenarioStepType.API.name())) {
// 获取接口定义相关的文件
ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(resourceId);
sourceDir = DefaultRepositoryDir.getApiDefinitionDir(apiDefinition.getProjectId(),
apiDefinition.getId());
} else if (StringUtils.equals(stepType, ApiScenarioStepType.API_CASE.name())) {
// 获取接口用例相关的文件
ApiTestCase apiTestCase = apiTestCaseMapper.selectByPrimaryKey(resourceId);
sourceDir = DefaultRepositoryDir.getApiCaseDir(apiTestCase.getProjectId(),
apiTestCase.getId());
}
// 复制的步骤将文件复制到临时目录并且保存新的文件ID
for (ApiFile localFile : localFiles) {
String newFileId = IDGenerator.nextStr();
String targetDir = DefaultRepositoryDir.getSystemTempDir();
// 复制文件到临时目录
apiFileResourceService.copyFile(sourceDir + "/" + localFile.getFileId(),
targetDir + "/" + newFileId,
localFile.getFileName());
localFile.setFileId(newFileId);
uploadFileIds.add(newFileId);
}
return uploadFileIds;
}
}
return List.of();
}
} }