FlowAble流程定义之节点组装

因为公司项目需要自定义流程前端组件,因此后端采用org.flowable.bpmn.model.BpmnModel形式定义部署流程,由后端解析前端数据结构并且组装BpmnModel对象,从而不再引入flowable-ui相关jar。 demo项目飞机 流程引擎Demo项目 Git clone飞机 Clone Https


一、主要使用对象

  • org.flowable.bpmn.model.BpmnModel:标准流程定义模板对象(可以包含多个Process)
  • org.flowable.bpmn.model.Process: 具体流程定义(流程详细信息)
  • org.flowable.bpmn.model.FlowElement: 流程处理节点抽象类(流程节点需实现该类[知道就可以了])
  • org.flowable.bpmn.model.StartEvent: 流程开始事件(流程实例的开始位置[必须])
  • org.flowable.bpmn.model.EndEvent: 流程结束事件
  • org.flowable.bpmn.model.SequenceFlow: 事件连接对象(多分枝条件判断也是该对象)
  • org.flowable.bpmn.model.UserTask: 用户任务节点(审批流核心)
  • org.flowable.bpmn.model.ExclusiveGateway: 排他网关
  • org.flowable.bpmn.model.ServiceTask: 服务任务节点(可自定义处理方案)

二、对象创建特定参数

以上提到的全部对象都可以通过 new 关键字创建, 此处只对部分必要参数进行说明。 注意:以上全部对象都需要ID,并且ID不能以数字开始。最简单的办法解决办法是 字符串+UUID。

/**
* 流程模版定义ID获取
 *
* @return uid
*/
static String getUid() {
    return String.format("%s%s", ID_PREFIX_STRING, UUID.randomUUID());
}
String ID_PREFIX_STRING = "sia-";

1. 开始事件

开始事件是流程启动流程实例的起点, 创建内容基本固定,没太多花里胡哨的操作。

    /**
     * 创建开始节点
     *
     * @return 开始节点对象
     */
    private StartEvent createStartNode() {
        //开始事件
        StartEvent startEvent = new StartEvent();
        startEvent.setId(FlowAbleBpmnConstant.START_EVENT_ID);
        startEvent.setName(FlowAbleBpmnConstant.START_EVENT_NAME);
        return startEvent;
    }

2.连接线

连接线的主要作用是告诉flowable节点与节点之间的关联,普通连接线和条件判断连接仅部分属性不同。

(1) 普通连线

    /**
     * 单纯连接节点
     *
     * @param sourceRef 上一级ID
     * @param targetRef 下一级ID
     * @return 连接对象
     */
    private SequenceFlow createSequenceFlow(String sourceRef, String targetRef) {
        SequenceFlow flow = new SequenceFlow();
        //网关节点ID
        flow.setId(FlowAbleBpmnConstant.getUid());
        //上一级节点ID
        flow.setSourceRef(sourceRef);
        //下一级节点ID
        flow.setTargetRef(targetRef);
        return flow;
    }

(2) 条件判断连线

条件判断连线可以用于多分支流程控制,可用于审批结果判断及其他各种流转条件判断。通过SequenceFlow.setConditionExpression(String conditionExpression)方法设定,内容为EL表达式。例如: 审批通过:

SequenceFlow flow = new SequenceFlow();
//上一级节点ID
flow.setSourceRef(sourceRef);
//下一级节点ID
flow.setTargetRef(targetRef);
 //条件分支节点ID
flow.setId(FlowAbleBpmnConstant.getUid());
//没有条件分支,默认条件分支
flow.setConditionExpression(FlowAbleBpmnConstant.TRUE_APPROVED_STRING);
......
/**
* 网关后条件判断参数-审批通过
*/
String TRUE_APPROVED_STRING = "${approved}";

条件属性判断: 假设提交审批中的数据中有高度(private Integer highValue)字段,则根据业务需求分支可写为:

flow.setConditionExpression("${highValue >= 50}");

3.用户任务

用户任务在flowabel几乎是用途最广的组件,此处用作审批流程, 并且审批用户信息采用自定义的用户组配置。多实例任务采用一票通过、一票拒绝、全票通过制完成。

(1) 创建用户任务节点:

UserTask userTask = new UserTask();
userTask.setId(FlowAbleBpmnConstant.getUid());
//写入节点名称(不讲究,你想叫它啥就是啥)
userTask.setName(approveNode.getNodeName());
//写入描述信息(也不讲究,你想描述啥就是啥,此处用来记录自定义处理标记)
userTask.setDocumentation(DOCUMENTATION);

