Liferay7 BPM門戶開發之11: Activiti工做流程開發的一些統一規則和實現原理(完整版)

注意:如下規則是我爲了規範流程的處理過程,不是Activiti公司的官方規定。html

一、流程啓動須要設置啓動者,在Demo程序中,「啓動者變量」名統一設置爲initUserId

啓動時要作的:
identityService.setAuthenticatedUserId(initUserId);
processInstance = runtimeService.startProcessInstanceByKey(流程ID, 業務Key, 變量map);

or
startProcessInstanceById(String processDefinitionId, String businessKey, Map variables) 

變量map定義的方法:
Map<String ,Object > variables = new HashMap<>();
variables.put("initUserId","wangxin");
variables.put("leaveReason","想休假了");

二、使用el表達式來作流程的動態屬性或方法定義


好比完成一個「請假銷假」的任務,須要流程發起者銷假,銷假環節就能找到正確的簽收者(activiti:assignee)了:前端

<startevent id="startevent1" name="Start" activiti:initiator="initUserId"></startevent>
<usertask id="reportBack" name="銷假" activiti:assignee="${initUserId}"></usertask>

 

三、「業務鍵」定義規則

業務鍵 = 流程ID + 實體實例ID;
businessKey = procDefId + "." + objIdjava

 

四、根據「業務鍵」查詢流程實例(反查)

在流程啓動的時候,咱們已經定義了業務Key,那麼只須要反查,便可獲得流程實例node

//根據業務鍵獲取流程實例
public ProcessInstance getProInstByBusinessKey(String businessKey) {
return runtimeService.createProcessInstanceQuery().processInstanceBusinessKey("LeaveBill.1").singleResult();
}

//根據業務鍵獲取任務
public List<Task> getTasksByBusinessKey(String businessKey) {
return taskService.createTaskQuery().processInstanceBusinessKey("LeaveBill.1").list();
}

 

五、經過流程實例ID獲取「業務鍵」

//一、經過任務對象獲取流程實例
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();

//二、經過流程實例獲取「業務鍵」
String businessKey = pi.getBusinessKey();

 

六、取得當前活動節點

String processInstanceId="1401";
// 經過流程實例ID查詢流程實例
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if(pi!=null){
System.out.println("當前流程節點在:" + pi.getActivityId());
}else{
System.out.println("流程已結束!!");
}

 

七、查詢某人的「候選公共任務」,用於實現「搶籤」

「候選公共任務」的認領者即屬於一堆候選人其中一個,好比財務審批能夠由張3、李4、王五審批,誰批均可以,手快者先認領就是簽收者。
這個查詢就是把符合條件的候選者的任務查出來,通常能夠和「我的任務」合併一塊兒放在「待辦任務」菜單裏。
也針對於把Task分配給一個角色時,例如部門領導,由於部門領導角色能夠指定多我的因此須要先簽收再辦理,特色:搶佔式。git

// 建立任務查詢對象
TaskQuery taskQuery = taskService.createTaskQuery();
// 配置查詢對象
String candidateUser="張三";
taskQuery
// 過濾條件
.taskCandidateUser(candidateUser)
// 排序條件
.orderByTaskCreateTime().desc();
// 執行查詢
List<Task> tasks = taskQuery.list();
System.out.println("======================【"+candidateUser+"】的候選公共任務列表=================");
for (Task task : tasks) {
System.out.print("id:"+task.getId()+",");
System.out.print("name:"+task.getName()+",");
System.out.print("createTime:"+task.getCreateTime()+",");
System.out.println("assignee:"+task.getAssignee());
}

 

八、查詢某人的「我的任務」,即簽收者(assignee)被明確指定。

好比銷假人被變量明確指定了:
<usertask id="reportBack" name="銷假" activiti:assignee="${initUserId}"></usertask>github

// 建立任務查詢對象
TaskQuery taskQuery = taskService.createTaskQuery();
// 配置查詢對象
// String assignee="user";
String assignee="李四";
taskQuery
// 過濾條件
.taskAssignee(assignee)
// 分頁條件
// .listPage(firstResult, maxResults)
// 排序條件
.orderByTaskCreateTime().desc();
// 執行查詢
List<Task> tasks = taskQuery.list();
System.out.println("======================【"+assignee+"】的代辦任務列表=================");
for (Task task : tasks) {
System.out.print("id:"+task.getId()+",");
System.out.print("name:"+task.getName()+",");
System.out.print("createTime:"+task.getCreateTime()+",");
System.out.println("assignee:"+task.getAssignee());
}

 

九、任務認領,經過認領,把「候選公共任務」變成指定用戶的「我的任務」

// claim 認領
String taskId="1404";
String userId="李四";
// 讓指定userId的用戶認領指定taskId的任務
taskService.claim(taskId, userId);

 

