refactor(工作台): 优化 API 统计时内存占用较大

This commit is contained in:
fit2-zhao 2024-11-26 10:54:21 +08:00 committed by Craftsman
parent c7bfb50fb0
commit ccf932f03b
5 changed files with 61 additions and 52 deletions

View File

@ -97,26 +97,29 @@ public class ApiDefinitionController {
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ)
@CheckOwner(resourceId = "#projectId", resourceType = "project") @CheckOwner(resourceId = "#projectId", resourceType = "project")
public ApiCoverageDTO rage(@PathVariable String projectId) { public ApiCoverageDTO rage(@PathVariable String projectId) {
List<String> apiAllIds = new ArrayList<>(); // 筛选出所有 API ID HTTP 类型的 API
List<ApiDefinition> httpApiList = new ArrayList<>(); List<ApiDefinition> apiDefinitions = extApiDefinitionMapper.selectBaseInfoByProjectId(projectId);
extApiDefinitionMapper.selectBaseInfoByProjectId(projectId).forEach(apiDefinition -> { List<String> apiAllIds = apiDefinitions.stream().map(ApiDefinition::getId).toList();
if (StringUtils.equalsIgnoreCase(apiDefinition.getProtocol(), "http")) { List<ApiDefinition> httpApiList = apiDefinitions.stream()
httpApiList.add(apiDefinition); .filter(api -> StringUtils.equalsIgnoreCase(api.getProtocol(), "http"))
} .toList();
apiAllIds.add(apiDefinition.getId());
});
// 获取 API 定义测试用例 ID 和场景步骤中的 API ID
List<String> apiDefinitionIdFromCase = extApiTestCaseMapper.selectApiId(projectId); List<String> apiDefinitionIdFromCase = extApiTestCaseMapper.selectApiId(projectId);
List<String> apiInScenarioStep = extApiScenarioStepMapper.selectResourceId(projectId, ApiScenarioStepType.API.name()); List<String> apiInScenarioStep = new ArrayList<>(extApiScenarioStepMapper.selectResourceId(projectId, ApiScenarioStepType.API.name()));
List<String> apiCaseIdInStep = extApiScenarioStepMapper.selectResourceId(projectId, ApiScenarioStepType.API_CASE.name()); List<String> apiCaseIdInStep = extApiScenarioStepMapper.selectResourceId(projectId, ApiScenarioStepType.API_CASE.name());
// 如果有场景步骤中的 API 用例 ID追加相关 API ID
if (CollectionUtils.isNotEmpty(apiCaseIdInStep)) { if (CollectionUtils.isNotEmpty(apiCaseIdInStep)) {
List<String> apiCaseIdInScenarioStep = extApiTestCaseMapper.selectApiIdByCaseId(apiCaseIdInStep); List<String> apiCaseIdInScenarioStep = extApiTestCaseMapper.selectApiIdByCaseId(apiCaseIdInStep);
apiInScenarioStep.addAll(apiCaseIdInScenarioStep); apiInScenarioStep.addAll(apiCaseIdInScenarioStep);
} }
List<String> apiInStepList = apiScenarioService.selectApiIdInCustomRequest(projectId, httpApiList); // 获取自定义步骤中的 API ID 并合并
List<String> apiInStepList = new ArrayList<>(apiScenarioService.selectApiIdInCustomRequest(projectId, httpApiList));
apiInStepList.addAll(apiInScenarioStep); apiInStepList.addAll(apiInScenarioStep);
// 构建结果 DTO
return new ApiCoverageDTO(apiAllIds, apiDefinitionIdFromCase, apiInStepList); return new ApiCoverageDTO(apiAllIds, apiDefinitionIdFromCase, apiInStepList);
} }

View File

