1. 回退(駁回)
回退的思路就是動態更改節點的流向。先遇水搭橋,最後再過河拆橋。
具體操作如下:
- 取得當前節點的資訊
- 取得當前節點的上一個節點的資訊
- 儲存當前節點的流向
- 新建流向,由當前節點指向上一個節點
- 將當前節點的流向設定為上面新建的流向
- 當前節點完成任務
- 將當前節點的流向還原
- 取得之前上個節點的執行人
- 設定上個節點的assignee為之前的執行人
程式碼實現起來可能是這樣的:
@Test
public void huitui() throws Exception {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processInstanceId("55001").singleResult();
backProcess(task);
}
/**
* 駁回 / 回退
* 按照這種方法,可以回退至任意節點
* @param task
* @throws Exception
*/
public void backProcess(Task task) throws Exception {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
HistoryService historyService = processEngine.getHistoryService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
String processInstanceId = task.getProcessInstanceId();
// 獲取所有歷史任務(按建立時間降序)
List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByTaskCreateTime()
.desc()
.list();
List<HistoricActivityInstance> hisActivityList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId).list();
if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
return;
}
// 當前任務
HistoricTaskInstance currentTask = hisTaskList.get(0);
// 前一個任務
HistoricTaskInstance lastTask = hisTaskList.get(1);
// 當前活動
HistoricActivityInstance currentActivity = hisActivityList.stream().filter(e -> currentTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);
// 前一個活動
HistoricActivityInstance lastActivity = hisActivityList.stream().filter(e -> lastTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
// 獲取前一個活動節點
FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivity.getActivityId());
// 獲取當前活動節點
FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivity.getActivityId());
// 臨時儲存當前活動的原始方向
List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
// 清理活動方向
currentFlowNode.getOutgoingFlows().clear();
// 建立新方向
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(currentFlowNode);
newSequenceFlow.setTargetFlowElement(lastFlowNode);
List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
newSequenceFlowList.add(newSequenceFlow);
// 當前節點指向新的方向
currentFlowNode.setOutgoingFlows(newSequenceFlowList);
// 完成當前任務
taskService.complete(task.getId());
// 重新查詢當前任務
Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
if (null != nextTask) {
taskService.setAssignee(nextTask.getId(), lastTask.getAssignee());
}
// 恢復原始方向
currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}
以請假為例
<process id="holiday" name="holiday" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="填寫請假單" activiti:assignee="${assignee1}"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="部門經理審批" activiti:assignee="${assignee2}"></userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="人事審批" activiti:candidateUsers="tom,jerry"></userTask>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
假設現在已經到“人事審批”這個節點了,當前活動是usertask3
接下來,我們執行上面的程式碼,回退到上一個節點“部門經理審批”,於是
流程重新從“部門經理審批”節點開始往下走,當流程走完以後
證明,思路正確,寫法沒啥問題。但是,上面的程式碼可以簡化一下,如下:
/**
* 跳到最開始的任務節點(直接打回)
* @param task 當前任務
*/
public void jumpToStart(Task task) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
HistoryService historyService = processEngine.getHistoryService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
String processInstanceId = task.getProcessInstanceId();
// 獲取所有歷史任務(按建立時間升序)
List<HistoricTaskInstance> hisTaskList = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByTaskCreateTime()
.asc()
.list();
if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
return;
}
// 第一個任務
HistoricTaskInstance startTask = hisTaskList.get(0);
// 當前任務
HistoricTaskInstance currentTask = hisTaskList.get(hisTaskList.size() - 1);
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
// 獲取第一個活動節點
FlowNode startFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(startTask.getTaskDefinitionKey());
// 獲取當前活動節點
FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentTask.getTaskDefinitionKey());
// 臨時儲存當前活動的原始方向
List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
// 清理活動方向
currentFlowNode.getOutgoingFlows().clear();
// 建立新方向
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(currentFlowNode);
newSequenceFlow.setTargetFlowElement(startFlowNode);
List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
newSequenceFlowList.add(newSequenceFlow);
// 當前節點指向新的方向
currentFlowNode.setOutgoingFlows(newSequenceFlowList);
// 完成當前任務
taskService.complete(task.getId());
// 重新查詢當前任務
Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
if (null != nextTask) {
taskService.setAssignee(nextTask.getId(), startTask.getAssignee());
}
// 恢復原始方向
currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}
2. 會籤
多個人同時處理一個任務,這種任務我們稱之為會籤任務 。Activiti實現會籤是基於多例項任務,將節點設定成多例項,主要通過在UserTask節點的屬性上配置。
會籤的種類:
- 按數量通過: 達到一定數量的通過表決後,會籤通過。
- 按比例通過: 達到一定比例的通過表決後,會籤通過。
- 一票否決: 只要有一個表決時否定的,會籤通過。
- 一票通過: 只要有一個表決通過的,會籤通過。
每個例項有以下變數:
- nrOfInstances: 例項總數
-
nrOfActiveInstances: 當前啟用的(未完成的)例項總數。 如果序列執行,則改值永遠是1
- nrOfCompletedInstances: 已完成的例項總數
條件${nrOfInstances == nrOfCompletedInstances}表示所有人員審批完成後會簽結束。
條件${ nrOfCompletedInstances == 1}表示一個人完成審批,該會籤就結束。
其他條件依次類推,同時這裡也可以寫自己新增的流程變數。
相關文件如下:
下面舉個例子:
<process id="countersign" name="countersign" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="申請" activiti:assignee="zhangsan"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="會籤審批" activiti:assignee="${approver}">
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="${approverList}" activiti:elementVariable="approver">
<completionCondition>${nrOfCompletedInstances == nrOfInstances}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="備案" activiti:assignee="tianqi"></userTask>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
編寫程式碼:
// 部署流程定義
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("diagram/countersign.bpmn")
.name("會籤示例")
.key("countersign")
.deploy();
// 啟動流程例項
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<>();
variables.put("approverList", Arrays.asList("lisi","wangwu","zhaoliu"));
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("countersign", variables);
// 完成任務
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processInstanceId("107501").taskAssignee("zhaoliu").singleResult();
if (null != task) {
taskService.complete(task.getId());
}
流程啟動後,首先是zhangsan審批
當zhangsan完成自己的任務後,進入會籤環節,於是我們看到當前有3個啟用的任務
當lisi完成任務以後,當前任務剩下2個
當wangwu和zhaoliu都完成任務了以後,會籤任務完成,進入下一個環節
剛才的例子中沒有考慮到審批不通過的情況,接下來我們完善一下,考慮下面的流程
<process id="countersign" name="countersign" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="申請" activiti:assignee="zhangsan"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="會籤審批" activiti:assignee="${approver}">
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${approverList}" activiti:elementVariable="approver">
<completionCondition>${nrOfCompletedInstances / nrOfInstances == 1 || pass == false}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<userTask id="usertask3" name="備案" activiti:assignee="tianqi"></userTask>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow5" sourceRef="usertask2" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow6" name="通過" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass == true}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" name="拒絕" sourceRef="exclusivegateway1" targetRef="usertask1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${pass == false}]]></conditionExpression>
</sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow8" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
在會籤審批完成任務時就要加上流程變數pass了
RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processInstanceId("152501").taskAssignee("lisi").singleResult();
if (null != task) {
Map<String, Object> variables = new HashMap<>();
variables.put("pass", true);
// variables.put("pass", false);
taskService.complete(task.getId(), variables);
runtimeService.getVariable(task.getExecutionId(), "nrOfCompletedInstances");
}
zhaoliu審批的時候pass傳的false,於是流程又走到zhangsan那裡,流程重新又走了一遍才全部完成
關於回退和會籤就先講到這裡
3. 參考
https://www.activiti.org/userguide/index.html#bpmnMultiInstance