(2)设置任务处理监听器

该自定义监听器主要是用于计算用户处理具体Task任务的结果(例如对审批通过或者拒绝结果进行计数)

//写入节点监听器
userTask.setTaskListeners(getTaskListeners());
...... getTaskListeners()方法详细内容如下
/**
* 获取用户任务监听节点
*
* @return 监听节点列表
*/
private List<FlowableListener> getTaskListeners() {
    List<FlowableListener> taskListeners = Lists.newArrayList();
    //监听器开始class
    FlowableListener listener = new FlowableListener();
    listener.setEvent(FlowAbleBpmnConstant.COMPLETE_EVENT_ID);
    listener.setImplementationType(FlowAbleBpmnConstant.LISTENER_TYPE);
    listener.setImplementation(FlowAbleBpmnConstant.TASK_CLASS_NAME_STRING);
    taskListeners.add(listener);
    return taskListeners;
}
...... 以上方法使用到的常量具体值如下
/**
* 完成节点ID
*/
String COMPLETE_EVENT_ID = "complete";
/**
* 监听器执行类型
*/
String LISTENER_TYPE = "class";
/**
* 用户处理任务监听器class全路径
*/
String TASK_CLASS_NAME_STRING = "com.zbzk.cmp.workflow.listener.MultiInstanceTaskListener";
...... MultiInstanceTaskListener监听类的具体实现内容
/**
 * 会签节点每个Activity的complete执行完成后都会回调此方法
 *
 * @author smile
 */
@Component
@Slf4j
public class MultiInstanceTaskListener implements TaskListener {

    private static final long serialVersionUID = -5645036734503266140L;
    private static final String REJECTED_STRING = "rejected";
    private static final String REJECT_STRING = "reject";
    private static final String APPROVED_STRING = "approved";

    @Override
    public void notify(DelegateTask delegateTask) {
        log.info("[MultiInstanceTaskListener].[notify] ------> delegateTask = {}", JSON.toJSONString(delegateTask));
        //result的值为控制类中taskService.complete(taskId, map)时,map中所设
        String result = delegateTask.getVariable(APPROVED_STRING).toString();
        //ExecutionListener类中设置的拒绝计数变量
        int rejectedCount = (int) delegateTask.getVariable(REJECTED_STRING);
        if (REJECT_STRING.equals(result)) {
            //拒绝
            delegateTask.setVariable(REJECTED_STRING, ++rejectedCount);
        }
    }
}

(3)设置任务开始和结束监听器

此处主要是用到了任务开始监听器,由该监听器在任务处理开始之前初始化计数器,任务结束监听器未做操作,目前仅打印了计数日志。

userTask.setExecutionListeners(getExecutionListeners());
...... getExecutionListeners()方法详细内容如下
/**
* 获取用户节点任务开始和结束监听器
*
* @return 监听器列表
*/
private List<FlowableListener> getExecutionListeners() {
    List<FlowableListener> executionListeners = Lists.newArrayList();
    //监听器开始class
    FlowableListener listener = new FlowableListener();
    listener.setEvent(FlowAbleBpmnConstant.START_EVENT_ID);
    listener.setImplementationType(FlowAbleBpmnConstant.LISTENER_TYPE);
    listener.setImplementation(FlowAbleBpmnConstant.START_EXECUTION_CLASS_NAME_STRING);
    executionListeners.add(listener);

    //监听器结束class
    listener = new FlowableListener();
    listener.setEvent(FlowAbleBpmnConstant.END_EVENT_ID);
    listener.setImplementationType(FlowAbleBpmnConstant.LISTENER_TYPE);
    listener.setImplementation(FlowAbleBpmnConstant.END_EXECUTION_CLASS_NAME_STRING);
    executionListeners.add(listener);
    return executionListeners;
}
...... 以上方法使用到的常量具体值如下
/**
 * 开始节点ID
 */
String START_EVENT_ID = "start";
/**
 * 监听器执行类型
*/
String LISTENER_TYPE = "class";
/**
 * 监听器执行开始class全路径
 */
String START_EXECUTION_CLASS_NAME_STRING = "com.zbzk.cmp.workflow.listener.MultiInstanceStartExecutionListener";
/**
 * 监听器执行结束class全路径
 */
