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";