activiti process rollback and process end

Learn to connect

[Workflow Activiti7] 3. Activiti7 rollback and signature
[Workflow Activiti7] 4. Activiti7 end/termination process

Activiti-jump to specified node and roll back

Activiti6.0 version process withdrawal, jump, rollback and other operations

ativiti6.0 enables free jump of process nodes, rejection/disagree/return to the previous node, process withdrawal, jump, rollback and other operations (general implementation, available for personal testing) - Nuggets
Activiti6.0 source code analysis: arbitrary jump of nodes - simple book

Activiti workflow core Java API usage, Activiti return, skip, forced end implementation, BpmnModel model operation, running flow chart, obtaining process variables, candidate operations

Activiti rollback and jump nodes

Activiti7 workflow is not terminated by the original process

2020-10-21Activiti countersign and countersign rejection

activiti learning (twenty-two) - regular task node jump (return, free jump function)

Process rollback

Go back to the previous node

flow chart

The simplest flow chart, with only 3 nodes in the middle, flowing from the beginning to the end
Insert image description here
Briefly analyze the following bpmn.xml:

  • The start tag uses startEvent (the id is the default startEvent1 and can be specified manually).
  • The task node label uses userTask. Each userTask has its own id attribute (the id is randomly generated by default and can be specified manually) and name attribute (manually defined).
  • Two userTasks are connected using the sequenceFlow tag. The sequenceFlow tag has sourceRef (source node id) and targetRef (target node id) as well as its own id attribute (the id is randomly generated by default and can be specified manually)
  • The end tag uses endEvent (the id is randomly generated by default and can be specified manually)
<?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>
Start the process

Deploy the above flow chart and start the process

/**
 * 开启流程
 */
@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);

}
Normal approval

Normal approval, all the way to personnel approval

/**
  * 完成任务
  */
 @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());
 }

At this time, the tableact_ru_execution is as follows (you can see that the act_id of the sub-execution instance id: 2502 is userTask3, and this userTask3 is the current activity id of this execution instance, that is, the userTask label id):
Insert image description here
Table act_ru_task is as follows (you can see that the task_def_key of the current task is the id of the userTask tag): Insert image description here
Table< /span> is as follows (you can see that the act_id of the historical activity instance is the id of the userTask tag): The table The id_ of the table is taskId): act_hi_taskinst is as follows (you can see that the task_def_key of the historical task is the id of the userTask tag. act_hi_taskinst
Insert image description here
act_hi_actinst
Insert image description here

At this time, query the tasks corresponding to the specified historical process instance.

/*
    查询指定历史流程实例所对应的任务
 */
@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("---------------------------------------------");
    }
}

The output is as follows:

历史任务实例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
历史任务结束时间: 
---------------------------------------------

At this time, query the tasks corresponding to the specified historical process instance.

/*
    查询指定历史流程实例所对应的所有活动实例
 */
@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("---------------------------------------------");
    }

}

The output is as follows:

历史活动实例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
历史活动结束时间: 
---------------------------------------------
Process rollback

The idea of ​​fallback is:动态更改节点的流向. First build a bridge when encountering water, and finally tear down a bridge when crossing a river.

First, let’s briefly understand the general structure of the activiti process elements. It can be seen that:

  • FlowElement is the element in the flow chart, which is divided into FlowNode流程节点 and SequenceFlow顺序流.
    • Among themFlowNode分为3种类型:事件(Event)、网关(Gateway)、活动(Activity) corresponds to each node in the bpmn graph, and 2 attributes are defined in the FlowNoe class: incomingFlows and outgoingFlows (all of SequenceFlow type, corresponding to the process line in the bpmn diagram)
    • Among themSequenceFlow顺序流 will record the IDs of the two connected nodes through sourceRef and targetRef

Insert image description here
The following is a relatively complete picture (for simplicity, many subcategories have been deleted):
Insert image description here

The specific operations are as follows:

  • Get information about the current node
  • Get the information of the previous node of the current node
  • Save the flow direction of the current node
  • Create a new flow direction, pointing from the current node to the previous node
  • Set the flow direction of the current node to the newly created flow direction above
  • The current node completes the task
  • Restore the flow direction of the current node
  • Get the executor of the previous node
  • Set the assignee of the previous node to the previous executor
@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);
}

At this time, the tableact_ru_execution is as follows (you can see that the act_id of the current execution instance is userTask2, indicating that it has returned to the previous node):
Insert image description here

Tableact_ru_task is as follows (the current task has been rolled back to the previous node):
Insert image description here

tableact_hi_taskinstas below:
Insert image description here

tableact_hi_actinstas below:
Insert image description here

Complete approval

Finally, after completing 2 more approvals, the entire process can be completed normally.

At this time, the tableact_ru_executiondata is empty (the process has ended)

Tableact_ru_taskThe data is empty (the process has ended)

tableact_hi_taskinstas below:
Insert image description here

tableact_hi_actinstas below:
Insert image description here

Go back to the first task

The operation method is almost the same as above. The difference is that when you get the FlowNode based on bpmnModel, you get it based on the taskDefinitionKey of the historical task instance (corresponding to the task_def_key field of the act_hi_taskint table). This taskDefinitionKey is actually the id of the tag, which is the history. activityId of the activity instance (corresponding to the act_id field of the act_hi_actinst table)

/**
 * 跳到最开始的任务节点(直接打回)
 * @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);
}

End/terminate process

Idea: The same idea as the rollback, jump directly from the current node to the end section (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);
}

Guess you like

Origin blog.csdn.net/qq_16992475/article/details/134621427