camunda 利用 Parallel Gateway实现会签

首先说一下, Parallel Gateway 并不是首选做会签的方法,具体请看 https://www.baidu.com/s?wd=activiti会签, 但这里还是通过 Parallel Gateway来实现会签,下图是一个简单的会签流程图

1600358934807.png

利用并行网关来做的话相对更加直观,也不需要在发起会签之前设置额外的会签用户列表,但最大的问题是,如何汇总并行网关的结果,一般来说,如果直接设置表达式的话,会比较繁琐,例如只要一个人审核通过,那么整个会签就通过:

${task1 == 'true'} || ${task2 == 'true'} || ${task3 == 'true'} || ${task4 || true}

这种还是简单的情况,如果要有两个人审核通过,会签才算通过的话,表达式能写死人,在这种情况下,可以借助一个额外的bean来简化表达式:


@Component
public class V {

    public final ProcessEngine processEngine;

    public V(ProcessEngine processEngine) {
        this.processEngine = processEngine;
    }

    public boolean rate(double rate) {
        Execution e = Context.getBpmnExecutionContext().getExecution();
        Calc calc = getCalc((ExecutionEntity) e);
        return calc.rate(rate);
    }

    private Calc getCalc(ExecutionEntity execution) {
        Gateway gateway = getGateway(execution);
        Calc calc = new Calc();
        for (SequenceFlow flow : gateway.getIncoming()) {
            FlowNode node = flow.getSource();
            if (node instanceof Task) {
                calc.results.add(getTaskResult(flow, execution));
                break;
            }
            if (node instanceof ParallelGateway) {
                for (SequenceFlow pflow : node.getIncoming()) {
                    calc.results.add(getTaskResult(pflow, execution));
                }
            }
        }
        calc.processInstanceId = execution.getProcessInstanceId();
        return calc;
    }

    private Gateway getGateway(ExecutionEntity execution) {
        FlowElement flowElement = execution.getBpmnModelElementInstance();
        if (!(flowElement instanceof Gateway)) {
            if (flowElement instanceof SequenceFlow) {
                flowElement = ((SequenceFlow) flowElement).getSource();
            }
        }

        if (flowElement instanceof Gateway) {
            return (Gateway) flowElement;
        }

        throw new RuntimeException("表达式必须指向一个网关");
    }

    private String getTaskResult(SequenceFlow flow, Execution execution) {
        FlowNode node = flow.getSource();
        if (!(node instanceof Task)) {
            return null;
        }
        Task task = (Task) node;
        org.camunda.bpm.engine.task.Task dbTask = processEngine.getTaskService().createTaskQuery().active().taskDefinitionKey(task.getId()).singleResult();
        if (dbTask != null) {
            Object variable = processEngine.getTaskService().getVariable(dbTask.getId(), dbTask.getId());
            return Objects.toString(variable, null);
        }
        List<HistoricTaskInstance> taskInstances = processEngine.getHistoryService()
                .createHistoricTaskInstanceQuery()
                .taskDefinitionKey(task.getId())
                .processInstanceId(execution.getProcessInstanceId())
                .finished()
                .orderByHistoricTaskInstanceEndTime().desc()
                .listPage(0, 1);

        if (!taskInstances.isEmpty()) {
            HistoricVariableInstance variableInstance = processEngine.getHistoryService().createHistoricVariableInstanceQuery().variableName(taskInstances.get(0).getId()).singleResult();
            if (variableInstance != null) {
                return Objects.toString(variableInstance.getValue(), null);
            }
        }
        return null;
    }

    private final class Calc {
        private String processInstanceId;
        private List<String> results = new ArrayList<>();

        public boolean rate(double rate) {
            if (results.isEmpty()) {
                return false;
            }
            boolean flag = (results.stream().filter(s -> "approve".equals(s)).count() / results.size()) > rate;

            String value = flag ? "approve" : "reject";
            processEngine.getRuntimeService().setVariable(processInstanceId, processInstanceId, value);
            return flag;
        }
    }
}

上述类用于判断通过的比例,例如表达式${v.rate(0.6)}表达式,如果有2/3的人投票通过,那么会签就通过,表达式设置在sequence flow上即可:

1600359457106.png

另外,如果会签结束之后整个流程就结束了,这个时候,我们没有办法判断整个流程是否审核通过,例如:

1600359620682.png

如果碰到这种情况,我们需要在结束前的sequence flow上设置一个 listener 表达式,例如:

1600359788541.png

这样的话,如果有一半人通过,那么代表整个流程通过

camunda bpmn-js-properties-panel简单使用
ExecutorService等待当前所有任务完成