diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiCaseBatchSyncRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiCaseBatchSyncRequest.java index b76187840a..e57a49dddd 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiCaseBatchSyncRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiCaseBatchSyncRequest.java @@ -19,8 +19,8 @@ public class ApiCaseBatchSyncRequest extends ApiTestCaseBatchRequest implements @Data public static class ApiCaseSyncNotificationRequest { - @Schema(description = "是否通知接口创建人", defaultValue = "true") - private Boolean apiCreator = true; + @Schema(description = "是否通知接口用例创建人", defaultValue = "true") + private Boolean apiCaseCreator = true; @Schema(description = "是否通知引用该用例的场景创建人", defaultValue = "true") private Boolean scenarioCreator = true; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiTestCaseDTO.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiTestCaseDTO.java index d5ea223a78..013b39a873 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiTestCaseDTO.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiTestCaseDTO.java @@ -35,6 +35,12 @@ public class ApiTestCaseDTO { @Schema(description = "接口fk") private String apiDefinitionId; + @Schema(description = "接口num") + private Long apiDefinitionNum; + + @Schema(description = "接口名称") + private String apiDefinitionName; + @Schema(description = "环境fk") private String environmentId; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.java b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.java index 99ca8b93aa..66d977791f 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.java @@ -111,4 +111,8 @@ public interface ExtApiTestCaseMapper { List getCaseListBySelectIds(@Param("isRepeat") boolean isRepeat, @Param("projectId") String projectId, @Param("ids") List ids, @Param("testPlanId") String testPlanId, @Param("protocols") List protocols); void setApiChangeByApiDefinitionId(@Param("apiDefinitionId") String apiDefinitionId); + + List getRefApiScenarioCreator(@Param("ids") List caseIds); + + void clearApiChange(@Param("ids") List ids); } \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.xml index 338cc194b5..62211e2a86 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.xml +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiTestCaseMapper.xml @@ -35,6 +35,14 @@ and api_change is false and ignore_api_change is false + + update api_test_case + set api_change = false + where api_change = true and id in + + #{id} + + + \ No newline at end of file diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseLogService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseLogService.java index 510b179125..8675faa51f 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseLogService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseLogService.java @@ -236,14 +236,13 @@ public class ApiTestCaseLogService { saveBatchLog(projectId, apiTestCases, operator, OperationLogType.RECOVER.name(), false, OperationLogModule.API_TEST_MANAGEMENT_RECYCLE); } - public void batchSyncLog(Map originMap, Map modifiedMap) { + public void batchSyncLog(Map originMap, Map modifiedMap, Project project) { List logs = new ArrayList<>(); originMap.forEach((id, origin) -> { ApiTestCaseLogDTO modified = modifiedMap.get(id); if (modified == null) { return; } - Project project = projectMapper.selectByPrimaryKey(origin.getProjectId()); LogDTO dto = LogDTOBuilder.builder() .projectId(project.getId()) .organizationId(project.getOrganizationId()) diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseNoticeService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseNoticeService.java index acec102fea..7692c39e31 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseNoticeService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseNoticeService.java @@ -2,9 +2,11 @@ package io.metersphere.api.service.definition; import io.metersphere.api.domain.ApiTestCase; import io.metersphere.api.domain.ApiTestCaseExample; +import io.metersphere.api.dto.definition.ApiCaseBatchSyncRequest; import io.metersphere.api.dto.definition.ApiTestCaseAddRequest; import io.metersphere.api.dto.definition.ApiTestCaseUpdateRequest; import io.metersphere.api.mapper.ApiTestCaseMapper; +import io.metersphere.api.mapper.ExtApiTestCaseMapper; import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.SubListUtils; @@ -15,11 +17,11 @@ import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.service.CommonNoticeSendService; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; @Service public class ApiTestCaseNoticeService { @@ -30,6 +32,8 @@ public class ApiTestCaseNoticeService { private ApiTestCaseMapper apiTestCaseMapper; @Resource private CommonNoticeSendService commonNoticeSendService; + @Resource + private ExtApiTestCaseMapper extApiTestCaseMapper; public ApiDefinitionCaseDTO addCaseDto(ApiTestCaseAddRequest request) { ApiDefinitionCaseDTO caseDTO = new ApiDefinitionCaseDTO(); @@ -70,4 +74,56 @@ public class ApiTestCaseNoticeService { }); } } + + public void batchSyncSendNotice(List apiTestCases, User user, String projectId, + ApiCaseBatchSyncRequest.ApiCaseSyncNotificationRequest notificationConfig, String event) { + + if (CollectionUtils.isEmpty(apiTestCases)) { + return; + } + + Map> caseRefApiScenarioCreatorMap = null; + if (BooleanUtils.isTrue(notificationConfig.getScenarioCreator())) { + List caseIds = apiTestCases.stream().map(ApiTestCase::getId).toList(); + // 获取引用该用例的场景的创建人信息 + List caseRefApiScenarioCreators = extApiTestCaseMapper.getRefApiScenarioCreator(caseIds); + // 构建用例和创建人的映射关系 + caseRefApiScenarioCreatorMap = caseRefApiScenarioCreators.stream().collect(Collectors.groupingBy(ApiTestCase::getId, + Collectors.mapping(ApiTestCase::getCreateUser, Collectors.toSet()))); + } + + List noticeLists = apiTestCases.stream() + .map(apiTestCase -> { + ApiDefinitionCaseDTO apiDefinitionCaseDTO = new ApiDefinitionCaseDTO(); + BeanUtils.copyBean(apiDefinitionCaseDTO, apiTestCase); + return apiDefinitionCaseDTO; + }) + .toList(); + List resources = new ArrayList<>(JSON.parseArray(JSON.toJSONString(noticeLists), Map.class)); + + if (BooleanUtils.isTrue(notificationConfig.getScenarioCreator()) || BooleanUtils.isTrue(notificationConfig.getApiCaseCreator())) { + for (Map resource : resources) { + String caseId = (String) resource.get("id"); + String relatedUsers = null; + if (BooleanUtils.isTrue(notificationConfig.getScenarioCreator())) { + // 添加引用该用例的场景的创建人 + Set userIds = Optional.ofNullable(caseRefApiScenarioCreatorMap.get(caseId)).orElse(new HashSet<>(1)); + relatedUsers = userIds.stream().collect(Collectors.joining(";")); + } + + if (BooleanUtils.isTrue(notificationConfig.getApiCaseCreator())) { + // 添加用例创建人 + String createUser = (String) resource.get("createUser"); + if (relatedUsers == null) { + relatedUsers = createUser; + } else { + relatedUsers += ";" + createUser; + } + } + // 添加特殊通知人 + resource.put("relatedUsers", relatedUsers); + } + } + commonNoticeSendService.sendNotice(NoticeConstants.TaskType.API_DEFINITION_TASK, event, resources, user, projectId); + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java index ff34d5bcf1..7289e64692 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiTestCaseService.java @@ -32,11 +32,13 @@ import io.metersphere.sdk.dto.api.task.*; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.mapper.EnvironmentMapper; import io.metersphere.sdk.util.*; +import io.metersphere.system.domain.User; import io.metersphere.system.dto.OperationHistoryDTO; import io.metersphere.system.dto.request.OperationHistoryRequest; import io.metersphere.system.dto.sdk.request.NodeMoveRequest; import io.metersphere.system.dto.sdk.request.PosRequest; import io.metersphere.system.log.constants.OperationLogModule; +import io.metersphere.system.mapper.UserMapper; import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.service.OperationHistoryService; import io.metersphere.system.service.UserLoginService; @@ -112,6 +114,8 @@ public class ApiTestCaseService extends MoveNodeService { private ExtApiReportMapper extApiReportMapper; @Resource private FunctionalCaseTestMapper functionalCaseTestMapper; + @Resource + private UserMapper userMapper; private static final String CASE_TABLE = "api_test_case"; @@ -239,6 +243,8 @@ public class ApiTestCaseService extends MoveNodeService { apiTestCaseDTO.setMethod(apiDefinition.getMethod()); apiTestCaseDTO.setPath(apiDefinition.getPath()); apiTestCaseDTO.setProtocol(apiDefinition.getProtocol()); + apiTestCaseDTO.setApiDefinitionNum(apiDefinition.getNum()); + apiTestCaseDTO.setApiDefinitionName(apiDefinition.getName()); ApiTestCaseFollowerExample example = new ApiTestCaseFollowerExample(); example.createCriteria().andCaseIdEqualTo(id).andUserIdEqualTo(userId); List followers = apiTestCaseFollowerMapper.selectByExample(example); @@ -1001,10 +1007,11 @@ public class ApiTestCaseService extends MoveNodeService { if (CollectionUtils.isEmpty(ids)) { return; } - SubListUtils.dealForSubList(ids, 500, subList -> doBatchSyncApiChange(request, subList, userId)); + Project project = projectMapper.selectByPrimaryKey(request.getProjectId()); + SubListUtils.dealForSubList(ids, 500, subList -> doBatchSyncApiChange(request, subList, userId, project)); } - public void doBatchSyncApiChange(ApiCaseBatchSyncRequest request, List ids, String userId) { + public void doBatchSyncApiChange(ApiCaseBatchSyncRequest request, List ids, String userId, Project project) { ApiTestCaseExample example = new ApiTestCaseExample(); example.createCriteria().andIdIn(ids); List apiTestCases = apiTestCaseMapper.selectByExample(example); @@ -1026,6 +1033,9 @@ public class ApiTestCaseService extends MoveNodeService { Map apiDefinitionBlobMap = apiDefinitionBlobMapper.selectByExampleWithBLOBs(apiDefinitionBlobExample) .stream() .collect(Collectors.toMap(ApiDefinitionBlob::getId, Function.identity())); + + // 清除接口变更标识 + extApiTestCaseMapper.clearApiChange(ids); try { for (ApiTestCase apiTestCase : apiTestCases) { ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMap.get(apiTestCase.getApiDefinitionId()); @@ -1034,6 +1044,7 @@ public class ApiTestCaseService extends MoveNodeService { AbstractMsTestElement apiTestCaseMsTestElement = getTestElement(apiTestCaseBlob); boolean requestParamDifferent = HttpRequestParamDiffUtils.isRequestParamDiff(request.getSyncItems(), apiMsTestElement, apiTestCaseMsTestElement); if (requestParamDifferent) { + // 如果参数与定义不一致,则同步参数,并记录日志和发送通知 ApiTestCaseLogDTO originCase = BeanUtils.copyBean(new ApiTestCaseLogDTO(), apiTestCase); originCase.setRequest(apiTestCaseMsTestElement); originMap.put(apiTestCase.getId(), originCase); @@ -1051,7 +1062,10 @@ public class ApiTestCaseService extends MoveNodeService { modifiedMap.put(apiTestCase.getId(), originCase); } } - apiTestCaseLogService.batchSyncLog(originMap, modifiedMap); + apiTestCaseLogService.batchSyncLog(originMap, modifiedMap, project); + + User user = userMapper.selectByPrimaryKey(userId); + apiTestCaseNoticeService.batchSyncSendNotice(new ArrayList<>(modifiedMap.values()), user, project.getId(), request.getNotificationConfig(), NoticeConstants.Event.CASE_UPDATE); } finally { sqlSession.flushStatements(); SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory); @@ -1064,6 +1078,8 @@ public class ApiTestCaseService extends MoveNodeService { ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(apiDefinition.getId()); AbstractMsTestElement apiMsTestElement = getApiMsTestElement(apiDefinitionBlob); AbstractMsTestElement apiTestCaseMsTestElement = ApiDataUtils.parseObject(JSON.toJSONString(request.getApiCaseRequest()), AbstractMsTestElement.class); + // 清除接口变更标识 + extApiTestCaseMapper.clearApiChange(List.of(request.getId())); return HttpRequestParamDiffUtils.syncRequestDiff(request, apiMsTestElement, apiTestCaseMsTestElement); } } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiTestCaseControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiTestCaseControllerTests.java index 286c797a5b..7e26a3fa30 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiTestCaseControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiTestCaseControllerTests.java @@ -17,6 +17,7 @@ import io.metersphere.api.service.ApiCommonService; import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.service.BaseFileManagementTestService; import io.metersphere.api.service.definition.ApiReportService; +import io.metersphere.api.service.definition.ApiTestCaseNoticeService; import io.metersphere.api.service.definition.ApiTestCaseService; import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.plan.domain.TestPlanApiCase; @@ -47,10 +48,13 @@ import io.metersphere.sdk.util.JSON; import io.metersphere.system.base.BaseTest; import io.metersphere.system.controller.handler.ResultHolder; import io.metersphere.system.controller.handler.result.MsHttpResultCode; +import io.metersphere.system.domain.User; import io.metersphere.system.dto.OperationHistoryDTO; import io.metersphere.system.dto.request.OperationHistoryRequest; import io.metersphere.system.dto.sdk.request.PosRequest; import io.metersphere.system.log.constants.OperationLogType; +import io.metersphere.system.mapper.UserMapper; +import io.metersphere.system.notice.constants.NoticeConstants; import io.metersphere.system.service.OperationHistoryService; import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.NumGenerator; @@ -165,6 +169,10 @@ public class ApiTestCaseControllerTests extends BaseTest { private TestPlanMapper testPlanMapper; @Resource private TestPlanApiCaseMapper testPlanApiCaseMapper; + @Resource + private ApiTestCaseNoticeService apiTestCaseNoticeService; + @Resource + private UserMapper userMapper; @Override public String getBasePath() { @@ -465,8 +473,9 @@ public class ApiTestCaseControllerTests extends BaseTest { updateCase.setId(apiTestCase.getId()); apiTestCaseMapper.updateByPrimaryKeySelective(updateCase); this.requestGetWithOk(API_CHANGE_CLEAR, apiTestCase.getId()); - Assertions.assertFalse(apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()).getApiChange()); - Assertions.assertTrue(apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()).getIgnoreApiDiff()); + ApiTestCase result = apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()); + Assertions.assertFalse(result.getApiChange()); + Assertions.assertTrue(result.getIgnoreApiDiff()); //校验日志 checkLog(apiTestCase.getId(), OperationLogType.UPDATE, getBasePath() + MessageFormat.format(API_CHANGE_CLEAR, apiTestCase.getId())); @@ -507,6 +516,8 @@ public class ApiTestCaseControllerTests extends BaseTest { public void batchSyncApiChange() throws Exception { ApiCaseBatchSyncRequest request = new ApiCaseBatchSyncRequest(); request.setProjectId(DEFAULT_PROJECT_ID); + request.getNotificationConfig().setApiCaseCreator(true); + request.getNotificationConfig().setScenarioCreator(true); this.requestPostWithOk(BATCH_API_CHANGE_SYNC, request); request.setSelectIds(List.of(apiTestCase.getId())); @@ -516,6 +527,16 @@ public class ApiTestCaseControllerTests extends BaseTest { ApiTestCase result = apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()); Assertions.assertFalse(result.getApiChange()); + User user = userMapper.selectByPrimaryKey("admin"); + request.getNotificationConfig().setScenarioCreator(false); + apiTestCaseNoticeService.batchSyncSendNotice(List.of(apiTestCase), user, DEFAULT_PROJECT_ID, request.getNotificationConfig(), NoticeConstants.Event.CASE_UPDATE); + + request.getNotificationConfig().setApiCaseCreator(false); + apiTestCaseNoticeService.batchSyncSendNotice(List.of(apiTestCase), user, DEFAULT_PROJECT_ID, request.getNotificationConfig(), NoticeConstants.Event.CASE_UPDATE); + + request.getNotificationConfig().setScenarioCreator(true); + apiTestCaseNoticeService.batchSyncSendNotice(List.of(apiTestCase), user, DEFAULT_PROJECT_ID, request.getNotificationConfig(), NoticeConstants.Event.CASE_UPDATE); + //校验日志 checkLog(apiTestCase.getId(), OperationLogType.UPDATE, getBasePath() + BATCH_API_CHANGE_SYNC); @@ -530,6 +551,8 @@ public class ApiTestCaseControllerTests extends BaseTest { request.setId(apiTestCase.getId()); request.setApiCaseRequest(JSON.parseObject(ApiDataUtils.toJSONString(new MsHTTPElement()))); this.requestPostWithOk(API_CHANGE_SYNC, request); + ApiTestCase result = apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()); + Assertions.assertFalse(result.getApiChange()); // @@校验权限 requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_UPDATE, API_CHANGE_SYNC, request); @@ -703,6 +726,8 @@ public class ApiTestCaseControllerTests extends BaseTest { msHTTPElement.setModuleId(apiDefinition.getModuleId()); msHTTPElement.setNum(apiDefinition.getNum()); copyApiTestCaseDTO.setRequest(msTestElement); + copyApiTestCaseDTO.setApiDefinitionName(apiDefinition.getName()); + copyApiTestCaseDTO.setApiDefinitionNum(apiDefinition.getNum()); msHTTPElement = (MsHTTPElement) apiTestCaseDTO.getRequest(); Assertions.assertEquals(msHTTPElement.getMethod(), apiDefinition.getMethod()); diff --git a/frontend/src/models/apiTest/management.ts b/frontend/src/models/apiTest/management.ts index a9753da58c..d5898853fe 100644 --- a/frontend/src/models/apiTest/management.ts +++ b/frontend/src/models/apiTest/management.ts @@ -413,7 +413,7 @@ export interface syncItem { // 批量同步 export interface batchSyncForm { notificationConfig: { - apiCreator: boolean; + apiCaseCreator: boolean; scenarioCreator: boolean; }; // 同步项目 diff --git a/frontend/src/views/api-test/management/components/management/case/syncModal.vue b/frontend/src/views/api-test/management/components/management/case/syncModal.vue index 644a220575..da0440d119 100644 --- a/frontend/src/views/api-test/management/components/management/case/syncModal.vue +++ b/frontend/src/views/api-test/management/components/management/case/syncModal.vue @@ -53,7 +53,7 @@
- +
{{ t('case.NoticeApiCaseCreator') }}
@@ -98,7 +98,7 @@ const initForm: batchSyncForm = { notificationConfig: { - apiCreator: false, + apiCaseCreator: false, scenarioCreator: false, }, // 同步项目