楊光
(西安慶安航空電子有限公司信息開發(fā)室,陜西西安 710077)
現(xiàn)代企業(yè)的辦公自動化OA 系統(tǒng)具有復雜的流程審簽業(yè)務,以公文審簽為例,起草人員編寫公文,部門負責人審核,公司領導批準后,公文會下發(fā)到各個部門和員工。若審批人員不同意,則可以駁回流程,在起草人員修改后重新發(fā)起流程,進入審批狀態(tài)。
在傳統(tǒng)的工作流中,任務的指派人員角色單一,只能按照人員或角色指派任務。隨著人員或角色的復雜度的逐步提高,單一指派任務的方式已經(jīng)不能完全滿足系統(tǒng)的需要。文中在任務單一指派的基礎上,提出了重構人員身份數(shù)據(jù)表的方法,可以將任務同時按人員或角色指派,提高了系統(tǒng)的靈活性。
工作流引擎ProcessEngine 類是Activiti 框架的核心類,通過它可以獲得Activiti 所需的所有Service類,以及生成流程運行時的各種實例、數(shù)據(jù)、監(jiān)控以及管理流程的正常運行[1]。
官方API 提供了多種獲取ProcessEngine 的方法,這里使用名為activiti.cfg.xml 的配置文件來獲取,如圖1 所示[2]。

圖1 工作流引擎的配置文件activiti.cfg.xml
通過ProcessEngine 對象可以生成各個Service類,各類的作用如表1 所示。

表1 各Service類及作用
業(yè)務流程建模與標注(Business Process Model and Notation,BPMN)是描述流程的基本符號,包括圖元組合成一個業(yè)務流程圖(Business Process Diagram)的方式[3],如圖2 所示。

圖2 描述流程的基本符號
IDE 安裝Activiti 插件后,可以提供繪制流程的簡單方法,繪制的流程圖是后綴名為bpmn 的文件,這類文件本質上是一個xml格式的文件[4]。
在xml 格式文件中,包含代表各類元素的標簽。IDE 提供繪制流程圖的方式,可以自動生成xml 格式的文件。流程圖中各個基本符號的屬性都可以轉換為xml文件中的各類標簽和標簽的屬性[5]。
Activiti 的后臺有數(shù)據(jù)庫的支持,所有的表名都以act_開頭,表名的第二部分是表示表用途的兩個字母標識,用途與服務的API 對應[6]。
Activiti 的組織機構表包括act_id_group(用戶組信息表)、act_id_info(用戶擴展信息表)、act_id_membership(用戶與用戶組對應信息表)、act_id_user(用戶信息表)。
鑒于OA 系統(tǒng)部門、人員、角色關系的復雜性,這4 張表不能完全滿足OA 系統(tǒng)對用戶、人員、部門、角色關系的要求,因此需要調整表的結構或者重新設計表。部門表可以使用act_id_group 表,并補充相應字段。用戶表可以使用act_id_user,再增加人員表person_info,與用戶表建立關聯(lián)關系。act_id_membership 可以作為人員-部門-角色表,通過補充人員id、部門id、角色code、角色名稱等字段,可以體現(xiàn)人員所在部門及人員在該部門的角色。通過這種方式,可以很好地對Activiti 組織機構表進行重構,以滿足OA 系統(tǒng)復雜的用戶、人員、部門、角色、權限等要求[8]。
進行數(shù)據(jù)庫的二次開發(fā)以后,由于沒有破壞原有的數(shù)據(jù)表結構,因此在流程中可以使用調整后的數(shù)據(jù)庫結構進行處理,包括流程變量、個人任務、組任務。所以二次開發(fā)可以完全兼容原數(shù)據(jù)庫及方法。
在OA 系統(tǒng)進行文件分發(fā)時,首先由起草人編寫文件,編寫完成后,部門負責人進行審核。如果部門負責人及后續(xù)的公司領導均審批通過,則根據(jù)公文的分發(fā)范圍進行分發(fā);如果審批不通過,則直接退回到起草人階段重新編寫。
使用Eclipse 的Activiti 插件繪制公文審簽流程圖,如圖3 所示。

圖3 公文審簽流程圖
為了滿足OA 系統(tǒng)越來越復雜的人員和角色設計,文中提出了可以分別按照人員和角色分配任務的方法。
在部門負責人審核任務中,由于起草人分屬不同的部門,所以部門負責人是變量,設置屬性中的Assignee 為變量${deptLeaderId},且部門負責人是單人。在起草人編寫任務完成時,根據(jù)該人員查詢所在部門的負責人id,將流程變量設置為部門負責人id,下一步的任務處理人即為部門負責人,如圖4所示。

圖4 部門負責人審核任務的屬性
在公司領導審批任務中,審批人為公司領導的角色,與流程發(fā)起人無關。因此,可以直接將公司領導審批任務的屬性Candidate groups 設置為company Leader 角色,如圖5 所示。

圖5 公司領導審批任務的屬性
首先使用Spring 框架注入流程引擎Process Engine 的實例,其次通過流程引擎創(chuàng)建倉庫Service,并通過倉庫Service 創(chuàng)建部署對象[9]。通過部署對象可以加載流程圖,并為流程圖指定名稱,如圖6所示。

圖6 部署流程定義
通過runtimeService 啟動流程實例,流程實例啟動后會生成流程實例id(processInstanceId),該數(shù)據(jù)會伴隨流程執(zhí)行的整個過程,如圖7 所示。

圖7 啟動流程實例
完成任務有多種方法,其中之一是使用taskService的complete 方法。如圖8 所示,首先可以查詢出任務的id,其次使用taskService 的complete 方法,傳入?yún)?shù)任務id,即可將任務完成。完成任務后,觀察數(shù)據(jù)庫可以發(fā)現(xiàn),歷史表中會出現(xiàn)該任務的信息,而正在執(zhí)行的表中該任務會消失。

圖8 任務完成
對于待辦和已辦列表的查詢,文中提出了同時根據(jù)人員id 和角色id 查詢,合并查詢結果集的方法。
以待辦列表的查詢?yōu)槔羧藛Tid為“RY00001”,需要查詢該人員的待辦列表,則按照以下步驟,如圖9 所示。

圖9 查詢任務
1)使用processEngine.getTaskService().createTask Query().taskAssignee("RY00001").list()語句獲得個人任務taskList1。
2)根據(jù)人員的id 可以查詢其所屬的角色,角色的id 為"JS00001",使用processEngine.getTaskService().createTaskQuery().taskCandidateGroup("JS00001").list()語句獲得個人任務taskList2。
3)使用List 的addAll 方法,將個人任務1 與個人任務2 合并,即可得到該人員的任務,taskList=taskList1.addAll(taskList2)。
使用上述方法查詢人員的任務列表時,由于需要將2 個以上的List 合并,且無法根據(jù)分頁進行查詢,所以任務數(shù)量會增加,從而導致數(shù)據(jù)重組的速率下降,影響系統(tǒng)性能[10]。經(jīng)過實驗得出,當一個人員具有3 個角色,且個人任務為7 條,第一個角色的任務為4 條,第二個角色的任務為6 條,第三個角色的任務為3 條,將3 類任務進行合并,并進行數(shù)據(jù)重組時,查詢耗時為4 718 ms,已經(jīng)超過了用戶的可忍受等待時間,影響了用戶的體驗,如圖10 所示。

