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

View File

@ -1,7 +1,6 @@
package io.metersphere.api.mapper;
import io.metersphere.api.domain.ApiScenarioCsvStep;
import io.metersphere.api.domain.ApiScenarioStep;
import io.metersphere.api.dto.scenario.ApiScenarioStepDTO;
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<ApiScenarioStep> selectCustomRequestConfigByProjectId(String projectId);
List<String> selectCustomRequestConfigByProjectId(String projectId);
}

View File

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

View File

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

View File

@ -226,7 +226,7 @@ public class ApiCalculateTest extends BaseTest {
Assertions.assertEquals(16, apiCoverageDTO.getUnCoverWithApiCase());
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(apiCoverageDTO.getScenarioCoverage(), CalculateUtils.reportPercentage(apiCoverageDTO.getCoverWithApiScenario(), apiCoverageDTO.getAllApiCount()));