十、結合Form表單提交(辦理)任務

String formId = request.getParameter("formId");
String procInstId = request.getParameter("procInstId"); //流程實例ID

Map<String, String[]> flowData = new HashMap<String, String[]>();

//將表單提交數據注入表單變量
flowData = request.getParameterMap();

formHelper.submitTaskFormData(request.getParameter("taskId"), flowData);

// 完成任務
taskService.complete(taskId );

 

十一、任務動態分配定製處理,好比尋找「某人的直屬領導」

Activiti的簽收人中只有候選人、候選組、分配人的概念,若是要實現更業務相關的簽收邏輯,須要擴展監聽器
好比MyLeaderHandler,即擴展實現了TaskListener接口:web

<userTask id="task1" name="My task" >
<extensionElements>
<activiti:taskListener event="create" class="org.activiti.MyLeaderHandler" />
</extensionElements>
</userTask>
//動態實現任務分配
public class MyLeaderHandler implements TaskListener {

public void notify(DelegateTask delegateTask) {

LeaderService ls =....
String userLeader = ls.findLeaderbyUserId(XXXXXXX);
delegateTask.setAssignee(userLeader);
delegateTask.addCandidateUser(XXX);
delegateTask.addCandidateGroup(XXXX);
...
}
}

還有一種更方便的方法,即經過el表達式:spring

可使用表達式把任務監聽器設置爲spring代理的bean, 讓這個監聽器監放任務的建立事件。
下面的例子中,執行者會經過調用ldapService這個spring bean的findManagerOfEmployee方法得到。 
流程變量emp會做爲參數傳遞給bean。sql

<userTask id="task" name="My Task" activiti:assignee="${ldapService.findManagerForEmployee(emp)}"/>數據庫

也能夠用來設置候選人和候選組:

<userTask id="task" name="My Task" activiti:candidateUsers="${ldapService.findAllSales()}"/>

ps:注意方法返回類型只能爲String或Collection<String> (對應候選人和候選組):

public class FakeLdapService {

public String findManagerForEmployee(String employee) {
return "Kermit";
}

public List<String> findAllSales() {
return Arrays.asList("kermit", "gonzo", "fozzie");
}
}

 

十二、會籤任務,即多實例

例如,一個任務必須全部領導都經過了才往下走。

activiti其實已經很是優雅的實現了,網上有一些繁瑣的實現,其實徹底沒有必要,好比下面:

http://jee-soft.cn/htsite/html/fzyyj/jsyj/2012/08/08/1344421504026.html

正確的打開方式是經過在Task節點增長multiInstanceCharacteristics節點,設置 collection和 elementVariable屬性

例子:

能夠指定一個(判斷完成)表達式,只有true的狀況下所有實例完成,流程繼續往下走。

若是表達式返回true,全部其餘的實例都會銷燬,多實例節點也會結束。 這個表達式必須定義在completionCondition子元素中。

<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="false"
     activiti:collection="assigneeList" activiti:elementVariable="assignee" >
    <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
  </multiInstanceLoopCharacteristics>
</userTask>

這裏例子中,會爲assigneeList集合的每一個元素建立一個並行的實例。 當60%的任務完成時,其餘任務就會刪除,流程繼續執行。

會籤環節中涉及的幾個默認的自帶流程變量:

  • 1. nrOfInstances 該會籤環節中總共有多少個實例
  • 2. nrOfActiveInstances 當前活動的實例的數量,即尚未 完成的實例數量。
  • 3. nrOfCompletedInstances 已經完成的實例的數量

實現會籤人員分配

public class AssgineeMultiInstancePer implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) throws Exception {
        System.out.println("設置會籤環節的人員.");
        execution.setVariable("pers", Arrays.asList("張三", "李四", "王五", "趙六"));
    }
}

設置完成會籤條件:

public class MulitiInstanceCompleteTask implements Serializable {
    private static final long serialVersionUID = 1L;
    public boolean completeTask(DelegateExecution execution) {
        System.out.println("總的會籤任務數量:" + execution.getVariable("nrOfInstances") + "當前獲取的會籤任務數量:" + execution.getVariable("nrOfActiveInstances") + " - " + "已經完成的會籤任務數量:" + execution.getVariable("nrOfCompletedInstances"));
        System.out.println("I am invoked.");
        return false;
    }
}

 

官方網站專門有一節(8.5.14. Multi-instance)詳細介紹:
http://www.activiti.org/userguide/index.html#bpmnMultiInstance

也能夠看:
http://itindex.net/detail/54540-activiti
https://my.oschina.net/winHerson/blog/139267
http://itindex.net/detail/51509-activiti-%E6%8C%82%E8%B5%B7-%E6%A0%B8%E5%BF%83
http://blog.csdn.net/chq1988/article/details/41513451
https://github.com/bluejoe2008/openwebflow

 

1三、加簽、減籤任務,很是符合中國式流程

Activiti其實已經很是優雅的實現了,網上有一些繁瑣的實現,其實徹底沒有必要

實際上一行代碼就能實現:

//人員加簽:

runtimeService.addUserIdentityLink(String processInstanceId, String userId, String identityLinkType)

//還能夠這樣實現,實際上是addUserIdentityLink的一個繼承,只是類型是IdentityLinkType.CANDIDATE,即參與者
runtimeService.addParticipantUser(String processInstanceId, String userId)

//組加簽:

runtimeService.addParticipantGroup(String processInstanceId, String groupId)

runtimeService.deleteParticipantUser(String processInstanceId, String userId)

//人員、組減籤:

runtimeService.deleteParticipantUser(String processInstanceId, String userId)

runtimeService.deleteParticipantGroup(String processInstanceId, String groupId)

值得注意的是,認證鏈類型IdentityLinkType是枚舉,有5個:

  • ASSIGNEE
  • CANDIDATE
  • OWNER
  • PARTICIPANT
  • STARTER

使用這裏的ASSIGNEE和OWNER還能夠有更加豐富的想象力。

 

1四、實現自定義的用戶和組織

http://www.verydemo.com/demo_c128_i11037.html
https://my.oschina.net/winHerson/blog/118172
http://rongjih.blog.163.com/blog/static/335744612012631112640968/
http://www.kafeitu.me/activiti/2012/04/23/synchronize-or-redesign-user-and-role-for-activiti.html


1五、實現自定義表單

這個想法挺好:
http://blog.csdn.net/tuzongxun/article/details/51093881
但距離實用還差很遠,實現產品化也是實現代價很是高,仍是使用jsp + bootstrip做爲前端最實際,也更容易擴展

筆者正好也作過動態自定義表單:

昕友silverlight表單設計器的使用 (原創 Form Designer)

積累過一些技術和思想。

1六、外置表單

在實際的開發中,實際上其自身的內置表單無任何意義,而Activiti還不具有所見即所得的自動錶單設計器(畢竟是開源產品),但另外一方面這也帶來了一個好處:便可以爲所欲爲的設計表單前端,能夠知足任務的可控性和可變性,很是強大,而不是限制在廠商的表單技術裏動彈不得,外置表單能夠用到的技術:JSP、Spring MVC視圖、Struct視圖。

Activiti提供的集成方式異常簡單而優雅,只須要注意4點:

  • 一、在BPMN文件的任務節點設置FormKey屬性,即<userTask activiti:formKey="XXX">...,這裏XXX能夠是Jsp頁面,也能夠是Struct Action,也能夠是Spring Controller名,以便對應表單ID,真的方便的想哭了;
  • 二、在流程運行時,根據TaskId獲取FormKey:
    StartFormData FormService.getStartFormData(String processDefinitionId)
    //Or
    TaskFormdata FormService.getTaskFormData(String taskId) String formKey
    = formData.getFormKey()
    ...
  • 三、而後在Spring 控制器裏重定向傳遞:XXXX?fromKey=XXX
  • 四、Form數據提交:
Map map=request.getParameterMap();
Set set=map.keySet();//全部參數名的set集
Iterator it=set.iterator();
while(it.hasNext()){
    String s=(String)it.next();//枚舉出參數
    String values[]=request.getParameterValues(s);//取得對應的參數值返回的數組
    而後幹你要乾的操做,好比
    for(int i=0;i<values.length;i++){
        out.println(values[i]); 
    }
}

提交:

ProcessInstance FormService.submitStartFormData(String processDefinitionId, Map<String,String> properties)
void FormService.submitStartFormData(String taskId, Map<String,String> properties)

 

1七、表單屬性和表單變量的關聯關係

實際上在業務開發中能夠不須要用到表單屬性,只用表單變量便可,須要注意表單變量的做用域,有兩個

  • 流程實例內全局可用
  • 僅任務內可用

在Activit內部,定義了流程屬性,會自動增長流程變量,也能夠手動設置關聯, 手動流程屬性和變量的關聯關係舉例:

一、屬性speaker 和 變量SpeakerName 相互關聯

  <activiti:formProperty id="speaker" name="Speaker" variable="SpeakerName" type="string" />

二、和bean關聯

  <activiti:formProperty id="street" expression="#{address.street}" required="true" />

