activiti 工作流會籤 / 多人審批時若一人通過即可

最近在工作中使用到了activiti 工作流引擎,跟大家遇到過的情況類似,在“中國式”的工作流中,常有一些需求是工作流引擎基本使用中無法實現的。在這過程中,我和我的小夥伴們也和大家一樣遇到很多困難,大海撈針似的在網上尋找着答案。特此,在這裏把我們遇到的需求和解決方案分享給大家,希望能幫助到你們!

以下是我們在項目中遇到的各(奇)種()需求,如果您也遇到了相同的可以借鑑:

1、工作流會籤;

2、多人審批時一人通過即可;

3、在當前節點獲取下一節點的信息;

4、流程部署後未發佈之前獲取所有節點的信息;

5、流程啓動前傳入後續節點辦理人;

6、節點設置多個監聽。


1、 activiti 工作流會籤時,所有的都審批通過纔可進入下一環節:


1.1 編寫監聽類

public class MyTaksListener implements TaskListener {
    public void notify(DelegateTask delegateTask) {
        System.out.println("delegateTask.getEventName() = " + delegateTask.getEventName());

         //添加會籤的人員,所有的都審批通過纔可進入下一環節

        List<String> assigneeList = new ArrayList<String>();
        assigneeList.add("wangba");
        assigneeList.add("wangjiu");
        delegateTask.setVariable("publicityList",assigneeList);
    }
}


1.2 “員工請假申請”中添加此監聽類

1.3 “項目組長審批”中


isSequential=false時,表示的並行執行,即該節點下的多條任務可以同時執行。
activiti:collection:執行該會籤環節的參與人,此處是使用的一個名叫publicityList的流程變量
activiti:elementVariable:表示的是每一個分支都有一個名叫publicity的流程變量,和上方的activiti:assignee結合



1.4 項目組長審批時,通過taskAssignee來獲取個人任務

// 獲取總記錄數

total = taskService.createTaskQuery().taskAssignee(userId).taskNameLike("%" + s_name + "%").count(); 
taskList = taskService.createTaskQuery()
// 根據用戶id查詢
.taskAssignee(userId)
// 根據任務名稱查詢
.taskNameLike("%" + s_name + "%")
// 返回帶分頁的結果集合
.listPage(pageInfo.getPageIndex(), pageInfo.getPageSize());

==================================================================================




2. activiti 工作流會籤,一人通過即可進入下一環節:


2.1 編寫監聽類

public class MangerTaskHandlerCandidateUsers implements TaskListener{
    public void notify(DelegateTask delegateTask) {
        //添加審批的人員,以下任何一人通過即可進入下一環節
        String[] empLoyees = {"wangba","wangjiu"};
        delegateTask.addCandidateUsers(Arrays.asList(empLoyees));
    }
}


2.2 “項目組長審批”中


2.3 項目組長審批時,通過taskCandidateUser來獲取節點任務

// 獲取總記錄數

total = taskService.createTaskQuery().taskCandidateUser(userId).taskNameLike("%" + s_name + "%").count(); 
taskList = taskService.createTaskQuery()
// 根據用戶id查詢
.taskCandidateUser(userId)
// 根據任務名稱查詢
.taskNameLike("%" + s_name + "%")
// 返回帶分頁的結果集合
.listPage(pageInfo.getPageIndex(), pageInfo.getPageSize());

============================================================================




3、在當前節點獲取下一節點的信息


