在最近的项目开发中,有一个需求为获取未审批节点的信息。在我们系统中,流程图没有特别的节点,主要包含了并行网关、排他网关、任务节点这几种类型的节点。因此在获取未审批节点的时候,就没有其他的业务处理逻辑。该问题作为记录,以便日后使用。
排他网关
在处理逻辑中,比较复杂的应该就是排他网关了。在排他网关的时候,是需要根据表达式计算行走的路径,直接影响了获取后续列表。因此我们可以定义一个工具类,用于计算表达式的具体值。
@UtilityClass public class ExpressionUtil { /** * 计算表达式结果值 * * @param expression 表达式信息 * @param variables 参与计算的变量 * @return 表达式结果 */ public Object evalExpression(String expression, Map<String, Object> variables) { DelegateExecution execution = new ExecutionEntityImpl(); if (Objects.nonNull(variables)) { variables.entrySet() .forEach(entry -> { execution.setTransientVariable(entry.getKey(), entry.getValue()); }); } // 执行表达式 return evalExpression(expression, execution); } /** * 获取表达式计算值 * * @param expression 表达式 * @param execution 执行容器 * @return 执行结果 */ public Object evalExpression(String expression, DelegateExecution execution) { // 获取系统内置的ExpressionManager ExpressionManager expressionManager = SpringUtils.getBean(ExpressionManager.class); Expression evalExpression = expressionManager.createExpression(expression); return evalExpression.getValue(execution); } /** * 判断条件表达式 * * @param expression 表达式字符串 * @param variables 参与计算的变量 * @return 返回结果 */ public boolean conditionExpression(String expression, Map<String, Object> variables) { Object val = evalExpression(expression, variables); if (val instanceof Boolean) { return (Boolean) val; } return false; } public static boolean isExpression(String expression) { return StringUtils.isNotBlank(expression) && expression.indexOf("${") > -1; } }
在上面的表达式中,主要使用了ExpressionManager
来根据表达式创建Expression
对象,该对象需要使用flowable自动创建的对象,因为flowable中部分语法属于扩展,如果自己创建,可能会导致语法的不支持。但是在flowable中,默认ExpressionManager
对象并没有暴露在spring容器中,因此需要将该对象暴露到spring容器中,则对应的配置对象如下:
configuration
@Slf4j @Configuration public class WorkflowConfiguration { @Bean public ExpressionManager expressionManager(ProcessEngine processEngine) { ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration(); if (configuration instanceof HasExpressionManagerEngineConfiguration) { return ((HasExpressionManagerEngineConfiguration) configuration).getExpressionManager(); } log.info("未找到ExpressionManager实例对象,则默认创建"); DelegateInterceptor delegateInterceptor = new DefaultDelegateInterceptor(); Map<Object, Object> beans = new HashMap<>(); ExpressionManager expressionManager = new ProcessExpressionManager(delegateInterceptor, beans); return expressionManager; } }
FlowableUtil
在有了以上的基础配置之后,则我们就可以开始获取未审批的节点元素列表了,具体实现如下:
/** * flowable表达式处理 * * @author xianglujun * @date 2022/11/30 10:55 */ @UtilityClass public class ExpressionUtil { private static CommandContext commandContext; static { commandContext = new CommandContextFactory().createCommandContext(commandContext1 -> { System.out.println("execute方法执行..."); return null; }); ProcessEngineConfigurationImpl configuration = SpringUtils.getBean(ProcessEngineConfigurationImpl.class); commandContext.setEngineConfigurations(configuration.getEngineConfigurations()); commandContext.setCommandExecutor(null); commandContext.setClassLoader(ExpressionUtil.class.getClassLoader()); commandContext.setUseClassForNameClassLoading(true); commandContext.setClock(new DefaultClockImpl()); commandContext.setObjectMapper(new ObjectMapper()); commandContext.setSessionFactories(configuration.getSessionFactories()); } /** * 计算表达式结果值 * * @param expression 表达式信息 * @param variables 参与计算的变量 * @return 表达式结果 */ public Object evalExpression(String expression, Map<String, Object> variables) { ExecutionEntityImpl execution = new ExecutionEntityImpl(); execution.setId(UUID.randomUUID().toString()); if (Objects.nonNull(variables)) { variables.entrySet() .forEach(entry -> { execution.setTransientVariable(entry.getKey(), entry.getValue()); }); } // 执行表达式 return evalExpression(expression, execution); } /** * 获取表达式计算值 * * @param expression 表达式 * @param execution 执行容器 * @return 执行结果 */ public Object evalExpression(String expression, DelegateExecution execution) { if (Objects.isNull(Context.getCommandContext())) { // 获取系统内置的ExpressionManager Context.setCommandContext(commandContext); } ExpressionManager expressionManager = SpringUtils.getBean(ExpressionManager.class); Expression evalExpression = expressionManager.createExpression(expression); return evalExpression.getValue(execution); } /** * 判断条件表达式 * * @param expression 表达式字符串 * @param variables 参与计算的变量 * @return 返回结果 */ public boolean conditionExpression(String expression, Map<String, Object> variables) { Object val = evalExpression(expression, variables); if (val instanceof Boolean) { return (Boolean) val; } return false; } public static boolean isExpression(String expression) { return StringUtils.isNotBlank(expression) && expression.indexOf("${") > -1; } }
在以上的实现中,包含了几个特别重要的参数:
- processingTaskKeys: 该参数主要是传入流程中正在执行的任务列表,我们可以根据正在执行的任务列表定位到当前流程所处的节点,然后从当前节点向后遍历。
- finishedTaskKeys: 该参数主要传入已经审批完成的任务key和已经遍历过的任务key, 这是因为,当流程配置异常的情况下,可能会配置循环的流程,因此当流程产生循环的时候就需要及时的跳出,防止进入死循环。同时当我们使用排他网关或者并行网关的时候,因为线条有分叉也有合并,因此在合并的时候,只需要处理一次即可。
- bpmnModel: 该对象则是流程定义的xml文件对象,其中包含了对流程的解析
- variables: 则是流程中已经产生的全局的变量,该变量会在解析排他网关的时候产生作用
获取流程详情列表
public List<WfTaskVo> queryDetailProcess(String procInsId, boolean filterFinished, boolean hasNext) { if (StringUtils.isNotBlank(procInsId)) { log.info("queryDetailProcess: 开始处理流程节点列表, 流程实例编号: {}", procInsId); Stopwatch outStopwatch = Stopwatch.createStarted(); List<HistoricTaskInstance> taskInstanceList = historyService.createHistoricTaskInstanceQuery() .processInstanceId(procInsId) .taskWithoutDeleteReason() .orderByHistoricTaskInstanceStartTime().desc() .list(); List<Comment> commentList = taskService.getProcessInstanceComments(procInsId); List<WfTaskVo> taskVoList = new ArrayList<>(taskInstanceList.size()); List<String> processTaskKeys = new ArrayList<>(); List<String> finishedTaskKeys = new ArrayList<>(); processTaskInfo(taskInstanceList, commentList, taskVoList, processTaskKeys, finishedTaskKeys, filterFinished); if (hasNext && CollectionUtil.isNotEmpty(processTaskKeys)) { String procDefId = taskInstanceList.get(0).getProcessDefinitionId(); log.info("queryDetailProcess: 开始处理审批流程后续节点, 流程实例: {}", procInsId); Stopwatch stopwatch = Stopwatch.createStarted(); List<WfTaskVo> wfTaskVos = handleNextFlows(procInsId, procDefId, processTaskKeys, finishedTaskKeys); if (CollectionUtil.isNotEmpty(wfTaskVos)) { CollectionUtil.reverse(wfTaskVos); taskVoList.addAll(0, wfTaskVos); } log.info("queryDetailProcess: 处理审批流程后续节点完成, 流程实例: {}, 用时: {}ms", procInsId, stopwatch.stop().elapsed(TimeUnit.MILLISECONDS)); } log.info("queryDetailProcess: 获取流程节点成功,实例编号: {}, 用时: {}ms", procInsId, outStopwatch.stop().elapsed(TimeUnit.MILLISECONDS)); return taskVoList; } return Collections.emptyList(); } /** * 获取未开始任务节点 * * @param procInsId 流程实例编号 * @param procDefId 流程定义编号 * @param processTaskKeys 正在处理的任务编号 * @param finishedTaskKeys 已完成任务编号 * @return 任务详情信息 */ private List<WfTaskVo> handleNextFlows(String procInsId, String procDefId, List<String> processTaskKeys, List<String> finishedTaskKeys) { // 查询变量列表 List<HistoricVariableInstance> variableInstances = historyService.createHistoricVariableInstanceQuery() .processInstanceId(procInsId) .list(); Map<String, Object> variables = new HashMap<>(); variableInstances.forEach(variable -> { variables.put(variable.getVariableName(), variable.getValue()); }); // 查询流程定义 BpmnModel bpmnModel = repositoryService.getBpmnModel(procDefId); if (Objects.isNull(bpmnModel)) { log.warn("handleNextFlows: 未查询到流程定义信息, 流程实例: {}, 流程定义: {}", procInsId, procDefId); return Collections.emptyList(); } // 获取流程后续节点 List<UserTask> userTasks = FlowableUtils.listNextFlowElement(processTaskKeys, finishedTaskKeys, bpmnModel, variables); if (CollectionUtil.isEmpty(userTasks)) { log.info("handleNextFlows: 未查询到后续节点, 跳过后续步骤。 流程实例: {}", procInsId); return Collections.emptyList(); } WfTaskDetermineBuilder determineBuilder = new WfTaskDetermineBuilder.Builder() .processDataTypeService(processDataTypeService) .variables(variables) .build(); // 开始处理后续节点 return userTasks.stream() .map(determineBuilder::build) .map(vo -> { vo.setProcInsId(procInsId); vo.setProcDefId(procDefId); return vo; }) .filter(Objects::nonNull) .collect(Collectors.toList()); } private void processTaskInfo( List<HistoricTaskInstance> taskInstanceList, List<Comment> commentList, List<WfTaskVo> taskVoList, List<String> processingTaskKeys, List<String> finishedTaskKeys, boolean filterFinishedTask) { taskInstanceList.forEach(taskInstance -> { if (filterFinishedTask && Objects.nonNull(taskInstance.getEndTime())) { return; } WfTaskVo taskVo = new WfTaskVo(); taskVo.setProcDefId(taskInstance.getProcessDefinitionId()); taskVo.setTaskId(taskInstance.getId()); taskVo.setTaskDefKey(taskInstance.getTaskDefinitionKey()); taskVo.setTaskName(taskInstance.getName()); taskVo.setCreateTime(taskInstance.getCreateTime()); taskVo.setFinishTime(taskInstance.getEndTime()); if (Objects.nonNull(taskInstance.getEndTime())) { finishedTaskKeys.add(taskInstance.getTaskDefinitionKey()); } else { processingTaskKeys.add(taskInstance.getTaskDefinitionKey()); } boolean isGroup = false; if (StringUtils.isNotBlank(taskInstance.getAssignee())) { Long userId = Long.parseLong(taskInstance.getAssignee()); UcUserVO user = userService.getUserById(userId); String nickName = user.getNickName(); nickName = StringUtils.isBlank(nickName) ? user.getName() : nickName; taskVo.setAssigneeId(user.getId()); taskVo.setAssigneeName(nickName); taskVo.setDeptName(user.getDepartmentName()); taskVo.setAssigneePosition(user.getPositionName()); } // 展示审批人员 List<HistoricIdentityLink> linksForTask = historyService.getHistoricIdentityLinksForTask(taskInstance.getId()); StringBuilder stringBuilder = new StringBuilder(); List<String> groupIds = new ArrayList<>(); for (HistoricIdentityLink identityLink : linksForTask) { if ("candidate".equals(identityLink.getType())) { if (StringUtils.isNotBlank(identityLink.getUserId())) { Long userId = Long.parseLong(identityLink.getUserId()); UcUserVO user = userService.getUserById(userId); stringBuilder.append(user.getNickName()).append(","); } if (StringUtils.isNotBlank(identityLink.getGroupId())) { groupIds.add(identityLink.getGroupId()); isGroup = true; } } } if (CollectionUtil.isNotEmpty(groupIds)) { String candidateNames = processDataTypeService.listDataDesc(groupIds); stringBuilder.append(candidateNames); } if (StringUtils.isNotBlank(stringBuilder)) { taskVo.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1)); } else { taskVo.setCandidate(taskVo.getAssigneeName()); } if (ObjectUtil.isNotNull(taskInstance.getDurationInMillis())) { taskVo.setDuration(DateUtil.formatBetween(taskInstance.getDurationInMillis(), BetweenFormatter.Level.SECOND)); } // 新增审批人是否分组标识 taskVo.setIsGroup(isGroup); // 获取意见评论内容 if (CollUtil.isNotEmpty(commentList)) { List<Comment> comments = new ArrayList<>(); // commentList.stream().filter(comment -> taskInstance.getId().equals(comment.getTaskId())).collect(Collectors.toList()); for (Comment comment : commentList) { if (comment.getTaskId().equals(taskInstance.getId())) { comments.add(comment); // taskVo.setComment(WfCommentDto.builder().type(comment.getType()).comment(comment.getFullMessage()).build()); } } taskVo.setCommentList(comments); } taskVoList.add(taskVo); }); }
在我的处理逻辑中我是必须要有已审批的节点的,这个是跟我们自身的业务逻辑来定的,这里的逻辑可以根据不同的需要做调整。
以上就是获取流程未审批节点的处理方式,能够处理简单的流程逻辑,提供了一个大概的思路。有什么问题,欢迎在评论区留言讨论。