学习连接
【工作流Activiti7】3、Activiti7 回退与会签
【工作流Activiti7】4、Activiti7 结束/终止流程
ativiti6.0 流程节点自由跳转实现、拒绝/不同意/返回上一节点、流程撤回、跳转、回退等操作(通用实现,亲测可用) - 掘金
activiti6.0源码剖析之节点任意跳转 -简书
activiti工作流核心java api使用,activiti退回、跳过、强制结束实现,BpmnModel模型操作,运行中流程图、获取流程变量、候选人操作
activiti学习(二十二)——常规任务节点跳转(退回、自由跳转功能)
流程回退
回退至上一节点
流程图
最简单的流程图,中间仅3个节点,依次从开始流转到结束
简要分析一下下面的bpmn.xml:
- 开始标签使用的是startEvent(id是默认的startEvent1,可以手动指定)。
- 任务节点标签使用的是userTask,每个userTask都有自己的id属性(id默认是随机生成的,可以手动指定)和name属性(手动定义)。
- 两个userTask之间使用sequenceFlow 标签连接起来,sequenceFlow 标签有sourceRef(源节点id)和targetRef(目标节点id)以及它自己的id属性(id默认是随机生成的,可以手动指定)
- 结束标签使用的是endEvent(id默认是随机生成的,可以手动指定)
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef">
<process id="back-key" name="back-name" isExecutable="true">
<startEvent id="start"></startEvent>
<userTask id="userTask1" name="填写请假单" activiti:assignee="zs">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<sequenceFlow id="sid-2CD9ABD5-E163-496D-8827-233545140F44" sourceRef="start" targetRef="userTask1"></sequenceFlow>
<userTask id="userTask2" name="部门经理审批" activiti:assignee="ls">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<sequenceFlow id="sid-EFF2B7AB-E8E4-4E77-A026-1A3CE1DD3BC3" sourceRef="userTask1" targetRef="userTask2"></sequenceFlow>
<userTask id="userTask3" name="人事审批" activiti:assignee="ww">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<sequenceFlow id="sid-5F827E4F-19A2-4693-8A53-32EF9E493C2C" sourceRef="userTask2" targetRef="userTask3"></sequenceFlow>
<endEvent id="end"></endEvent>
<sequenceFlow id="sid-A9D46FB6-2D46-47AA-A57D-EAF6495AFC2F" sourceRef="userTask3" targetRef="end"></sequenceFlow>
</process>
</definitions>
开启流程
部署上述流程图,开启流程
/**
* 开启流程
*/
@Test
public void test_03() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();
HashMap<String, Object> vars = new HashMap<>();
runtimeService.startProcessInstanceByKey("back-key", vars);
}
正常审批
正常审批,一直到人事审批
/**
* 完成任务
*/
@Test
public void test_04() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processInstanceId("2501").singleResult();
taskService.complete(task.getId());
}
此时,表act_ru_execution
如下(可以看到,子执行实例id:2502 的act_id为userTask3,这个userTask3就是此执行实例当前的活动id,即userTask标签的id):
表act_ru_task
如下(可以看到,当前任务的task_def_key就是userTask标签的id):
表act_hi_taskinst
如下(可以看到,历史任务的task_def_key就是userTask标签的id。act_hi_taskinst
表的id_就是taskId):
表act_hi_actinst
如下(可以看到,历史活动实例的act_id就是userTask标签的id):
此时查询指定历史流程实例所对应的任务
/*
查询指定历史流程实例所对应的任务
*/
@Test
public void testHistoryTaskInstance() {
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
HistoryService historyService = engine.getHistoryService();
TaskService taskService = engine.getTaskService();
// 查询指定历史流程实例所对应的所有任务
List<HistoricTaskInstance> historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery()
.processInstanceId("2501")
.orderByHistoricTaskInstanceStartTime()
.asc()
.list();
HashMap<String, Object> taskId2CommentMap = new HashMap<>();
historicTaskInstanceList.stream().forEach(hisTaskInst -> {
List<Comment> taskComments = taskService.getTaskComments(hisTaskInst.getId());
if (!CollectionUtils.isEmpty(taskComments)) {
taskId2CommentMap.put(hisTaskInst.getId(), taskComments.get(0).getFullMessage());
}
});
for (HistoricTaskInstance historicTaskInstance : historicTaskInstanceList) {
log.info("历史任务实例id: {}", historicTaskInstance.getId());
log.info("历史任务名: {}", historicTaskInstance.getName());
log.info("任务办理人: {}", historicTaskInstance.getAssignee());
log.info("历史任务开始时间: {}", DateUtils.fmtDate(historicTaskInstance.getStartTime()));
log.info("历史任务结束时间: {}", DateUtils.fmtDate(historicTaskInstance.getEndTime()));
if (taskId2CommentMap.get(historicTaskInstance.getId()) != null) {
log.info("审批意见: {}", taskId2CommentMap.get(historicTaskInstance.getId()));
}
log.info("---------------------------------------------");
}
}
输出如下:
历史任务实例id: 2505
历史任务名: 填写请假单
任务办理人: zs
历史任务开始时间: 2023-11-25 23:27:59
历史任务结束时间: 2023-11-25 23:28:33
---------------------------------------------
历史任务实例id: 5002
历史任务名: 部门经理审批
任务办理人: ls
历史任务开始时间: 2023-11-25 23:28:33
历史任务结束时间: 2023-11-25 23:28:42
---------------------------------------------
历史任务实例id: 7502
历史任务名: 人事审批
任务办理人: ww
历史任务开始时间: 2023-11-25 23:28:42
历史任务结束时间:
---------------------------------------------
此时,再查询指定历史流程实例所对应的任务
/*
查询指定历史流程实例所对应的所有活动实例
*/
@Test
public void testHistoryActivityInstance() {
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
HistoryService historyService = engine.getHistoryService();
// 查询指定历史流程实例所对应的所有活动实例
List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId("2501")
.orderByHistoricActivityInstanceStartTime()
.asc()
.list();
for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
log.info("历史活动实例id: {}", historicActivityInstance.getId());
log.info("历史活动名: {}", historicActivityInstance.getActivityName());
log.info("历史活动类型: {}", historicActivityInstance.getActivityType());
log.info("任务活动办理人: {}", historicActivityInstance.getAssignee());
log.info("历史活动开始时间: {}", DateUtils.fmtDate(historicActivityInstance.getStartTime()));
log.info("历史活动结束时间: {}", DateUtils.fmtDate(historicActivityInstance.getEndTime()));
log.info("---------------------------------------------");
}
}
输出如下:
历史活动实例id: 2503
历史活动名: null
历史活动类型: startEvent
任务活动办理人: null
历史活动开始时间: 2023-11-25 23:27:59
历史活动结束时间: 2023-11-25 23:27:59
---------------------------------------------
历史活动实例id: 2504
历史活动名: 填写请假单
历史活动类型: userTask
任务活动办理人: zs
历史活动开始时间: 2023-11-25 23:27:59
历史活动结束时间: 2023-11-25 23:28:33
---------------------------------------------
历史活动实例id: 5001
历史活动名: 部门经理审批
历史活动类型: userTask
任务活动办理人: ls
历史活动开始时间: 2023-11-25 23:28:33
历史活动结束时间: 2023-11-25 23:28:42
---------------------------------------------
历史活动实例id: 7501
历史活动名: 人事审批
历史活动类型: userTask
任务活动办理人: ww
历史活动开始时间: 2023-11-25 23:28:42
历史活动结束时间:
---------------------------------------------
流程回退
回退的思路就是:动态更改节点的流向
。先遇水搭桥,最后再过河拆桥。
先简要了解下activiti流程元素的大致结构,可以看出来:
FlowElement
就是流程图中的元素,它分为FlowNode流程节点
和SequenceFlow顺序流
。- 其中
FlowNode分为3种类型:事件(Event)、网关(Gateway)、活动(Activity)
,对应的就是bpmn图中的各个节点,并且FlowNoe类中定义了2个属性:incomingFlows
和outgoingFlows
(都是SequenceFlow类型,对应的就是bpmn图中的流程线) - 其中
SequenceFlow顺序流
会通过sourceRef和targetRef分别记录连接的2个节点的id
- 其中
下面是比较全的一张图(为了简化,还是删了不少的子类):
具体操作如下:
- 取得当前节点的信息
- 取得当前节点的上一个节点的信息
- 保存当前节点的流向
- 新建流向,由当前节点指向上一个节点
- 将当前节点的流向设置为上面新建的流向
- 当前节点完成任务
- 将当前节点的流向还原
- 取得之前上个节点的执行人
- 设置上个节点的assignee为之前的执行人
@Test
public void back() throws Exception {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processInstanceId("2501").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();
// 如果历史活动实例为空 或者 数量才1个 说明不存在或者才刚开启流程
if (CollectionUtils.isEmpty(hisTaskList) || hisTaskList.size() < 2) {
return;
}
// 当前任务(通过历史任务按时间倒序,查询第1个)
HistoricTaskInstance currentTask = hisTaskList.get(0);
// 前一个任务(通过历史任务按时间倒序,查询第2个)
HistoricTaskInstance lastTask = hisTaskList.get(1);
// 当前活动(遍历当前流程实例的所有的历史活动实例, 根据当前任务的id(就是taskId)与历史活动实例的taskId相等, 找到对应的历史活动实例)
HistoricActivityInstance currentActivity = hisActivityList.stream().filter(e -> currentTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);
// 前一个活动(遍历当前流程实例的所有的历史活动实例, 根据前一个任务的id(就是taskId)与历史活动实例的taskId相等, 找到对应的历史活动实例)
HistoricActivityInstance lastActivity = hisActivityList.stream().filter(e -> lastTask.getId().equals(e.getTaskId())).collect(Collectors.toList()).get(0);
// 使用repositoryService, 根据流程定义id获取 【bpmn模型对象】
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
// 获取前一个活动节点(从bpmn模型对象中,根据活动实例的活动id(就是标签的id属性)找到FlowNode。所以活动实例其实就是把taskId和活动id给结合起来了)
FlowNode lastFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(lastActivity.getActivityId());
// 获取当前活动节点(从bpmn模型对象中,根据活动实例的活动id(就是标签的id属性)找到FlowNode。所以活动实例其实就是把taskId和活动id给结合起来了)
FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivity.getActivityId());
// 临时保存当前活动的原始方向(这说明每1个FlowNode都包含1个SequenceFlow的outgoingFlows集合)
List<SequenceFlow> originalSequenceFlowList = new ArrayList<>();
originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
// 清理活动方向
currentFlowNode.getOutgoingFlows().clear();
// 建立新方向
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(currentFlowNode); // 当前flowNode
newSequenceFlow.setTargetFlowElement(lastFlowNode); // 上一个flowNode
List<SequenceFlow> newSequenceFlowList = new ArrayList<>();
newSequenceFlowList.add(newSequenceFlow);
// 当前节点指向新的方向
currentFlowNode.setOutgoingFlows(newSequenceFlowList);
taskService.addComment(task.getId(), task.getProcessInstanceId(), "部门经理意见未填写, 回退至上一节点");
// 完成当前任务(将会沿着新给定的方向流转到指定的节点)
taskService.complete(task.getId());
// 重新查询当前任务
Task nextTask = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
if (null != nextTask) {
// 因为现在已经退回到上一个节点了, 这里从历史任务中获取上1个节点的任务负责人, 设置到当前任务中
taskService.setAssignee(nextTask.getId(), lastTask.getAssignee());
}
// 恢复原始方向
currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}
此时,表act_ru_execution
如下(可以看到,当前执行实例的act_id为userTask2,说明已经回到上1个节点了):
表act_ru_task
如下(当前任务已经是回退到了上1个节点了):
表act_hi_taskinst
如下:
表act_hi_actinst
如下:
完成审批
最后,再完成2次审批,可以正常的走完整个流程。
此时,表act_ru_execution
数据为空(流程已经结束了)
表act_ru_task
数据为空(流程已经结束了)
表act_hi_taskinst
如下:
表act_hi_actinst
如下:
回退至第一个任务
操作方法与上面几乎一样,区别就在于:根据bpmnModel去拿FlowNode的时候,是根据历史任务实例的taskDefinitionKey(对应表act_hi_taskint表的task_def_key字段)去拿的,这个taskDefinitionKey其实就是标签的id,也就是历史活动实例的activityId(对应act_hi_actinst表的act_id字段)
/**
* 跳到最开始的任务节点(直接打回)
* @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);
}
结束/终止流程
思路:跟回退一样的思路一样,直接从当前节点跳到结束节(EndEvent)
/**
* 结束任务
* @param taskId 当前任务ID
*/
public void endTask(String taskId) {
// 当前任务
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
List endEventList = bpmnModel.getMainProcess().findFlowElementsOfType(EndEvent.class);
FlowNode endFlowNode = endEventList.get(0);
FlowNode currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());
// 临时保存当前活动的原始方向
List originalSequenceFlowList = new ArrayList<>();
originalSequenceFlowList.addAll(currentFlowNode.getOutgoingFlows());
// 清理活动方向
currentFlowNode.getOutgoingFlows().clear();
// 建立新方向
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(currentFlowNode);
newSequenceFlow.setTargetFlowElement(endFlowNode);
List newSequenceFlowList = new ArrayList<>();
newSequenceFlowList.add(newSequenceFlow);
// 当前节点指向新的方向
currentFlowNode.setOutgoingFlows(newSequenceFlowList);
// 完成当前任务
taskService.complete(task.getId());
// 可以不用恢复原始方向,不影响其它的流程
currentFlowNode.setOutgoingFlows(originalSequenceFlowList);
}