diff --git a/test-track/backend/src/main/java/io/metersphere/controller/TestCaseNodeController.java b/test-track/backend/src/main/java/io/metersphere/controller/TestCaseNodeController.java index 4bc3aad656..958f710502 100644 --- a/test-track/backend/src/main/java/io/metersphere/controller/TestCaseNodeController.java +++ b/test-track/backend/src/main/java/io/metersphere/controller/TestCaseNodeController.java @@ -4,21 +4,21 @@ import io.metersphere.base.domain.TestCaseNode; import io.metersphere.commons.constants.OperLogConstants; import io.metersphere.commons.constants.OperLogModule; import io.metersphere.commons.constants.PermissionConstants; +import io.metersphere.dto.TestCaseNodeDTO; import io.metersphere.log.annotation.MsAuditLog; import io.metersphere.log.annotation.MsRequestLog; +import io.metersphere.plan.request.function.QueryTestPlanCaseRequest; import io.metersphere.request.testcase.DragNodeRequest; import io.metersphere.request.testcase.QueryNodeRequest; import io.metersphere.request.testcase.QueryTestCaseRequest; -import io.metersphere.plan.request.function.QueryTestPlanCaseRequest; import io.metersphere.request.testreview.QueryCaseReviewRequest; -import io.metersphere.dto.TestCaseNodeDTO; import io.metersphere.service.BaseCheckPermissionService; import io.metersphere.service.TestCaseNodeService; import io.metersphere.service.wapper.CheckPermissionService; +import jakarta.annotation.Resource; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; import java.util.List; import java.util.Map; import java.util.Optional; diff --git a/test-track/backend/src/main/java/io/metersphere/listener/ProjectCreatedListener.java b/test-track/backend/src/main/java/io/metersphere/listener/ProjectCreatedListener.java index 0ce854958c..a47409576c 100644 --- a/test-track/backend/src/main/java/io/metersphere/listener/ProjectCreatedListener.java +++ b/test-track/backend/src/main/java/io/metersphere/listener/ProjectCreatedListener.java @@ -1,21 +1,16 @@ package io.metersphere.listener; -import io.metersphere.base.domain.ModuleNode; import io.metersphere.base.domain.Project; -import io.metersphere.base.domain.TestCaseNodeExample; import io.metersphere.base.mapper.ProjectMapper; import io.metersphere.base.mapper.ext.ExtModuleNodeMapper; import io.metersphere.commons.constants.KafkaTopicConstants; -import io.metersphere.commons.constants.ProjectModuleDefaultNodeEnum; import io.metersphere.commons.utils.LogUtil; +import io.metersphere.service.TestCaseNodeService; +import jakarta.annotation.Resource; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Component; -import jakarta.annotation.Resource; -import java.util.List; -import java.util.UUID; - @Component public class ProjectCreatedListener { public static final String CONSUME_ID = "test_track_project-created"; @@ -24,6 +19,8 @@ public class ProjectCreatedListener { private ExtModuleNodeMapper extModuleNodeMapper; @Resource private ProjectMapper projectMapper; + @Resource + private TestCaseNodeService testCaseNodeService; @KafkaListener(id = CONSUME_ID, topics = KafkaTopicConstants.PROJECT_CREATED_TOPIC, groupId = "${spring.application.name}") public void consume(ConsumerRecord record) { @@ -37,23 +34,7 @@ public class ProjectCreatedListener { if (project == null) { return; } - - // 防止重复创建功能用例默认节点 - TestCaseNodeExample example = new TestCaseNodeExample(); - example.createCriteria() - .andProjectIdEqualTo(projectId).andNameEqualTo(ProjectModuleDefaultNodeEnum.TEST_CASE_DEFAULT_NODE.getNodeName()); - List moduleNodes = extModuleNodeMapper.selectByExample(ProjectModuleDefaultNodeEnum.TEST_CASE_DEFAULT_NODE.getTableName(), example); - if (moduleNodes.size() == 0) { - ModuleNode record = new ModuleNode(); - record.setId(UUID.randomUUID().toString()); - record.setCreateUser(project.getCreateUser()); - record.setPos(1.0); - record.setLevel(1); - record.setCreateTime(System.currentTimeMillis()); - record.setUpdateTime(System.currentTimeMillis()); - record.setProjectId(projectId); - record.setName(ProjectModuleDefaultNodeEnum.TEST_CASE_DEFAULT_NODE.getNodeName()); - extModuleNodeMapper.insert(ProjectModuleDefaultNodeEnum.TEST_CASE_DEFAULT_NODE.getTableName(), record); - } + // 创建功能用例默认模块 + testCaseNodeService.createDefaultNode(projectId); } } diff --git a/test-track/backend/src/main/java/io/metersphere/service/TestCaseNodeService.java b/test-track/backend/src/main/java/io/metersphere/service/TestCaseNodeService.java index 4f0be0ba72..208a931661 100644 --- a/test-track/backend/src/main/java/io/metersphere/service/TestCaseNodeService.java +++ b/test-track/backend/src/main/java/io/metersphere/service/TestCaseNodeService.java @@ -12,6 +12,7 @@ import io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper; import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper; import io.metersphere.base.mapper.ext.ExtTestReviewCaseMapper; import io.metersphere.commons.constants.MicroServiceName; +import io.metersphere.commons.constants.ProjectModuleDefaultNodeEnum; import io.metersphere.commons.constants.TestCaseConstants; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.*; @@ -36,6 +37,8 @@ import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionUtils; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -69,6 +72,10 @@ public class TestCaseNodeService extends NodeTreeService { TestPlanProjectService testPlanProjectService; @Resource TestPlanService testPlanService; + @Resource + private RedissonClient redissonClient; + + private static final String TEST_CASE_DEFAULT_NODE_CREATE_KEY = "TEST_CASE:DEFAULT_NODE:CREATE"; public TestCaseNodeService() { super(TestCaseNodeDTO.class); @@ -122,25 +129,12 @@ public class TestCaseNodeService extends NodeTreeService { } } - public TestCaseNode getDefaultNode(String projectId) { - TestCaseNodeExample example = new TestCaseNodeExample(); - example.createCriteria().andProjectIdEqualTo(projectId).andNameEqualTo("未规划用例").andParentIdIsNull(); - List list = testCaseNodeMapper.selectByExample(example); - if (CollectionUtils.isEmpty(list)) { - NodeNumDTO record = new NodeNumDTO(); - record.setId(UUID.randomUUID().toString()); - record.setCreateUser(SessionUtils.getUserId()); - record.setName("未规划用例"); - record.setPos(1.0); - record.setLevel(1); - record.setCreateTime(System.currentTimeMillis()); - record.setUpdateTime(System.currentTimeMillis()); - record.setProjectId(projectId); - testCaseNodeMapper.insert(record); - record.setCaseNum(0); - return record; + public TestCaseNode checkDefaultNode(String projectId) { + TestCaseNode defaultNode = getDefaultNode(projectId); + if (defaultNode == null) { + return this.createDefaultNode(projectId); } else { - return list.get(0); + return defaultNode; } } @@ -222,7 +216,7 @@ public class TestCaseNodeService extends NodeTreeService { request.setQueryUi(queryUi); this.setRequestWeekParam(request); // 判断当前项目下是否有默认模块,没有添加默认模块 - this.getDefaultNode(projectId); + this.checkDefaultNode(projectId); request.setProjectId(projectId); request.setUserId(SessionUtils.getUserId()); request.setNodeIds(null); @@ -291,8 +285,10 @@ public class TestCaseNodeService extends NodeTreeService { public List getTrashCaseNode(String projectId, QueryTestCaseRequest request) { // 初始化回收站中模块被删除的用例, 挂在默认未规划模块, 获取回收站模块节点数据 - TestCaseNode defaultNode = this.getDefaultNode(projectId); - extTestCaseMapper.updateNoModuleTrashNodeToDefault(projectId, defaultNode.getId(), defaultNode.getName()); + TestCaseNode defaultNode = this.checkDefaultNode(projectId); + if (defaultNode != null) { + extTestCaseMapper.updateNoModuleTrashNodeToDefault(projectId, defaultNode.getId(), defaultNode.getName()); + } request.setProjectId(projectId); request.setNodeIds(null); ServiceUtils.setBaseQueryRequestCustomMultipleFields(request); @@ -761,4 +757,49 @@ public class TestCaseNodeService extends NodeTreeService { } } } + + public NodeNumDTO createDefaultNode(String projectId) { + // 加锁, 防止并发创建 + RLock lock = redissonClient.getLock(TEST_CASE_DEFAULT_NODE_CREATE_KEY + ":" + projectId); + if (lock.tryLock()) { + try { + // 双重检查, 判断是否已经存在默认节点 + if (getDefaultNode(projectId) != null) { + return null; + } + + // 创建默认节点, 只执行一次 + NodeNumDTO record = new NodeNumDTO(); + record.setId(UUID.randomUUID().toString()); + record.setCreateUser(SessionUtils.getUserId()); + record.setName(ProjectModuleDefaultNodeEnum.TEST_CASE_DEFAULT_NODE.getNodeName()); + record.setPos(1.0); + record.setLevel(1); + record.setCreateTime(System.currentTimeMillis()); + record.setUpdateTime(System.currentTimeMillis()); + record.setProjectId(projectId); + testCaseNodeMapper.insert(record); + record.setCaseNum(0); + return record; + }catch (Exception e){ + LogUtil.error(e); + return null; + } finally { + lock.unlock(); + } + } else { + return null; + } + } + + public TestCaseNode getDefaultNode(String projectId) { + TestCaseNodeExample example = new TestCaseNodeExample(); + example.createCriteria().andProjectIdEqualTo(projectId).andNameEqualTo(ProjectModuleDefaultNodeEnum.TEST_CASE_DEFAULT_NODE.getNodeName()).andParentIdIsNull(); + List defaultNodes = testCaseNodeMapper.selectByExample(example); + if (CollectionUtils.isEmpty(defaultNodes)) { + return null; + } else { + return defaultNodes.get(0); + } + } }