fix:修复充值/扣款申请中开启申请没有查询到数据的bug

This commit is contained in:
dzc committed 2026-09-01 17:40:02 +08:00
1 parent 0136a8707c
commit 779bad9314
6 files changed
+234 -12

No files matched your search

@@ -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);
@@ -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;
}
/**
@@ -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;
}
}
@@ -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);
}
}
@@ -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());
}
/**
@@ -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;
}
}