refactor(接口测试): 重构接口导出功能

This commit is contained in:
Jianguo-Genius 2024-09-04 11:21:17 +08:00 committed by Craftsman
parent 3781f72523
commit b6b93775b8
20 changed files with 474 additions and 27 deletions

View File

@ -1,12 +1,16 @@
package io.metersphere.listener;
import io.metersphere.api.event.ApiEventSource;
import io.metersphere.functional.domain.ExportTask;
import io.metersphere.functional.domain.ExportTaskExample;
import io.metersphere.functional.mapper.ExportTaskMapper;
import io.metersphere.plan.listener.ExecEventListener;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.MinioRepository;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.system.constants.ExportConstants;
import io.metersphere.system.service.BaseScheduleService;
import io.metersphere.system.service.PluginLoadService;
import io.metersphere.system.uid.impl.DefaultUidGenerator;
@ -30,6 +34,9 @@ public class AppStartListener implements ApplicationRunner {
@Resource
private DefaultUidGenerator defaultUidGenerator;
@Resource
private ExportTaskMapper exportTaskMapper;
@Override
public void run(ApplicationArguments args) throws Exception {
LogUtils.info("================= 应用启动 =================");
@ -40,6 +47,13 @@ public class AppStartListener implements ApplicationRunner {
LogUtils.info("初始化定时任务");
baseScheduleService.startEnableSchedules();
LogUtils.info("初始化导出未完成任务的状态");
ExportTaskExample exportTaskExample = new ExportTaskExample();
exportTaskExample.createCriteria().andStateEqualTo(ExportConstants.ExportState.PREPARED.name());
ExportTask exportTask = new ExportTask();
exportTask.setState(ExportConstants.ExportState.STOP.name());
exportTaskMapper.updateByExampleSelective(exportTask, exportTaskExample);
// 注册所有监听源
LogUtils.info("初始化接口事件源");
ApiEventSource apiEventSource = CommonBeanFactory.getBean(ApiEventSource.class);

View File

@ -6,7 +6,6 @@ import io.metersphere.api.domain.ApiDefinition;
import io.metersphere.api.dto.ReferenceDTO;
import io.metersphere.api.dto.ReferenceRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.ApiExportResponse;
import io.metersphere.api.dto.request.ApiEditPosRequest;
import io.metersphere.api.dto.request.ApiTransferRequest;
import io.metersphere.api.dto.request.ImportRequest;
@ -33,6 +32,7 @@ import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.Logical;
@ -314,7 +314,24 @@ public class ApiDefinitionController {
@PostMapping(value = "/export/{type}")
@Operation(summary = "接口测试-接口管理-导出接口定义")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_EXPORT)
public ApiExportResponse export(@RequestBody ApiDefinitionBatchExportRequest request, @PathVariable String type) {
return apiDefinitionExportService.export(request, type, SessionUtils.getUserId());
public String newExport(@RequestBody ApiDefinitionBatchExportRequest request, @PathVariable String type) {
return apiDefinitionExportService.exportApiDefinition(request, type, SessionUtils.getUserId());
}
@GetMapping("/stop/{taskId}")
@Operation(summary = "接口测试-接口管理-导出-停止导出")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_EXPORT)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public void caseStopExport(@PathVariable String taskId) {
apiDefinitionExportService.stopExport(taskId, SessionUtils.getUserId());
}
@GetMapping(value = "/download/file/{projectId}/{fileId}")
@Operation(summary = "接口测试-接口管理-下载文件")
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_EXPORT)
@CheckOwner(resourceId = "#projectId", resourceType = "project")
public void downloadImgById(@PathVariable String projectId, @PathVariable String fileId, HttpServletResponse httpServletResponse) {
apiDefinitionExportService.downloadFile(projectId, fileId, SessionUtils.getUserId(), httpServletResponse);
}
}

View File

@ -23,6 +23,9 @@ public class ApiDefinitionBatchExportRequest extends ApiDefinitionBatchRequest i
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "文件id")
@NotBlank
private String fileId;
@Schema(description = "是否同步导出接口用例")
private boolean exportApiCase;

View File

