天天看點

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

上一章通過一個activiti7的helloworld,了解到了activiti7的工作流程和原理,接下來介紹一些實際企業開發過程中會用到的功能。

一、變量(variable)

分為三類:流程執行個體變量(processVariable,又稱global變量)、執行執行個體變量(executionVariable)和任務變量(taskVariable倆稱為local變量),主要差別就是作用域不同。

1、流程執行個體變量

流程變量即流程執行個體執行過程中産生的一些資料,是流程執行個體的一些附屬變量資料。流程變量的資料在整個流程執行個體範圍内是共享的。怎麼形容流程變量和流程執行個體的關系呢?就好比session中的資料之于會話,ThreadLocal中的資料之于線程。

1.1作用

總結起來,流程變量有三大作用:

作用1:比對連線的condition決定流程的走向。

作用2:配合UEL表達式動态設定任務等節點的屬性,如任務的assignee(辦理人),candidate(候選人)和候選人組等。

作用3:儲存流程執行個體生命周期中的資料。

1.2、涉及的api

涉及倆表:act_ru_variable——運作時變量表     act_hi_varinst——曆史記錄變量表

涉及倆service:runtimeService和 historyService

1.3、如何操作流程執行個體變量?

就是通過調用runtimeService或historyService的方法,凡是方法名中帶有variable字眼的都是流程變量相關的操作。(ps:多去嘗試一下,如果還不明白的可以點進去看一些源碼的解釋)

需要注意的就是,看一下該方法操作的是流程執行個體變量、執行執行個體變量、還是任務變量,因為他們作用域不一樣。

除此之外在啟動流程和完成任務的時候也能設定流程變量,也要注意一下作用域問題。

下面給出一些示例:

String businessKey = "1";
//啟動該流程執行個體時,添加一些流程執行個體變量
Map<String,Object> variables = new HashMap<>();
variables.put("variable01","aa");
variables.put("variable02","bb");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday",businessKey,variables);

//通過runtimeService的setVariablexxx()/getVariablexxx()直接操作流程變量
runtimeService.setVariable(String executionId, String variableName, Object value);
runtimeService.setVariables(String executionId, Map<String, ? extends Object> variables);

//VariableLocal指的是執行執行個體或任務級别的變量
runtimeService.setVariableLocal(String executionId, String variableName, Object value);
runtimeService.setVariablesLocal(String executionId, Map<String, ? extends Object> variables);
           

當我們通過以上api,給運作中的流程設定流程變量後,就可以在act_ru_variable表中看到設定的資料了:

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

1.4、區分流程定義、流程執行個體、執行執行個體、任務?

通過上面文字介紹和截圖中藍色框标記的地方,我們發現有流程執行個體ID、執行執行個體ID、任務ID,怎麼區分它們呢,這裡簡單介紹一下,如下圖所示:

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

這是一張業務流程圖,holiday.bpmn檔案

流程定義(processDefinition):這一個業務流程圖就認為是一個流程定義,相當于java中的類。

流程執行個體(processInstance):由流程定義派生出多個流程執行個體,相當于java中由類建立的一個對象,每個對象有自己的記憶體空間。

執行執行個體(execution):一個流程執行個體可能包含多條執行執行個體,就是一個流程執行個體中的流程分支,上圖中标紅的就是一個執行執行個體,一般當并行網關出現的時候,往往會産生多條執行執行個體。

任務(task):代表具體要執行的事情,上圖中每個綠色的圓角矩形就是一個任務。

1.5、作用2:配合UEL表達式動态設定assignee任務辦理人

UEL表達式是javaEE6規範中的一個表達式語言,文法和EL表達式基本一緻。分為value表達式和method表達式如下所示:

${myProperty}
${myBean.myProperty}
${myBean.myMethod()}
${myBean.myMethod("參數")}
           

可參考:http://www.myexceptions.net/h/1367205.html

基本使用:

1、畫圖時,通過UEL表達式,給填寫請假單這個任務的assignee設定成userId這個變量

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

2、在上一個任務完成之後,現在是啟動流程的時候,通過流程變量動态設定userId的值如下:

Map<String,Object> variables = new HashMap<>();
String businessId = "holiday:1";
//該變量是為了設定UEL表達式的值
variables.put("userId",2L);
runtimeService.startProcessInstanceByKey(definitionKey, businessKey,variables);
           

原理:給流程執行個體添加流程變量,當流程變量的key和畫的流程圖中UEL表達式的key一緻時,就會給UEL表達式設定值,以達到動态設定任務辦理人的目的。 項目中,往往根據公司的人員組織架構、或者專門的配置裡動态讀取并設定任務辦理人。

現在act_ru_task表中該任務的assignee字段的值就變成了2,然後業務系統中使用者Id為2的使用者,就能查詢出他的待辦任務了。表單詳情可根據businessId關聯業務系統查詢。 

1.6、作用3:配合業務連線的condition決定流程的走向

步驟:

1、畫圖時用UEL表達式,給連線設定condition,如下圖所示:

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

2、完成填寫請假單任務時,通過添加流程變量day的值,讓activiti決定走哪條分支,代碼如下:

String taskId = "70002";
Integer day = 2;
TaskService taskService = processEngine.getTaskService();

Map<String,Object> variables = new HashMap<>();
variables.put("day",day);
//完成任務并設定流程變量day的值
taskService.complete(taskId,variables);
           

正常情況下應該為,先根據assignee查詢待辦任務清單,然後前端将taskId和變量day的值傳給背景來完成任務,此處省略了該步驟。

二、網關

前面講BPMN2.0規範的時候已經講過了,有興趣的可以再看一下。對于網關,就目前來說,隻需要知道常用網關的特點,以及依據網關特點把他添加到畫業務流程圖中就可以了。除此之外,網關可以設定Listener,監聽當流程執行到目前網關時,要做哪些事情,有需要的可以自行研究下,很簡單,這裡不多贅述。

2.1、排他網關

根據條件判斷,當有多個流程分支時隻走一個。

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

 2.2、并行網關

忽略條件判斷,同時并行執行多個流程分支。

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

2.3、包含網關

既能根據條件判斷走流程,當有多個條件都滿足時,又能同時執行多條流程分支,可看做排他網關和并行網關的結合體。

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

 值得注意的是,當業務流程很複雜時,多個網關都存在一個業務流程圖中,并且每個網關都出現好幾次也是有可能的。

三:任務

任務分為個人任務群組任務

1、個人任務

個人任務,顧名思義就是一個人的任務。前面将BPMN2.0規範 任務元素的時候講過,人工任務可配置設定給三種人:assignee(辦理人、受讓人),candidate(候選人),candidateGroup(候選人組)。對于assignee和candidate而言,都是個人任務,對于候選人組而言,任務就是組任務了。一個任務的辦理人隻能有一個,但是可以同時有多個候選人,候選人需要拾取任務後才能成為任務的辦理人,在完成任務之前,也可以不拾取任務(unclaim),從assignee再次變回candidate。

對于個人任務來說有三種配置設定方式:

1、固定配置設定:畫流程圖時直接寫死。

2、UEL表達式配置設定:畫流程圖時,設定一個變量,然後在程式中動态設定該變量的值。

3、任務監聽器配置設定:先實作TaskListener接口,然後在畫流程圖時指定該監聽器的全類名。

前兩種方式前面已經講過了,這次主要說明任務監聽器方式。

1.1、任務監聽器方式配置設定任務辦理人

步驟:

1、自定義類,實作TaskListener接口并實作其notify()方法,如下所示:

public class MyTestTaskListener implements TaskListener {

    private static final long serialVersionUID = 5146425833880890542L;

