页面有时需要在完成当前任务之前,展示后面可能进入的审批节点。此时下一条任务尚未创建,不能简单查询待办列表;需要从流程定义模型中分析。

不过,“下一节点”可能是网关、服务任务或结束事件,也可能产生多个并行用户任务。应先明确想要的是“模型上可到达的候选”还是“按当前变量实际会进入的任务”。二者不能混用。

从当前任务定位模型节点

下面使用 Activiti 7.1.0.M6 引擎层接口。当前任务仍在运行时,可以直接取得定义 ID 和节点 ID:

org.activiti.engine.task.Task task =
        taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null || task.getProcessDefinitionId() == null) {
    throw new IllegalArgumentException("没有对应的运行中流程任务");
}

BpmnModel model = repositoryService.getBpmnModel(task.getProcessDefinitionId());
String nodeId = task.getTaskDefinitionKey();

已经结束的任务应先查历史任务,不要为了取得节点 ID 再查询可能已被删除的运行时 execution。标识的区别见 taskId、节点 ID 与历史活动。

一个有边界的静态候选查找

下面的 Java 8 方法针对主流程同一层级,沿出线查找每条路径遇到的第一个用户任务。它不计算条件,不跨入子流程,也不执行服务任务。需要导入 java.util.* 和相应 BPMN 模型类型。

public static List<UserTask> nextUserTaskCandidates(
        BpmnModel model, String currentNodeId) {
    org.activiti.bpmn.model.Process process = model.getMainProcess();
    FlowElement current = process.getFlowElement(currentNodeId);
    if (!(current instanceof FlowNode)) {
        throw new IllegalArgumentException("当前元素不是可遍历节点");
    }

    Deque<String> pending = new ArrayDeque<>();
    for (SequenceFlow flow : ((FlowNode) current).getOutgoingFlows()) {
        pending.addLast(flow.getTargetRef());
    }

    Set<String> visited = new HashSet<>();
    Map<String, UserTask> result = new LinkedHashMap<>();
    while (!pending.isEmpty()) {
        String id = pending.removeFirst();
        if (!visited.add(id)) {
            continue;
        }
        FlowElement element = process.getFlowElement(id);
        if (element instanceof UserTask) {
            result.put(id, (UserTask) element);
            continue;
        }
        if (element instanceof SubProcess || element instanceof CallActivity) {
            throw new UnsupportedOperationException("不跨子流程或调用活动预判");
        }
        if (element instanceof FlowNode) {
            for (SequenceFlow flow : ((FlowNode) element).getOutgoingFlows()) {
                pending.addLast(flow.getTargetRef());
            }
        }
    }
    return new ArrayList<>(result.values());
}

visited 防止模型中的环导致无限遍历;到达用户任务即停止沿该路径继续,避免把更远的审批节点也混入“下一批候选”。到达没有出线的结束节点时,自然没有新候选。

这段代码回答的是图上的可达关系。某条路径上有计时器、事件或服务任务时,即使找到了候选,也不代表任务会立即生成。模型对象的检索和节点关系可参考同版本 BpmnModel。

条件网关为什么不能随便模拟

实际执行需要考虑条件表达式、默认流、变量作用域,以及流程期间对变量的修改。自行取出表达式字符串做简单比较,很容易与引擎的求值语义不同。

对于排他网关,应该遵循对应引擎的条件与默认流规则;对于并行网关,结果可能是多条路径;包容网关和事件网关又有不同语义。不能仅凭“有多条出线”就认定只选其中一条。

如果业务要求精确的执行预判,需要明确支持的 BPMN 子集,复用匹配版本的表达式和变量上下文,并在测试中覆盖默认流、条件失败、循环和并行,而不是让一个通用递归函数假装支持整个 BPMN。

前端怎样使用这个结果

适合把静态结果展示为“后续可能进入的节点”,不要在完成任务前就把它当作已经存在的待办,也不要根据预览结果直接给尚未授权的人分配任务。

真正完成当前任务后,以引擎生成的任务与事件为准。如果完成监听器、服务任务或外部系统改变了条件,预览和实际结果可能不同;这时应更新页面,而不是强迫运行时符合预览。