feat(接口测试): 添加执行文件下载接口

This commit is contained in:
AgAngle 2024-02-28 15:29:08 +08:00 committed by Craftsman
parent bc85e9e40d
commit 49942eb646
5 changed files with 130 additions and 36 deletions

View File

@ -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);
}
}

View File

@ -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<ApiExecuteFileInfo> 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<String> uploadFileIds = request.getUploadFileIds();
if (CollectionUtils.isNotEmpty(uploadFileIds)) {
// 去掉文件管理的文件即通过本地上传的临时文件
List<ApiExecuteFileInfo> localTempFiles = uploadFileIds.stream()
.map(tempFileId -> {
String fileName = apiFileResourceService.getTempFileNameByFileId(tempFileId);
@ -299,40 +310,15 @@ public class ApiExecuteService {
List<String> linkFileIds = request.getLinkFileIds();
// 没有保存的文件管理临时文件
if (CollectionUtils.isNotEmpty(linkFileIds)) {
List<ApiExecuteFileInfo> 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<FileMetadata> fileMetadataList = fileMetadataService.getByFileIds(linkFileIds);
// 添加临时的文件管理的文件
refFiles.addAll(refTempFiles);
refFiles.addAll(getApiExecuteFileInfo(fileMetadataList));
}
taskRequest.setRefFiles(refFiles);
// 获取函数jar包
List<FileMetadata> 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<ApiExecuteFileInfo> getApiExecuteFileInfo(List<FileMetadata> 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);
}
}
}
}

View File

@ -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());
}
}

View File

@ -24,6 +24,9 @@ public class FileInfo implements Serializable {
@Schema(description = "文件名称")
private String fileName;
@Schema(description = "原始文件名")
private String originalName;
@Schema(description = "文件大小")
private Long size;

View File

@ -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,