@ -1,7 +1,6 @@
package io.metersphere.api.mapper; package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiScenarioCsvStep; import io.metersphere.api.domain.ApiScenarioCsvStep;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.scenario.ApiScenarioStepDTO; import io.metersphere.api.dto.scenario.ApiScenarioStepDTO;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@ -29,5 +28,5 @@ public interface ExtApiScenarioStepMapper {
List<String> selectResourceId(@Param("projectId") String projectId, @Param("stepType") String stepType); List<String> selectResourceId(@Param("projectId") String projectId, @Param("stepType") String stepType);
List<ApiScenarioStep> selectCustomRequestConfigByProjectId(String projectId); List<String> selectCustomRequestConfigByProjectId(String projectId);
} }

View File

@ -37,12 +37,14 @@
AND scenario.project_id = #{projectId} AND scenario.project_id = #{projectId}
AND scenario.deleted IS FALSE AND scenario.deleted IS FALSE
</select> </select>
<select id="selectCustomRequestConfigByProjectId" resultType="io.metersphere.api.domain.ApiScenarioStep"> <select id="selectCustomRequestConfigByProjectId" resultType="java.lang.String">
select step.id, step.config select step.id
from api_scenario_step step from api_scenario_step step
INNER JOIN api_scenario scenario INNER JOIN api_scenario scenario
ON step.scenario_id = scenario.id ON step.scenario_id = scenario.id
where scenario.project_id = #{0} where scenario.project_id = #{0}
AND scenario.deleted IS FALSE AND scenario.deleted IS FALSE
AND step.step_type IN ('API', 'API_CASE', 'CUSTOM_REQUEST')
AND JSON_EXTRACT(step.config, '$.protocol') = 'http'
</select> </select>
</mapper> </mapper>

View File