    //delegateTask(代理任務),相當于實際被建立、辦理的task
    @Override
    public void notify(DelegateTask delegateTask) {
        //通過操作資料表的方式,設定任務辦理人,這個assignee的值,可以選擇從外部擷取
        //String assignee = "zhaoliu";

        //delegateTask.setAssignee(assignee);
        //String timeSign = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        //delegateTask.setDescription("一個被代理任務的描述資訊,将直接插入到act_ru_task表的DESCRIPTION_字段中:時間:"+timeSign);
        System.out.println("taskListener被觸發!!!");
    }
}
           

2、将該任務設定到畫的業務流程圖中去,如下圖所示:

activiti7(四):activiti開發之實用的功能一、變量(variable)二、網關三:任務四、監聽器 五、其他

這樣就将我們自定義的任務監聽器,綁定到了【填寫請假單】這個任務中了。

通過上圖我們可以看到,一個taskListener需要有Event、Type、Class、Field四個屬性。

其中Event指定了目前listener的觸發時機,有如下五個值,也代表了觸發的先後順序:@see org.activiti.engine.delegate.BaseTaskListener

  • assignment:當任務配置設定給某人時觸發。比如通過流程變量設定了任務的assignee或candidate時。
  • create:當任務由activiti建立完成并設定完屬性後觸發。
  • complete:當通過complete()完成任務時觸發。
  • delete:目前運作時任務要被從資料表删除時觸發。
  • all:目前任務的整個生命周期内的變化,都會觸發。

2、組任務

組任務是當給任務配置設定了candidate或candidateGroup時的情況。此時該任務可能還沒有assignee(任務辦理人),此時該任務對所有的候選人來說就是共享的,任何候選人都能通過拾取任務變成該任務的assignee(任務辦理人)。

應用場景:

有時候當任務的assignee太忙或有事情等原因,無法即時完成該任務時,任務的候選人可以喧賓奪主、烏鴉變鳳凰、小三變正妻把他完成任務,好讓整個流程繼續執行下去。

涉及到的名詞:任務拾取、任務歸還、任務交接、任務委派、任務轉辦。

下面依次對這些名詞進行解釋,以及對應操作的說明:

1)任務拾取   

解釋:候選人拾取任務變成該任務的辦理人,此時資料庫中該任務的assignee的值将變成該拾取人。

代碼解釋:

/**
     * 任務拾取,就是設定assignee的值
     */
    @Test
    public void taskClaim(){
        String taskId = "97502";
        String userId = "otherUser";
        TaskService taskService = processEngine.getTaskService();
        //任務拾取,如果不是候選人可以拾取嗎?可以,沒限制。當userId為null時,資料表中assignee的值将被置成null,表示任務歸還
        taskService.claim(taskId,userId);
    }
           

2)任務歸還

文字解釋:候選人成為該任務的任務辦理人之後,又不想辦理了,可以歸還該任務重新變成組任務,此時資料庫中該任務的assignee的值變成null。

代碼解釋:

/**
     * 任務歸還,就是将assignee的值設定成null
     */
    @Test
    public void taskUnClaim(){
        String taskId = "97502";
        String userId = "";
        TaskService taskService = processEngine.getTaskService();
        //任務歸還,等價于taskService.claim(taskId,null);
        taskService.unclaim(taskId);
    }
           

3)任務交接

解釋:一般是任務的辦理人将任務交接給其他人。即重新設定任務辦理人,改變資料庫中該任務的assignee值。

代碼解釋:

/**
     * 任務交接,就是重新設定assignee的值
     */
    @Test
    public void taskHandover(){
        String taskId = "97502";
        String userId = "receiveTaskUser";
        TaskService taskService = processEngine.getTaskService();
        taskService.setAssignee(taskId,userId);
    }
           

4)任務委派

解釋:任務辦理人将任務委派給他人解決,他人解決完後,任務自動回到委派人手中。

(可以了解成上級将任務交給下級完成,下級完成後告訴上級一聲我完成了,然後上級看到後将目前節點完成,使得流程繼續往下執行)

表現在:委派給他人後,任務的owner變成委派人,任務的assignee是被委派人(同時DELEGATION_字段的值變成PENDING(等待))。等被委派人解決完任務後,任務的assignee的值變回委派人(DELEGATION_顯示為RESOLVED(已解決))。 注意:被委托人解決完任務後,該任務隻是标記為RESOLVED,還沒有完成,還需要委托人complete()任務後,才會從ru_task表中消失,并繼續執行流程。

代碼解釋:

/**
    * 任務委派
    */    
    @Test
    public void delegateTask(){
        String taskId = "2507";
        String userId = "張三";
        String delegateUserId = "張三_delegate";
        TaskService taskService = processEngine.getTaskService();

        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (!task.getAssignee().equals(userId)){
           throw new IllegalArgumentException(userId+ "不是任務辦理人,隻有任務辦理人才能委派任務!");
        }


        //張三 委派任務給 張三_delegate
        taskService.delegateTask(taskId,delegateUserId);
    }


    //被委派人解決委派任務
    @Test
    public void delegateTaskComplete(){
        String delegateUserId = "張三_delegate";
        TaskService taskService = processEngine.getTaskService();
        //查詢被委托人的待辦任務
        Task task = taskService.createTaskQuery().taskAssignee(delegateUserId).singleResult();
        if (task != null){
            System.out.println("查詢到了任務!");
            //解決任務
           taskService.resolveTask(task.getId());
        }
    }
           

5)任務轉辦

直接将任務轉移給别人處理。

表現在:任務的assignee變成别人,任務的owner為null。 如果要查詢自己轉移給他人的任務,可以在轉移的時候同時設定任務的owner(ru_task表和hi_task表都會被設定),友善查詢。

代碼解釋:

@Test
    public void transferTask(){
        String taskId = "2507";
        String owner = "張三";
        String userId = "張三_transfer";
        TaskService taskService = processEngine.getTaskService();

        //任務轉辦
        taskService.setAssignee(taskId,userId);

        taskService.setOwner(taskId,owner);

    }
           

四、監聽器 

1、任務監聽器TaskListener

userTask配合TaskListener,可實作人工任務的自動完成并且可添加額外的業務邏輯,例如抄送任務(先完成任務,再更新,不然會報錯)。如下所示:

@Slf4j
public class TestTaskListener implements TaskListener {
    private static final long serialVersionUID = -7072569040339927907L;

    @Override
    public void notify(DelegateTask delegateTask) {

        TaskService taskService = SpringContextUtils.getBean(TaskService.class);

        //先完成任務
        taskService.complete(delegateTask.getId());

        TaskEntityImpl task = new TaskEntityImpl();
        task.setId(delegateTask.getId());
        task.setAssignee(delegateTask.getAssignee());
        //抄送任務
        task.setCategory(TaskCategoryEnum.CC.getCategory());
        task.setCreateTime(new Date());
        ...

        // 再更新,不然就會報資料庫更新的錯誤
        taskService.saveTask(task);

        log.info("抄送任務自動完成...");
    }
}
           

五、其他

1、設定流程發起人

操作流程執行個體表的start_user_id字段

activiti7設定流程發起人

在啟動流程執行個體之前,Authentication.setAuthenticatedUserId("流程發起人id");

原因:

參見ExecutionEntityManagerImpl的createProcessInstanceExecution方法,大約在228行

activiti6要用 identityService.setAuthenticatedUserId("流程發起人id");

2、taskService.complete()預設設定的是流程執行個體和任務執行個體的變量,加上localScope=true才隻設定任務級别的變量。如果在完成任務之前修改了本任務完成時要設定的變量,并且沒有設定任務級别的變量,将會觸發樂觀鎖復原(表現為變量表的REV_增加了一次,但是對應變量的值卻是修改之前的。)

3、連線的條件使用的是流程執行個體級别的變量,而不是任務級别的變量。