使用Camunda流程引擎开发,【取回】、【撤销】代码实现

在用Camunda流程引擎做业务开发时,因为实际业务需求,都会提出一些不同的需求(也就是功能),其中【取回】、【撤销】需求是最为常见的。
 
取回:流程发起后,因流程发起人后来发现其中提交的信息不对,可信息又需要重新填写,对这种问题有2种处理方式:
1)告诉下一节点的审核人,让他驳回重新填写。这种只适应下一个节点是审核的用户节点流程,不适用所用流程图(该功能这里就不讨论)
2)由流程发起人主动取回来,重新填写。可适用所有的流程图
 
撤销:流程发起后,发起人后来觉得没必要发起这样的流程,这个时候就想取消发起的流程
以上对【取回】、【撤销】的理解,其中有一个必要的条件是:发起人
 
OK,既然理解了,下面就看此功能应该如何实现。
 
一、取回
 
测试流程图:
 

 

 

 

此流程图的XML文件:

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0gaphv8" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0">
  <bpmn:process id="Process_0cwop9s" name="配制了中断事件节点的并行流程" isExecutable="true">
    <bpmn:startEvent id="StartEvent_1">
      <bpmn:outgoing>Flow_02in6iu</bpmn:outgoing>
    </bpmn:startEvent>
    <bpmn:sequenceFlow id="Flow_02in6iu" sourceRef="StartEvent_1" targetRef="Activity_0z8xsuw" />
    <bpmn:sequenceFlow id="Flow_0lz52r3" sourceRef="Activity_0z8xsuw" targetRef="Gateway_052dhcu" />
    <bpmn:userTask id="Activity_0z8xsuw" name="任务" camunda:assignee="${assignee}">
      <bpmn:incoming>Flow_02in6iu</bpmn:incoming>
      <bpmn:outgoing>Flow_0lz52r3</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:parallelGateway id="Gateway_052dhcu">
      <bpmn:incoming>Flow_0lz52r3</bpmn:incoming>
      <bpmn:outgoing>Flow_1kl4krz</bpmn:outgoing>
      <bpmn:outgoing>Flow_14hjfsn</bpmn:outgoing>
    </bpmn:parallelGateway>
    <bpmn:userTask id="Activity_044h6ho" name="任务2" camunda:assignee="${assignee}">
      <bpmn:incoming>Flow_1kl4krz</bpmn:incoming>
      <bpmn:outgoing>Flow_1c9dvbg</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:endEvent id="Event_09jul9g">
      <bpmn:incoming>Flow_1c9dvbg</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="Flow_1c9dvbg" sourceRef="Activity_044h6ho" targetRef="Event_09jul9g" />
    <bpmn:sequenceFlow id="Flow_1kl4krz" sourceRef="Gateway_052dhcu" targetRef="Activity_044h6ho" />
    <bpmn:sequenceFlow id="Flow_14hjfsn" sourceRef="Gateway_052dhcu" targetRef="Activity_1tkc8qi" />
    <bpmn:userTask id="Activity_1tkc8qi" name="任务三" camunda:assignee="${assignee}">
      <bpmn:incoming>Flow_14hjfsn</bpmn:incoming>
      <bpmn:outgoing>Flow_1t02lwf</bpmn:outgoing>
    </bpmn:userTask>
    <bpmn:endEvent id="Event_0t9d637">
      <bpmn:incoming>Flow_1t02lwf</bpmn:incoming>
    </bpmn:endEvent>
    <bpmn:sequenceFlow id="Flow_1t02lwf" sourceRef="Activity_1tkc8qi" targetRef="Event_0t9d637" />
  </bpmn:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0cwop9s">
      <bpmndi:BPMNEdge id="Flow_02in6iu_di" bpmnElement="Flow_02in6iu">
        <di:waypoint x="215" y="227" />
        <di:waypoint x="320" y="227" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0lz52r3_di" bpmnElement="Flow_0lz52r3">
        <di:waypoint x="420" y="227" />
        <di:waypoint x="495" y="227" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1c9dvbg_di" bpmnElement="Flow_1c9dvbg">
        <di:waypoint x="580" y="120" />
        <di:waypoint x="642" y="120" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1kl4krz_di" bpmnElement="Flow_1kl4krz">
        <di:waypoint x="520" y="202" />
        <di:waypoint x="520" y="160" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_14hjfsn_di" bpmnElement="Flow_14hjfsn">
        <di:waypoint x="545" y="227" />
        <di:waypoint x="600" y="227" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1t02lwf_di" bpmnElement="Flow_1t02lwf">
        <di:waypoint x="700" y="227" />
        <di:waypoint x="762" y="227" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
        <dc:Bounds x="179" y="209" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_0yvqzhz_di" bpmnElement="Activity_0z8xsuw">
        <dc:Bounds x="320" y="187" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Gateway_1nj20k7_di" bpmnElement="Gateway_052dhcu">
        <dc:Bounds x="495" y="202" width="50" height="50" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_14sqxzc_di" bpmnElement="Activity_044h6ho">
        <dc:Bounds x="480" y="80" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Event_09jul9g_di" bpmnElement="Event_09jul9g">
        <dc:Bounds x="642" y="102" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_0zbig2w_di" bpmnElement="Activity_1tkc8qi">
        <dc:Bounds x="600" y="187" width="100" height="80" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Event_0t9d637_di" bpmnElement="Event_0t9d637">
        <dc:Bounds x="762" y="209" width="36" height="36" />
      </bpmndi:BPMNShape>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn:definitions>