圖10 任務列表查詢時間
使用面向切面的編程SpringAOP 以及非關系型數(shù)據(jù)庫Redis 可以有效地解決這個問題[11],具體如下所示:
1)使用SpringAOP 面向切面編程,每次用戶完成任務時,使用后置增強獲取人員id。利用切面來完成獲取人員id并將人員的任務存入Redis緩存。利用SpringAOP 的目的是使得將任務存入Redis 的操作與業(yè)務進行解耦。首先定義切面類TaskAspect,在切面類中定義切面的增強類型是后置增強@After,后置增強的方法是*com.*.taskExecute(..)。
在該方法中,查詢出該人員的待辦和已辦任務列表,并將其序列化為JSON 字符串[12]。
2)使用Java 調用Redis 的API,將該人員的待辦和已辦任務列表的JSON 字符串存入Redis 中。其中,key 可以是人員的id+任務類型(已辦和待辦),value 可以是任務列表的JSON 字符串。
3)每次查詢人員的待辦和已辦任務列表時,直接從Redis 中取值,并反序列化為任務列表[13-14]。
由于Redis 是非關系型數(shù)據(jù)庫,適合于作為緩存,讀取速度快,因此,從Redis 取值查詢人員的待辦和已辦列表時,可以有效降低查詢時間[15-20]。
實驗表明,使用Redis 作為緩存,在相同的查詢條件下,可以將任務的查詢時間從4 718 ms 降低至43 ms,如圖11 所示。

圖11 從Redis緩存中查詢任務列表時間
4)使用Redis 緩存任務列表的JSON 字符串的方法,可以有效降低查詢已辦和待辦任務列表的時間。然而,這樣會引入另一個問題,當用戶發(fā)起流程或者處理流程時,由于需要將用戶的已辦和待辦任務列表全部查出,并序列化為JSON 字符串,存入Redis,因此當用戶的任務列表數(shù)據(jù)量過大時,查詢和轉換的時間也會增加,這樣導致用戶點擊發(fā)起流程按鈕或點擊審批流程按鈕時,會產(chǎn)生長時間的等待,如圖12所示。

圖12 用戶操作時將任務列表緩存入Redis的時間
實驗表明,在相同的條件下,當用戶操作時,直接將任務列表緩存入Redis,會導致用戶的等待時間為3 209 ms,帶來了不良的操作體驗。
針對出現(xiàn)的此問題,文中提出了如下解決方式:
首先修改用戶操作后將任務列表緩存入Redis的方式,從同步修改為異步。當用戶操作時,仍然使用SpringAOP 面向切面編程,此時將執(zhí)行操作的用戶id 及操作類型存入Redis 緩存。由于獲取用戶id及操作類型的耗時極短,因此用戶發(fā)起流程和完成任務的操作需要極短的時間,操作后會在Redis 中形成操作用戶的隊列。
操作用戶的隊列采用“FIFO+定時任務”的方式,每隔1 s 從Redis 中取出任務列表有變化的用戶id,將該用戶對應的任務列表查詢出來,并序列化后存入Redis 緩存。通過這種方式,將查詢任務列表的時間放在了后臺,提升了用戶的體驗。
以SpringBoot 的定時任務為例來說明定時任務的實現(xiàn)方法。
首先在SpringBoot 的啟動類上增加@Enable Scheduling 的注解,用來啟動定時任務;之后,在需要執(zhí)行定時任務方法的類上增加注解@Component,納入容器進行管理;通過cron 表達式可以定義多種形式的定時任務,每隔1 s 執(zhí)行一次,在需要執(zhí)行定時任務的方法上增加注解@Scheduled(cron="*/1 * * ** ?")。這個定時任務的方法所完成的功能就是從Redis 中取出任務列表有變化的用戶id 隊列的隊頭,并且將該用戶的任務列表查詢出來,序列化后存入Redis,最后將該用戶從隊列中刪除。通過這種方式,可以將用戶操作時等待的時間轉化為后臺執(zhí)行程序的時間,提高用戶的體驗。
實驗表明,在相同條件下,使用異步的方式,可以將用戶操作等待時間從3 209 ms 降低為36 ms,性能提高了兩個數(shù)量級,如圖13 所示。

圖13 用戶操作時將用戶id緩存入Redis的時間
文中研究了Activiti 工作流框架在OA 系統(tǒng)中的應用,提出了重構人員身份數(shù)據(jù)表的方法和同時根據(jù)人員和角色進行工作流的流轉方法,并優(yōu)化了待辦和已辦任務列表的查詢。通過這些研究,可以使得復雜信息化系統(tǒng)中的工作流配置更加靈活,同時使得待辦和已辦任務列表的查詢效率大幅提高。該研究對于構建大型流程信息化系統(tǒng)具有一定的實際意義。