fix 修复 待审批的节点无法展示审批人信息问题

fix 修复 流程发布接口未适配事件节点 发布提示没有审批人问题
This commit is contained in:
疯狂的狮子Li 2026-10-26 17:21:13 +08:00
parent 6daa3162eb
commit 1f618c6909
4 changed files with 205 additions and 36 deletions

View File

@ -20,10 +20,15 @@ import org.dromara.warm.flow.core.dto.PromptContent;
import org.dromara.warm.flow.core.enums.NodeType;
import org.dromara.warm.flow.core.utils.MapUtil;
import org.dromara.warm.flow.orm.entity.FlowHisTask;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.warm.flow.orm.entity.FlowUser;
import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
import org.dromara.warm.flow.orm.mapper.FlowUserMapper;
import org.dromara.warm.flow.ui.service.ChartExtService;
import org.dromara.workflow.common.ConditionalOnEnable;
import org.dromara.workflow.common.constant.FlowConstant;
import org.dromara.workflow.common.enums.TaskAssigneeType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@ -45,6 +50,8 @@ public class FlwChartExtServiceImpl implements ChartExtService {
private final UserService userService;
private final DeptService deptService;
private final FlowHisTaskMapper flowHisTaskMapper;
private final FlowTaskMapper flowTaskMapper;
private final FlowUserMapper flowUserMapper;
private final DictService dictService;
@Value("${warm-flow.node-tooltip:true}")
private boolean nodeTooltip;
@ -61,45 +68,80 @@ public class FlwChartExtServiceImpl implements ChartExtService {
return;
}
// 根据流程实例ID查询所有相关的历史任务列表
List<FlowHisTask> flowHisTasks = this.getHisTaskGroupedByNode(defJson.getInstance().getId());
if (CollUtil.isEmpty(flowHisTasks)) {
Long instanceId = defJson.getInstance().getId();
List<FlowHisTask> flowHisTasks = this.getHisTaskGroupedByNode(instanceId);
List<FlowTask> activeTasks = flowTaskMapper.selectList(Wrappers.<FlowTask>lambdaQuery()
.eq(FlowTask::getInstanceId, instanceId)
.eq(FlowTask::getNodeType, NodeType.BETWEEN.getKey()));
if (CollUtil.isEmpty(flowHisTasks) && CollUtil.isEmpty(activeTasks)) {
return;
}
// 按节点编号(nodeCode)对历史任务进行分组
Map<String, List<FlowHisTask>> groupedByNode = StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode);
Map<String, List<FlowHisTask>> groupedByNode = CollUtil.isEmpty(flowHisTasks)
? Collections.emptyMap() : StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode);
Map<Long, String> taskNodeCodes = activeTasks.stream()
.collect(Collectors.toMap(FlowTask::getId, FlowTask::getNodeCode));
List<FlowUser> activeUsers = taskNodeCodes.isEmpty() ? Collections.emptyList()
: flowUserMapper.selectList(Wrappers.<FlowUser>lambdaQuery()
.in(FlowUser::getAssociated, taskNodeCodes.keySet())
.in(FlowUser::getType, List.of(TaskAssigneeType.APPROVER.getCode(),
TaskAssigneeType.TRANSFER.getCode(), TaskAssigneeType.DELEGATE.getCode())));
Map<String, Set<Long>> pendingUsersByNode = new HashMap<>();
for (FlowUser activeUser : activeUsers) {
Long userId = Convert.toLong(activeUser.getProcessedBy(), null);
String nodeCode = taskNodeCodes.get(activeUser.getAssociated());
if (userId != null && nodeCode != null) {
pendingUsersByNode.computeIfAbsent(nodeCode, key -> new LinkedHashSet<>()).add(userId);
}
}
// 批量查询所有审批人的用户信息
List<UserDTO> userDTOList = userService.selectListByIds(StreamUtils.toSet(flowHisTasks, e -> Convert.toLong(e.getApprover())));
Set<Long> userIds = new HashSet<>();
flowHisTasks.forEach(task -> {
Long approverId = Convert.toLong(task.getApprover(), null);
if (approverId != null) {
userIds.add(approverId);
}
});
pendingUsersByNode.values().forEach(userIds::addAll);
if (userIds.isEmpty()) {
return;
}
List<UserDTO> userDTOList = userService.selectListByIds(userIds);
// 将查询到的用户列表转换为以用户ID为key的映射
Map<Long, UserDTO> userMap = StreamUtils.toIdentityMap(userDTOList, UserDTO::getUserId);
Set<Long> deptIds = StreamUtils.toSet(userDTOList, UserDTO::getDeptId);
Map<Long, String> deptNameMap = deptService.selectDeptNamesByIds(deptIds);
deptIds.remove(null);
Map<Long, String> deptNameMap = deptIds.isEmpty() ? Collections.emptyMap()
: deptService.selectDeptNamesByIds(deptIds);
Map<String, String> dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS);
Map<String, String> dictType = flowHisTasks.isEmpty() ? Collections.emptyMap()
: dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS);
for (NodeJson nodeJson : defJson.getNodeList()) {
List<FlowHisTask> taskList = groupedByNode.get(nodeJson.getNodeCode());
if (CollUtil.isEmpty(taskList)) {
continue;
if (CollUtil.isNotEmpty(taskList)) {
List<FlowHisTask> latestPerApprover = taskList.stream()
.filter(task -> StringUtils.isNotBlank(task.getApprover()))
.collect(Collectors.collectingAndThen(
Collectors.toMap(
FlowHisTask::getApprover,
Function.identity(),
(oldTask, newTask) -> newTask.getUpdateTime().after(oldTask.getUpdateTime()) ? newTask : oldTask,
LinkedHashMap::new
),
map -> new ArrayList<>(map.values())
));
this.processNodeExtInfo(nodeJson, latestPerApprover, userMap, deptNameMap, dictType);
}
for (Long userId : pendingUsersByNode.getOrDefault(nodeJson.getNodeCode(), Collections.emptySet())) {
UserDTO user = userMap.get(userId);
if (user != null) {
addUserInfo(nodeJson.getPromptContent().getInfo(), user,
deptNameMap.getOrDefault(user.getDeptId(), StringUtils.EMPTY));
nodeJson.getPromptContent().getInfo().add(buildInfoItem("审批状态", "待审批"));
}
}
// 按审批人分组去重,保留最新处理记录,最终转换成 List
List<FlowHisTask> latestPerApprover = taskList.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(
FlowHisTask::getApprover,
Function.identity(),
(oldTask, newTask) -> newTask.getUpdateTime().after(oldTask.getUpdateTime()) ? newTask : oldTask,
LinkedHashMap::new
),
map -> new ArrayList<>(map.values())
));
// 处理当前节点的扩展信息
this.processNodeExtInfo(nodeJson, latestPerApprover, userMap, deptNameMap, dictType);
}
}
@ -183,10 +225,16 @@ public class FlwChartExtServiceImpl implements ChartExtService {
continue;
}
String deptName = deptNameMap.getOrDefault(userDTO.getDeptId(), StringUtils.EMPTY);
addUserInfo(info, userDTO, deptNameMap.getOrDefault(userDTO.getDeptId(), StringUtils.EMPTY));
// 添加标题项,如:👤 张三(市场部)
info.add(new PromptContent.InfoItem()
info.add(buildInfoItem("审批状态", dictType.get(task.getFlowStatus())));
info.add(buildInfoItem("审批耗时", DateUtils.formatBetweenBySecond(task.getUpdateTime(), task.getCreateTime())));
info.add(buildInfoItem("办理时间", DateUtils.formatDateTime(task.getUpdateTime())));
}
}
private void addUserInfo(List<PromptContent.InfoItem> info, UserDTO userDTO, String deptName) {
info.add(new PromptContent.InfoItem()
.setPrefix(StringUtils.format("👥 {}({})", userDTO.getNickName(), deptName))
.setPrefixStyle(Map.of(
"fontWeight", "bold",
@ -199,12 +247,7 @@ public class FlwChartExtServiceImpl implements ChartExtService {
))
);
// 添加具体信息项:账号、耗时、时间
info.add(buildInfoItem("用户账号", userDTO.getUserName()));
info.add(buildInfoItem("审批状态", dictType.get(task.getFlowStatus())));
info.add(buildInfoItem("审批耗时", DateUtils.formatBetweenBySecond(task.getUpdateTime(), task.getCreateTime())));
info.add(buildInfoItem("办理时间", DateUtils.formatDateTime(task.getUpdateTime())));
}
info.add(buildInfoItem("用户账号", userDTO.getUserName()));
}
/**

View File

@ -126,7 +126,7 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
if (CollUtil.isNotEmpty(flowNodes)) {
String applyNodeCode = flwCommonService.applyNodeCode(id);
for (FlowNode flowNode : flowNodes) {
if (StringUtils.isBlank(flowNode.getPermissionFlag()) && !applyNodeCode.equals(flowNode.getNodeCode()) && NodeType.BETWEEN.getKey().equals(flowNode.getNodeType())) {
if (requiresAssignee(flowNode, applyNodeCode) && StringUtils.isBlank(flowNode.getPermissionFlag())) {
errorMsg.add(flowNode.getNodeName());
}
}
@ -137,6 +137,12 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
return defService.publish(id);
}
static boolean requiresAssignee(FlowNode node, String applyNodeCode) {
return NodeType.BETWEEN.getKey().equals(node.getNodeType())
&& !applyNodeCode.equals(node.getNodeCode())
&& StringUtils.isBlank(node.getEventType());
}
/**
* 导入流程定义
*

View File

@ -0,0 +1,81 @@
package org.dromara.workflow.service.impl;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.session.Configuration;
import org.dromara.common.core.service.DictService;
import org.dromara.system.api.DeptService;
import org.dromara.system.api.UserService;
import org.dromara.system.api.domain.UserDTO;
import org.dromara.warm.flow.core.dto.DefJson;
import org.dromara.warm.flow.core.dto.NodeJson;
import org.dromara.warm.flow.orm.entity.FlowInstance;
import org.dromara.warm.flow.orm.entity.FlowTask;
import org.dromara.warm.flow.orm.entity.FlowUser;
import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
import org.dromara.warm.flow.orm.mapper.FlowUserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class FlwChartExtServiceImplTest {
@Test
void showsCurrentAssigneeWithoutApprovalHistory() {
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"), FlowUser.class);
UserService userService = mock(UserService.class);
DeptService deptService = mock(DeptService.class);
FlowHisTaskMapper historyMapper = mock(FlowHisTaskMapper.class);
FlowTaskMapper taskMapper = mock(FlowTaskMapper.class);
FlowUserMapper flowUserMapper = mock(FlowUserMapper.class);
DictService dictService = mock(DictService.class);
FlwChartExtServiceImpl service = new FlwChartExtServiceImpl(
userService, deptService, historyMapper, taskMapper, flowUserMapper, dictService);
ReflectionTestUtils.setField(service, "nodeTooltip", true);
NodeJson node = new NodeJson().setNodeCode("approve").setNodeName("部门审批");
DefJson definition = new DefJson()
.setFlowName("请假申请")
.setInstance(new FlowInstance().setId(1L))
.setNodeList(List.of(node));
service.initPromptContent(definition);
when(historyMapper.selectList(any())).thenReturn(List.of());
when(taskMapper.selectList(any())).thenReturn(List.of(
new FlowTask().setId(10L).setNodeCode("approve")));
when(flowUserMapper.selectList(any())).thenReturn(List.of(
new FlowUser().setAssociated(10L).setType("1").setProcessedBy("7")));
UserDTO user = new UserDTO();
user.setUserId(7L);
user.setDeptId(3L);
user.setNickName("张三");
user.setUserName("zhangsan");
when(userService.selectListByIds(any())).thenReturn(List.of(user));
when(deptService.selectDeptNamesByIds(any())).thenReturn(Map.of(3L, "研发部"));
when(dictService.getAllDictByDictType(any())).thenReturn(Map.of());
service.execute(definition);
var info = node.getPromptContent().getInfo();
assertTrue(info.stream().anyMatch(item -> item.getPrefix().contains("张三")
&& item.getPrefix().contains("研发部")));
assertTrue(info.stream().anyMatch(item -> "用户账号: ".equals(item.getPrefix())
&& "zhangsan".equals(item.getContent())));
assertTrue(info.stream().anyMatch(item -> "审批状态: ".equals(item.getPrefix())
&& "待审批".equals(item.getContent())));
verify(userService).selectListByIds(argThat(ids -> ids.size() == 1 && ids.contains(7L)));
verify(deptService).selectDeptNamesByIds(argThat(ids -> ids.size() == 1 && ids.contains(3L)));
assertEquals(4, info.size());
}
}

View File

@ -0,0 +1,39 @@
package org.dromara.workflow.service.impl;
import org.dromara.warm.flow.core.enums.NodeType;
import org.dromara.warm.flow.orm.entity.FlowNode;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class FlwDefinitionServiceImplTest {
@Test
void eventNodesDoNotRequireAssignee() {
for (String eventType : new String[]{"MESSAGE_START", "TIMER_CATCH", "ERROR_BOUNDARY", "TERMINATE_END"}) {
FlowNode node = new FlowNode()
.setNodeType(NodeType.BETWEEN.getKey())
.setNodeCode("event")
.setEventType(eventType);
assertFalse(FlwDefinitionServiceImpl.requiresAssignee(node, "apply"), eventType);
}
}
@Test
void manualApprovalStillRequiresAssignee() {
FlowNode approval = new FlowNode()
.setNodeType(NodeType.BETWEEN.getKey())
.setNodeCode("approve");
assertTrue(FlwDefinitionServiceImpl.requiresAssignee(approval, "apply"));
assertFalse(FlwDefinitionServiceImpl.requiresAssignee(approval, "approve"));
}
@Test
void serviceTaskDoesNotRequireAssignee() {
FlowNode service = new FlowNode()
.setNodeType(NodeType.SERVICE.getKey())
.setNodeCode("service");
assertFalse(FlwDefinitionServiceImpl.requiresAssignee(service, "apply"));
}
}