/**
     * 根據實例編號查找下一個任務節點
     * 
     * @param String
     *     procInstId :實例編號
     * @return
     */
    @RequestMapping("/backTaskTab")
    public TaskDefinition backTaskTab(String taskId) {

        Task task = taskService.createTaskQuery() // 創建任務查詢
                .taskId(taskId) // 根據任務id查詢
                .singleResult();

        String procInstId = task.getProcessInstanceId();
        // 流程標示
        String processDefinitionId = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId)
                .singleResult().getProcessDefinitionId();

        ProcessDefinitionEntity def = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                .getDeployedProcessDefinition(processDefinitionId);
        // 執行實例
        ExecutionEntity execution = (ExecutionEntity) runtimeService.createProcessInstanceQuery()
                .processInstanceId(procInstId).singleResult();
        // 當前實例的執行到哪個節點
        String activitiId = execution.getActivityId();
        // 獲得當前任務的所有節點
        List<ActivityImpl> activitiList = def.getActivities();
        ActivityImpl activityImpl=null;
        for(int i=0;i< activitiList.size();i++){
            String flag=activitiList.get(i).getId();
            if(flag.equals(activitiId)){
                activityImpl=activitiList.get(i);
            }
        }
        String id = null;
        int num=activitiList.indexOf(activityImpl);
        ActivityImpl activityImpl_=activitiList.get(num+1);
        TaskDefinition taskDefinition = ((UserTaskActivityBehavior) activityImpl_.getActivityBehavior())
                .getTaskDefinition();
        // 獲取下一節點的代辦人
        System.out.println(taskDefinition.getCandidateGroupIdExpressions().toArray()[0]);
        return null;
    }

============================================================================




4、流程部署後未發佈之前獲取所有節點的信息


解決思路是這樣的:部署完工作流之後,爲UserTask節點動態分配任務執行者,或者在分支節點上添加條件判斷的功能。爲了實現這個功能,需要解析流程定義文件,取出文件中定義的所有節點。這裏有兩個方法可以實現此功能:

方法一(流程部署至服務器上之後可使用):


//processDefinitionId爲流程定義Id,該Id可以通過多種方式獲得,如通過ProcessDefinitionQuery可以查詢一個 //ProcessDefinition對象,Task對象中也包含    

processDefinitionIdBpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
        if (model != null) {
            Collection<FlowElement> flowElements = model.getMainProcess().getFlowElements();
            for (FlowElement e : flowElements) {
                System.out.println("flowelement id:" + e.getId() + "  name:" + e.getName() + "   class:"
                        + e.getClass().toString());
            }
        }


該方法適用於流程部署至服務器上之後,通過該方法可以簡單快速的獲取流程定義文件中各個節點信息。


方法二 讀取流程定義文件方式


