feat(接口测试): 场景导出

This commit is contained in:
Jianguo-Genius 2024-10-15 15:05:00 +08:00 committed by Craftsman
parent 3b437fe180
commit 710d51a0bc
26 changed files with 560 additions and 99 deletions

View File

@ -55,7 +55,6 @@ public class LocalRepositoryDir {
public static String getSystemTempDir() { public static String getSystemTempDir() {
return SYSTEM_TEMP_DIR; return SYSTEM_TEMP_DIR;
} }
public static String getSystemCacheDir() { public static String getSystemCacheDir() {
return SYSTEM_CACHE_DIR; return SYSTEM_CACHE_DIR;
} }

View File

@ -4,8 +4,10 @@ import io.metersphere.sdk.dto.FileMetadataRepositoryDTO;
import io.metersphere.sdk.dto.FileModuleRepositoryDTO; import io.metersphere.sdk.dto.FileModuleRepositoryDTO;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
@Data @Data
@NoArgsConstructor
public class FileRequest { public class FileRequest {
private String folder; private String folder;
@ -22,6 +24,12 @@ public class FileRequest {
public void setGitFileRequest(FileModuleRepositoryDTO repository, FileMetadataRepositoryDTO file) { public void setGitFileRequest(FileModuleRepositoryDTO repository, FileMetadataRepositoryDTO file) {
gitFileRequest = new GitFileRequest(repository.getUrl(), repository.getToken(), repository.getUserName(), file.getBranch(), file.getCommitId()); gitFileRequest = new GitFileRequest(repository.getUrl(), repository.getToken(), repository.getUserName(), file.getBranch(), file.getCommitId());
} }
public FileRequest(String folder, String storage, String fileName) {
this.folder = folder;
this.storage = storage;
this.fileName = fileName;
}
} }
@Data @Data

View File

@ -1,5 +1,6 @@
package io.metersphere.sdk.util; package io.metersphere.sdk.util;
import io.metersphere.sdk.exception.MSException;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -138,4 +139,21 @@ public class TempFileUtils {
} }
return previewByte; return previewByte;
} }
public static void writeExportFile(String fileAbsoluteName, Object exportResponse) {
File createFile = new File(fileAbsoluteName);
if (!createFile.exists()) {
try {
createFile.getParentFile().mkdirs();
createFile.createNewFile();
} catch (IOException e) {
throw new MSException(e);
}
}
try {
FileUtils.writeByteArrayToFile(createFile, JSON.toJSONString(exportResponse).getBytes());
} catch (Exception e) {
LogUtils.error(e);
}
}
} }

View File

@ -0,0 +1,6 @@
package io.metersphere.api.constants;
public enum ApiScenarioExportType {
METERSPHERE_SIMPLE,
METERSPHERE_ALL_DATA
}

View File

@ -37,6 +37,7 @@ import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
@ -330,4 +331,20 @@ public class ApiScenarioController {
request.setOperator(SessionUtils.getUserId()); request.setOperator(SessionUtils.getUserId());
apiScenarioDataTransferService.importScenario(file, request); apiScenarioDataTransferService.importScenario(file, request);
} }
@GetMapping("/stop/{taskId}")
@Operation(summary = "接口测试-接口场景管理-导出-停止导出")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXPORT)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public void caseStopExport(@PathVariable String taskId) {
apiScenarioDataTransferService.stopExport(taskId, SessionUtils.getUserId());
}
@GetMapping(value = "/download/file/{projectId}/{fileId}")
@Operation(summary = "接口测试-接口场景管理-下载文件")
@RequiresPermissions(PermissionConstants.PROJECT_API_SCENARIO_EXPORT)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public void downloadImgById(@PathVariable String projectId, @PathVariable String fileId, HttpServletResponse httpServletResponse) {
apiScenarioDataTransferService.downloadFile(projectId, fileId, SessionUtils.getUserId(), httpServletResponse);
}
} }

View File

@ -9,7 +9,7 @@ import java.io.Serializable;
* @author wx * @author wx
*/ */
@Data @Data
public class ApiExportResponse implements Serializable { public class ApiDefinitionExportResponse implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -0,0 +1,21 @@
package io.metersphere.api.dto.export;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* @author wx
*/
@Data
public class ApiScenarioExportResponse implements Serializable {
private String organizationId;
private String projectId;
@Serial
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.export;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import lombok.Data;
@Data
public class ApiScenarioStepExportDTO extends ApiScenarioStep {
private AbstractMsTestElement stepComponent;
}

View File

@ -10,7 +10,7 @@ import java.util.List;
* @author wx * @author wx
*/ */
@Data @Data
public class MetersphereApiExportResponse extends ApiExportResponse { public class MetersphereApiDefinitionExportResponse extends ApiDefinitionExportResponse {
private List<ApiDefinitionExportDetail> apiDefinitions = new ArrayList<>(); private List<ApiDefinitionExportDetail> apiDefinitions = new ArrayList<>();

View File

@ -0,0 +1,65 @@
package io.metersphere.api.dto.export;
import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.api.domain.ApiScenarioCsv;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.scenario.ApiScenarioDetail;
import io.metersphere.api.dto.scenario.ApiScenarioStepDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author wx
*/
@Data
public class MetersphereApiScenarioExportResponse extends ApiScenarioExportResponse {
@Schema(description = "导出的场景")
private List<ApiScenarioDetail> exportScenarioList = new ArrayList<>();
@Schema(description = "场景CSV相关的数据")
private List<ApiScenarioCsv> apiScenarioCsvList = new ArrayList<>();
@Schema(description = "所有场景步骤")
private List<ApiScenarioStepDTO> scenarioStepList = new ArrayList<>();
@Schema(description = "所有场景步骤内容")
private Map<String, String> scenarioStepBlobMap = new HashMap<>();
@Schema(description = "有关联的接口定义")
private List<ApiDefinitionDetail> relatedApiDefinitions = new ArrayList<>();
@Schema(description = "有关联的接口用例")
private List<ApiTestCaseDTO> relatedApiTestCaseList = new ArrayList<>();
@Schema(description = "有关联的场景")
private List<ApiScenarioDetail> relatedScenarioList = new ArrayList<>();
public void addExportApi(ApiDefinitionDetail detail) {
relatedApiDefinitions.add(detail);
}
public void addExportApiCase(ApiTestCaseDTO apiCase) {
relatedApiTestCaseList.add(apiCase);
}
public void addExportScenario(ApiScenarioDetail apiScenarioDetail) {
exportScenarioList.add(apiScenarioDetail);
}
public void setStepTypeToCustomRequest() {
scenarioStepList.forEach(step -> {
if (StringUtils.equalsAnyIgnoreCase(step.getStepType(), ApiScenarioStepType.API.name(), ApiScenarioStepType.API_CASE.name())) {
step.setStepType(ApiScenarioStepType.CUSTOM_REQUEST.name());
}
});
}
}

View File

@ -9,7 +9,7 @@ import java.util.List;
* @author wx * @author wx
*/ */
@Data @Data
public class SwaggerApiExportResponse extends ApiExportResponse{ public class SwaggerApiDefinitionExportResponse extends ApiDefinitionExportResponse {
private String openapi; private String openapi;
private SwaggerInfo info; private SwaggerInfo info;

View File

@ -14,7 +14,7 @@ public class ApiScenarioImportRequest {
@NotBlank @NotBlank
private String projectId; private String projectId;
@Schema(description = "导入的类型 暂定 METERSPHERE JMETER") @Schema(description = "导入的类型 METERSPHERE/JMETER")
@NotBlank @NotBlank
private String type; private String type;

View File

@ -764,5 +764,4 @@
WHERE module_id = #{0} WHERE module_id = #{0}
AND deleted = false AND deleted = false
</select> </select>
</mapper> </mapper>

View File

@ -2,8 +2,8 @@ package io.metersphere.api.parser.api;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail; import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.ApiExportResponse; import io.metersphere.api.dto.export.ApiDefinitionExportResponse;
import io.metersphere.api.dto.export.MetersphereApiExportResponse; import io.metersphere.api.dto.export.MetersphereApiDefinitionExportResponse;
import io.metersphere.api.dto.mockserver.MockMatchRule; import io.metersphere.api.dto.mockserver.MockMatchRule;
import io.metersphere.api.dto.mockserver.MockResponse; import io.metersphere.api.dto.mockserver.MockResponse;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
@ -18,12 +18,12 @@ import java.util.stream.Collectors;
public class MetersphereExportParser { public class MetersphereExportParser {
public ApiExportResponse parse(List<ApiDefinitionWithBlob> apiDefinitionList, List<ApiTestCaseWithBlob> apiTestCaseList, List<ApiMockWithBlob> apiMockList, Map<String, String> moduleMap) { public ApiDefinitionExportResponse parse(List<ApiDefinitionWithBlob> apiDefinitionList, List<ApiTestCaseWithBlob> apiTestCaseList, List<ApiMockWithBlob> apiMockList, Map<String, String> moduleMap) {
Map<String, List<ApiTestCaseWithBlob>> apiTestCaseMap = apiTestCaseList.stream().collect(Collectors.groupingBy(ApiTestCaseWithBlob::getApiDefinitionId)); Map<String, List<ApiTestCaseWithBlob>> apiTestCaseMap = apiTestCaseList.stream().collect(Collectors.groupingBy(ApiTestCaseWithBlob::getApiDefinitionId));
Map<String, List<ApiMockWithBlob>> apiMockMap = apiMockList.stream().collect(Collectors.groupingBy(ApiMockWithBlob::getApiDefinitionId)); Map<String, List<ApiMockWithBlob>> apiMockMap = apiMockList.stream().collect(Collectors.groupingBy(ApiMockWithBlob::getApiDefinitionId));
MetersphereApiExportResponse response = new MetersphereApiExportResponse(); MetersphereApiDefinitionExportResponse response = new MetersphereApiDefinitionExportResponse();
for (ApiDefinitionWithBlob blob : apiDefinitionList) { for (ApiDefinitionWithBlob blob : apiDefinitionList) {
ApiDefinitionExportDetail detail = new ApiDefinitionExportDetail(); ApiDefinitionExportDetail detail = new ApiDefinitionExportDetail();
if (blob.getRequest() != null) { if (blob.getRequest() != null) {

View File

@ -24,12 +24,12 @@ import org.springframework.http.MediaType;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
public class Swagger3ExportParser implements ExportParser<ApiExportResponse> { public class Swagger3ExportParser implements ExportParser<ApiDefinitionExportResponse> {
@Override @Override
public ApiExportResponse parse(List<ApiDefinitionWithBlob> list, Project project, Map<String, String> moduleMap) throws Exception { public ApiDefinitionExportResponse parse(List<ApiDefinitionWithBlob> list, Project project, Map<String, String> moduleMap) throws Exception {
SwaggerApiExportResponse response = new SwaggerApiExportResponse(); SwaggerApiDefinitionExportResponse response = new SwaggerApiDefinitionExportResponse();
//openapi //openapi
response.setOpenapi("3.0.2"); response.setOpenapi("3.0.2");
//info //info

View File

@ -7,7 +7,7 @@ import io.metersphere.api.dto.converter.ApiDefinitionImportDataAnalysisResult;
import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult; import io.metersphere.api.dto.converter.ApiDefinitionImportFileParseResult;
import io.metersphere.api.dto.definition.ApiDefinitionMockDTO; import io.metersphere.api.dto.definition.ApiDefinitionMockDTO;
import io.metersphere.api.dto.definition.ApiTestCaseDTO; import io.metersphere.api.dto.definition.ApiTestCaseDTO;
import io.metersphere.api.dto.export.MetersphereApiExportResponse; import io.metersphere.api.dto.export.MetersphereApiDefinitionExportResponse;
import io.metersphere.api.dto.request.ImportRequest; import io.metersphere.api.dto.request.ImportRequest;
import io.metersphere.api.parser.ApiDefinitionImportParser; import io.metersphere.api.parser.ApiDefinitionImportParser;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
@ -29,9 +29,9 @@ public class MetersphereParserApiDefinition implements ApiDefinitionImportParser
@Override @Override
public ApiDefinitionImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception { public ApiDefinitionImportFileParseResult parse(InputStream source, ImportRequest request) throws Exception {
MetersphereApiExportResponse metersphereApiExportResponse = null; MetersphereApiDefinitionExportResponse metersphereApiExportResponse = null;
try { try {
metersphereApiExportResponse = ApiDataUtils.parseObject(source, MetersphereApiExportResponse.class); metersphereApiExportResponse = ApiDataUtils.parseObject(source, MetersphereApiDefinitionExportResponse.class);
} catch (Exception e) { } catch (Exception e) {
LogUtils.error(e.getMessage(), e); LogUtils.error(e.getMessage(), e);
throw new MSException(e.getMessage()); throw new MSException(e.getMessage());

View File

@ -1,23 +1,35 @@
package io.metersphere.api.service; package io.metersphere.api.service;
import io.metersphere.api.constants.ApiScenarioExportType;
import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.api.domain.*; import io.metersphere.api.domain.*;
import io.metersphere.api.dto.ApiFile; import io.metersphere.api.dto.ApiFile;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult; import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult;
import io.metersphere.api.dto.definition.ApiScenarioBatchExportRequest; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.ApiScenarioExportResponse;
import io.metersphere.api.dto.export.MetersphereApiScenarioExportResponse;
import io.metersphere.api.dto.scenario.*; import io.metersphere.api.dto.scenario.*;
import io.metersphere.api.mapper.*; import io.metersphere.api.mapper.*;
import io.metersphere.api.parser.ApiScenarioImportParser; import io.metersphere.api.parser.ApiScenarioImportParser;
import io.metersphere.api.parser.ImportParserFactory; import io.metersphere.api.parser.ImportParserFactory;
import io.metersphere.api.service.definition.ApiDefinitionExportService;
import io.metersphere.api.service.scenario.ApiScenarioLogService;
import io.metersphere.api.service.scenario.ApiScenarioModuleService; import io.metersphere.api.service.scenario.ApiScenarioModuleService;
import io.metersphere.api.service.scenario.ApiScenarioService; import io.metersphere.api.service.scenario.ApiScenarioService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.ApiDefinitionImportUtils; import io.metersphere.api.utils.ApiDefinitionImportUtils;
import io.metersphere.api.utils.ApiScenarioImportUtils; import io.metersphere.api.utils.ApiScenarioImportUtils;
import io.metersphere.functional.domain.ExportTask;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.domain.Project; import io.metersphere.project.domain.Project;
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
import io.metersphere.project.mapper.ProjectMapper; import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.project.utils.FileDownloadUtils;
import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.dto.ExportMsgDTO;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.*; import io.metersphere.sdk.util.*;
import io.metersphere.system.constants.ExportConstants; import io.metersphere.system.constants.ExportConstants;
import io.metersphere.system.domain.User; import io.metersphere.system.domain.User;
@ -30,9 +42,13 @@ import io.metersphere.system.manager.ExportTaskManager;
import io.metersphere.system.mapper.UserMapper; import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.service.CommonNoticeSendService; import io.metersphere.system.service.CommonNoticeSendService;
import io.metersphere.system.service.FileService;
import io.metersphere.system.service.NoticeSendService;
import io.metersphere.system.socket.ExportWebSocketHandler;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.TreeNodeParseUtils; import io.metersphere.system.utils.TreeNodeParseUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils; import org.apache.commons.collections4.ListUtils;
@ -46,6 +62,8 @@ 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.io.File;
import java.io.InputStream;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -56,6 +74,12 @@ import static io.metersphere.project.utils.NodeSortUtils.DEFAULT_NODE_INTERVAL_P
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public class ApiScenarioDataTransferService { public class ApiScenarioDataTransferService {
private final ThreadLocal<Long> currentApiScenarioOrder = new ThreadLocal<>();
private final ThreadLocal<Long> currentModuleOrder = new ThreadLocal<>();
private static final String EXPORT_CASE_TMP_DIR = "apiScenario";
@Resource @Resource
private ProjectMapper projectMapper; private ProjectMapper projectMapper;
@Resource @Resource
@ -66,7 +90,13 @@ public class ApiScenarioDataTransferService {
private ExportTaskManager exportTaskManager; private ExportTaskManager exportTaskManager;
@Resource @Resource
private ExtApiScenarioMapper extApiScenarioMapper; private ExtApiScenarioMapper extApiScenarioMapper;
@Resource
private ExtApiDefinitionMapper extApiDefinitionMapper;
@Resource
private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource
private FileService fileService;
@Resource @Resource
private CommonNoticeSendService commonNoticeSendService; private CommonNoticeSendService commonNoticeSendService;
@Resource @Resource
@ -79,17 +109,17 @@ public class ApiScenarioDataTransferService {
private SqlSessionFactory sqlSessionFactory; private SqlSessionFactory sqlSessionFactory;
@Resource @Resource
private OperationLogService operationLogService; private OperationLogService operationLogService;
@Resource
private final ThreadLocal<Long> currentApiScenarioOrder = new ThreadLocal<>(); private NoticeSendService noticeSendService;
@Resource
private final ThreadLocal<Long> currentModuleOrder = new ThreadLocal<>(); private ApiScenarioLogService apiScenarioLogService;
@Resource
private ApiDefinitionExportService apiDefinitionExportService;
public String exportScenario(ApiScenarioBatchExportRequest request, String type, String userId) { public String exportScenario(ApiScenarioBatchExportRequest request, String type, String userId) {
String returnId; String returnId;
try { try {
exportTaskManager.exportCheck(request.getProjectId(), ExportConstants.ExportType.API_SCENARIO.toString(), userId); exportTaskManager.exportCheck(request.getProjectId(), ExportConstants.ExportType.API_SCENARIO.toString(), userId);
returnId = exportTaskManager.exportAsyncTask( returnId = exportTaskManager.exportAsyncTask(
request.getProjectId(), request.getProjectId(),
request.getFileId(), userId, request.getFileId(), userId,
@ -323,7 +353,7 @@ public class ApiScenarioDataTransferService {
module.setParentId(t.getParentId()); module.setParentId(t.getParentId());
module.setProjectId(projectId); module.setProjectId(projectId);
module.setCreateUser(operator); module.setCreateUser(operator);
module.setPos(getImportNextModuleOrder(apiScenarioModuleService::getNextOrder, projectId)); module.setPos(getImportNextOrder(apiScenarioModuleService::getNextOrder, currentModuleOrder, projectId));
module.setCreateTime(System.currentTimeMillis()); module.setCreateTime(System.currentTimeMillis());
module.setUpdateUser(operator); module.setUpdateUser(operator);
module.setUpdateTime(module.getCreateTime()); module.setUpdateTime(module.getCreateTime());
@ -352,7 +382,7 @@ public class ApiScenarioDataTransferService {
ApiScenario scenario = new ApiScenario(); ApiScenario scenario = new ApiScenario();
BeanUtils.copyBean(scenario, t); BeanUtils.copyBean(scenario, t);
scenario.setNum(apiScenarioService.getNextNum(projectId)); scenario.setNum(apiScenarioService.getNextNum(projectId));
scenario.setPos(getImportNextModuleOrder(apiScenarioService::getNextOrder, projectId)); scenario.setPos(getImportNextOrder(apiScenarioService::getNextOrder, currentApiScenarioOrder, projectId));
scenario.setLatest(true); scenario.setLatest(true);
scenario.setCreateUser(operator); scenario.setCreateUser(operator);
scenario.setUpdateUser(operator); scenario.setUpdateUser(operator);
@ -479,13 +509,13 @@ public class ApiScenarioDataTransferService {
} }
} }
private Long getImportNextModuleOrder(Function<String, Long> subFunc, String projectId) { private Long getImportNextOrder(Function<String, Long> subFunc, ThreadLocal<Long> nextOrder, String projectId) {
Long order = currentModuleOrder.get(); Long order = nextOrder.get();
if (order == null) { if (order == null) {
order = subFunc.apply(projectId); order = subFunc.apply(projectId);
} }
order = order + DEFAULT_NODE_INTERVAL_POS; order = order + DEFAULT_NODE_INTERVAL_POS;
currentModuleOrder.set(order); nextOrder.set(order);
return order; return order;
} }
@ -542,8 +572,167 @@ public class ApiScenarioDataTransferService {
return analysisResult; return analysisResult;
} }
private String exportApiScenarioZip(ApiScenarioBatchExportRequest request, String type, String userId) { private String exportApiScenarioZip(ApiScenarioBatchExportRequest request, String exportType, String userId) throws Exception {
// todo 场景导出 File tmpDir = new File(LocalRepositoryDir.getSystemTempDir() + File.separatorChar + EXPORT_CASE_TMP_DIR + "_" + IDGenerator.nextStr());
if (!tmpDir.mkdirs()) {
throw new MSException(Translator.get("upload_fail"));
}
String fileType = "zip";
try {
User user = userMapper.selectByPrimaryKey(userId);
noticeSendService.setLanguage(user.getLanguage());
//获取导出的ids集合
List<String> ids = apiScenarioService.doSelectIds(request, false);
if (CollectionUtils.isEmpty(ids)) {
return null; return null;
} }
Map<String, String> moduleMap = this.apiScenarioModuleService.getTree(request.getProjectId()).stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath));
String fileFolder = tmpDir.getPath() + File.separatorChar + request.getFileId();
int fileIndex = 1;
SubListUtils.dealForSubList(ids, 500, subList -> {
request.setSelectIds(subList);
ApiScenarioExportResponse exportResponse = this.genMetersphereExportResponse(request, moduleMap, exportType, userId);
TempFileUtils.writeExportFile(fileFolder + File.separatorChar + "scenario_export_" + fileIndex + ".ms", exportResponse);
});
File zipFile = MsFileUtils.zipFile(tmpDir.getPath(), request.getFileId());
if (zipFile == null) {
return null;
}
fileService.upload(zipFile, new FileRequest(DefaultRepositoryDir.getExportApiTempDir(), StorageType.MINIO.name(), request.getFileId()));
// 生成日志
LogDTO logDTO = apiScenarioLogService.exportExcelLog(request.getFileId(), exportType, userId, projectMapper.selectByPrimaryKey(request.getProjectId()));
operationLogService.add(logDTO);
String taskId = exportTaskManager.getExportTaskId(request.getProjectId(), ExportConstants.ExportType.API_SCENARIO.name(), ExportConstants.ExportState.PREPARED.toString(), userId, null, fileType);
ExportWebSocketHandler.sendMessageSingle(
new ExportMsgDTO(request.getFileId(), taskId, ids.size(), true, MsgType.EXEC_RESULT.name())
);
} catch (Exception e) {
LogUtils.error(e);
List<ExportTask> exportTasks = exportTaskManager.getExportTasks(request.getProjectId(), ExportConstants.ExportType.API_SCENARIO.name(), ExportConstants.ExportState.PREPARED.toString(), userId, null);
if (CollectionUtils.isNotEmpty(exportTasks)) {
exportTaskManager.updateExportTask(ExportConstants.ExportState.ERROR.name(), exportTasks.getFirst().getId(), fileType);
}
ExportMsgDTO exportMsgDTO = new ExportMsgDTO(request.getFileId(), "", 0, false, MsgType.EXEC_RESULT.name());
ExportWebSocketHandler.sendMessageSingle(exportMsgDTO);
throw new MSException(e);
} finally {
MsFileUtils.deleteDir(tmpDir.getPath());
}
return null;
}
private ApiScenarioExportResponse genMetersphereExportResponse(ApiScenarioBatchExportRequest request, Map<String, String> moduleMap, String exportType, String userId) {
Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
MetersphereApiScenarioExportResponse response = apiScenarioService.selectAndSortScenarioDetailWithIds(request.getSelectIds());
response.setProjectId(project.getId());
response.setOrganizationId(project.getOrganizationId());
if (StringUtils.equalsIgnoreCase(ApiScenarioExportType.METERSPHERE_ALL_DATA.name(), exportType)) {
// 全量导出导出引用的apiapiCase
List<String> apiDefinitionIdList = new ArrayList<>();
List<String> apiCaseIdList = new ArrayList<>();
response.getScenarioStepList().forEach(step -> {
if (StringUtils.isNotBlank(step.getResourceId())) {
if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API.name())) {
if (!apiDefinitionIdList.contains(step.getResourceId())) {
apiDefinitionIdList.add(step.getResourceId());
}
} else if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API_CASE.name())) {
if (!apiCaseIdList.contains(step.getResourceId())) {
apiCaseIdList.add(step.getResourceId());
}
}
}
});
List<ApiTestCaseWithBlob> apiTestCaseWithBlobs = extApiTestCaseMapper.selectAllDetailByApiIds(apiCaseIdList);
if (CollectionUtils.isNotEmpty(apiCaseIdList)) {
apiTestCaseWithBlobs.forEach(item -> {
if (!apiDefinitionIdList.contains(item.getApiDefinitionId())) {
apiDefinitionIdList.add(item.getApiDefinitionId());
}
});
}
Map<String, Map<String, String>> projectApiModuleIdMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(apiDefinitionIdList)) {
List<ApiDefinitionWithBlob> apiList = extApiDefinitionMapper.selectApiDefinitionWithBlob(apiDefinitionIdList);
List<String> projectIdList = apiList.stream().map(ApiDefinitionWithBlob::getProjectId).distinct().toList();
projectIdList.forEach(projectId -> projectApiModuleIdMap.put(projectId, apiDefinitionExportService.buildModuleIdPathMap(request.getProjectId())));
for (ApiDefinitionWithBlob blob : apiList) {
ApiDefinitionExportDetail detail = new ApiDefinitionExportDetail();
if (blob.getRequest() != null) {
detail.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class));
}
if (blob.getResponse() != null) {
detail.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class));
}
detail.setName(blob.getName());
detail.setProtocol(blob.getProtocol());
detail.setMethod(blob.getMethod());
detail.setPath(blob.getPath());
detail.setStatus(blob.getStatus());
detail.setTags(blob.getTags());
detail.setPos(blob.getPos());
detail.setDescription(blob.getDescription());
String moduleName;
if (StringUtils.equals(blob.getModuleId(), ModuleConstants.DEFAULT_NODE_ID)) {
moduleName = Translator.get("api_unplanned_request");
} else if (projectApiModuleIdMap.containsKey(detail.getProjectId()) && projectApiModuleIdMap.get(detail.getProjectId()).containsKey(blob.getModuleId())) {
moduleName = projectApiModuleIdMap.get(detail.getProjectId()).get(blob.getModuleId());
} else {
detail.setModuleId(ModuleConstants.DEFAULT_NODE_ID);
moduleName = Translator.get("api_unplanned_request");
}
detail.setModulePath(moduleName);
response.addExportApi(detail);
}
}
if (CollectionUtils.isNotEmpty(apiTestCaseWithBlobs)) {
for (ApiTestCaseWithBlob apiTestCaseWithBlob : apiTestCaseWithBlobs) {
ApiTestCaseDTO dto = new ApiTestCaseDTO();
dto.setName(apiTestCaseWithBlob.getName());
dto.setPriority(apiTestCaseWithBlob.getPriority());
dto.setStatus(apiTestCaseWithBlob.getStatus());
dto.setTags(apiTestCaseWithBlob.getTags());
dto.setRequest(ApiDataUtils.parseObject(new String(apiTestCaseWithBlob.getRequest()), AbstractMsTestElement.class));
response.addExportApiCase(dto);
}
}
} else {
// 普通导出,所有的引用都改为复制并且ApiApiCase改为CUSTOM_REQUEST
response.setStepTypeToCustomRequest();
}
return response;
}
public void stopExport(String taskId, String userId) {
exportTaskManager.sendStopMessage(taskId, userId);
}
public void downloadFile(String projectId, String fileId, String userId, HttpServletResponse httpServletResponse) {
List<ExportTask> exportTasks = exportTaskManager.getExportTasks(projectId, ExportConstants.ExportType.API_SCENARIO.name(), ExportConstants.ExportState.SUCCESS.toString(), userId, fileId);
if (CollectionUtils.isEmpty(exportTasks)) {
return;
}
ExportTask tasksFirst = exportTasks.getFirst();
Project project = projectMapper.selectByPrimaryKey(projectId);
FileRequest fileRequest = new FileRequest();
fileRequest.setFileName(tasksFirst.getFileId());
fileRequest.setFolder(DefaultRepositoryDir.getExportApiTempDir());
fileRequest.setStorage(StorageType.MINIO.name());
String fileName = "Metersphere_scenario_" + project.getName() + "." + tasksFirst.getFileType();
try {
InputStream fileInputStream = fileService.getFileAsStream(fileRequest);
FileDownloadUtils.zipFilesWithResponse(fileName, fileInputStream, httpServletResponse);
} catch (Exception e) {
throw new MSException("get file error");
}
}
} }

View File

@ -1,8 +1,7 @@
package io.metersphere.api.service.definition; package io.metersphere.api.service.definition;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.ApiExportResponse; import io.metersphere.api.dto.export.ApiDefinitionExportResponse;
import io.metersphere.api.mapper.ApiDefinitionModuleMapper;
import io.metersphere.api.mapper.ExtApiDefinitionMapper; import io.metersphere.api.mapper.ExtApiDefinitionMapper;
import io.metersphere.api.mapper.ExtApiDefinitionMockMapper; import io.metersphere.api.mapper.ExtApiDefinitionMockMapper;
import io.metersphere.api.mapper.ExtApiTestCaseMapper; import io.metersphere.api.mapper.ExtApiTestCaseMapper;
@ -31,13 +30,10 @@ import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -52,12 +48,9 @@ import java.util.stream.Collectors;
@Service @Service
public class ApiDefinitionExportService { public class ApiDefinitionExportService {
@Resource @Resource
private ExtApiDefinitionMapper extApiDefinitionMapper; private ExtApiDefinitionMapper extApiDefinitionMapper;
@Resource @Resource
private ApiDefinitionModuleMapper apiDefinitionModuleMapper;
@Resource
private ExtApiTestCaseMapper extApiTestCaseMapper; private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource @Resource
private ExtApiDefinitionMockMapper extApiDefinitionMockMapper; private ExtApiDefinitionMockMapper extApiDefinitionMockMapper;
@ -75,10 +68,12 @@ public class ApiDefinitionExportService {
private OperationLogService operationLogService; private OperationLogService operationLogService;
@Resource @Resource
private ApiDefinitionImportService apiDefinitionImportService; private ApiDefinitionImportService apiDefinitionImportService;
@Resource
private FileService fileService;
private static final String EXPORT_CASE_TMP_DIR = "tmp"; private static final String EXPORT_CASE_TMP_DIR = "apiDefinition";
private Map<String, String> buildModuleIdPathMap(String projectId) { public Map<String, String> buildModuleIdPathMap(String projectId) {
List<BaseTreeNode> apiModules = apiDefinitionImportService.buildTreeData(projectId, null); List<BaseTreeNode> apiModules = apiDefinitionImportService.buildTreeData(projectId, null);
Map<String, String> modulePathMap = new HashMap<>(); Map<String, String> modulePathMap = new HashMap<>();
apiModules.forEach(item -> { apiModules.forEach(item -> {
@ -87,12 +82,12 @@ public class ApiDefinitionExportService {
return modulePathMap; return modulePathMap;
} }
public ApiExportResponse genApiExportResponse(ApiDefinitionBatchExportRequest request, Map<String, String> moduleMap, String type, String userId) { public ApiDefinitionExportResponse genApiExportResponse(ApiDefinitionBatchExportRequest request, Map<String, String> moduleMap, String type, String userId) {
List<ApiDefinitionWithBlob> list = this.selectAndSortByIds(request.getSelectIds()); List<ApiDefinitionWithBlob> list = this.selectAndSortByIds(request.getSelectIds());
return switch (type.toLowerCase()) { return switch (type.toLowerCase()) {
case "swagger" -> exportSwagger(request, list, moduleMap); case "swagger" -> exportSwagger(request, list, moduleMap);
case "metersphere" -> exportMetersphere(request, list, moduleMap); case "metersphere" -> exportMetersphere(request, list, moduleMap);
default -> new ApiExportResponse(); default -> new ApiDefinitionExportResponse();
}; };
} }
@ -118,31 +113,17 @@ public class ApiDefinitionExportService {
return returnId; return returnId;
} }
@Resource
private FileService fileService;
public void uploadFileToMinioExportFolder(File file, String fileName) throws Exception {
FileRequest fileRequest = new FileRequest();
fileRequest.setFileName(fileName);
fileRequest.setFolder(DefaultRepositoryDir.getExportApiTempDir());
fileRequest.setStorage(StorageType.MINIO.name());
try (FileInputStream inputStream = new FileInputStream(file)) {
fileService.upload(inputStream, fileRequest);
}
}
public String exportApiDefinitionZip(ApiDefinitionBatchExportRequest request, String exportType, String userId) throws Exception { public String exportApiDefinitionZip(ApiDefinitionBatchExportRequest request, String exportType, String userId) throws Exception {
// 为避免客户端未及时开启ws此处延迟1s // 为避免客户端未及时开启ws此处延迟1s
Thread.sleep(1000); Thread.sleep(1000);
File tmpDir = null; File tmpDir = new File(LocalRepositoryDir.getSystemTempDir() + File.separatorChar + EXPORT_CASE_TMP_DIR + "_" + IDGenerator.nextStr());
String fileType = ""; if (!tmpDir.mkdirs()) {
throw new MSException(Translator.get("upload_fail"));
}
String fileType = "zip";
try { try {
User user = userMapper.selectByPrimaryKey(userId); User user = userMapper.selectByPrimaryKey(userId);
noticeSendService.setLanguage(user.getLanguage()); noticeSendService.setLanguage(user.getLanguage());
tmpDir = new File(LocalRepositoryDir.getSystemTempDir() + File.separatorChar + EXPORT_CASE_TMP_DIR + "_" + IDGenerator.nextStr());
if (!tmpDir.exists() && !tmpDir.mkdirs()) {
throw new MSException(Translator.get("upload_fail"));
}
//获取导出的ids集合 //获取导出的ids集合
List<String> ids = this.getBatchExportApiIds(request, request.getProjectId(), userId); List<String> ids = this.getBatchExportApiIds(request, request.getProjectId(), userId);
if (CollectionUtils.isEmpty(ids)) { if (CollectionUtils.isEmpty(ids)) {
@ -155,44 +136,23 @@ public class ApiDefinitionExportService {
int fileIndex = 1; int fileIndex = 1;
SubListUtils.dealForSubList(ids, 1000, subList -> { SubListUtils.dealForSubList(ids, 1000, subList -> {
request.setSelectIds(subList); request.setSelectIds(subList);
ApiExportResponse exportResponse = this.genApiExportResponse(request, moduleMap, exportType, userId); ApiDefinitionExportResponse exportResponse = this.genApiExportResponse(request, moduleMap, exportType, userId);
File createFile = new File(fileFolder + File.separatorChar + fileIndex + ".json"); TempFileUtils.writeExportFile(fileFolder + File.separatorChar + fileIndex + ".json", exportResponse);
if (!createFile.exists()) {
try {
createFile.getParentFile().mkdirs();
createFile.createNewFile();
} catch (IOException e) {
throw new MSException(e);
}
}
try {
FileUtils.writeByteArrayToFile(createFile, JSON.toJSONString(exportResponse).getBytes());
} catch (Exception e) {
LogUtils.error(e);
}
}); });
File zipFile = MsFileUtils.zipFile(tmpDir.getPath(), request.getFileId()); File zipFile = MsFileUtils.zipFile(tmpDir.getPath(), request.getFileId());
if (zipFile == null) { if (zipFile == null) {
return null; return null;
} }
fileService.upload(zipFile, new FileRequest(DefaultRepositoryDir.getExportApiTempDir(), StorageType.MINIO.name(), request.getFileId()));
uploadFileToMinioExportFolder(zipFile, request.getFileId());
// 生成日志 // 生成日志
LogDTO logDTO = apiDefinitionLogService.exportExcelLog(request, exportType, userId, projectMapper.selectByPrimaryKey(request.getProjectId()).getOrganizationId()); LogDTO logDTO = apiDefinitionLogService.exportExcelLog(request, exportType, userId, projectMapper.selectByPrimaryKey(request.getProjectId()).getOrganizationId());
operationLogService.add(logDTO); operationLogService.add(logDTO);
List<ExportTask> exportTasks = exportTaskManager.getExportTasks(request.getProjectId(), ExportConstants.ExportType.API_DEFINITION.name(), ExportConstants.ExportState.PREPARED.toString(), userId, null); String taskId = exportTaskManager.getExportTaskId(request.getProjectId(), ExportConstants.ExportType.API_DEFINITION.name(), ExportConstants.ExportState.PREPARED.toString(), userId, null, fileType);
String taskId; ExportWebSocketHandler.sendMessageSingle(
if (CollectionUtils.isNotEmpty(exportTasks)) { new ExportMsgDTO(request.getFileId(), taskId, ids.size(), true, MsgType.EXEC_RESULT.name())
taskId = exportTasks.getFirst().getId(); );
exportTaskManager.updateExportTask(ExportConstants.ExportState.SUCCESS.name(), taskId, fileType);
} else {
taskId = MsgType.CONNECT.name();
}
ExportMsgDTO exportMsgDTO = new ExportMsgDTO(request.getFileId(), taskId, ids.size(), true, MsgType.EXEC_RESULT.name());
ExportWebSocketHandler.sendMessageSingle(exportMsgDTO);
} catch (Exception e) { } catch (Exception e) {
LogUtils.error(e); LogUtils.error(e);
List<ExportTask> exportTasks = exportTaskManager.getExportTasks(request.getProjectId(), ExportConstants.ExportType.API_DEFINITION.name(), ExportConstants.ExportState.PREPARED.toString(), userId, null); List<ExportTask> exportTasks = exportTaskManager.getExportTasks(request.getProjectId(), ExportConstants.ExportType.API_DEFINITION.name(), ExportConstants.ExportState.PREPARED.toString(), userId, null);
@ -231,7 +191,7 @@ public class ApiDefinitionExportService {
} }
} }
private ApiExportResponse exportSwagger(ApiDefinitionBatchRequest request, List<ApiDefinitionWithBlob> list, Map<String, String> moduleMap) { private ApiDefinitionExportResponse exportSwagger(ApiDefinitionBatchRequest request, List<ApiDefinitionWithBlob> list, Map<String, String> moduleMap) {
Project project = projectMapper.selectByPrimaryKey(request.getProjectId()); Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
Swagger3ExportParser swagger3Parser = new Swagger3ExportParser(); Swagger3ExportParser swagger3Parser = new Swagger3ExportParser();
try { try {
@ -241,7 +201,7 @@ public class ApiDefinitionExportService {
} }
} }
private ApiExportResponse exportMetersphere(ApiDefinitionBatchExportRequest request, List<ApiDefinitionWithBlob> list, Map<String, String> moduleMap) { private ApiDefinitionExportResponse exportMetersphere(ApiDefinitionBatchExportRequest request, List<ApiDefinitionWithBlob> list, Map<String, String> moduleMap) {
try { try {
List<String> apiIds = list.stream().map(ApiDefinitionWithBlob::getId).toList(); List<String> apiIds = list.stream().map(ApiDefinitionWithBlob::getId).toList();
List<ApiTestCaseWithBlob> apiTestCaseWithBlobs = new ArrayList<>(); List<ApiTestCaseWithBlob> apiTestCaseWithBlobs = new ArrayList<>();
@ -273,7 +233,7 @@ public class ApiDefinitionExportService {
fileRequest.setFileName(tasksFirst.getFileId()); fileRequest.setFileName(tasksFirst.getFileId());
fileRequest.setFolder(DefaultRepositoryDir.getExportApiTempDir()); fileRequest.setFolder(DefaultRepositoryDir.getExportApiTempDir());
fileRequest.setStorage(StorageType.MINIO.name()); fileRequest.setStorage(StorageType.MINIO.name());
String fileName = "Metersphere_case_" + project.getName() + "." + tasksFirst.getFileType(); String fileName = "Metersphere_api_" + project.getName() + "." + tasksFirst.getFileType();
try { try {
InputStream fileInputStream = fileService.getFileAsStream(fileRequest); InputStream fileInputStream = fileService.getFileAsStream(fileRequest);
FileDownloadUtils.zipFilesWithResponse(fileName, fileInputStream, httpServletResponse); FileDownloadUtils.zipFilesWithResponse(fileName, fileInputStream, httpServletResponse);

View File

@ -18,6 +18,7 @@ import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO; import io.metersphere.system.log.dto.LogDTO;
import io.metersphere.system.log.service.OperationLogService; import io.metersphere.system.log.service.OperationLogService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -233,4 +234,19 @@ public class ApiScenarioLogService {
); );
operationLogService.batchAdd(logs); operationLogService.batchAdd(logs);
} }
public LogDTO exportExcelLog(String sourceId, String exportType, String userId, @NotNull Project project) {
LogDTO dto = new LogDTO(
project.getId(),
project.getOrganizationId(),
sourceId,
userId,
OperationLogType.EXPORT.name(),
OperationLogModule.API_SCENARIO_MANAGEMENT_SCENARIO,
"");
dto.setHistory(true);
dto.setPath("/api/scenario/export/" + exportType);
dto.setMethod(HttpMethodConstants.POST.name());
return dto;
}
} }

View File

@ -8,6 +8,7 @@ import io.metersphere.api.dto.*;
import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest;
import io.metersphere.api.dto.definition.ExecutePageRequest; import io.metersphere.api.dto.definition.ExecutePageRequest;
import io.metersphere.api.dto.definition.ExecuteReportDTO; import io.metersphere.api.dto.definition.ExecuteReportDTO;
import io.metersphere.api.dto.export.MetersphereApiScenarioExportResponse;
import io.metersphere.api.dto.request.ApiTransferRequest; import io.metersphere.api.dto.request.ApiTransferRequest;
import io.metersphere.api.dto.request.controller.MsScriptElement; import io.metersphere.api.dto.request.controller.MsScriptElement;
import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.api.dto.request.http.MsHTTPElement;
@ -23,6 +24,7 @@ import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.api.service.definition.ApiTestCaseService; import io.metersphere.api.service.definition.ApiTestCaseService;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.ApiScenarioBatchOperationUtils; import io.metersphere.api.utils.ApiScenarioBatchOperationUtils;
import io.metersphere.api.utils.ApiScenarioUtils;
import io.metersphere.functional.domain.FunctionalCaseTestExample; import io.metersphere.functional.domain.FunctionalCaseTestExample;
import io.metersphere.functional.mapper.FunctionalCaseTestMapper; import io.metersphere.functional.mapper.FunctionalCaseTestMapper;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -64,6 +66,7 @@ import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.ScheduleUtils; import io.metersphere.system.utils.ScheduleUtils;
import io.metersphere.system.utils.ServiceUtils; import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
@ -2463,4 +2466,50 @@ public class ApiScenarioService extends MoveNodeService {
setParamFunc.accept(apiStepResourceInfo, resource); setParamFunc.accept(apiStepResourceInfo, resource);
return apiStepResourceInfo; return apiStepResourceInfo;
} }
public MetersphereApiScenarioExportResponse selectAndSortScenarioDetailWithIds(@Valid List<@NotBlank(message = "{id must not be blank}") String> scenarioIds) {
MetersphereApiScenarioExportResponse response = new MetersphereApiScenarioExportResponse();
// 数据准备
{
ApiScenarioExample scenarioExample = new ApiScenarioExample();
scenarioExample.createCriteria().andIdIn(scenarioIds);
List<ApiScenario> exportApiScenarios = apiScenarioMapper.selectByExample(scenarioExample);
// 获取所有步骤包含引用的场景
List<ApiScenarioStepDTO> allSteps = getAllStepsByScenarioIds(scenarioIds)
.stream()
.distinct() // 这里可能存在多次引用相同场景步骤可能会重复去重
.collect(Collectors.toList());
//查询引用的场景ID
List<String> stepScenarioIds = allSteps.stream().filter(step -> isScenarioStep(step.getStepType())).map(ApiScenarioStepDTO::getResourceId).toList();
// 查询所有场景的blob
List<String> allScenarioIds = new ArrayList<>(scenarioIds);
allScenarioIds.addAll(stepScenarioIds);
ApiScenarioBlobExample scenarioBlobExample = new ApiScenarioBlobExample();
scenarioBlobExample.createCriteria().andIdIn(allScenarioIds);
Map<String, ApiScenarioBlob> scenarioBlobMap = apiScenarioBlobMapper.selectByExampleWithBLOBs(scenarioBlobExample).stream().collect(Collectors.toMap(ApiScenarioBlob::getId, Function.identity()));
// 场景CSV相关的信息
ApiScenarioCsvExample apiScenarioCsvExample = new ApiScenarioCsvExample();
apiScenarioCsvExample.createCriteria().andScenarioIdIn(allScenarioIds);
response.setApiScenarioCsvList(apiScenarioCsvMapper.selectByExample(apiScenarioCsvExample));
// 导出的场景
response.setExportScenarioList(ApiScenarioUtils.parseApiScenarioDetail(exportApiScenarios, scenarioBlobMap));
//步骤引用的场景
if (CollectionUtils.isNotEmpty(stepScenarioIds)) {
scenarioExample.clear();
scenarioExample.createCriteria().andIdIn(stepScenarioIds);
List<ApiScenario> otherScenarios = apiScenarioMapper.selectByExample(scenarioExample);
response.setExportScenarioList(ApiScenarioUtils.parseApiScenarioDetail(otherScenarios, scenarioBlobMap));
}
response.setScenarioStepBlobMap(getPartialRefStepDetailMap(allSteps));
}
return response;
}
} }

View File

@ -0,0 +1,28 @@
package io.metersphere.api.utils;
import io.metersphere.api.domain.ApiScenario;
import io.metersphere.api.domain.ApiScenarioBlob;
import io.metersphere.api.dto.scenario.ApiScenarioDetail;
import io.metersphere.api.dto.scenario.ScenarioConfig;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.JSON;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ApiScenarioUtils {
public static List<ApiScenarioDetail> parseApiScenarioDetail(List<ApiScenario> apiScenarios, Map<String, ApiScenarioBlob> scenarioBlobMap) {
List<ApiScenarioDetail> returnList = new ArrayList<>();
for (ApiScenario apiScenario : apiScenarios) {
ApiScenarioDetail apiScenarioDetail = BeanUtils.copyBean(new ApiScenarioDetail(), apiScenario);
apiScenarioDetail.setSteps(List.of());
ApiScenarioBlob apiScenarioBlob = scenarioBlobMap.get(apiScenario.getId());
if (apiScenarioBlob != null) {
apiScenarioDetail.setScenarioConfig(JSON.parseObject(new String(apiScenarioBlob.getConfig()), ScenarioConfig.class));
}
returnList.add(apiScenarioDetail);
}
return returnList;
}
}

View File

@ -9,7 +9,7 @@ import io.metersphere.api.domain.*;
import io.metersphere.api.dto.ApiFile; import io.metersphere.api.dto.ApiFile;
import io.metersphere.api.dto.ReferenceRequest; import io.metersphere.api.dto.ReferenceRequest;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.MetersphereApiExportResponse; import io.metersphere.api.dto.export.MetersphereApiDefinitionExportResponse;
import io.metersphere.api.dto.request.ApiEditPosRequest; import io.metersphere.api.dto.request.ApiEditPosRequest;
import io.metersphere.api.dto.request.ApiTransferRequest; import io.metersphere.api.dto.request.ApiTransferRequest;
import io.metersphere.api.dto.request.ImportRequest; import io.metersphere.api.dto.request.ImportRequest;
@ -2117,7 +2117,7 @@ public class ApiDefinitionControllerTests extends BaseTest {
Assertions.assertEquals(files.length, 1); Assertions.assertEquals(files.length, 1);
String fileContent = FileUtils.readFileToString(files[0], StandardCharsets.UTF_8); String fileContent = FileUtils.readFileToString(files[0], StandardCharsets.UTF_8);
MetersphereApiExportResponse exportResponse = ApiDataUtils.parseObject(fileContent, MetersphereApiExportResponse.class); MetersphereApiDefinitionExportResponse exportResponse = ApiDataUtils.parseObject(fileContent, MetersphereApiDefinitionExportResponse.class);
apiDefinitionImportTestService.compareApiExport(exportResponse, exportApiBlobs); apiDefinitionImportTestService.compareApiExport(exportResponse, exportApiBlobs);

View File

@ -1,21 +1,33 @@
package io.metersphere.api.controller; package io.metersphere.api.controller;
import io.metersphere.api.dto.definition.ApiDefinitionBatchExportRequest; import io.metersphere.api.dto.definition.ApiDefinitionBatchExportRequest;
import io.metersphere.api.dto.export.MetersphereApiScenarioExportResponse;
import io.metersphere.api.dto.scenario.ApiScenarioImportRequest; import io.metersphere.api.dto.scenario.ApiScenarioImportRequest;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.functional.domain.ExportTask;
import io.metersphere.project.domain.Project; import io.metersphere.project.domain.Project;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.MsFileUtils;
import io.metersphere.system.base.BaseTest; import io.metersphere.system.base.BaseTest;
import io.metersphere.system.constants.ExportConstants;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.dto.AddProjectRequest; import io.metersphere.system.dto.AddProjectRequest;
import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.manager.ExportTaskManager;
import io.metersphere.system.service.CommonProjectService; import io.metersphere.system.service.CommonProjectService;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
@ -81,9 +93,14 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest {
} }
} }
@Resource
private ExportTaskManager exportTaskManager;
@Test @Test
@Order(1) @Order(2)
public void testExport() throws Exception { public void testExport() throws Exception {
MsFileUtils.deleteDir("/tmp/api-scenario-export/");
ApiDefinitionBatchExportRequest exportRequest = new ApiDefinitionBatchExportRequest(); ApiDefinitionBatchExportRequest exportRequest = new ApiDefinitionBatchExportRequest();
String fileId = IDGenerator.nextStr(); String fileId = IDGenerator.nextStr();
exportRequest.setProjectId(project.getId()); exportRequest.setProjectId(project.getId());
@ -93,5 +110,43 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest {
exportRequest.setExportApiMock(true); exportRequest.setExportApiMock(true);
MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_EXPORT + "metersphere", exportRequest); MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_EXPORT + "metersphere", exportRequest);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
JSON.parseObject(returnData, ResultHolder.class).getData().toString();
Assertions.assertTrue(StringUtils.isNotBlank(fileId));
List<ExportTask> taskList = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId);
while (CollectionUtils.isEmpty(taskList)) {
Thread.sleep(1000);
taskList = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId);
}
ExportTask task = taskList.getFirst();
while (!StringUtils.equalsIgnoreCase(task.getState(), ExportConstants.ExportState.SUCCESS.name())) {
Thread.sleep(1000);
task = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId).getFirst();
}
mvcResult = this.download(exportRequest.getProjectId(), fileId);
byte[] fileBytes = mvcResult.getResponse().getContentAsByteArray();
File zipFile = new File("/tmp/api-scenario-export/downloadFiles.zip");
FileUtils.writeByteArrayToFile(zipFile, fileBytes);
File[] files = MsFileUtils.unZipFile(zipFile, "/tmp/api-scenario-export/unzip/");
assert files != null;
Assertions.assertEquals(files.length, 1);
String fileContent = FileUtils.readFileToString(files[0], StandardCharsets.UTF_8);
MetersphereApiScenarioExportResponse exportResponse = ApiDataUtils.parseObject(fileContent, MetersphereApiScenarioExportResponse.class);
Assertions.assertEquals(exportResponse.getExportScenarioList().size(), 3);
MsFileUtils.deleteDir("/tmp/api-scenario-export/");
}
private MvcResult download(String projectId, String fileId) throws Exception {
return mockMvc.perform(MockMvcRequestBuilders.get("/api/scenario/download/file/" + projectId + "/" + fileId)
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)).andReturn();
} }
} }

View File

@ -2,7 +2,7 @@ package io.metersphere.api.service;
import io.metersphere.api.domain.*; import io.metersphere.api.domain.*;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail; import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import io.metersphere.api.dto.export.MetersphereApiExportResponse; import io.metersphere.api.dto.export.MetersphereApiDefinitionExportResponse;
import io.metersphere.api.dto.request.http.MsHTTPElement; import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.mapper.ApiDefinitionBlobMapper; import io.metersphere.api.mapper.ApiDefinitionBlobMapper;
import io.metersphere.api.mapper.ApiDefinitionMapper; import io.metersphere.api.mapper.ApiDefinitionMapper;
@ -99,7 +99,7 @@ public class ApiDefinitionImportTestService extends ApiDefinitionImportService {
Assertions.assertEquals(moduleChangeCount, diffApiCount); Assertions.assertEquals(moduleChangeCount, diffApiCount);
} }
public void compareApiExport(MetersphereApiExportResponse exportResponse, List<ApiDefinitionBlob> exportApiBlobs) { public void compareApiExport(MetersphereApiDefinitionExportResponse exportResponse, List<ApiDefinitionBlob> exportApiBlobs) {
Assertions.assertEquals(exportResponse.getApiDefinitions().size(), exportApiBlobs.size()); Assertions.assertEquals(exportResponse.getApiDefinitions().size(), exportApiBlobs.size());
List<ApiDefinitionExportDetail> compareList = new ArrayList<>(); List<ApiDefinitionExportDetail> compareList = new ArrayList<>();
for (ApiDefinitionBlob blob : exportApiBlobs) { for (ApiDefinitionBlob blob : exportApiBlobs) {

View File

@ -4,6 +4,7 @@ import io.metersphere.functional.domain.ExportTask;
import io.metersphere.functional.domain.ExportTaskExample; import io.metersphere.functional.domain.ExportTaskExample;
import io.metersphere.functional.mapper.ExportTaskMapper; import io.metersphere.functional.mapper.ExportTaskMapper;
import io.metersphere.sdk.constants.KafkaTopicConstants; import io.metersphere.sdk.constants.KafkaTopicConstants;
import io.metersphere.sdk.constants.MsgType;
import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils; import io.metersphere.sdk.util.LogUtils;
@ -11,6 +12,7 @@ import io.metersphere.sdk.util.Translator;
import io.metersphere.system.constants.ExportConstants; import io.metersphere.system.constants.ExportConstants;
import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.annotation.KafkaListener;
@ -111,6 +113,17 @@ public class ExportTaskManager {
} }
} }
public String getExportTaskId(String projectId, String exportType, String exportState, String userId, String fileId, String fileType) {
List<ExportTask> exportTasks = this.getExportTasks(projectId, exportType, exportState, userId, fileId);
String taskId;
if (CollectionUtils.isNotEmpty(exportTasks)) {
taskId = exportTasks.getFirst().getId();
this.updateExportTask(ExportConstants.ExportState.SUCCESS.name(), taskId, fileType);
} else {
taskId = MsgType.CONNECT.name();
}
return taskId;
}
public List<ExportTask> getExportTasks(String projectId, String exportType, String exportState, String userId, String fileId) { public List<ExportTask> getExportTasks(String projectId, String exportType, String exportState, String userId, String fileId) {
ExportTaskExample exportTaskExample = new ExportTaskExample(); ExportTaskExample exportTaskExample = new ExportTaskExample();
ExportTaskExample.Criteria criteria = exportTaskExample.createCriteria().andCreateUserEqualTo(userId).andProjectIdEqualTo(projectId); ExportTaskExample.Criteria criteria = exportTaskExample.createCriteria().andCreateUserEqualTo(userId).andProjectIdEqualTo(projectId);

View File

@ -5,6 +5,8 @@ import io.metersphere.sdk.file.FileRequest;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream; import java.io.InputStream;
@Service @Service
@ -13,6 +15,12 @@ public class FileService {
return FileCenter.getRepository(request.getStorage()).saveFile(file, request); return FileCenter.getRepository(request.getStorage()).saveFile(file, request);
} }
public void upload(File file, FileRequest request) throws Exception {
try (FileInputStream inputStream = new FileInputStream(file)) {
this.upload(inputStream, request);
}
}
public String upload(InputStream inputStream, FileRequest request) throws Exception { public String upload(InputStream inputStream, FileRequest request) throws Exception {
return FileCenter.getRepository(request.getStorage()).saveFile(inputStream, request); return FileCenter.getRepository(request.getStorage()).saveFile(inputStream, request);
} }