@ -55,6 +55,9 @@ public class Swagger3ExportParser implements ExportParser<ApiExportResponse> {
JSONObject components = new JSONObject();
List<JSONObject> schemas = new LinkedList<>();
for (ApiDefinitionWithBlob apiDefinition : list) {
if (apiDefinition.getPath() == null) {
continue;
}
SwaggerApiInfo swaggerApiInfo = new SwaggerApiInfo();
swaggerApiInfo.setSummary(apiDefinition.getName());
String moduleName = "";

View File

@ -4,18 +4,45 @@ import io.metersphere.api.domain.ApiDefinitionModule;
import io.metersphere.api.domain.ApiDefinitionModuleExample;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.ApiExportResponse;
import io.metersphere.api.mapper.*;
import io.metersphere.api.mapper.ApiDefinitionModuleMapper;
import io.metersphere.api.mapper.ExtApiDefinitionMapper;
import io.metersphere.api.mapper.ExtApiDefinitionMockMapper;
import io.metersphere.api.mapper.ExtApiTestCaseMapper;
import io.metersphere.api.parser.api.MetersphereExportParser;
import io.metersphere.api.parser.api.Swagger3ExportParser;
import io.metersphere.functional.domain.ExportTask;
import io.metersphere.project.domain.Project;
import io.metersphere.project.mapper.ProjectMapper;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.project.utils.FileDownloadUtils;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.dto.ExportMsgDTO;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.sdk.util.MsFileUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.constants.ExportConstants;
import io.metersphere.system.domain.User;
import io.metersphere.system.log.dto.LogDTO;
import io.metersphere.system.log.service.OperationLogService;
import io.metersphere.system.manager.ExportTaskManager;
import io.metersphere.system.mapper.UserMapper;
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 jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -40,10 +67,18 @@ public class ApiDefinitionExportService {
@Resource
private ProjectMapper projectMapper;
@Resource
private ApiDefinitionMapper apiDefinitionMapper;
private UserMapper userMapper;
@Resource
private ExportTaskManager exportTaskManager;
@Resource
private NoticeSendService noticeSendService;
@Resource
private ApiDefinitionLogService apiDefinitionLogService;
@Resource
private OperationLogService operationLogService;
private static final String EXPORT_CASE_TMP_DIR = "tmp";
public ApiExportResponse export(ApiDefinitionBatchExportRequest request, String type, String userId) {
public ApiExportResponse genApiExportResponse(ApiDefinitionBatchExportRequest request, String type, String userId) {
List<String> ids = this.getBatchExportApiIds(request, request.getProjectId(), userId);
if (CollectionUtils.isEmpty(ids)) {
return null;
@ -61,6 +96,99 @@ public class ApiDefinitionExportService {
};
}
public String exportApiDefinition(ApiDefinitionBatchExportRequest request, String type, String userId) {
String returnId;
try {
exportTaskManager.exportCheck(request.getProjectId(), ExportConstants.ExportType.API_DEFINITION.toString(), userId);
ExportTask exportTask = exportTaskManager.exportAsyncTask(
request.getProjectId(),
request.getFileId(), userId,
ExportConstants.ExportType.API_DEFINITION.name(), request, t -> {
try {
return exportApiDefinitionZip(request, type, userId);
} catch (Exception e) {
throw new MSException(e);
}
});
returnId = exportTask.getId();
} catch (InterruptedException e) {
LogUtils.error("导出失败:" + e);
throw new MSException(e);
}
return returnId;
}
@Resource
private FileService fileService;
public void uploadFileToMinio(String fileType, File file, String fileId) throws Exception {
FileRequest fileRequest = new FileRequest();
fileRequest.setFileName(fileId.concat(".").concat(fileType));
fileRequest.setFolder(DefaultRepositoryDir.getExportExcelTempDir());
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 {
File tmpDir = null;
String fileType = "";
try {
User user = userMapper.selectByPrimaryKey(userId);
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集合
List<String> ids = this.getBatchExportApiIds(request, request.getProjectId(), userId);
if (CollectionUtils.isEmpty(ids)) {
return null;
}
ApiExportResponse exportResponse = this.genApiExportResponse(request, exportType, userId);
File createFile = new File(tmpDir.getPath() + File.separatorChar + request.getFileId() + ".json");
if (!createFile.exists()) {
try {
createFile.createNewFile();
} catch (IOException e) {
throw new MSException(e);
}
}
FileUtils.writeByteArrayToFile(createFile, JSON.toJSONString(exportResponse).getBytes());
fileType = "json";
uploadFileToMinio(fileType, createFile, request.getFileId());
// 生成日志
LogDTO logDTO = apiDefinitionLogService.exportExcelLog(request, exportType, userId, projectMapper.selectByPrimaryKey(request.getProjectId()).getOrganizationId());
operationLogService.add(logDTO);
List<ExportTask> exportTasks = exportTaskManager.getExportTasks(request.getProjectId(), ExportConstants.ExportType.API_DEFINITION.name(), ExportConstants.ExportState.PREPARED.toString(), userId, null);
String taskId;
if (CollectionUtils.isNotEmpty(exportTasks)) {
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) {
List<ExportTask> exportTasks = exportTaskManager.getExportTasks(request.getProjectId(), ExportConstants.ExportType.API_DEFINITION.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);
LogUtils.error(e);
throw new MSException(e);
} finally {
MsFileUtils.deleteDir(tmpDir.getPath());
}
return null;
}
private List<ApiDefinitionWithBlob> selectAndSortByIds(List<String> ids) {
Map<String, ApiDefinitionWithBlob> apiMap = extApiDefinitionMapper.selectApiDefinitionWithBlob(ids).stream().collect(Collectors.toMap(ApiDefinitionWithBlob::getId, v -> v));
return ids.stream().map(apiMap::get).toList();
@ -109,4 +237,28 @@ public class ApiDefinitionExportService {
throw new MSException(e);
}
}
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_DEFINITION.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().concat(".").concat("json"));
fileRequest.setFolder(DefaultRepositoryDir.getExportExcelTempDir());
fileRequest.setStorage(StorageType.MINIO.name());
String fileName = "Metersphere_case_" + 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

@ -370,4 +370,18 @@ public class ApiDefinitionLogService {
}
public LogDTO exportExcelLog(ApiDefinitionBatchExportRequest request, String exportType, String userId, String orgId) {
LogDTO dto = new LogDTO(
request.getProjectId(),
orgId,
request.getFileId(),
userId,
OperationLogType.EXPORT.name(),
OperationLogModule.API_TEST_MANAGEMENT_DEFINITION,
"");
dto.setHistory(true);
dto.setPath("/api/definition/export/" + exportType);
dto.setMethod(HttpMethodConstants.POST.name());
return dto;
}
}

View File

@ -26,6 +26,7 @@ import io.metersphere.api.service.BaseFileManagementTestService;
import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.api.service.definition.ApiTestCaseService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.functional.domain.ExportTask;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.domain.Project;
import io.metersphere.project.dto.filemanagement.FileInfo;
@ -40,6 +41,7 @@ import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.*;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.constants.ExportConstants;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.controller.handler.result.MsHttpResultCode;
import io.metersphere.system.domain.OperationHistory;
@ -50,6 +52,7 @@ import io.metersphere.system.dto.request.OperationHistoryVersionRequest;
import io.metersphere.sdk.dto.BaseCondition;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.manager.ExportTaskManager;
import io.metersphere.system.mapper.OperationHistoryMapper;
import io.metersphere.system.service.CommonProjectService;
import io.metersphere.system.service.UserLoginService;
@ -58,6 +61,7 @@ import io.metersphere.system.uid.NumGenerator;
import io.metersphere.system.utils.Pager;
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.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@ -2043,17 +2047,54 @@ public class ApiDefinitionControllerTests extends BaseTest {
Assertions.assertEquals(2, apiDefinitionIdList.size());
}
@Resource
private ExportTaskManager exportTaskManager;
private void testExportAndImport(String exportProjectId, List<ApiDefinitionBlob> exportApiBlobs) throws Exception {
ApiDefinitionBatchExportRequest exportRequest = new ApiDefinitionBatchExportRequest();
String fileId = IDGenerator.nextStr();
exportRequest.setProjectId(exportProjectId);
exportRequest.setFileId(fileId);
exportRequest.setSelectAll(true);
exportRequest.setExportApiCase(true);
exportRequest.setExportApiMock(true);
MvcResult mvcResult = this.requestPostWithOkAndReturn(EXPORT + "metersphere", exportRequest);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
MetersphereApiExportResponse exportResponse = ApiDataUtils.parseObject(JSON.toJSONString(resultHolder.getData()), MetersphereApiExportResponse.class);
String taskId = JSON.parseObject(returnData, ResultHolder.class).getData().toString();
Assertions.assertTrue(StringUtils.isNotBlank(fileId));
List<ExportTask> taskList = exportTaskManager.getExportTasks(exportProjectId, null, null, "admin", fileId);
while (CollectionUtils.isEmpty(taskList)) {
Thread.sleep(1000);
taskList = exportTaskManager.getExportTasks(exportProjectId, null, null, "admin", fileId);
}
ExportTask task = taskList.getFirst();
while (!StringUtils.equalsIgnoreCase(task.getState(), ExportConstants.ExportState.SUCCESS.name())) {
Thread.sleep(1000);
task = exportTaskManager.getExportTasks(exportProjectId, null, null, "admin", fileId).getFirst();
}
mvcResult = this.download(exportProjectId, fileId);
byte[] fileBytes = mvcResult.getResponse().getContentAsByteArray();
File file = new File("/tmp/test.json");
FileUtils.writeByteArrayToFile(file, fileBytes);
String fileContent = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
MetersphereApiExportResponse exportResponse = ApiDataUtils.parseObject(fileContent, MetersphereApiExportResponse.class);
apiDefinitionImportTestService.compareApiExport(exportResponse, exportApiBlobs);
//测试stop
this.requestGetWithOk("/stop/" + taskId);
}
private MvcResult download(String projectId, String fileId) throws Exception {
return mockMvc.perform(MockMvcRequestBuilders.get("/api/definition/download/file/" + projectId + "/" + fileId)
.header(SessionConstants.HEADER_TOKEN, sessionId)
.header(SessionConstants.CSRF_TOKEN, csrfToken)).andReturn();
}
protected MvcResult requestMultipart(String url, MultiValueMap<String, Object> paramMap, ResultMatcher resultMatcher) throws Exception {

View File

@ -27,7 +27,6 @@ import io.metersphere.functional.mapper.ExportTaskMapper;
import io.metersphere.functional.mapper.ExtFunctionalCaseCommentMapper;
import io.metersphere.functional.request.FunctionalCaseExportRequest;
import io.metersphere.functional.request.FunctionalCaseImportRequest;
import io.metersphere.functional.socket.ExportWebSocketHandler;
import io.metersphere.functional.xmind.parser.XMindCaseParser;
import io.metersphere.plan.domain.TestPlanCaseExecuteHistory;
import io.metersphere.project.domain.Project;
@ -56,6 +55,7 @@ import io.metersphere.system.mapper.SystemParameterMapper;
import io.metersphere.system.mapper.UserMapper;
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.utils.ServiceUtils;
import jakarta.annotation.Resource;
@ -564,6 +564,7 @@ public class FunctionalCaseFileService {
throw new MSException(e);
}
}
//生成临时EXCEL
EasyExcel.write(createFile)
.head(Optional.ofNullable(headList).orElse(new ArrayList<>()))

View File

@ -5,7 +5,6 @@ import io.metersphere.functional.domain.FunctionalCase;
import io.metersphere.functional.domain.FunctionalCaseBlob;
import io.metersphere.functional.domain.FunctionalCaseCustomField;
import io.metersphere.functional.request.FunctionalCaseExportRequest;
import io.metersphere.functional.socket.ExportWebSocketHandler;
import io.metersphere.functional.xmind.domain.FunctionalCaseXmindDTO;
import io.metersphere.functional.xmind.domain.FunctionalCaseXmindData;
import io.metersphere.functional.xmind.utils.XmindExportUtil;
@ -25,6 +24,7 @@ import io.metersphere.system.log.service.OperationLogService;
import io.metersphere.system.manager.ExportTaskManager;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.service.NoticeSendService;
import io.metersphere.system.socket.ExportWebSocketHandler;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;

View File

@ -3,10 +3,7 @@ package io.metersphere.project.utils;
import io.metersphere.sdk.util.LogUtils;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.zip.ZipEntry;
@ -43,6 +40,23 @@ public class FileDownloadUtils {
}
}
public static void zipFilesWithResponse(String fileName, InputStream fileInputStream, HttpServletResponse response) {
try (OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[512];
int num;
while ((num = fileInputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, num);
}
outputStream.close();
response.setContentType("application/zip");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
} catch (Exception e) {
LogUtils.error(e);
}
}
public static byte[] listFileBytesToZip(Map<String, byte[]> mapReport) {
try {
if (!mapReport.isEmpty()) {

View File

@ -3,7 +3,7 @@ package io.metersphere.system.constants;
public class ExportConstants {
public enum ExportType {
API, CASE
API_DEFINITION, CASE
}
public enum ExportState {

View File

@ -1,10 +1,13 @@
package io.metersphere.system.manager;
import io.metersphere.functional.domain.ExportTask;
import io.metersphere.functional.domain.ExportTaskExample;
import io.metersphere.functional.mapper.ExportTaskMapper;
import io.metersphere.sdk.constants.KafkaTopicConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.LogUtils;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.constants.ExportConstants;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
@ -15,6 +18,7 @@ import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
@ -96,4 +100,38 @@ public class ExportTaskManager {
}
}
}
public void exportCheck(String projectId, String exportType, String userId) {
ExportTaskExample exportTaskExample = new ExportTaskExample();
exportTaskExample.createCriteria().andTypeEqualTo(exportType).andStateEqualTo(ExportConstants.ExportState.PREPARED.toString())
.andCreateUserEqualTo(userId).andProjectIdEqualTo(projectId);
exportTaskExample.setOrderByClause("create_time desc");
if (exportTaskMapper.countByExample(exportTaskExample) > 0) {
throw new MSException(Translator.get("export_case_task_existed"));
}
}
public List<ExportTask> getExportTasks(String projectId, String exportType, String exportState, String userId, String fileId) {
ExportTaskExample exportTaskExample = new ExportTaskExample();
ExportTaskExample.Criteria criteria = exportTaskExample.createCriteria().andCreateUserEqualTo(userId).andProjectIdEqualTo(projectId);
if (StringUtils.isNotBlank(exportType)) {
criteria.andTypeEqualTo(exportType);
}
if (StringUtils.isNotBlank(exportState)) {
criteria.andStateEqualTo(exportState);
}
if (StringUtils.isNotBlank(fileId)) {
criteria.andFileIdEqualTo(fileId);
}
exportTaskExample.setOrderByClause("create_time desc");
return exportTaskMapper.selectByExample(exportTaskExample);
}
public void updateExportTask(String state, String taskId, String fileType) {
ExportTask exportTask = new ExportTask();
exportTask.setState(state);
exportTask.setFileType(fileType);
exportTask.setId(taskId);
exportTaskMapper.updateByPrimaryKeySelective(exportTask);
}
}

View File

@ -1,5 +1,7 @@
package io.metersphere.system.service;
import io.metersphere.functional.domain.ExportTaskExample;
import io.metersphere.functional.mapper.ExportTaskMapper;
import io.metersphere.project.domain.Project;
import io.metersphere.project.domain.ProjectExample;
import io.metersphere.project.domain.ProjectTestResourcePool;
@ -50,6 +52,8 @@ public class CommonProjectService {
@Resource
private ProjectMapper projectMapper;
@Resource
private ExportTaskMapper exportTaskMapper;
@Resource
private UserMapper userMapper;
@Resource
private UserRoleRelationMapper userRoleRelationMapper;
@ -551,6 +555,10 @@ public class CommonProjectService {
ProjectTestResourcePoolExample projectTestResourcePoolExample = new ProjectTestResourcePoolExample();
projectTestResourcePoolExample.createCriteria().andProjectIdEqualTo(project.getId());
projectTestResourcePoolMapper.deleteByExample(projectTestResourcePoolExample);
//删除导出任务
ExportTaskExample exportTaskExample = new ExportTaskExample();
exportTaskExample.createCriteria().andProjectIdEqualTo(project.getId());
exportTaskMapper.deleteByExample(exportTaskExample);
// delete project
projectMapper.deleteByPrimaryKey(project.getId());
LogDTO logDTO = new LogDTO(OperationLogConstants.SYSTEM, project.getOrganizationId(), project.getId(), Translator.get("scheduled_tasks"), OperationLogType.DELETE.name(), OperationLogModule.SETTING_ORGANIZATION_PROJECT, Translator.get("delete") + Translator.get("project") + ": " + project.getName());

View File

@ -25,6 +25,10 @@ public class FileService {
return FileCenter.getRepository(request.getStorage()).getFile(request);
}
public InputStream getFileAsStream(FileRequest request) throws Exception {
return FileCenter.getRepository(request.getStorage()).getFileAsStream(request);
}
public void deleteFile(FileRequest request) throws Exception {
FileCenter.getRepository(request.getStorage()).delete(request);
}

View File

@ -1,4 +1,4 @@
package io.metersphere.functional.socket;
package io.metersphere.system.socket;
import io.metersphere.sdk.constants.MsgType;
import io.metersphere.sdk.dto.ExportMsgDTO;

View File

@ -40,6 +40,7 @@ import {
diffDataUrl,
ExecuteCaseUrl,
ExportDefinitionUrl,
GetApiDownloadFileUrl,
GetCaseDetailUrl,
GetCaseReportByIdUrl,
GetCaseReportDetailUrl,
@ -72,6 +73,7 @@ import {
SaveOperationHistoryUrl,
SortCaseUrl,
SortDefinitionUrl,
StopApiExportUrl,
SwitchDefinitionScheduleUrl,
ToggleFollowCaseUrl,
ToggleFollowDefinitionUrl,
@ -93,6 +95,8 @@ import {
UploadTempFileUrl,
UploadTempMockFileUrl,
} from '@/api/requrls/api-test/management';
import { StopCaseExportUrl } from '@/api/requrls/case-management/featureCase';
import { BatchDownloadFileUrl } from '@/api/requrls/project-management/fileManagement';
import { ApiCaseReportDetail, ExecuteRequestParams } from '@/models/apiTest/common';
import {
@ -201,6 +205,25 @@ export function updateDefinition(data: ApiDefinitionUpdateParams) {
return MSR.post({ url: UpdateDefinitionUrl, data });
}
export function stopApiExport(taskId: string) {
return MSR.get({ url: `${StopApiExportUrl}/${taskId}` });
}
// 获取导出的文件
export function getApiDownloadFile(projectId: string, fileId: string) {
// return MSR.get(
// { url: `${GetApiDownloadFileUrl}/${projectId}/${fileId}`, responseType: 'blob' },
// { isTransformResponse: false, isReturnNativeResponse: true }
// );
return MSR.get(
{
url: `${GetApiDownloadFileUrl}/${projectId}/${fileId}`,
responseType: 'blob',
},
{ isTransformResponse: false }
);
}
// 获取接口定义详情
export function getDefinitionDetail(id: string | number) {
return MSR.get<ApiDefinitionDetail>({ url: GetDefinitionDetailUrl, params: id });

View File

@ -12,6 +12,8 @@ export const DeleteModuleUrl = '/api/definition/module/delete'; // 删除模块
export const DefinitionPageUrl = '/api/definition/page'; // 接口定义列表
export const AddDefinitionUrl = '/api/definition/add'; // 添加接口定义
export const UpdateDefinitionUrl = '/api/definition/update'; // 更新接口定义
export const StopApiExportUrl = '/api/definition/stop';
export const GetApiDownloadFileUrl = '/api/definition/download/file';
export const GetDefinitionDetailUrl = '/api/definition/get-detail'; // 获取接口定义详情
export const TransferFileUrl = '/api/definition/transfer'; // 文件转存
export const TransferFileModuleOptionUrl = '/api/definition/transfer/options'; // 文件转存目录

View File

@ -85,6 +85,11 @@ export enum RequestImportFormat {
Jmeter = 'Jmeter',
Har = 'Har',
}
export enum RequestExportFormat {
SWAGGER = 'Swagger',
MeterSphere = 'MeterSphere',
}
// 接口导入方式
export enum RequestImportType {
API = 'API',

View File

@ -187,6 +187,7 @@ export interface ApiDefinitionBatchParams extends BatchApiParams {
export interface ApiDefinitionBatchExportParams extends ApiDefinitionBatchParams {
exportApiCase: boolean;
exportApiMock: boolean;
fileId: string;
sort: Record<string, any>;
}
// 批量更新定义参数

View File

@ -308,24 +308,27 @@
batchUpdateDefinition,
deleteDefinition,
exportApiDefinition,
getApiDownloadFile,
getDefinitionPage,
sortDefinition,
stopApiExport,
updateDefinition,
} from '@/api/modules/api-test/management';
import { getProjectInfo } from '@/api/modules/project-management/basicInfo';
import { getSocket } from '@/api/modules/project-management/commonScript';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app';
import useCacheStore from '@/store/modules/cache/cache';
import { characterLimit, downloadByteFile, operationWidth } from '@/utils';
import { characterLimit, downloadByteFile, getGenerateId, operationWidth } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { ProtocolItem } from '@/models/apiTest/common';
import { ApiDefinitionDetail, ApiDefinitionGetModuleParams } from '@/models/apiTest/management';
import { DragSortParams, ModuleTreeNode } from '@/models/common';
import { FilterType, ViewTypeEnum } from '@/enums/advancedFilterEnum';
import { RequestDefinitionStatus, RequestImportFormat, RequestMethods } from '@/enums/apiEnum';
import { RequestDefinitionStatus, RequestExportFormat, RequestImportFormat, RequestMethods } from '@/enums/apiEnum';
import { CacheTabTypeEnum } from '@/enums/cacheTabEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -1057,21 +1060,122 @@
const platformList = [
{
name: 'Swagger',
value: RequestImportFormat.SWAGGER,
value: RequestExportFormat.SWAGGER,
},
{
name: 'MeterSphere',
value: RequestImportFormat.MeterSphere,
value: RequestExportFormat.MeterSphere,
},
];
const exportPlatform = ref(RequestImportFormat.SWAGGER);
const exportPlatform = ref(RequestExportFormat.SWAGGER);
const exportApiCase = ref(false);
const exportApiMock = ref(false);
const exportLoading = ref(false);
function cancelExport() {
showExportModal.value = false;
exportPlatform.value = RequestImportFormat.SWAGGER;
exportPlatform.value = RequestExportFormat.SWAGGER;
}
//
async function downloadFile(id: string) {
try {
const response = await getApiDownloadFile(appStore.currentProjectId, id);
downloadByteFile(response, 'metersphere-export.json');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
function showExportSuccessfulMessage(id: string, count: number) {
Message.success({
content: () =>
h('div', { class: 'flex flex-col gap-[8px] items-start' }, [
h('div', { class: 'font-medium' }, t('common.exportSuccessful')),
h('div', { class: 'flex items-center gap-[12px]' }, [
h('div', t('caseManagement.featureCase.exportCaseCount', { number: count })),
h(
MsButton,
{
type: 'text',
onClick() {
downloadFile(id);
},
},
{ default: () => t('common.downloadFile') }
),
]),
]),
duration: 999999999, //
closable: true,
});
}
const websocket = ref<WebSocket>();
const reportId = ref('');
const isShowExportingMessage = ref(false); //
const exportingMessage = ref();
// websocket
function startWebsocketGetExportResult() {
websocket.value = getSocket(reportId.value, '/ws/export');
websocket.value.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.msgType === 'EXEC_RESULT') {
exportingMessage.value.close();
reportId.value = data.fileId;
// taskId.value = data.taskId;
if (data.isSuccessful) {
showExportSuccessfulMessage(reportId.value, data.count);
} else {
Message.error({
content: t('common.exportFailed'),
duration: 999999999, //
closable: true,
});
}
websocket.value?.close();
}
});
}
//
async function stopExport(taskId: string) {
try {
await stopApiExport(taskId);
exportingMessage.value.close();
websocket.value?.close();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
function showExportingMessage(taskId: string) {
if (isShowExportingMessage.value) return;
isShowExportingMessage.value = true;
exportingMessage.value = Message.loading({
content: () =>
h('div', { class: 'flex items-center gap-[12px]' }, [
h('div', t('common.exporting')),
h(
MsButton,
{
type: 'text',
onClick() {
stopExport(taskId);
},
},
{ default: () => t('common.cancel') }
),
]),
duration: 999999999, //
closable: true,
onClose() {
isShowExportingMessage.value = false;
},
});
}
/**
@ -1080,8 +1184,10 @@
async function exportApi() {
try {
exportLoading.value = true;
reportId.value = getGenerateId();
startWebsocketGetExportResult();
const batchConditionParams = await getBatchConditionParams();
const result = await exportApiDefinition(
const res = await exportApiDefinition(
{
selectIds: tableSelected.value as string[],
selectAll: !!batchParams.value?.selectAll,
@ -1090,11 +1196,12 @@
exportApiCase: exportApiCase.value,
exportApiMock: exportApiMock.value,
sort: propsRes.value.sorter || {},
fileId: reportId.value,
},
exportPlatform.value
);
const res = await getProjectInfo(appStore.currentProjectId);
downloadByteFile(new Blob([JSON.stringify(result)]), `Swagger_Api_${res.name}.json`);
showExportingMessage(res);
showExportModal.value = false;
} catch (error) {
// eslint-disable-next-line no-console