fix:修复充值/扣款申请中开启申请没有查询到数据的bug
This commit is contained in:
1 parent
0136a8707c
commit
779bad9314
6 files changed
+234
-12
No files matched your search
+24
-7
@@ -297,18 +297,35 @@ public class EngineFlowController {
|
||||
return R.ok(pageResultDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据流程实例查询当前用户最新的一条活动待办任务。
|
||||
*
|
||||
* <p>正常情况下同一用户在一个流程实例中只有一条活动待办。历史流程可能因用户同时拥有多个审批角色而
|
||||
* 产生重复任务,此处兼容返回最新任务,避免业务页面无法继续处理,并通过告警保留异常排查信息。</p>
|
||||
*
|
||||
* @param processInstanceId 流程实例 ID
|
||||
* @return 当前用户最新的活动待办;不存在时返回明确失败信息
|
||||
*/
|
||||
@GetMapping("/queryAssignTaskOne")
|
||||
public R<TaskDto> queryAssignTaskOne(@RequestParam String processInstanceId) {
|
||||
String assign = SecurityUtils.getUser().getId().toString();
|
||||
TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(assign).orderByTaskCreateTime().processInstanceId(processInstanceId).active().desc();
|
||||
long count = taskQuery.count();
|
||||
if (count != 1) {
|
||||
log.debug("queryAssignTaskOne查询错误,count!=1,processInstanceId:{}", processInstanceId);
|
||||
return R.failed();
|
||||
TaskQuery taskQuery = taskService.createTaskQuery()
|
||||
.taskAssignee(assign)
|
||||
.processInstanceId(processInstanceId)
|
||||
.active()
|
||||
.orderByTaskCreateTime()
|
||||
.desc();
|
||||
List<Task> taskList = taskQuery.listPage(0, 2);
|
||||
if (CollUtil.isEmpty(taskList)) {
|
||||
log.debug("当前用户没有活动待办,userId={},processInstanceId={}", assign, processInstanceId);
|
||||
return R.failed("当前用户没有可处理的待办任务");
|
||||
}
|
||||
Task task = taskQuery.singleResult();
|
||||
if (taskList.size() > 1) {
|
||||
log.warn("同一用户在同一流程实例中存在多条活动待办,将返回最新任务,userId={},processInstanceId={}", assign, processInstanceId);
|
||||
}
|
||||
Task task = taskList.get(0);
|
||||
String taskId = task.getId();
|
||||
log.debug("queryAssignTaskOne查询错误(taskId) {} processInstanceId={} executrionId={}", task.getName(), processInstanceId, task.getExecutionId());
|
||||
log.debug("查询到活动待办(taskId) {} processInstanceId={} executionId={}", taskId, processInstanceId, task.getExecutionId());
|
||||
String taskDefinitionKey = task.getTaskDefinitionKey();
|
||||
String processDefinitionId = task.getProcessDefinitionId();
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId);
|
||||
|
||||
+6
-4
@@ -18,6 +18,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -78,11 +79,12 @@ public class MultiInstanceHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// 标记为空
|
||||
if (CollUtil.isEmpty(assignList)) {
|
||||
assignList.add(ProcessInstanceConstant.DEFAULT_EMPTY_ASSIGN);
|
||||
// 不同角色、部门或自选来源可能解析到同一用户,多实例任务必须按用户唯一创建。
|
||||
List<Long> distinctAssignList = new ArrayList<>(new LinkedHashSet<>(assignList));
|
||||
if (CollUtil.isEmpty(distinctAssignList)) {
|
||||
distinctAssignList.add(ProcessInstanceConstant.DEFAULT_EMPTY_ASSIGN);
|
||||
}
|
||||
return assignList;
|
||||
return distinctAssignList;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package com.bsd.pjcloud.flow.engine.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.service.PjcloudUser;
|
||||
import com.bsd.pjcloud.flow.task.dto.TaskDto;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.RepositoryService;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.flowable.task.api.TaskQuery;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 工作流查询控制器测试,覆盖历史重复待办数据的兼容读取行为。
|
||||
*/
|
||||
class EngineFlowControllerTest {
|
||||
|
||||
/**
|
||||
* 每条测试结束后清理认证上下文,避免登录用户泄漏到其他测试。
|
||||
*/
|
||||
@AfterEach
|
||||
void clearSecurityContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同一用户存在两条历史重复活动待办时,应返回按创建时间倒序查询到的第一条任务。
|
||||
*/
|
||||
@Test
|
||||
void shouldReturnLatestTaskWhenHistoricalDuplicateTasksExist() {
|
||||
PjcloudUser user = mock(PjcloudUser.class);
|
||||
when(user.getId()).thenReturn(1L);
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new UsernamePasswordAuthenticationToken(user, null, List.of()));
|
||||
|
||||
TaskService taskService = mock(TaskService.class);
|
||||
TaskQuery taskQuery = mock(TaskQuery.class);
|
||||
when(taskService.createTaskQuery()).thenReturn(taskQuery);
|
||||
when(taskQuery.taskAssignee("1")).thenReturn(taskQuery);
|
||||
when(taskQuery.processInstanceId("process-instance")).thenReturn(taskQuery);
|
||||
when(taskQuery.active()).thenReturn(taskQuery);
|
||||
when(taskQuery.orderByTaskCreateTime()).thenReturn(taskQuery);
|
||||
when(taskQuery.desc()).thenReturn(taskQuery);
|
||||
Task latestTask = task("latest-task");
|
||||
Task earlierTask = task("earlier-task");
|
||||
when(taskQuery.listPage(0, 2)).thenReturn(List.of(latestTask, earlierTask));
|
||||
EngineFlowController controller = new EngineFlowController(
|
||||
taskService,
|
||||
mock(HistoryService.class),
|
||||
mock(RepositoryService.class),
|
||||
mock(RuntimeService.class),
|
||||
new ObjectMapper());
|
||||
|
||||
R<TaskDto> result = controller.queryAssignTaskOne("process-instance");
|
||||
|
||||
assertEquals(0, result.getCode());
|
||||
assertEquals("latest-task", result.getData().getTaskId());
|
||||
verify(taskQuery, never()).singleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造当前用户的活动审批任务。
|
||||
*
|
||||
* @param taskId 任务 ID
|
||||
* @return Flowable 任务
|
||||
*/
|
||||
private static Task task(String taskId) {
|
||||
Task task = mock(Task.class);
|
||||
when(task.getId()).thenReturn(taskId);
|
||||
when(task.getName()).thenReturn("审批人");
|
||||
when(task.getExecutionId()).thenReturn(taskId + "-execution");
|
||||
when(task.getTaskDefinitionKey()).thenReturn("approve-node");
|
||||
when(task.getProcessDefinitionId()).thenReturn("test-flow:1:definition");
|
||||
when(task.getAssignee()).thenReturn("1");
|
||||
return task;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.bsd.pjcloud.flow.engine.node;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.flow.task.api.feign.RemoteFlowTaskService;
|
||||
import com.bsd.pjcloud.flow.task.constant.ProcessInstanceConstant;
|
||||
import com.bsd.pjcloud.flow.task.dto.Node;
|
||||
import com.bsd.pjcloud.flow.task.dto.NodeUser;
|
||||
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 多实例审批人解析测试,验证进入 Flowable 前的用户唯一性约束。
|
||||
*/
|
||||
class MultiInstanceHandlerTest {
|
||||
|
||||
/**
|
||||
* 上游策略返回重复用户时,流程引擎只能为每个用户创建一个多实例任务。
|
||||
*/
|
||||
@Test
|
||||
void shouldDeduplicateAssigneesBeforeCreatingMultiInstanceTasks() throws Exception {
|
||||
RemoteFlowTaskService remoteFlowTaskService = mock(RemoteFlowTaskService.class);
|
||||
AssignUserStrategy assignUserStrategy = mock(AssignUserStrategy.class);
|
||||
String strategyName = ProcessInstanceConstant.AssignedTypeClass.ROLE + "AssignUserStrategy";
|
||||
MultiInstanceHandler handler = new MultiInstanceHandler(
|
||||
remoteFlowTaskService, Map.of(strategyName, assignUserStrategy), new ObjectMapper());
|
||||
|
||||
ExecutionEntityImpl execution = mock(ExecutionEntityImpl.class);
|
||||
NodeUser rootUser = NodeUser.builder().id(9L).name("发起人").build();
|
||||
Node node = new Node();
|
||||
node.setAssignedType(ProcessInstanceConstant.AssignedTypeClass.ROLE);
|
||||
when(execution.getProcessDefinitionKey()).thenReturn("test-flow");
|
||||
when(execution.getActivityId()).thenReturn("approve-node");
|
||||
when(execution.getActivityName()).thenReturn("审批人");
|
||||
when(execution.getVariable("root")).thenReturn(List.of(rootUser));
|
||||
when(execution.getVariables()).thenReturn(Map.of());
|
||||
when(remoteFlowTaskService.queryNodeOriData("test-flow", "approve-node")).thenReturn(R.ok(node));
|
||||
when(assignUserStrategy.handle(node, rootUser, Map.of())).thenReturn(List.of(1L, 2L, 1L));
|
||||
|
||||
List<Long> assignees = handler.resolveAssignee(execution);
|
||||
|
||||
assertEquals(List.of(1L, 2L), assignees);
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -1084,9 +1084,19 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色 ID 列表查询唯一用户 ID,避免同一用户拥有多个目标角色时被重复返回。
|
||||
*
|
||||
* @param roleIdList 角色 ID 列表
|
||||
* @return 按角色关系查询顺序去重后的用户 ID 列表
|
||||
*/
|
||||
@Override
|
||||
public List<Long> listUserIdByRoleIds(List<Long> roleIdList) {
|
||||
return sysUserRoleMapper.selectList(Wrappers.<SysUserRole>lambdaQuery().in(SysUserRole::getRoleId, roleIdList)).stream().map(SysUserRole::getUserId).collect(Collectors.toList());
|
||||
return sysUserRoleMapper.selectList(Wrappers.<SysUserRole>lambdaQuery().in(SysUserRole::getRoleId, roleIdList))
|
||||
.stream()
|
||||
.map(SysUserRole::getUserId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.bsd.pjcloud.admin.service.impl;
|
||||
|
||||
import com.bsd.pjcloud.admin.api.entity.SysUserRole;
|
||||
import com.bsd.pjcloud.admin.mapper.SysUserRoleMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 用户角色解析测试,验证多个审批角色命中同一用户时只返回一个用户 ID。
|
||||
*/
|
||||
class SysUserRoleResolutionTest {
|
||||
|
||||
/**
|
||||
* 同一用户同时拥有两个目标角色时,查询结果必须保持首次出现顺序并去重。
|
||||
*/
|
||||
@Test
|
||||
void shouldReturnDistinctUserIdsAcrossRoles() {
|
||||
SysUserRoleMapper userRoleMapper = mock(SysUserRoleMapper.class);
|
||||
SysUserServiceImpl userService = mock(SysUserServiceImpl.class, CALLS_REAL_METHODS);
|
||||
ReflectionTestUtils.setField(userService, "sysUserRoleMapper", userRoleMapper);
|
||||
when(userRoleMapper.selectList(any())).thenReturn(List.of(
|
||||
userRole(1L, 101L),
|
||||
userRole(2L, 101L),
|
||||
userRole(1L, 102L)));
|
||||
|
||||
List<Long> userIds = userService.listUserIdByRoleIds(List.of(101L, 102L));
|
||||
|
||||
assertEquals(List.of(1L, 2L), userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造测试使用的用户角色关系。
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @param roleId 角色 ID
|
||||
* @return 用户角色关系
|
||||
*/
|
||||
private static SysUserRole userRole(Long userId, Long roleId) {
|
||||
SysUserRole userRole = new SysUserRole();
|
||||
userRole.setUserId(userId);
|
||||
userRole.setRoleId(roleId);
|
||||
return userRole;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user