String END_EXECUTION_CLASS_NAME_STRING = "com.zbzk.cmp.workflow.listener.MultiInstanceEndExecutionListener";
...... 监听器具体实现内容
/**
 * 会签节点开始时调用的 Listener
 *
 * @author smile
 */
@Component
@Slf4j
public class MultiInstanceStartExecutionListener implements ExecutionListener {
    private static final long serialVersionUID = -3124311419896463037L;
    private static final String UNRELATED_STRING = "unrelated";
    private static final String REJECTED_STRING = "rejected";
    private static final int NUMBER_ZERO = 0;

    @Override
    public void notify(DelegateExecution execution) {
        log.info("[MultiInstanceStartExecutionListener].[notify] ------> execution = {}",
                JSON.toJSONString(ObjectUtils.isEmpty(execution.getId()) ? null : execution.getId()));
        execution.setVariable(UNRELATED_STRING, NUMBER_ZERO);
        execution.setVariable(REJECTED_STRING, NUMBER_ZERO);
    }
}
...... 监听器具体实现内容
/**
 * 会签节点结束时调用的 Listener
 *
 * @author smile
 */
@Component
@Slf4j
public class MultiInstanceEndExecutionListener implements ExecutionListener {
    private static final long serialVersionUID = 5056711029991557627L;

    @Override
    public void notify(DelegateExecution execution) {
        log.info("[MultiInstanceEndExecutionListener].[notify] ------> execution = {}",
                JSON.toJSONString(ObjectUtils.isEmpty(execution.getId()) ? null : execution.getId()));
    }
}

(4)设置多实例属性

多实例属性也就是多人会签(或签),其中主要属性有任务循环次数、用户执行结果处理等,此处循环次数和用户列表全部通过EL表达式调用自定义方法获取。

//写入审批人EL表达式
userTask.setAssignee("${assignee}");
//设置多实例属性
userTask.setLoopCharacteristics(getCountersign(approveNode.getResIds(), approveNode.getNodeType()));
...... getCountersign()方法详细内容如下
/**
 * 获取会签信息
 *
 * @param resIds 审批用户组
 * @param type   会签类型(1-或签[一票通过], 2-会签[全票通过])
 * @return 会签节点信息
 */
private MultiInstanceLoopCharacteristics getCountersign(List<String> resIds, Integer type) {
    MultiInstanceLoopCharacteristics characteristics = new MultiInstanceLoopCharacteristics();
    // 设置并行执行(每个审批人可以同时执行)
    characteristics.setSequential(false);
    // 设置完成条件 提交会签类型,由FLowAble自定义方法处理结果
    characteristics.setCompletionCondition(
            String.format("%s%s%s", FlowAbleBpmnConstant.CONDITION_START_STRING, type, FlowAbleBpmnConstant.CONDITION_END_STRING));
    //审批人集合参数
    StringBuilder stringBuffer = new StringBuilder();
    for (String id : resIds) {
        stringBuffer.append(id).append(",");
    }
    //审批用户组
    characteristics.setInputDataItem(
            FlowAbleBpmnConstant.COLLECTION_START_STRING + stringBuffer.substring(0, stringBuffer.toString().length() - 1) + FlowAbleBpmnConstant.CARDINALITY_END_STRING);
    //循环次数获取方法
    characteristics.setLoopCardinality(
            FlowAbleBpmnConstant.CARDINALITY_START_STRING + stringBuffer.substring(0, stringBuffer.toString().length() - 1) + FlowAbleBpmnConstant.CARDINALITY_END_STRING);
    //迭代集合
    characteristics.setElementVariable(FlowAbleBpmnConstant.ASSIGNEE_STRING);
    return characteristics;
}
...... 以上方法使用到的常量具体值如下
/**
 * 审批结束调用方法(组装参数调用自定义方法)
 */
String CONDITION_START_STRING = "${multiInstanceCompleteTask.accessCondition(execution,";
String CONDITION_END_STRING = ")}";
/**
 * 审批该节点的用户组信息(组装参数调用自定义方法)
 */
String COLLECTION_START_STRING = "${fakeLdapService.findAllSales(\"";
String CARDINALITY_END_STRING = "\", departmentId)}";
/**
 * 用户节点循环次数获取方法
 */
String CARDINALITY_START_STRING = "${fakeLdapService.cycleFrequency(\"";
/**
 * 审批人参数
 */
String ASSIGNEE_STRING = "assignee";