使用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;
}

接口調用圖:

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM