refactor(接口测试): 重构接口导出功能
This commit is contained in:
parent
3781f72523
commit
b6b93775b8
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = "";
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<>()))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -3,7 +3,7 @@ package io.metersphere.system.constants;
|
|||
public class ExportConstants {
|
||||
|
||||
public enum ExportType {
|
||||
API, CASE
|
||||
API_DEFINITION, CASE
|
||||
}
|
||||
|
||||
public enum ExportState {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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 });
|
||||
|
|
|
@ -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'; // 文件转存目录
|
||||
|
|
|
@ -85,6 +85,11 @@ export enum RequestImportFormat {
|
|||
Jmeter = 'Jmeter',
|
||||
Har = 'Har',
|
||||
}
|
||||
|
||||
export enum RequestExportFormat {
|
||||
SWAGGER = 'Swagger',
|
||||
MeterSphere = 'MeterSphere',
|
||||
}
|
||||
// 接口导入方式
|
||||
export enum RequestImportType {
|
||||
API = 'API',
|
||||
|
|
|
@ -187,6 +187,7 @@ export interface ApiDefinitionBatchParams extends BatchApiParams {
|
|||
export interface ApiDefinitionBatchExportParams extends ApiDefinitionBatchParams {
|
||||
exportApiCase: boolean;
|
||||
exportApiMock: boolean;
|
||||
fileId: string;
|
||||
sort: Record<string, any>;
|
||||
}
|
||||
// 批量更新定义参数
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue