diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java index 389597560..70e6501ab 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwInstanceController.java @@ -171,6 +171,16 @@ public class FlwInstanceController extends BaseController { return R.ok(flwInstanceService.flowHisTaskList(businessId)); } + /** + * 根据流程实例 ID、业务 ID 或两者查询流程进度。 + */ + @GetMapping("/flowHisTaskList") + @SaCheckPermission("workflow:instance:query") + public R> flowHisTaskList(@RequestParam(required = false) Long instanceId, + @RequestParam(required = false) String businessId) { + return R.ok(flwInstanceService.flowHisTaskList(instanceId, businessId)); + } + /** * 获取流程变量。 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java index 38b50e08e..a2627b932 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/IFlwInstanceService.java @@ -119,6 +119,15 @@ public interface IFlwInstanceService { */ Map flowHisTaskList(String businessId); + /** + * 按实例 ID、业务 ID 查询流程图及历史办理记录;同时提供时须指向同一实例。 + * + * @param instanceId 流程实例 ID,可为空 + * @param businessId 业务 ID,可为空 + * @return 流程图与办理记录 + */ + Map flowHisTaskList(Long instanceId, String businessId); + /** * 按照实例id更新状态 * diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java index 3a7a681e1..4a19c6b75 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java @@ -303,15 +303,26 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { */ @Override public Map flowHisTaskList(String businessId) { - FlowInstance flowInstance = this.selectInstByBusinessId(businessId); - if (ObjectUtil.isNull(flowInstance)) { + return flowHisTaskList(null, businessId); + } + + @Override + public Map flowHisTaskList(Long instanceId, String businessId) { + if (instanceId == null && StringUtils.isBlank(businessId)) { + throw new ServiceException("流程实例 ID 和业务 ID 不能同时为空"); + } + FlowInstance flowInstance = instanceId != null + ? this.selectInstById(instanceId) + : this.selectInstByBusinessId(businessId); + if (ObjectUtil.isNull(flowInstance) + || (StringUtils.isNotBlank(businessId) && !businessId.equals(flowInstance.getBusinessId()))) { throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE); } - Long instanceId = flowInstance.getId(); + Long resolvedInstanceId = flowInstance.getId(); // 先组装待审批任务(运行中的任务) List runningTaskVos = new ArrayList<>(); - List runningTasks = flwTaskService.selectByInstId(instanceId); + List runningTasks = flwTaskService.selectByInstId(resolvedInstanceId); if (CollUtil.isNotEmpty(runningTasks)) { runningTaskVos = BeanUtil.copyToList(runningTasks, FlowHisTaskVo.class); @@ -336,7 +347,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { List hisTaskVos = new ArrayList<>(); List hisTasks = flowHisTaskMapper.selectList( QueryBuilder.lambda(FlowHisTask.class) - .eq(FlowHisTask::getInstanceId, instanceId) + .eq(FlowHisTask::getInstanceId, resolvedInstanceId) .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()) .orderByDesc(FlowHisTask::getUpdateTime) .build() @@ -350,7 +361,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService { combinedList.addAll(runningTaskVos); combinedList.addAll(hisTaskVos); - return Map.of("list", combinedList, "instanceId", instanceId); + return Map.of("list", combinedList, "instanceId", resolvedInstanceId); } /** diff --git a/ruoyi-modules/ruoyi-workflow/src/test/java/org/dromara/workflow/service/impl/FlwInstanceProgressTest.java b/ruoyi-modules/ruoyi-workflow/src/test/java/org/dromara/workflow/service/impl/FlwInstanceProgressTest.java new file mode 100644 index 000000000..f73b1e6d3 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/test/java/org/dromara/workflow/service/impl/FlwInstanceProgressTest.java @@ -0,0 +1,80 @@ +package org.dromara.workflow.service.impl; + +import org.dromara.common.core.exception.ServiceException; +import org.dromara.warm.flow.core.service.DefService; +import org.dromara.warm.flow.core.service.InsService; +import org.dromara.warm.flow.core.service.TaskService; +import org.dromara.warm.flow.orm.entity.FlowInstance; +import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper; +import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper; +import org.dromara.workflow.mapper.FlwCategoryMapper; +import org.dromara.workflow.mapper.FlwInstanceMapper; +import org.dromara.workflow.service.IFlwTaskService; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +class FlwInstanceProgressTest { + + @Test + void instanceIdAloneLoadsProgress() { + FlowInstanceMapper instanceMapper = mock(FlowInstanceMapper.class); + FlowHisTaskMapper historyMapper = mock(FlowHisTaskMapper.class); + IFlwTaskService taskService = mock(IFlwTaskService.class); + FlwInstanceServiceImpl service = service(instanceMapper, historyMapper, taskService); + FlowInstance instance = mock(FlowInstance.class); + when(instance.getId()).thenReturn(12L); + when(instanceMapper.selectById(12L)).thenReturn(instance); + when(taskService.selectByInstId(12L)).thenReturn(List.of()); + when(historyMapper.selectList(any())).thenReturn(List.of()); + + assertEquals(12L, service.flowHisTaskList(12L, null).get("instanceId")); + verify(instanceMapper).selectById(12L); + } + + @Test + void rejectsMismatchedIdsBeforeLoadingTasks() { + FlowInstanceMapper instanceMapper = mock(FlowInstanceMapper.class); + FlowHisTaskMapper historyMapper = mock(FlowHisTaskMapper.class); + IFlwTaskService taskService = mock(IFlwTaskService.class); + FlwInstanceServiceImpl service = service(instanceMapper, historyMapper, taskService); + FlowInstance instance = mock(FlowInstance.class); + when(instance.getBusinessId()).thenReturn("business-1"); + when(instanceMapper.selectById(12L)).thenReturn(instance); + + assertThrows(ServiceException.class, () -> service.flowHisTaskList(12L, "business-2")); + verifyNoInteractions(taskService, historyMapper); + } + + @Test + void matchingIdsLoadSameInstance() { + FlowInstanceMapper instanceMapper = mock(FlowInstanceMapper.class); + FlowHisTaskMapper historyMapper = mock(FlowHisTaskMapper.class); + IFlwTaskService taskService = mock(IFlwTaskService.class); + FlwInstanceServiceImpl service = service(instanceMapper, historyMapper, taskService); + FlowInstance instance = mock(FlowInstance.class); + when(instance.getId()).thenReturn(12L); + when(instance.getBusinessId()).thenReturn("business-1"); + when(instanceMapper.selectById(12L)).thenReturn(instance); + when(taskService.selectByInstId(12L)).thenReturn(List.of()); + when(historyMapper.selectList(any())).thenReturn(List.of()); + + assertEquals(12L, service.flowHisTaskList(12L, "business-1").get("instanceId")); + verify(instanceMapper).selectById(12L); + } + + private FlwInstanceServiceImpl service(FlowInstanceMapper instanceMapper, + FlowHisTaskMapper historyMapper, IFlwTaskService taskService) { + return new FlwInstanceServiceImpl(mock(InsService.class), mock(DefService.class), + mock(TaskService.class), historyMapper, instanceMapper, taskService, + mock(FlwInstanceMapper.class), mock(FlwCategoryMapper.class)); + } +}