From c452b0729d5ee255b34aafd4d91b1ab4b068591f Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 9 Oct 2026 20:53:19 +0800 Subject: [PATCH] fix(pms): 串行化个人排序更新并统一项目删除加锁顺序 --- yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectService.java | 10 ++++++++++ yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImpl.java | 12 ++++++++++-- yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImpl.java | 25 +++++++++++++++++++++++-- yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImplTest.java | 30 ++++++++++++++++++++++++++++++ yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemServiceImplTest.java | 23 +++++++++++++++++++++-- yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImplTest.java | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-pms/src/test/resources/sql/clean.sql | 1 + 7 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImplTest.java diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectService.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectService.java index 560dff7..8330577 100644 --- a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectService.java +++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectService.java @@ -45,6 +45,16 @@ public interface PmsProjectService { PmsProjectDO getProject(Long id); /** + * 获得项目,并锁定项目行 + * + * 需在事务中调用,锁保持至事务结束 + * + * @param id 项目编号 + * @return 项目 + */ + PmsProjectDO getProjectForUpdate(Long id); + + /** * 获得项目详情并记录访问时间 * * @param id 项目编号 diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImpl.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImpl.java index 8674dc8..bef6c65 100644 --- a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImpl.java +++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImpl.java @@ -118,6 +118,11 @@ public class PmsProjectServiceImpl implements PmsProjectService { } @Override + public PmsProjectDO getProjectForUpdate(Long id) { + return projectMapper.selectOneForUpdate(PmsProjectDO::getId, id); + } + + @Override public PmsProjectDO getProjectAndUpdateVisitTime(Long id, Long userId) { // 1. 校验当前用户可以查看项目 PmsProjectDO project = projectMemberService.validateProjectReadable(id, userId); @@ -307,8 +312,11 @@ public class PmsProjectServiceImpl implements PmsProjectService { @Override @Transactional(rollbackFor = Exception.class) public void deleteProject(Long id, Long userId) { - // 1.1 校验项目存在 - PmsProjectDO project = validateProjectExists(id); + // 1.1 锁定项目并校验存在,与个人排序保持一致的加锁顺序(https://t.zsxq.com/V48Hl) + PmsProjectDO project = getProjectForUpdate(id); + if (project == null) { + throw exception(PROJECT_NOT_EXISTS); + } // 1.2 校验项目位于回收站 if (ObjectUtil.notEqual(PmsProjectStatusEnum.RECYCLED.getStatus(), project.getStatus())) { throw exception(PROJECT_STATUS_INVALID); diff --git a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImpl.java b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImpl.java index c40f3db..652b38a 100644 --- a/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImpl.java +++ b/yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImpl.java @@ -1,10 +1,14 @@ package cn.iocoder.yudao.module.pms.service.pm.workitem; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO; import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemDO; import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO; import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemUserSortMapper; +import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectStatusEnum; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectService; import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -13,7 +17,10 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS; +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID; /** * PMS 工作项个人排序 Service 实现类 @@ -27,13 +34,27 @@ public class PmsWorkItemUserSortServiceImpl implements PmsWorkItemUserSortServic @Resource private PmsWorkItemUserSortMapper workItemUserSortMapper; + @Lazy // 延迟加载,避免循环依赖 + @Resource + private PmsProjectService projectService; + @Override @Transactional(rollbackFor = Exception.class) public void updateWorkItemUserSort(Long projectId, List workItemIds, Long userId) { - // 1. 删除原有个人排序 + // 1.1 锁定项目,避免并发更新个人排序产生重复记录(https://t.zsxq.com/V48Hl) + PmsProjectDO project = projectService.getProjectForUpdate(projectId); + // 1.2 校验锁定后的项目状态,避免项目并发回收、删除后继续写入个人排序 + if (project == null) { + throw exception(PROJECT_NOT_EXISTS); + } + if (!PmsProjectStatusEnum.ACTIVE.getStatus().equals(project.getStatus())) { + throw exception(PROJECT_STATUS_INVALID); + } + + // 2. 删除原有个人排序 workItemUserSortMapper.deleteByProjectIdAndUserId(projectId, userId); - // 2. 按请求顺序保存个人排序 + // 3. 按请求顺序保存个人排序 for (int index = 0; index < workItemIds.size(); index++) { workItemUserSortMapper.insert(new PmsWorkItemUserSortDO().setProjectId(projectId) .setWorkItemId(workItemIds.get(index)).setUserId(userId).setSort(index + 1)); diff --git a/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImplTest.java b/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImplTest.java index ffecc25..3fc1f8d 100644 --- a/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImplTest.java +++ b/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImplTest.java @@ -33,6 +33,7 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_ADMIN_REQUIRED; +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS; import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_OWNER_REQUIRED; import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID; import static java.util.Arrays.asList; @@ -237,6 +238,35 @@ public class PmsProjectServiceImplTest extends BaseDbUnitTest { } @Test + public void testDeleteProject_success() { + // mock 数据 + Long userId = randomLongId(); + PmsProjectDO project = randomProjectDO(PmsProjectStatusEnum.RECYCLED.getStatus()); + projectMapper.insert(project); + when(projectMemberService.hasProjectOwnerPermission(project.getId(), userId)).thenReturn(true); + + // 调用 + projectService.deleteProject(project.getId(), userId); + + // 断言 + assertNull(projectMapper.selectById(project.getId())); + verify(workItemWorkLogService).deleteWorkItemWorkLogListByProjectId(project.getId()); + verify(workItemService).deleteWorkItemListByProjectId(project.getId()); + verify(iterationService).deleteIterationListByProjectId(project.getId()); + verify(projectMemberService).deleteProjectMemberListByProjectId(project.getId()); + } + + @Test + public void testDeleteProject_projectNotExists() { + // 准备参数 + Long projectId = randomLongId(); + Long userId = randomLongId(); + + // 调用,并断言异常 + assertServiceException(() -> projectService.deleteProject(projectId, userId), PROJECT_NOT_EXISTS); + } + + @Test public void testDeleteProject_statusInvalid() { // mock 数据 Long userId = randomLongId(); diff --git a/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemServiceImplTest.java b/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemServiceImplTest.java index 0dfd682..ccb16ae 100644 --- a/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemServiceImplTest.java +++ b/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemServiceImplTest.java @@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemMemberD import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemStatusDO; import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO; import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemWorkLogDO; +import cn.iocoder.yudao.module.pms.dal.mysql.pm.project.PmsProjectMapper; import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemBoardMapper; import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemCommentMapper; import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemMapper; @@ -39,9 +40,14 @@ import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemPriorityEnum; import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemStatusTypeEnum; import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemTypeEnum; import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectAnnouncementService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectGroupService; import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectServiceImpl; import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi; import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; @@ -88,7 +94,8 @@ import static org.mockito.Mockito.when; * * @author 芋道源码 */ -@Import({PmsWorkItemServiceImpl.class, PmsWorkItemUserSortServiceImpl.class, PmsWorkItemStatusServiceImpl.class, +@Import({PmsWorkItemServiceImpl.class, PmsWorkItemUserSortServiceImpl.class, PmsProjectServiceImpl.class, + PmsWorkItemStatusServiceImpl.class, PmsWorkItemBoardServiceImpl.class, PmsWorkItemCommentServiceImpl.class, PmsWorkItemWorkLogServiceImpl.class}) public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { @@ -110,10 +117,18 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { private PmsWorkItemWorkLogMapper workItemWorkLogMapper; @Resource private PmsWorkItemUserSortMapper workItemUserSortMapper; + @Resource + private PmsProjectMapper projectMapper; @MockitoBean private PmsProjectMemberService projectMemberService; @MockitoBean + private PmsProjectGroupService projectGroupService; + @MockitoBean + private PmsProjectFavoriteService projectFavoriteService; + @MockitoBean + private PmsProjectAnnouncementService projectAnnouncementService; + @MockitoBean private PmsIterationService iterationService; @MockitoBean private PmsWorkItemLabelService workItemLabelService; @@ -126,6 +141,8 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { private NotifyMessageSendApi notifyMessageSendApi; @MockitoBean private AdminUserApi adminUserApi; + @MockitoBean + private PermissionApi permissionApi; @Test public void testCreateWorkItem_success() { @@ -510,6 +527,8 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { // mock 数据 Long userId = randomLongId(); Long projectId = randomLongId(); + PmsProjectDO project = randomProjectDO(projectId, PmsProjectTypeEnum.AGILE.getType()); + projectMapper.insert(project); PmsWorkItemStatusDO taskStatus = randomStatusDO(projectId, PmsWorkItemTypeEnum.TASK.getType(), PmsWorkItemStatusTypeEnum.PENDING.getType(), true, 1); workItemStatusMapper.insert(taskStatus); @@ -521,7 +540,7 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest { PmsWorkItemDO second = randomWorkItemDO(projectId, PmsWorkItemTypeEnum.DEFECT.getType(), defectStatus, 2); workItemMapper.insert(second); when(projectMemberService.validateProjectWritable(projectId, userId)) - .thenReturn(randomProjectDO(projectId, PmsProjectTypeEnum.AGILE.getType())); + .thenReturn(project); // 准备参数 PmsWorkItemPlanningSortReqVO reqVO = new PmsWorkItemPlanningSortReqVO().setProjectId(projectId) .setWorkItemIds(Arrays.asList(second.getId(), first.getId())); diff --git a/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImplTest.java b/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImplTest.java new file mode 100644 index 0000000..d695079 --- /dev/null +++ b/yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImplTest.java @@ -0,0 +1,166 @@ +package cn.iocoder.yudao.module.pms.service.pm.workitem; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO; +import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO; +import cn.iocoder.yudao.module.pms.dal.mysql.pm.project.PmsProjectMapper; +import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemUserSortMapper; +import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectStatusEnum; +import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectAnnouncementService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectGroupService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService; +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectServiceImpl; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS; +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link PmsWorkItemUserSortServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Import({PmsWorkItemUserSortServiceImpl.class, PmsProjectServiceImpl.class}) +public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest { + + @Resource + private PmsWorkItemUserSortServiceImpl workItemUserSortService; + + @Resource + private PmsWorkItemUserSortMapper workItemUserSortMapper; + @Resource + private PmsProjectMapper projectMapper; + + @MockitoBean + private PmsProjectMemberService projectMemberService; + @MockitoBean + private PmsProjectGroupService projectGroupService; + @MockitoBean + private PmsProjectFavoriteService projectFavoriteService; + @MockitoBean + private PmsProjectAnnouncementService projectAnnouncementService; + @MockitoBean + private PmsIterationService iterationService; + @MockitoBean + private PmsWorkItemService workItemService; + @MockitoBean + private PmsWorkItemStatusService workItemStatusService; + @MockitoBean + private PmsWorkItemWorkLogService workItemWorkLogService; + @MockitoBean + private PermissionApi permissionApi; + + @Test + public void testUpdateWorkItemUserSort_success() { + // mock 数据 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1); + projectMapper.insert(project); + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L, 51L), 238L); + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L, 51L), 239L); + + // 调用:连续调整个人排序,并移除不再位于待规划区域的工作项 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(51L, 50L), 238L); + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L), 238L); + + // 断言:仅保留当前顺序,不影响其他用户的个人排序 + List userSorts = workItemUserSortMapper + .selectListByProjectIdAndUserId(project.getId(), 238L); + assertEquals(List.of(50L), convertList(userSorts, PmsWorkItemUserSortDO::getWorkItemId)); + assertEquals(1, userSorts.get(0).getSort()); + assertEquals(List.of(50L, 51L), convertList(workItemUserSortMapper + .selectListByProjectIdAndUserId(project.getId(), 239L), PmsWorkItemUserSortDO::getWorkItemId)); + } + + @Test + public void testUpdateWorkItemUserSort_projectNotExists() { + // 调用,并断言异常 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(999L, + List.of(50L), 238L), PROJECT_NOT_EXISTS); + assertEquals(0L, workItemUserSortMapper.selectCount()); + } + + @Test + public void testUpdateWorkItemUserSort_projectDeleted() { + // mock 数据 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1); + projectMapper.insert(project); + projectMapper.deleteById(project.getId()); + + // 调用,并断言异常 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(project.getId(), + List.of(50L), 238L), PROJECT_NOT_EXISTS); + assertEquals(0L, workItemUserSortMapper.selectCount()); + } + + @Test + public void testUpdateWorkItemUserSort_projectStatusInvalid() { + // mock 数据 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1) + .setStatus(PmsProjectStatusEnum.RECYCLED.getStatus()); + projectMapper.insert(project); + workItemUserSortMapper.insert(new PmsWorkItemUserSortDO().setProjectId(project.getId()) + .setWorkItemId(50L).setUserId(238L).setSort(1)); + + // 调用,并断言异常 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(project.getId(), + List.of(51L), 238L), PROJECT_STATUS_INVALID); + // 断言:拒绝更新时保留原有个人排序 + assertEquals(List.of(50L), convertList(workItemUserSortMapper + .selectListByProjectIdAndUserId(project.getId(), 238L), PmsWorkItemUserSortDO::getWorkItemId)); + } + + @Test + public void testUpdateWorkItemUserSort_concurrent() throws Exception { + // mock 数据 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1); + projectMapper.insert(project); + // 准备参数 + CountDownLatch startLatch = new CountDownLatch(1); + ExecutorService executor = Executors.newFixedThreadPool(4); + List> futures = new ArrayList<>(); + + // 调用:同一项目、同一用户并发保存个人排序 + try { + for (int index = 0; index < 4; index++) { + futures.add(executor.submit(() -> { + startLatch.await(); + for (int count = 0; count < 5; count++) { + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(51L, 50L), 238L); + } + return null; + })); + } + startLatch.countDown(); + for (Future future : futures) { + future.get(20, TimeUnit.SECONDS); + } + } finally { + executor.shutdownNow(); + executor.awaitTermination(10, TimeUnit.SECONDS); + } + + // 断言:没有重复的有效记录,顺序与最后一次保存一致 + List userSorts = workItemUserSortMapper + .selectListByProjectIdAndUserId(project.getId(), 238L); + assertEquals(List.of(51L, 50L), convertList(userSorts, PmsWorkItemUserSortDO::getWorkItemId)); + assertEquals(List.of(1, 2), convertList(userSorts, PmsWorkItemUserSortDO::getSort)); + } + +} diff --git a/yudao-module-pms/src/test/resources/sql/clean.sql b/yudao-module-pms/src/test/resources/sql/clean.sql index c8a8df1..9a627d9 100644 --- a/yudao-module-pms/src/test/resources/sql/clean.sql +++ b/yudao-module-pms/src/test/resources/sql/clean.sql @@ -5,6 +5,7 @@ DELETE FROM "pms_work_item_comment"; DELETE FROM "pms_work_item_label"; DELETE FROM "pms_work_item_work_log"; DELETE FROM "pms_work_item_member"; +DELETE FROM "pms_work_item_user_sort"; DELETE FROM "pms_work_item"; DELETE FROM "pms_work_item_status"; DELETE FROM "pms_iteration"; -- libgit2 0.21.4