activiti流程回退与流程结束

学习连接

【工作流Activiti7】3、Activiti7 回退与会签
【工作流Activiti7】4、Activiti7 结束/终止流程

Activiti-跳转到指定节点、回退

Activiti6.0版本流程撤回、跳转、回退等操作

ativiti6.0 流程节点自由跳转实现、拒绝/不同意/返回上一节点、流程撤回、跳转、回退等操作(通用实现,亲测可用) - 掘金
activiti6.0源码剖析之节点任意跳转 -简书

activiti工作流核心java api使用,activiti退回、跳过、强制结束实现,BpmnModel模型操作,运行中流程图、获取流程变量、候选人操作

Activiti回退与跳转节点

Activiti7 工作流非原流程终止

2020-10-21Activiti会签以及会签驳回

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个属性:incomingFlowsoutgoingFlows(都是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);
}

猜你喜欢

转载自blog.csdn.net/qq_16992475/article/details/134621427