@ -2375,7 +2375,6 @@ public class ApiScenarioService extends MoveNodeService {
} }
public List<OperationHistoryDTO> operationHistoryList(OperationHistoryRequest request) { public List<OperationHistoryDTO> operationHistoryList(OperationHistoryRequest request) {
return operationHistoryService.listWidthTable(request, SCENARIO_TABLE); return operationHistoryService.listWidthTable(request, SCENARIO_TABLE);
} }
@ -2626,45 +2625,51 @@ public class ApiScenarioService extends MoveNodeService {
} }
public List<String> selectApiIdInCustomRequest(String projectId, List<ApiDefinition> apiDefinitions) { public List<String> selectApiIdInCustomRequest(String projectId, List<ApiDefinition> apiDefinitions) {
List<String> returnList = new ArrayList<>(); if (apiDefinitions == null || apiDefinitions.isEmpty()) {
List<ApiScenarioStep> stepConfigList = extApiScenarioStepMapper.selectCustomRequestConfigByProjectId(projectId); return Collections.emptyList();
List<String> requestIdList = new ArrayList<>(); }
stepConfigList.forEach(step -> requestIdList.add(step.getId()));
// 获取项目中所有的自定义请求 ID
List<String> requestIdList = extApiScenarioStepMapper.selectCustomRequestConfigByProjectId(projectId);
if (requestIdList.isEmpty()) { if (requestIdList.isEmpty()) {
return returnList; return Collections.emptyList();
} }
ApiScenarioStepBlobExample scenarioStepBlobExample = new ApiScenarioStepBlobExample();
scenarioStepBlobExample.createCriteria().andIdIn(requestIdList);
List<ApiScenarioStepBlob> httpRequestStopBlobList = apiScenarioStepBlobMapper.selectByExampleWithBLOBs(scenarioStepBlobExample); // 分批处理配置请求 ID 列表
Map<String, List<String>> methodPathMap = new HashMap<>(); Map<String, Set<String>> methodPathMap = new HashMap<>();
httpRequestStopBlobList.forEach(blob -> { SubListUtils.dealForSubList(requestIdList, 200, batchIds -> {
if (blob.getContent() != null) { // 查询当前批次的 Blob 数据
try { ApiScenarioStepBlobExample scenarioStepBlobExample = new ApiScenarioStepBlobExample();
AbstractMsProtocolTestElement protocolTestElement = ApiDataUtils.parseObject(new String(blob.getContent()), AbstractMsProtocolTestElement.class); scenarioStepBlobExample.createCriteria().andIdIn(batchIds);
if (protocolTestElement instanceof MsHTTPElement msHTTPElement) { List<ApiScenarioStepBlob> blobList = apiScenarioStepBlobMapper.selectByExampleWithBLOBs(scenarioStepBlobExample);
String method = msHTTPElement.getMethod();
if (methodPathMap.containsKey(method)) { // 解析并构建方法与路径映射处理完一个批次后释放内存
methodPathMap.get(method).add(msHTTPElement.getPath()); blobList.stream()
} else { .map(ApiScenarioStepBlob::getContent)
List<String> pathList = new ArrayList<>(); .filter(Objects::nonNull)
pathList.add(msHTTPElement.getPath()); .forEach(content -> {
methodPathMap.put(method, pathList); try {
AbstractMsProtocolTestElement protocolTestElement = ApiDataUtils.parseObject(new String(content), AbstractMsProtocolTestElement.class);
if (protocolTestElement instanceof MsHTTPElement msHTTPElement) {
methodPathMap.computeIfAbsent(msHTTPElement.getMethod(), k -> new HashSet<>())
.add(msHTTPElement.getPath());
}
} catch (Exception e) {
LogUtils.error(e);
} }
} });
} catch (Exception e) { blobList.clear(); // 清空当前批次释放内存
LogUtils.error(e);
}
}
}); });
for (ApiDefinition apiDefinition : apiDefinitions) {
if (methodPathMap.containsKey(apiDefinition.getMethod())) { // 遍历 API 定义匹配路径
String apiPath = apiDefinition.getPath(); return apiDefinitions.stream()
List<String> customUrlList = methodPathMap.get(apiDefinition.getMethod()); .filter(apiDefinition -> {
if (ApiDefinitionUtils.isUrlInList(apiPath, customUrlList)) { Set<String> customUrls = methodPathMap.get(apiDefinition.getMethod());
returnList.add(apiDefinition.getId()); return customUrls != null && ApiDefinitionUtils.isUrlInList(apiDefinition.getPath(), customUrls);
} })
} .map(ApiDefinition::getId)
} .toList();
return returnList;
} }
} }

View File

@ -226,7 +226,7 @@ public class ApiCalculateTest extends BaseTest {
Assertions.assertEquals(16, apiCoverageDTO.getUnCoverWithApiCase()); Assertions.assertEquals(16, apiCoverageDTO.getUnCoverWithApiCase());
Assertions.assertEquals(apiCoverageDTO.getApiCaseCoverage(), CalculateUtils.reportPercentage(apiCoverageDTO.getCoverWithApiCase(), apiCoverageDTO.getAllApiCount())); Assertions.assertEquals(apiCoverageDTO.getApiCaseCoverage(), CalculateUtils.reportPercentage(apiCoverageDTO.getCoverWithApiCase(), apiCoverageDTO.getAllApiCount()));
Assertions.assertEquals(8, apiCoverageDTO.getCoverWithApiScenario()); Assertions.assertEquals(4, apiCoverageDTO.getCoverWithApiScenario());
Assertions.assertEquals(12, apiCoverageDTO.getUnCoverWithApiScenario()); Assertions.assertEquals(12, apiCoverageDTO.getUnCoverWithApiScenario());
Assertions.assertEquals(apiCoverageDTO.getScenarioCoverage(), CalculateUtils.reportPercentage(apiCoverageDTO.getCoverWithApiScenario(), apiCoverageDTO.getAllApiCount())); Assertions.assertEquals(apiCoverageDTO.getScenarioCoverage(), CalculateUtils.reportPercentage(apiCoverageDTO.getCoverWithApiScenario(), apiCoverageDTO.getAllApiCount()));