在最近的项目开发中,有一个需求为获取未审批节点的信息。在我们系统中,流程图没有特别的节点,主要包含了并行网关、排他网关、任务节点这几种类型的节点。因此在获取未审批节点的时候,就没有其他的业务处理逻辑。该问题作为记录,以便日后使用。
排他网关
在处理逻辑中,比较复杂的应该就是排他网关了。在排他网关的时候,是需要根据表达式计算行走的路径,直接影响了获取后续列表。因此我们可以定义一个工具类,用于计算表达式的具体值。
@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);
});
}
在我的处理逻辑中我是必须要有已审批的节点的,这个是跟我们自身的业务逻辑来定的,这里的逻辑可以根据不同的需要做调整。
以上就是获取流程未审批节点的处理方式,能够处理简单的流程逻辑,提供了一个大概的思路。有什么问题,欢迎在评论区留言讨论。