InputStream resouceStream = this.getClass().getClassLoader().getResourceAsStream("leave-  formkey.bpmn20.xml");
        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader in;
        XMLStreamReader xtr;
        try {
            in = new InputStreamReader(resouceStream, "UTF-8");
            xtr = xif.createXMLStreamReader(in);
            BpmnModel model = new BpmnXMLConverter().convertToBpmnModel(xtr);
            Collection<FlowElement> flowElements = model.getMainProcess().getFlowElements();
            for (FlowElement e : flowElements) {
                System.out.println("flowelement id:" + e.getId() + "  name:" + e.getName() + "   class:"
                        + e.getClass().toString());
            }
        } catch (XMLStreamException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


       該方法使用到了activiti的activiti-bpmn-converter-5.20.0.jar和activiti-bpmn-model-5.20.0.jar,用到了其中比較關鍵的一個類BpmnXMLConverter,該類將xml定義文件解析成BpmnModel對象,使用BpmnModel的getMainProcess()獲取一個Process對象,該對象實際是一個繼承自BaseElement、FlowElementContainer的節點容器,通過getFlowElements()獲取當前流程定義文件中所有的節點對象。該方法的好處在於可以解析本地或者未部署至Activiti引擎中的流程定義文件。

兩次測試打印結果如下:

flowelement id:startevent1  name:Start   class:class org.activiti.bpmn.model.StartEvent  
flowelement id:deptLeaderAudit  name:部門領導審批   class:class org.activiti.bpmn.model.UserTask  
flowelement id:exclusivegateway5  name:Exclusive Gateway   class:class org.activiti.bpmn.model.ExclusiveGateway  
flowelement id:modifyApply  name:調整申請   class:class org.activiti.bpmn.model.UserTask  
flowelement id:hrAudit  name:人事審批   class:class org.activiti.bpmn.model.UserTask  
flowelement id:exclusivegateway6  name:Exclusive Gateway   class:class org.activiti.bpmn.model.ExclusiveGateway  
flowelement id:reportBack  name:銷假   class:class org.activiti.bpmn.model.UserTask  
flowelement id:endevent1  name:End   class:class org.activiti.bpmn.model.EndEvent  
flowelement id:exclusivegateway7  name:Exclusive Gateway   class:class org.activiti.bpmn.model.ExclusiveGateway  
flowelement id:flow2  name:   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow3  name:   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow4  name:不同意   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow5  name:同意   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow6  name:   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow7  name:同意   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow8  name:   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow9  name:不同意   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow10  name:重新申請   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow11  name:   class:class org.activiti.bpmn.model.SequenceFlow  
flowelement id:flow12  name:結束流程   class:class org.activiti.bpmn.model.SequenceFlow 


流程定義文件leave-formkey.bpmn20.xml:
[html] 
<?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: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="OFFICE">  
  <process id="leave-formkey" name="病事假申請">  
    <documentation>請假流程演示</documentation>  
    <startEvent id="startevent1" name="Start" activiti:initiator="applyUserId"></startEvent>  
    <userTask id="deptLeaderAudit" name="部門領導審批" activiti:assignee="${applyUserId}" activiti:formKey="leaveHandle.form"></userTask>  
    <exclusiveGateway id="exclusivegateway5" name="Exclusive Gateway"></exclusiveGateway>  
    <userTask id="modifyApply" name="調整申請" activiti:assignee="${applyUserId}" activiti:formKey="leaveApplyAgain.form"></userTask>  
    <userTask id="hrAudit" name="人事審批" activiti:assignee="${applyUserId}" activiti:formKey="leaveHandle.form"></userTask>  
    <exclusiveGateway id="exclusivegateway6" name="Exclusive Gateway"></exclusiveGateway>  
    <userTask id="reportBack" name="銷假" activiti:assignee="${applyUserId}" activiti:formKey="leaveHandle.form"></userTask>  
    <endEvent id="endevent1" name="End"></endEvent>  
    <exclusiveGateway id="exclusivegateway7" name="Exclusive Gateway"></exclusiveGateway>  
    <sequenceFlow id="flow2" name="" sourceRef="startevent1" targetRef="deptLeaderAudit"></sequenceFlow>  
    <sequenceFlow id="flow3" name="" sourceRef="deptLeaderAudit" targetRef="exclusivegateway5"></sequenceFlow>  
    <sequenceFlow id="flow4" name="不同意" sourceRef="exclusivegateway5" targetRef="modifyApply">  
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderPass == 'false'}]]></conditionExpression>  
    </sequenceFlow>  
    <sequenceFlow id="flow5" name="同意" sourceRef="exclusivegateway5" targetRef="hrAudit">  
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${deptLeaderPass == 'true'}]]></conditionExpression>  
    </sequenceFlow>  
    <sequenceFlow id="flow6" name="" sourceRef="hrAudit" targetRef="exclusivegateway6"></sequenceFlow>  
    <sequenceFlow id="flow7" name="同意" sourceRef="exclusivegateway6" targetRef="reportBack">  
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrPass == 'true'}]]></conditionExpression>  
    </sequenceFlow>  
    <sequenceFlow id="flow8" name="" sourceRef="reportBack" targetRef="endevent1"></sequenceFlow>  
    <sequenceFlow id="flow9" name="不同意" sourceRef="exclusivegateway6" targetRef="modifyApply">  
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${hrPass == 'false'}]]></conditionExpression>  
    </sequenceFlow>  
    <sequenceFlow id="flow10" name="重新申請" sourceRef="exclusivegateway7" targetRef="deptLeaderAudit">  
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'true'}]]></conditionExpression>  
    </sequenceFlow>  
    <sequenceFlow id="flow11" name="" sourceRef="modifyApply" targetRef="exclusivegateway7"></sequenceFlow>  
    <sequenceFlow id="flow12" name="結束流程" sourceRef="exclusivegateway7" targetRef="endevent1">  
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${reApply == 'false'}]]></conditionExpression>  
    </sequenceFlow>  
  </process>  
  <bpmndi:BPMNDiagram id="BPMNDiagram_leave-formkey">  
    <bpmndi:BPMNPlane bpmnElement="leave-formkey" id="BPMNPlane_leave-formkey">  
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">  
        <omgdc:Bounds height="35" width="35" x="10" y="90"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="deptLeaderAudit" id="BPMNShape_deptLeaderAudit">  
        <omgdc:Bounds height="55" width="105" x="90" y="80"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="exclusivegateway5" id="BPMNShape_exclusivegateway5">  
        <omgdc:Bounds height="40" width="40" x="250" y="87"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="modifyApply" id="BPMNShape_modifyApply">  
        <omgdc:Bounds height="55" width="105" x="218" y="190"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="hrAudit" id="BPMNShape_hrAudit">  
        <omgdc:Bounds height="55" width="105" x="358" y="80"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="exclusivegateway6" id="BPMNShape_exclusivegateway6">  
        <omgdc:Bounds height="40" width="40" x="495" y="87"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="reportBack" id="BPMNShape_reportBack">  
        <omgdc:Bounds height="55" width="105" x="590" y="80"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">  
        <omgdc:Bounds height="35" width="35" x="625" y="283"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNShape bpmnElement="exclusivegateway7" id="BPMNShape_exclusivegateway7">  
        <omgdc:Bounds height="40" width="40" x="250" y="280"></omgdc:Bounds>  
      </bpmndi:BPMNShape>  
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">  
        <omgdi:waypoint x="45" y="107"></omgdi:waypoint>  
        <omgdi:waypoint x="90" y="107"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">  
        <omgdi:waypoint x="195" y="107"></omgdi:waypoint>  
        <omgdi:waypoint x="250" y="107"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">  
        <omgdi:waypoint x="270" y="127"></omgdi:waypoint>  
        <omgdi:waypoint x="270" y="190"></omgdi:waypoint>  
        <bpmndi:BPMNLabel>  
          <omgdc:Bounds height="11" width="100" x="10" y="0"></omgdc:Bounds>  
        </bpmndi:BPMNLabel>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">  
        <omgdi:waypoint x="290" y="107"></omgdi:waypoint>  
        <omgdi:waypoint x="358" y="107"></omgdi:waypoint>  
        <bpmndi:BPMNLabel>  
          <omgdc:Bounds height="11" width="100" x="-24" y="-17"></omgdc:Bounds>  
        </bpmndi:BPMNLabel>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">  
        <omgdi:waypoint x="463" y="107"></omgdi:waypoint>  
        <omgdi:waypoint x="495" y="107"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">  
        <omgdi:waypoint x="535" y="107"></omgdi:waypoint>  
        <omgdi:waypoint x="590" y="107"></omgdi:waypoint>  
        <bpmndi:BPMNLabel>  
          <omgdc:Bounds height="11" width="100" x="-22" y="-17"></omgdc:Bounds>  
        </bpmndi:BPMNLabel>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">  
        <omgdi:waypoint x="642" y="135"></omgdi:waypoint>  
        <omgdi:waypoint x="642" y="283"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">  
        <omgdi:waypoint x="515" y="127"></omgdi:waypoint>  
        <omgdi:waypoint x="514" y="217"></omgdi:waypoint>  
        <omgdi:waypoint x="323" y="217"></omgdi:waypoint>  
        <bpmndi:BPMNLabel>  
          <omgdc:Bounds height="11" width="100" x="10" y="0"></omgdc:Bounds>  
        </bpmndi:BPMNLabel>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow10" id="BPMNEdge_flow10">  
        <omgdi:waypoint x="250" y="300"></omgdi:waypoint>  
        <omgdi:waypoint x="142" y="299"></omgdi:waypoint>  
        <omgdi:waypoint x="142" y="135"></omgdi:waypoint>  
        <bpmndi:BPMNLabel>  
          <omgdc:Bounds height="11" width="100" x="10" y="0"></omgdc:Bounds>  
        </bpmndi:BPMNLabel>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow11" id="BPMNEdge_flow11">  
        <omgdi:waypoint x="270" y="245"></omgdi:waypoint>  
        <omgdi:waypoint x="270" y="280"></omgdi:waypoint>  
      </bpmndi:BPMNEdge>  
      <bpmndi:BPMNEdge bpmnElement="flow12" id="BPMNEdge_flow12">  
        <omgdi:waypoint x="290" y="300"></omgdi:waypoint>  
        <omgdi:waypoint x="625" y="300"></omgdi:waypoint>  
        <bpmndi:BPMNLabel>  
          <omgdc:Bounds height="11" width="100" x="10" y="0"></omgdc:Bounds>  
        </bpmndi:BPMNLabel>  
      </bpmndi:BPMNEdge>  
    </bpmndi:BPMNPlane>  
  </bpmndi:BPMNDiagram>  