form property的5種類型:

  • string (org.activiti.engine.impl.form.StringFormType

  • long (org.activiti.engine.impl.form.LongFormType)

  • enum (org.activiti.engine.impl.form.EnumFormType)

  • date (org.activiti.engine.impl.form.DateFormType)

  • boolean (org.activiti.engine.impl.form.BooleanFormType)

獲取表單屬性的辦法:

  • List<FormProperty> formService.getStartFormData(String processDefinitionId).getFormProperties()
    List<FormProperty> formService.getTaskFormData(String taskId).getFormProperties()

FormProperty實際是一個接口,允許去你去自由擴展,具體定義:

public interface FormProperty {
  /** the key used to submit the property in {@link FormService#submitStartFormData(String, java.util.Map)}
   * or {@link FormService#submitTaskFormData(String, java.util.Map)} */
  String getId();
  /** the display label */
  String getName();
  /** one of the types defined in this interface like e.g. {@link #TYPE_STRING} */
  FormType getType();
  /** optional value that should be used to display in this property */
  String getValue();
  /** is this property read to be displayed in the form and made accessible with the methods
   * {@link FormService#getStartFormData(String)} and {@link FormService#getTaskFormData(String)}. */
  boolean isReadable();
  /** is this property expected when a user submits the form? */
  boolean isWritable();
  /** is this property a required input field */
  boolean isRequired();
}

獲取表單屬性的名稱:

formProperty.getType().getName()

獲取表單屬性的值:

formProperty.getType().getValue()

獲取某一屬性,好比XML定義:

<activiti:formProperty id="start" type="date" datePattern="dd-MMM-yyyy" />

formProperty.getType().getInformation("datePattern")

獲取枚舉值:

formProperty.getType().getInformation("values")

 

1八、自定義「待辦列表」的一個方法

Activit內置的待辦任務查詢相似:

List<Task> tasks = taskService.createTaskQuery()
    .taskAssignee("kermit")
    .processVariableValueEquals("orderId", "0815")
    .orderByDueDate().asc()
    .list();

若是要直接SQL查詢,能夠這樣:

List<Task> tasks = taskService.createNativeTaskQuery()
  .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}")
  .parameter("taskName", "gonzoTask")
  .list();

long count = taskService.createNativeTaskQuery()
  .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, "
    + managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
  .count();

 

1九、深刻理解流程變量

首先須要明確一些容易混淆的概念。

"execution是什麼?"
就是流程實例的當前執行節點;

"execution node和process instance的區別是什麼?"
執行節點和流程實例的關係很是密切,能夠想象一個樹形結構,流程實例(process instance)是主幹,由無數的執行樹枝(execution)組成的;
一個重要的公式:process instance id = the root execution id
但一旦流程不是一條主幹線一直到結束,而是分裂爲多個分支了,那麼就有多個execution id,能夠當作樹枝。

"activity、task、job的區別是什麼?"

  • Activity = 這個是模型定義BPMN的一個基本元素(注意這個不是運行時的概念),好比Start、user task、鏈接線......
  • task = 這個是運行時的概念,就是任務的意思,好比用戶任務、服務任務......
  • job = 一個定時器任務

設置流程變量(RuntimeService)的方法:

void setVariable(String executionId, String variableName, Object value);
void setVariableLocal(String executionId, String variableName, Object value);
void setVariables(String executionId, Map<String, ? extends Object> variables);
void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);

讀取流程變量的方法:

//讀取變量(基於TaskService)
Map<String, Object> getVariables(String executionId);
Map<String, Object> getVariablesLocal(String executionId);
Map<String, Object> getVariables(String executionId, Collection<String> variableNames);
Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
Object getVariable(String executionId, String variableName);
<T> T getVariable(String executionId, String variableName, Class<T> variableClass);

//讀取變量(基於execution 執行分支對象)
execution.getVariables();
execution.getVariables(Collection<String> variableNames);
execution.getVariable(String variableName);
execution.setVariables(Map<String, object> variables);
execution.setVariable(String variableName, Object value);

Activiti5.17版本以後的新API

因爲歷史版本緣由,在執行任何上述方法的時候,activiti默認是將全部的變量從數據庫取出來,意味着數據庫存有10個變量,如今你須要取出名叫myVariable的變量,可是其他的9個也會被取出來而且緩存起來。這並非不好,由於後期你取變量就不會再從數據庫取出,固然若是你有大量的變量或者在查詢方面你想進一步控制數據庫,這時所有取出就不怎麼合適了。從Activiti5.17版本開始,新添加了的方法支持是否所有查詢輸出到緩存:若是是true則所有抓取

Map<String, Object> getVariables(Collection<String> variableNames, boolean fetchAllVariables);
Object getVariable(String variableName, boolean fetchAllVariables);
void setVariable(String variableName, Object value, boolean fetchAllVariables);
相關文章
相關標籤/搜索