流程现在已经发起,运行第一个任务后,产生了2个并行任务,任务2和任务三:

 

 

 

现在调用【取回】接口,看实际情况:

 

 

 

 

 

接口调用成功了,那此功能的代码应如何写呢?
 
要写此功能代码,首先来整理下思路,也就是此功能需要做那那些事。
 
  1. 验证。 具体要验证什么呢?包含当前流程实例状态、当前执行人是否为流程发起人、验证当前活动实例
  2. 取消产生的任务  查找此流程产生的任务并取消
  3. 创建流程第1个USER TASK 任务  删除所有已经产生的任务,并重新创建流程第一个USE TASK 任务
 
理解了此功能需要做什么事情,那接下捊代码就好了:
 
控制层:
@ApiOperation("取回: 参数procId 为流程实例ID")
@GetMapping("/fetchBack")
@ApiImplicitParam(name = "procId", value = "流程实例ID", required = true)
public ResponseStatusDto<Void> fetchBack (String procId, @CurrentUserInfo UserInfo userInfo){
    if( StringUtils.isEmpty(procId)){
        return ResponseStatusDto.failure("取回失败,流程实例ID不正确");
    }
    int type = workflowTaskService.fetchBack(procId, userInfo);
    return type == 0 ? ResponseStatusDto.success() : ResponseStatusDto.failure(FetchBackFailEnum.getMsgByCode(type));
}

服务层:

protected int checkProcessInstanceState(String procId){
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(procId).singleResult();
    if(ObjectUtils.isEmpty(processInstance)){
        return 5;
    }

    if(processInstance.isEnded()) {
        return 6;
    }

    return 0;
}

protected int checkProcessStarter(String procId, UserInfo userInfo){
    ProcInstEntity procInstEntity = workflowTaskMapper.queryProcInstEntityById(procId);
    if(ObjectUtils.isEmpty(procInstEntity) || !procInstEntity.getStartUserId().equals(userInfo.getUserId())){
        return 8;
    }
    return 0;
}

protected String getInstanceIdForActivity(ActivityInstance activityInstance, String activityId) {
    ActivityInstance instance = getChildInstanceForActivity(activityInstance, activityId);
    if (instance != null) {
        return instance.getId();
    }
    return null;
}

protected ActivityInstance getChildInstanceForActivity(ActivityInstance activityInstance, String activityId) {
    if (activityId.equals(activityInstance.getActivityId())) {
        return activityInstance;
    }
    for (ActivityInstance childInstance : activityInstance.getChildActivityInstances()) {
        ActivityInstance instance = getChildInstanceForActivity(childInstance, activityId);
        if (instance != null) {
            return instance;
        }
    }
    return null;
}

