Activiti工做流引擎使用

Activiti工做流引擎使用

1.簡單介工做流引擎與Activiti

對於工做流引擎的解釋請參考百度百科:工做流引擎javascript

1.1 我與工做流引擎

在第一家公司工做的時候主要任務就是開發OA系統,固然基本都 是有工做流的支持,不過當時使用的工做流引擎是公司一些牛人開發的(聽說是用一個開源的引擎修改的),名稱叫CoreFlow;功能相對Activiti 來講比較弱,可是能知足平常的使用,固然也有很多的問題因此後來咱們只能修改引擎的代碼打補丁。php

如今是我工做的第二家公司,由於要開發ERP、OA等系統須要使用工做流,在項目調研階段我先搜索資料選擇使用哪一個開源工做流引擎,最終肯定了Activiti5並基於公司的架構作了一些DEMO。html

1.2 Activiti與JBPM5?

對於Activiti、jBPM四、jBPM5咱們應該如何選擇,在InfoQ上有一篇文章寫的很好,從大的層面比較各個引擎之間的差別,請參考文章:縱觀jBPM:從jBPM3到jBPM5以及Activiti5java

1.3 Activiti資料

2.初次使用遇到問題收集

由於Activiti剛剛退出不久因此資料比較空缺,中文資料更是少的可憐,因此開始的時候一頭霧水(雖然以前用過工做流,可是感受差距不少),並且官方的手冊還不是很全面;因此我把我在學習使用的過程遇到的一些疑問都羅列出來分享給你們;如下幾點是我遇到和想到的,若是你還有什麼疑問能夠在評論中和我交流再補充。架構

2.1 部署流程圖後中文亂碼

亂碼是一直纏繞着國人的問題,以前各個技術、工具出現亂碼的問題寫過不少文章,這裏也不例外……,Activiti的亂碼問題在流程圖中。

流程圖的亂碼以下圖所示:

Activiti工做流引擎使用

解決辦法有兩種:

2.1.1 修改源代碼方式

修改源碼

org.activiti.engine.impl.bpmn.diagram.ProcessDiagramCanvas

在構造方法

public ProcessDiagramCanvas(int width, int height)

中有一行代碼是設置字體的,默認是用 Arial 字體,這就是亂碼產生的緣由,把字改成本地的中文字體便可,例如:

Font font = new Font("WenQuanYi Micro Hei", Font.BOLD, 11);

固然若是你有配置文件讀取工具那麼能夠設置在*.properties文件中,我就是這麼作的:

Font font = new Font(PropertyFileUtil.get("activiti.diagram.canvas.font"), Font.BOLD, 11);

2.1.2 使用壓縮包方式部署

Activiti支持部署*.bpmn20.xml、bar、zip格式的流程定義。

使用Activit Deisigner工具設計流程圖的時候會有三個類型的文件:

  • .activiti設計工具使用的文件

  • .bpmn20.xml設計工具自動根據.activiti文件生成的xml文件

  • .png流程圖圖片

解決辦法就是把xml文件和圖片文件同時部署,由於在單獨部署xml文件的時候Activiti會自動生成一張流程圖的圖片文件,可是這樣在使用的時候座標和圖片對應不起來……

因此把xml和圖片同時部署的時候Activiti自動關聯xml和圖片,當須要獲取圖片的時候直接返回部署時壓縮包裏面的圖片文件,而不是Activiti自動生成的圖片文件

2.1.2.1 使用工具打包Bar文件

右鍵項目名稱而後點擊「Create deployment artifacts」,會在src目錄中建立deployment文件夾,裏面包含*.bar文件.

2.1.2.2 使用Ant腳本打包Zip文件

這也是咱們採用的辦法,你能夠手動選擇xml和png打包成zip格式的文件,也能夠像咱們同樣採用ant target的方式打包這兩個文件。

<?xml version="1.0" encoding="UTF-8"?>
<project name="foo">

    <property name="workflow.definition" value="foo-common-core/src/main/resources/diagrams" />
    <property name="workflow.deployments" value="foo-common-core/src/main/resources/deployments" />

<target name="workflow.package.oa.leave">
		<echo>打包流程定義及流程圖::OA-請假</echo>
		<zip destfile="${workflow.deployments}/oa/leave.zip" basedir="${workflow.definition}/oa/leave" update="true"
			includes="*.xml,*.png" />
	</target>
</project>



這樣當修改流程定義文件後只要運行ant命令就能夠打包了:

ant workflow.package.oa.leave

如今部署bar或者zip文件查看流程圖圖片就不是亂碼了,而是你的壓縮包裏面的png文件。

2.2 使用引擎提供的Form仍是自定義業務Form

2.2.1 引擎提供的Form

定義表單的方式在每一個Task標籤中定義extensionElementsactiviti:formProperty便可,到達這個節點的時候能夠經過API讀取表單元素。

Activiti官方的例子使用的就是在流程定義中設置每個節點顯示什麼樣的表單哪些字段須要顯示、哪些字段只讀、哪些字段必填。

可是這種方式僅僅適用於比較簡單的流程,對於稍微複雜或者頁面須要業務邏輯的判斷的狀況就不適用了。

對於數據的保存都是在引擎的表中,不利於和其餘表的關聯、對整個系統的規劃也不利!

2.2.2 自定義業務Form

這種方式應該是你們用的最多的了,由於通常的業務系統業務邏輯都會比較複雜,並且數據庫中不少表都會有依賴關係,表單中有不少狀態判斷。

例如咱們的系統適用jQuery UI做爲UI,有不少javascript代碼,頁面的不少操做須要特殊處理(例如:多個選項的互斥、每一個節點根據類型和操做人顯示不一樣的按鈕);基本每 個公司都有一套本身的UI風格,要保持多個系統的操做習慣一致只能使用自定義表單才能知足。

2.3 業務和流程的關聯方式

這個問題在羣裏面不少人都問過,這也是我剛剛開始迷惑的地方;

後來看了如下API發現RuntimeService有兩個方法:

2.3.1 startProcessInstanceByKey

javadoc對其說明:

?

1
2
startProcessInstanceByKey(String processDefinitionKey, Map variabes)
           Starts a new process instance in the latest version of the process definition with the given key


其中businessKey就是業務ID,例如要申請請假,那麼先填寫登記信息,而後(保存+啓動流程),由於請假是單獨設計的數據表,因此保存後獲得實體ID就能夠把它傳給processInstanceBusinessKey方法啓動流程。當須要根據businessKey查詢流程的時候就能夠經過API查詢:

?

1
runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(processInstanceBusinessKey, processDefinitionKey);


議數據庫冗餘設計:在業務表設計的時候添加一列:PROCESS_INSTANCE_ID varchar2(64),在流程啓動以後把流程ID更新到業務表中,這樣無論從業務仍是流程均可以查詢到對方!

特別說明: 此方法啓動時自動選擇最新版本的流程定義。

2.3.2 startProcessInstanceById

javadoc對其說明:

?

1
2
startProcessInstanceById(String processDefinitionId, String businessKey, Map variables)
           Starts a new process instance in the exactly specified version of the process definition with the given id.


processDefinitionId:這個參數的值能夠經過repositoryService.createProcessDefinitionQuery()方法查詢,對應數據庫:ACT_RE_PROCDEF;每次部署一次流程定義就會添加一條數據,同名的版本號累加。

特別說明: 此能夠指定不一樣版本的流程定義,讓用戶多一層選擇。

2.3.3 如何選擇

建議使用startProcessInstanceByKey,特殊狀況須要使用以往的版本選擇使用startProcessInstanceById

2.4 同步用戶數據

這個問題也是比較多的人詢問過,Activiti支持對任務分配到:指定人、指定組、二者組合,而這些人和組的信息都保存在ACT_ID..表中,有本身的用戶和組(角色)管理讓不少人不知所措了;緣由是由於每一個系統都會存在一個權限管理模塊(維護:用戶、部門、角色、受權),不知道該怎麼和Activiti同步。

2.4.1 建議處理方式

Activiti有一個IdentityService接口,經過這個接口能夠操控Activiti的ACT_ID_*表的數據,通常的作法是用業務系統的權限管理模塊維護用戶數據,當進行CRUD操做的時候在原有業務邏輯後面添加同步到Activiti的代碼;例如添加一個用戶時同步Activiti User的代碼片斷:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
  * 保存用戶信息 而且同步用戶信息到activiti的identity.User,同時設置角色
  * @param user
  * @param roleIds
  */
public void saveUser(User user, List<Long> roleIds, boolean synToActiviti) {
     accountManager.saveEntity(user);
     String userId = user.getId().toString();
 
     if (synToActiviti) {
         List<org.activiti.engine.identity.User> activitiUsers = identityService.createUserQuery().userId(userId).list();
         if (activitiUsers.size() == 1 ) {
             //更新信息
             org.activiti.engine.identity.User activitiUser = activitiUsers.get( 0 );
             activitiUser.setFirstName(user.getName());
             activitiUser.setLastName( "" );
             activitiUser.setPassword(user.getPassword());
             activitiUser.setEmail(user.getEmail());
             identityService.saveUser(activitiUser);
 
             // 刪除用戶的membership
             List<Group> activitiGroups = identityService.createGroupQuery().groupMember(userId).list();
             for (Group group : activitiGroups) {
                 identityService.deleteMembership(userId, group.getId());
             }
 
             // 添加membership
             for (Long roleId : roleIds) {
                 Role role = roleManager.getEntity(roleId);
                 identityService.createMembership(userId, role.getEnName());
             }
 
         } else {
             org.activiti.engine.identity.User newUser = identityService.newUser(userId);
             newUser.setFirstName(user.getName());
             newUser.setLastName( "" );
             newUser.setPassword(user.getPassword());
             newUser.setEmail(user.getEmail());
             identityService.saveUser(newUser);
 
             // 添加membership
             for (Long roleId : roleIds) {
                 Role role = roleManager.getEntity(roleId);
                 identityService.createMembership(userId, role.getEnName());
             }
         }
     }
 
}


刪除操做也和這個相似!

無論從業務系統維護用戶仍是從Activiti維護,確定要肯定一方,而後CRUD的時候同步到對方,若是須要同步多個子系統那麼能夠再調用WebService實現。

2.5 流程圖設計工具用什麼

Activiti提供了兩個流程設計工具,可是面向對象不一樣。

  • Activiti Modeler,面向業務人員,使用開源的BPMN設計工具Signavio,使用BPMN描述業務流程圖

  • Eclipse Designer,面向開發人員,Eclipse的插件,可讓開發人員定製每一個節點的屬性(ID、Name、Listener、Attr等)

2.5.1 咱們的方式

可能你會驚訝,由於咱們沒有使用Activiti Modeler,咱們認爲用Viso已經能表達流程圖的意思了,並且項目經理也是技術出身,和開發人員也容易溝通。

目前這個項目是第一個使用Activiti的,開始咱們在需求調研階段使用Viso設計流程圖,利用泳道流程圖設計和客戶溝通,肯定後由負責流程的開發人員用Eclipse Designer設計獲得bpmn20.xml,最後部署。

2.6 Eclipse Designer存在的問題

這個插件有一個很討厭的Bug一直未修復,安裝了插件後Eclipse的複製和粘帖快捷鍵會被更換爲(Ctrl+Insert、Shift+Insert);Bug描述請見:

因此最後咱們只能單獨開一個安裝了Eclipse Designer的Eclipse專門用來設計流程圖,這樣就不影響正常使用Eclipse JAVAEE了。

3.配置

3.1 集成Spring

對於和Spring的集成Activiti作的不錯,簡單配置一些Bean代理便可實現,可是有兩個和事務相關的地方要提示:

  • 配置processEngineConfiguration的時候屬性transactionManager要使用和業務功能的同一個事務管理Bean,不然事務不一樣步。

  • 對於實現了org.activiti.engine.delegate包中的接口的類須要被事務控制的實現類須要被Spring代理,而且添加事務的Annotation或者在xml中配置,例如:

    ?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
      * 建立繳費流程的時候自動建立實體
      *
      * @author HenryYan
      */
    @Service
    @Transactional
    publicclass CreatePaymentProcessListener implementsExecutionListener {
        ....
    }


?

4.使用單元測試

單元測試均使用Spring的AbstractTransactionalJUnit4SpringContextTests做爲SuperClass,而且在測試類添加:

?

1
2
@ContextConfiguration (locations = { "/applicationContext-test.xml" })
@RunWith (SpringJUnit4ClassRunner. class )

?

雖然Activiti也提供了測試的一些超類,可是感受很差用,因此本身封裝了一些方法。

代碼請轉移:https://gist.github.com/2182847

4.1 驗證流程圖設計是否正確

代碼請轉移:https://gist.github.com/2182869

4.2 業務對象和流程關聯測試

代碼請轉移:https://gist.github.com/2182973

5.各類狀態的任務查詢以及和業務對象關聯

咱們目前分爲4中狀態:未簽收、辦理中、運行中、已完成。

查詢到任務或者流程實例後要顯示在頁面,這個時候須要添加業務數據,最終結果就是業務和流程的並集,請參考6.2

5.1 未簽收(Task)

