diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/ApiExecuteResourceController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/ApiExecuteResourceController.java index 744a1ce899..41d5ea6f61 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/ApiExecuteResourceController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/ApiExecuteResourceController.java @@ -1,14 +1,13 @@ package io.metersphere.api.controller; import io.metersphere.api.service.ApiExecuteService; +import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.util.LogUtils; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.Optional; @@ -27,6 +26,7 @@ public class ApiExecuteResourceController { /** * 获取执行脚本 + * * @param reportId * @param testId * @return @@ -39,4 +39,18 @@ public class ApiExecuteResourceController { stringRedisTemplate.delete(key); return Optional.ofNullable(script).orElse(StringUtils.EMPTY); } + + /** + * 下载执行所需的文件 + * + * @return + */ + @PostMapping("/file") + public void downloadFile(@RequestParam("reportId") String reportId, + @RequestParam("testId") String testId, + @RequestBody FileRequest fileRequest, + HttpServletResponse response) throws Exception { + apiExecuteService.downloadFile(reportId, testId, fileRequest, response); + } + } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java index ef9b2c2a5b..aaec18001c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiExecuteService.java @@ -24,6 +24,9 @@ import io.metersphere.sdk.dto.api.task.ApiExecuteFileInfo; import io.metersphere.sdk.dto.api.task.ApiRunModeConfigDTO; import io.metersphere.sdk.dto.api.task.TaskRequestDTO; import io.metersphere.sdk.exception.MSException; +import io.metersphere.sdk.file.FileCenter; +import io.metersphere.sdk.file.FileRepository; +import io.metersphere.sdk.file.FileRequest; import io.metersphere.sdk.util.*; import io.metersphere.system.config.MinioProperties; import io.metersphere.system.domain.TestResourcePool; @@ -36,16 +39,22 @@ import io.metersphere.system.service.TestResourcePoolService; import io.metersphere.system.utils.TaskRunnerClient; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.jmeter.util.JMeterUtils; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.*; import java.util.stream.Collectors; @@ -185,6 +194,9 @@ public class ApiExecuteService { String endpoint = TaskRunnerClient.getEndpoint(testResourceNodeDTO.getIp(), testResourceNodeDTO.getPort()); LogUtils.info(String.format("开始发送请求【 %s 】到 %s 节点执行", testId, endpoint), reportId); TaskRunnerClient.debugApi(endpoint, taskRequest); + // 清空mino和kafka配置信息,避免前端获取 + taskRequest.setMinioConfig(null); + taskRequest.setKafkaConfig(null); return taskRequest; } catch (Exception e) { LogUtils.error(e); @@ -273,7 +285,7 @@ public class ApiExecuteService { List refFiles = fileAssociationService.getFiles(request.getId()). stream() .map(file -> { - ApiExecuteFileInfo refFileInfo = getApiExecuteFileInfo(file.getFileId(), file.getFileName(), + ApiExecuteFileInfo refFileInfo = getApiExecuteFileInfo(file.getFileId(), file.getOriginalName(), file.getProjectId(), file.getStorage()); if (StorageType.isGit(file.getStorage())) { // 设置Git信息 @@ -286,7 +298,6 @@ public class ApiExecuteService { // 没有保存的本地临时文件 List uploadFileIds = request.getUploadFileIds(); if (CollectionUtils.isNotEmpty(uploadFileIds)) { - // 去掉文件管理的文件,即通过本地上传的临时文件 List localTempFiles = uploadFileIds.stream() .map(tempFileId -> { String fileName = apiFileResourceService.getTempFileNameByFileId(tempFileId); @@ -299,40 +310,15 @@ public class ApiExecuteService { List linkFileIds = request.getLinkFileIds(); // 没有保存的文件管理临时文件 if (CollectionUtils.isNotEmpty(linkFileIds)) { - List refTempFiles = fileMetadataService.getByFileIds(linkFileIds) - .stream() - .map(file -> { - String fileName = file.getName(); - if (StringUtils.isNotBlank(file.getType())) { - fileName += "." + file.getType(); - } - ApiExecuteFileInfo tempFileInfo = getApiExecuteFileInfo(file.getId(), fileName, - file.getProjectId(), file.getStorage()); - if (StorageType.isGit(file.getStorage())) { - // 设置Git信息 - tempFileInfo.setFileMetadataRepositoryDTO(fileManagementService.getFileMetadataRepositoryDTO(file.getId())); - tempFileInfo.setFileModuleRepositoryDTO(fileManagementService.getFileModuleRepositoryDTO(file.getModuleId())); - } - return tempFileInfo; - }).toList(); + List fileMetadataList = fileMetadataService.getByFileIds(linkFileIds); // 添加临时的文件管理的文件 - refFiles.addAll(refTempFiles); + refFiles.addAll(getApiExecuteFileInfo(fileMetadataList)); } taskRequest.setRefFiles(refFiles); // 获取函数jar包 List fileMetadataList = fileManagementService.findJarByProjectId(List.of(taskRequest.getProjectId())); - taskRequest.setFuncJars(fileMetadataList.stream() - .map(file -> { - String fileName = file.getOriginalName(); - ApiExecuteFileInfo tempFileInfo = getApiExecuteFileInfo(file.getId(), fileName, file.getProjectId(), file.getStorage()); - if (StorageType.isGit(file.getStorage())) { - // 设置Git信息 - tempFileInfo.setFileMetadataRepositoryDTO(fileManagementService.getFileMetadataRepositoryDTO(file.getId())); - tempFileInfo.setFileModuleRepositoryDTO(fileManagementService.getFileModuleRepositoryDTO(file.getModuleId())); - } - return tempFileInfo; - }).toList()); + taskRequest.setFuncJars(getApiExecuteFileInfo(fileMetadataList)); // TODO 当前项目没有包分两种情况,1 之前存在被删除,2 一直不存在 // 为了兼容1 这种情况需要初始化一条空的数据,由执行机去做卸载 @@ -343,11 +329,25 @@ public class ApiExecuteService { } } - private static ApiExecuteFileInfo getApiExecuteFileInfo(String fileId, String fileName, String projectId) { + private List getApiExecuteFileInfo(List fileMetadataList) { + return fileMetadataList.stream() + .map(file -> { + ApiExecuteFileInfo tempFileInfo = getApiExecuteFileInfo(file.getId(), file.getOriginalName(), + file.getProjectId(), file.getStorage()); + if (StorageType.isGit(file.getStorage())) { + // 设置Git信息 + tempFileInfo.setFileMetadataRepositoryDTO(fileManagementService.getFileMetadataRepositoryDTO(file.getId())); + tempFileInfo.setFileModuleRepositoryDTO(fileManagementService.getFileModuleRepositoryDTO(file.getModuleId())); + } + return tempFileInfo; + }).toList(); + } + + private ApiExecuteFileInfo getApiExecuteFileInfo(String fileId, String fileName, String projectId) { return getApiExecuteFileInfo(fileId, fileName, projectId, StorageType.MINIO.name()); } - private static ApiExecuteFileInfo getApiExecuteFileInfo(String fileId, String fileName, String projectId, String storage) { + private ApiExecuteFileInfo getApiExecuteFileInfo(String fileId, String fileName, String projectId, String storage) { ApiExecuteFileInfo apiExecuteFileInfo = new ApiExecuteFileInfo(); apiExecuteFileInfo.setStorage(storage); apiExecuteFileInfo.setFileName(fileName); @@ -412,4 +412,33 @@ public class ApiExecuteService { } return (String) configMap.get(ProjectApplicationType.API.API_RESOURCE_POOL_ID.name()); } + + public void downloadFile(String reportId, String testId, FileRequest fileRequest, HttpServletResponse response) throws Exception { + String key = getScriptRedisKey(reportId, testId); + if (BooleanUtils.isTrue(stringRedisTemplate.hasKey(key))) { + FileRepository repository = StringUtils.isBlank(fileRequest.getStorage()) ? FileCenter.getDefaultRepository() + : FileCenter.getRepository(fileRequest.getStorage()); + write2Response(repository.getFileAsStream(fileRequest), response); + } + } + + public void write2Response(InputStream in, HttpServletResponse response) { + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + try (OutputStream out = response.getOutputStream()) { + int len; + byte[] bytes = new byte[1024 * 2]; + while ((len = in.read(bytes)) != -1) { + out.write(bytes, 0, len); + } + out.flush(); + } catch (Exception e) { + LogUtils.error(e); + } finally { + try { + in.close(); + } catch (IOException e) { + LogUtils.error(e); + } + } + } } \ No newline at end of file diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiExecuteResourceControllerTest.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiExecuteResourceControllerTest.java index 2d2bba83ba..be463078e8 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiExecuteResourceControllerTest.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiExecuteResourceControllerTest.java @@ -1,11 +1,27 @@ package io.metersphere.api.controller; +import io.metersphere.api.service.ApiExecuteService; +import io.metersphere.api.service.debug.ApiDebugService; +import io.metersphere.sdk.constants.DefaultRepositoryDir; +import io.metersphere.sdk.constants.StorageType; +import io.metersphere.sdk.file.FileCopyRequest; +import io.metersphere.sdk.file.FileRequest; import io.metersphere.system.base.BaseTest; +import io.metersphere.system.uid.IDGenerator; +import jakarta.annotation.Resource; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @Author: jianxing @@ -18,6 +34,13 @@ public class ApiExecuteResourceControllerTest extends BaseTest { private static final String BASE_PATH = "/api/execute/resource/"; private static final String SCRIPT = "script?reportId={0}&testId={1}"; + private static final String FILE = "file?reportId={0}&testId={1}"; + @Resource + private StringRedisTemplate stringRedisTemplate; + @Resource + private ApiExecuteService apiExecuteService; + @Resource + private ApiDebugService apiDebugService; @Override public String getBasePath() { @@ -28,4 +51,28 @@ public class ApiExecuteResourceControllerTest extends BaseTest { public void getScript() throws Exception { this.requestGetWithOk(SCRIPT, "reportId", "testId"); } + + @Test + public void downloadFile() throws Exception { + String fileName = IDGenerator.nextStr() + "_file_upload.JPG"; + MockMultipartFile file = new MockMultipartFile("file", fileName, MediaType.APPLICATION_OCTET_STREAM_VALUE, "aa".getBytes()); + String fileId = apiDebugService.uploadTempFile(file); + FileRequest fileRequest = new FileCopyRequest(); + fileRequest.setFileName(fileName); + fileRequest.setFolder(DefaultRepositoryDir.getSystemTempDir() + "/" + fileId); + mockMvc.perform(getPostRequestBuilder(FILE, fileRequest, "reportId", "testId")) + .andExpect(status().isOk()); + + String reportId = UUID.randomUUID().toString(); + String testId = UUID.randomUUID().toString(); + String scriptRedisKey = apiExecuteService.getScriptRedisKey(reportId, testId); + stringRedisTemplate.opsForValue().set(scriptRedisKey, "aaa"); + mockMvc.perform(getPostRequestBuilder(FILE, fileRequest, reportId, testId)) + .andExpect(status().isOk()); + + fileRequest.setStorage(StorageType.MINIO.name()); + mockMvc.perform(getPostRequestBuilder(FILE, fileRequest, reportId, testId)) + .andExpect(status().isOk()); + + } } diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/dto/filemanagement/FileInfo.java b/backend/services/project-management/src/main/java/io/metersphere/project/dto/filemanagement/FileInfo.java index 683bd6e9aa..028e10fbc1 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/dto/filemanagement/FileInfo.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/dto/filemanagement/FileInfo.java @@ -24,6 +24,9 @@ public class FileInfo implements Serializable { @Schema(description = "文件名称") private String fileName; + @Schema(description = "原始文件名") + private String originalName; + @Schema(description = "文件大小") private Long size; diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileAssociationMapper.xml b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileAssociationMapper.xml index 5ba6897f81..dfd23bba4e 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileAssociationMapper.xml +++ b/backend/services/project-management/src/main/java/io/metersphere/project/mapper/ExtFileAssociationMapper.xml @@ -20,6 +20,7 @@ file_association.id AS id, file_association.file_id AS fileId, CONCAT( file_metadata.`name`, IF(LENGTH(file_metadata.type) = 0, '', '.'), file_metadata.type ) AS fileName, + file_metadata.original_name, file_metadata.size AS size, file_metadata.storage, file_metadata.project_id,