@Override
@Transactional(rollbackFor = Exception.class)
public int fetchBack(String procId, UserInfo userInfo) {
    try {
        // 判断当前流程的状态
        int state = checkProcessInstanceState(procId);
        if( state != 0 ){
            return state;
        }

        // 判断是否有任务
        List<Task> taskList = taskService.createTaskQuery().processInstanceId(procId).list();
        if (ObjectUtils.isEmpty(taskList)) {
            return 1;
        }

        // 判断当前执行人是否为流程发起人
        state = checkProcessStarter(procId, userInfo);
        if( state != 0 ){
            return state;
        }

        String processInstanceId = taskList.get(0).getProcessInstanceId();
        ActivityInstance activityInstance = runtimeService.getActivityInstance(processInstanceId);
        // 判断当前实例是否为空
        if(ObjectUtils.isEmpty(activityInstance) || ObjectUtils.isEmpty(activityInstance.getChildActivityInstances())){
            return 7;
        }

        // 判断是否处于第一个用户任务节点
        List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .activityType("userTask")
                .unfinished().orderByHistoricActivityInstanceStartTime()
                .asc().list();
        if (ObjectUtils.isEmpty(historicActivityInstanceList) ||
                historicActivityInstanceList.get(0).getActivityId().equals(activityInstance.getChildActivityInstances()[0].getActivityId())) {
            return 3;
        }

        String toActId = historicActivityInstanceList.get(0).getActivityId();
        String assignee = historicActivityInstanceList.get(0).getAssignee();
        //设置当前处理人
        Map<String, Object> taskVariable = new HashMap<>();
        taskVariable.put("assignee", assignee);

        runtimeService.createProcessInstanceModification(processInstanceId)
                //关闭相关任务
                .cancelActivityInstance(getInstanceIdForActivity(activityInstance, taskList.get(0).getTaskDefinitionKey()))
                .setAnnotation("进行了取回到节点操作")
                //启动目标活动节点
                .startBeforeActivity(toActId)
                //流程的可变参数赋值
                .setVariables(taskVariable)
                .execute();

        // 删除任务表其它任务
        List<String> taskIdList = new ArrayList<>();
        List<String> actIdList = new ArrayList<>();
        for (int i = 1; i < taskList.size(); i++) {
            taskIdList.add(taskList.get(i).getId());
            actIdList.add(getInstanceIdForActivity(activityInstance, taskList.get(i).getTaskDefinitionKey()));
        }

        // 特别说明:以上所有方法都是调用流程提供的方法完成的功能。可后来发现对于并行的任务,只能取消其中一个,另外的任务取消不了,所以只能自己操作表,去删除、更新数据状态

        if(ObjectUtils.isNotEmpty(taskIdList) && ObjectUtils.isNotEmpty(actIdList)){
            // 删除ACT_RU_EXECUTION 表中的实例
            workflowTaskMapper.deleteTaskByIdArray(taskIdList);
            // 删除ACT_RU_EXECUTION数据
            workflowTaskMapper.deleteExecutionByProcInstIdAndActInstIdArray(processInstanceId, actIdList);
            // 更新ACT_HI_TASKINST表
            workflowTaskMapper.updateHiTaskInstByIdArray(taskIdList, DateUtils.getCurrentTime());
            // 更新ACT_HI_ACTINST数据
            workflowTaskMapper.updateHiActInstById(actIdList, DateUtils.getCurrentTime());
        }

        return 0;
    }catch (Exception e){
        log.error("取回异常:{}", e.getMessage());
        return 4;
    }
}


 

数据操作层:

@Delete(" <script> " +
        " delete from ACT_RU_TASK where ID_ in " +
        " <foreach collection = 'taskIdLit' item = 't' separator = ',' open = '(' close = ')' > " +
        " #{t}" +
        " </foreach> " +
        " </script> ")
int deleteTaskByIdArray(List<String> taskIdLit);


@Update(" <script> " +
        " update ACT_HI_TASKINST set " +
        " END_TIME_ = #{endTime},  DELETE_REASON_ = 'deleted' " +
        " where ID_ in " +
        " <foreach collection = 'taskIdLit' item = 't' separator = ',' open = '(' close = ')' > " +
        " #{t}" +
        " </foreach> " +
        " </script> ")