此類任務針對於把Task分配給一個角色時,例如部門領導,由於部門領導角色能夠指定多我的因此須要先簽收再辦理,術語:搶佔式

對應的API查詢:

?

1
2
3
4
5
6
7
8
9
10
/**
  * 獲取未簽收的任務查詢對象
  * @param userId    用戶ID
  */
@Transactional (readOnly = true )
publicTaskQuery createUnsignedTaskQuery(String userId) {
     TaskQuery taskCandidateUserQuery = taskService.createTaskQuery().processDefinitionKey(getProcessDefKey())
             .taskCandidateUser(userId);
     returntaskCandidateUserQuery;
}

?

5.2 辦理中(Task)

此類任務數據類源有兩種:

  • 簽收後的,5.1中籤收後就應該爲辦理中狀態

  • 節點指定的是具體到一我的,而不是角色

對應的API查詢:

?

1
2
3
4
5
6
7
8
9
/**
  * 獲取正在處理的任務查詢對象
  * @param userId    用戶ID
  */
@Transactional (readOnly = true )
publicTaskQuery createTodoTaskQuery(String userId) {
     TaskQuery taskAssigneeQuery = taskService.createTaskQuery().processDefinitionKey(getProcessDefKey()).taskAssignee(userId);
     returntaskAssigneeQuery;
}

?

5.3 運行中(ProcessInstance)

說白了就是沒有結束的流程,全部參與過的人都應該能夠看到這個實例,可是Activiti的API沒有能夠經過用戶查詢的方法,這個只能本身用hack的方式處理了,我目前尚未處理。

從表ACT_RU_EXECUTION中查詢數據。

對應的API查詢:

?

1
2
3
4
5
6
7
8
9
10
/**
  * 獲取未經完成的流程實例查詢對象
  * @param userId    用戶ID
  */
@Transactional (readOnly = true )
publicProcessInstanceQuery createUnFinishedProcessInstanceQuery(String userId) {
     ProcessInstanceQuery unfinishedQuery = runtimeService.createProcessInstanceQuery().processDefinitionKey(getProcessDefKey())
             .active();
     returnunfinishedQuery;
}

?

5.4 已完成(HistoricProcessInstance)

已經結束的流程實例。

從表ACT_HI_PROCINST中查詢數據。

?

1
2
3
4
5
6
7
8
9
10
/**
  * 獲取已經完成的流程實例查詢對象
  * @param userId    用戶ID
  */
@Transactional (readOnly = true )
publicHistoricProcessInstanceQuery createFinishedProcessInstanceQuery(String userId) {
     HistoricProcessInstanceQuery finishedQuery = historyService.createHistoricProcessInstanceQuery()
             .processDefinitionKey(getProcessDefKey()).finished();
     returnfinishedQuery;
}


?

5.5 查詢時和業務關聯

提示:以前在業務對象添加了PROCESS_INSTANCE_ID字段

思路:如今能夠利用這個字段查詢了,不論是Task仍是ProcessInstance均可以獲得流程實例ID,能夠根據流程實例ID查詢實體而後把流程對象設置到實體的一個屬性中由Action或者Controller輸出到前臺。

代碼請參考:https://gist.github.com/2183557

6.UI及截圖

結合實際業務描述一個業務從開始到結束的過程,對於迷惑的同窗看完豁然開朗了;這裏使用請假做爲例子。

6.1 單獨一個列表負責申請

這樣的好處是申請和流程辦理分離開處理,列表顯示未啓動流程的請假記錄(數據庫PROCESS_INSTANCE_ID爲空)。

申請界面的截圖:

Activiti工做流引擎使用

6.2 流程狀態

Activiti工做流引擎使用

6.3 流程跟蹤

圖片方式顯示當前節點:

Activiti工做流引擎使用

列表形式顯示流程流轉過程:

Activiti工做流引擎使用

6.3.1 當前節點定位JS

Java代碼請移步:https://gist.github.com/2183712

Javascript思路:先經過Ajax獲取當前節點的座標,在指定位置添加紅色邊框,而後加載圖片。

代碼移步:https://gist.github.com/2183804

7.開啓Logger

  1. 添加log4j的jar

  2. 設置log4j.logger.java.sql=DEBUG

8.結束

以前就想寫這篇文章,如今終於完成了,花費了幾個小時,但願能節省你幾天的時間。

請讀者仔細閱讀Activiti的用戶手冊和Javadoc。

來自:http://blog.csdn.net/howareyoutodaysoft/article/details/8081003

相關文章
相關標籤/搜索