</definitions> 

============================================================================




5.流程啓動前傳入後續節點辦理人;


//下面name2和name3是前臺傳過來的第二個和第三個節點的辦理人

Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("leaveId", leaveId);
        variables.put("name2", "XXX");//(前臺傳過來的第二個節點的辦理人)
        variables.put("name3", "YYY");//(前臺傳過來的第三個節點的辦理人)
        // 啓動流程
        pi = runtimeService.startProcessInstanceByKey("activitiemployeeProcess", variables);

在第一個節點指定第二個節點的監聽

public class MyTaksListener2 implements TaskListener {

    public void notify(DelegateTask delegateTask) {
        Map<String, Object> variables=delegateTask.getVariables();
       variables.get("name2");(前臺傳過來的第二個節點的辦理人)
        //拆分variables
        List<String> assigneeList = new ArrayList<String>(); 
        assigneeList.add("wangba");
        delegateTask.setVariable("publicityList",assigneeList);
    }
}


在第三個節點指定本節點的辦理人監聽

public class MyTaksListener3 implements TaskListener {

    public void notify(DelegateTask delegateTask) {
        Map<String, Object> variables=delegateTask.getVariables();
        System.out.println(variables);
        variables.get("name3");
//        String result=(String) variables.get("name3");(前臺傳過來的第三個節點的辦理人)
        String[] empLoyees = {"szx"};
        delegateTask.addCandidateUsers(Arrays.asList(empLoyees));
    }
}


============================================================================




6、節點設置多個監聽


在同一節點設置兩個監聽,一個是設置本節點的監聽,指定辦理人;另一個是設置下一個節點的監聽,指定會籤人。


設置本節點的監聽,指定辦理人

public class MyTaksListener3 implements TaskListener {

    public void notify(DelegateTask delegateTask) {
        Map<String, Object> variables=delegateTask.getVariables();
        System.out.println(variables);
        String result=(String) variables.get("name3");
        
        String[] empLoyees = {"szx"};
        delegateTask.addCandidateUsers(Arrays.asList(empLoyees));
    }
}

設置下一個節點的監聽,指定會籤人

public class MyTaksListener4 implements TaskListener {

    public void notify(DelegateTask delegateTask) {
        Map<String, Object> variables=delegateTask.getVariables();
        String result=(String)variables.get("name2");
        
        List<String> assigneeList = new ArrayList<String>(); 
        assigneeList.add("ss");
        delegateTask.setVariable("publicityList",assigneeList);
    }
}





至此,項目中遇到的各(奇)種(葩)問題迎刃而解。“中國式”工作流有時確實很讓人頭疼,但也體現了中國程序猿的強大。希望看到這裏的你也能從中得到啓發,儘早解決您在項目當中遇到的問題。

喜歡此文,或是能幫助您解決實際問題的話,歡迎轉載,以便能幫助到更多的人,謝謝!


發佈了436 篇原創文章 · 獲贊 121 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章