int updateHiTaskInstByIdArray(@Param("taskIdLit")List<String> taskIdLit, @Param("endTime") String endTime);


@Update(" <script> " +
        " update ACT_HI_ACTINST set END_TIME_ = #{endTime} where ID_ in" +
        " <foreach collection = 'actInstIdList' item = 't' separator = ',' open = '(' close = ')' > " +
        " #{t}" +
        " </foreach> " +
        " </script> ")
int updateHiActInstById(@Param("actInstIdList")List<String> actInstIdList, @Param("endTime")String endTime);


@Delete(" <script> " +
        " delete from ACT_RU_EXECUTION where PROC_INST_ID_ = #{procInstId} AND " +
        " ACT_INST_ID_ in " +
        " <foreach collection = 'actInstIdList' item = 't' separator = ',' open = '(' close = ')' > " +
        " #{t}" +
        " </foreach> " +
        " </script> ")
int deleteExecutionByProcInstIdAndActInstIdArray(@Param("procInstId")String procInstId , @Param("actInstIdList")List<String> actInstIdList);

 

 

二、撤销

流程图还是继续沿用上面的流程图。
要写此功能代码,首先来整理下思路,此功能需要做那那些事。
  1. 验证。 具体要验证什么呢?包含当前流程实例状态、当前执行人是否为流程发起人、验证当前活动实例、
  2. 取消产生的任务 查找此流程产生的任务并取消
  3. 删除此流程实例
理解了此功能需要做什么事情,那接下捊代码就好了:
 

控制层:

@ApiOperation("撤回: 参数procId 为流程实例ID")
@GetMapping("/withDraw")
@ApiImplicitParam(name = "procId", value = "流程实例ID", required = true)
public ResponseStatusDto<Void> withDraw (String procId, @CurrentUserInfo UserInfo userInfo){
    if(StringUtils.isEmpty(procId)){
        return ResponseStatusDto.failure("撤回失败,参数不正确");
    }


    int type = workflowTaskService.withDraw(procId, userInfo);
    return type == 0 ? ResponseStatusDto.success() : ResponseStatusDto.failure(WithDrawEnum.getMsgByCode(type));
}

 

服务层:

@Override
public int withDraw(String procId, UserInfo userInfo) {
    int state = checkProcessInstanceState(procId);
    if( state != 0 ){
        return state;
    }

    List<Task> taskList = taskService.createTaskQuery().processInstanceId(procId).list();
    if(CollectionUtils.isEmpty(taskList)){
        return 1;
    }

    // 判断当前执行人是否为流程发起人
    state = checkProcessStarter(procId, userInfo);
    if( state != 0 ){
        return state;
    }

    Task task = taskList.get(0);
    List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery()
            .processInstanceId(task.getProcessInstanceId())
            .activityType("userTask")
            .finished().orderByHistoricActivityInstanceEndTime()
            .asc().list();

    if(historicActivityInstanceList == null || historicActivityInstanceList.isEmpty()){
        return 2;
    }

    ActivityInstance activityInstance = runtimeService.getActivityInstance(task.getProcessInstanceId());
    String toActId = historicActivityInstanceList.get(0).getActivityId();
    String assignee = historicActivityInstanceList.get(0).getAssignee();
    Map<String, Object> taskVariable = new HashMap<>();
    //设置当前处理人
    taskVariable.put("assignee", assignee);

    runtimeService.createProcessInstanceModification(task.getProcessInstanceId())
            //关闭相关任务
            .cancelActivityInstance(getInstanceIdForActivity(activityInstance, task.getTaskDefinitionKey()))
            .setAnnotation("进行了撤回到节点操作")
            //启动目标活动节点
            .startBeforeActivity(toActId)
            //流程的可变参数赋值
            .setVariables(taskVariable)
            .execute();

    runtimeService.deleteProcessInstance(task.getProcessInstanceId(), String.format("%s 用户执行了撤回操作", userInfo.getUserId()));

    return 0;
}

接口调用图:

 

 

posted @ 2022-02-18 18:15  a周周  阅读(2016)  评论(1编辑  收藏  举报