1. 動態(tài)表單特點
一般而言菊霜,工作流引擎常用表單有三種:普通表單奠衔、外置表單和動態(tài)表單。各自都有其優(yōu)缺點戚绕,可根據(jù)具體場景靈活選用纹坐。需要說明的是,三種表單方式只是在任務(wù)節(jié)點上用戶的表單定義方式上面有差別舞丛,而流程的運轉(zhuǎn)機制則完全相同恰画。
如圖所示,區(qū)別于普通表單和外置表單瓷马,動態(tài)表單是直接將工作流節(jié)點處的表單嵌入流程定義文件BPMN中,系統(tǒng)利用JS或者模板引擎根據(jù)流程定義中表單定義的各個子控件及其屬性動態(tài)渲染出表單加載出來跨晴。
2. 動態(tài)表單流程設(shè)計
在模板管理界面欧聘,點擊新增模板按鈕,進(jìn)入流程模板設(shè)計頁面端盆。
編輯流程信息:流程key怀骤、流程name等:
拖拽添加啟動節(jié)點,點擊啟動節(jié)點焕妙,在下面的屬性中點擊動態(tài)表單屬性:
編輯啟動節(jié)點的動態(tài)表單屬性蒋伦,編輯活動編號、名稱焚鹊、類型痕届、必輸、可讀末患、可寫等屬性后研叫,點擊保存:
添加下一個活動事件,并編輯該節(jié)點屬性信息璧针,重點是代理嚷炉、和動態(tài)表單屬性信息:
點擊代理,編輯該事件的代理人探橱、候選人(組)申屹,點擊保存:
中間流程設(shè)計不詳細(xì)講述绘证,最后添加一個結(jié)束節(jié)點并連接:
動態(tài)表單節(jié)點的常用屬性介紹:
流程全部設(shè)計完成后,點擊保存按鈕進(jìn)行保存:
在流程玩法-流程列表界面點擊部署流程按鈕:
3. 流程列表
流程部署成功后哗讥,在待啟動流程列表界面可以看到已部署的流程:
點擊啟動按鈕嚷那,彈出啟動節(jié)點的動態(tài)表單,輸入信息后點擊啟動流程按鈕:
4. 任務(wù)列表
啟動成功后忌栅,登錄流程設(shè)計的該節(jié)點候選人(組)用戶登錄车酣,在任務(wù)列表界面可以看到該流程,并可以點擊簽收按鈕進(jìn)行簽收:
簽收成功后索绪,該項操作會變成“辦理”狀態(tài)湖员,可以點擊進(jìn)行辦理:
點擊辦理按鈕,彈出該節(jié)點定義的動態(tài)表單瑞驱,并進(jìn)行提交操作:
5. 運行中流程
點擊運行中流程菜單可以查看已啟動但未結(jié)束的流程列表娘摔,并且可以查看每個流程正在運行的節(jié)點:
點擊當(dāng)前節(jié)點,可以查看每個流程圖及當(dāng)前運行節(jié)點位置:
6. 已結(jié)束流程
在已結(jié)束流程頁面可以看到已經(jīng)結(jié)束的流程列表:
7. 動態(tài)表單開發(fā)關(guān)鍵點
標(biāo)準(zhǔn)流程的啟動和運轉(zhuǎn)直接調(diào)用Activiti的通用API即可實現(xiàn)唤反,下面主要從以下幾個方面講解凳寺。
1) 動態(tài)表單渲染
動態(tài)表單將表單定義在了流程定義的文件中,因此在啟動節(jié)點和任務(wù)節(jié)點處能分別通過流程定義ID和任務(wù)ID去獲取節(jié)點處的表單屬性JSON彤侍,如下所示:
獲取啟動節(jié)點處表單數(shù)據(jù)鏈接:
<u>http://localhost:8083/form/dynamic/get-form/start/leave-dynamic-from:2:47515</u>
獲取表單定義數(shù)據(jù)結(jié)果:
*{*
*"form":{*
*"deploymentId":"47512",*
*"formKey":"",*
*"formProperties":[*
*{*
*"id":"startDate",*
*"name":"請假開始日期",*
*"readable":true,*
*"required":true,*
*"type":{*
*"name":"date"*
*},*
*"value":"",*
*"writable":true*
*},*
*{*
*"id":"endDate",*
*"name":"請假結(jié)束日期",*
*"readable":true,*
*"required":true,*
*"type":{*
*"name":"date"*
*},*
*"value":"",*
*"writable":true*
*},*
*{*
*"id":"reason",*
*"name":"請假原因",*
*"readable":true,*
*"required":true,*
*"type":{*
*"mimeType":"text/plain",*
*"name":"string"*
*},*
*"value":"",*
*"writable":true*
*}*
*],*
*"processDefinition":""*
*}*
*}*
獲取到了表單定義屬性文件后肠缨,就可以利用JS或者模板引擎渲染出表單了。比如利用layui的模板引擎來渲染盏阶,就可以定義如下模板:
***var****getTpl =* *`*
*<form class="layui-form" lay-filter="form-tpl" style="padding: 10px;">*
*{{# $.each(d.taskFormData.formProperties, function(i,v1) { }} {{# console.log(i,v1)}}*
*<div>*
*{{# if(v1.type.name=="string" ){ }}*
*<div class="layui-form-item">*
*<label class="layui-form-label">{{v1.name}}</label>*
*<div class="layui-input-block">*
*<input class="layui-input" type="text" name="{{v1.writable ? 'fp_'+v1.id : v1.id}}" autocomplete="off" {{v1.required? 'lay-verify="required"': ''}} placeholder="{{v1.name}}" lay-blur lay-verType="tips" value="{{v1.value}}" {{v1.writable ? '':'disabled'}}/>*
*</div>*
*</div>*
*{{# } }}*
*<div></div>*
*{{# if(v1.type.name=="date" ){ }}*
*<div class="layui-form-item">*
*<label class="layui-form-label">{{v1.name}}</label>*
*<div class="layui-input-block">*
*<input class="layui-input date" type="text" name="{{v1.writable ? 'fp_'+v1.id : v1.id}}" autocomplete="off" {{v1.required? 'lay-verify="required|date"': ''}} placeholder="yyyy-MM-dd" lay-verType="tips" value="{{v1.value}}" {{v1.writable ? '':'disabled'}}/>*
*</div>*
*</div>*
*{{# } }}*
*<div></div>*
*{{# if(v1.type.name=="enum" ){ }}*
*<div class="layui-form-item">*
*<label class="layui-form-label">{{v1.name}}</label>*
*<div class="layui-input-block">*
*<select name="{{v1.writable ? 'fp_'+v1.id : v1.id}}" {{v1.writable ? '':'disabled'}}>*
*<option value=""></option>*
*{{# $.each(d[v1.id+''],function(i2,v2){ }}*
*<option value="{{i2}}" {{i2==v1.value ? 'selected':''}}>{{v2}}</option>*
*{{# }) }}*
*</select>*
*</div>*
*</div>*
*{{# } }}*
*</div>*
*{{# }) }}*
*<div style="display:none;">*
*<!--此處隱藏但不能省略,為觸發(fā)事件準(zhǔn)備-->*
*<button lay-submit>提交</button><button type="reset">重置</button>*
*</div>*
*</form>*
*<style type="text/css">*
*.layui-layer-page .layui-layer-content {*
*overflow: visible;*
*}*
*</style>*
*`**;*
上述模板對常見的string晒奕、date、enum類型進(jìn)行了解析和渲染名斟,有更多類型可以自己根據(jù)需要添加脑慧。
此外,需要注意的是砰盐,要區(qū)別開表單中可編輯參數(shù)與不可編輯參數(shù)的屬性配置闷袒,如下所示(紅色標(biāo)注的部分),可編輯參數(shù)的name屬性值前面加上“fp_”(約定)岩梳。這樣配置的好處是后臺在接收到參數(shù)后可以區(qū)分開哪些參數(shù)是當(dāng)前節(jié)點需要保存的參數(shù)信息囊骤,以便進(jìn)行保存(見下面第二點表單的參數(shù)解析部分)。
*<input class="layui-input" type="text"* *name="{{v1.writable ? 'fp_'+v1.id : v1.id}}"** autocomplete="off" {{v1.required? 'lay-verify="required"': ''}} placeholder="{{v1.name}}" lay-blur lay-verType="tips" value="{{v1.value}}" {{v1.writable ? '':'disabled'}}/>*
使用方法是在需要進(jìn)行動態(tài)表單渲染的頁面JS中引入該模板:
//引入動態(tài)表單渲染模板
$.use(ctx+'/static/js/dynamic-form-common.js', **function****(){});
然后在獲得表單屬性數(shù)據(jù)后蒋腮,調(diào)用renderForm方法淘捡,傳入data數(shù)據(jù)和需要渲染的頁面dom節(jié)點元素即可:
renderForm(JSON.parse(form), $form.get(0));
2) 表單參數(shù)解析
前文已經(jīng)提到,在表單渲染時就已經(jīng)通過設(shè)置不同的name屬性值來區(qū)分開了可編輯參數(shù)和不可編輯參數(shù)池摧,因此在后臺進(jìn)行參數(shù)解析時就能很方便地對可編輯參數(shù)進(jìn)行提冉钩:
// 從request中讀取參數(shù)然后轉(zhuǎn)換
Map<String, String[]> parameterMap= request.getParameterMap();
Set<Entry<String, String[]>> entrySet= parameterMap.entrySet();
for****(Entry<String, String[]> entry: entrySet) {
String key= entry.getKey();
// fp_的意思是form <u>paremeter</u>
if**** (StringUtils.defaultString(key).startsWith("fp_")) { formProperties.put(key.replaceFirst("fp_", ""), entry.getValue()[0]);
*}
}
上述代碼就能將可編輯參數(shù)封裝在formProperties這個HashMap中。
3) 動態(tài)表單自定義類型
下面以上傳附件為例作彤,講述自定義表單類型的步驟和流程:
a. 定義表單擴展類型
***public******class****FileFormType* ***extends**** AbstractFormType {*
*/***
* **
* */*
***private******static******final******long******serialVersionUID**** = 1L;*
*@Override*
***public**** String getName() {*
*//* ***TODO**** Auto-generated method stub*
***return****"file"**;*
*}*
*@Override*
***public****Object convertFormValueToModelValue(String* *propertyValue**) {*
*//* ***TODO**** Auto-generated method stub*
***return****propertyValue**;*
*}*
*@Override*
***public****String convertModelValueToFormValue(Object* *modelValue**) {*
*//* ***TODO**** Auto-generated method stub*
***return**** (String)**modelValue**;*
*}*
*}*
b. 在activiti配置類中注冊表單擴展類型
*//注冊自定義表單類型*
*List<AbstractFormType>* *formTypes**=* ***new**** ArrayList<>();*
*formTypes**.add(****new**** FileFormType()); **processEngineConfiguration**.setCustomFormTypes(**formTypes**);*
c. 針對表單自定義類型新增動態(tài)渲染解析器
*{{# if(v1.type.name=="file" ){ }}*
*<div class="layui-form-item">*
*<label class="layui-form-label">{{v1.name}}</label>*
*<div>*
*<button type="button" class="layui-btn layui-btn-normal" id="test8">選擇文件</button>*
*</div>*
*</div>*
*{{# } }}*
d. 任務(wù)辦理時判斷是否包含附件類型膘魄,如果包含則應(yīng)將表單類型設(shè)置為“multipart/form-data”:
***if****($(**"#test8"**)){*
* $form.attr(**'enctype'**,**'multipart/form-data'**);*
*}*
e. 在任務(wù)辦理時新增附件的保存邏輯:
首先接收參數(shù)應(yīng)新增:
@RequestParam(value="file", required=false) MultipartFile file
在參數(shù)解析后乌逐,保存file:
**if**(**null**!= file) {
attachmentService.createAttachment(file, taskId, processInstanceId, formProperties.get("attachmentDescription"), request);
}
4) 常用API總結(jié)
引擎API是與Activiti打交道的最常用方式。 從ProcessEngine中创葡,你可以獲得很多囊括工作流/BPM方法的服務(wù)浙踢。 ProcessEngine和服務(wù)類都是線程安全的。 你可以在整個服務(wù)器中僅保持它們的一個引用就可以了灿渴。
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
① ProcessEngines:
ProcessEngines.getDefaultProcessEngine()會在第一次調(diào)用時 初始化并創(chuàng)建一個流程引擎洛波,以后再調(diào)用就會返回相同的流程引擎。 使用對應(yīng)的方法可以創(chuàng)建和關(guān)閉所有流程引擎:ProcessEngines.init()和 ProcessEngines.destroy()骚露。
ProcessEngines會掃描所有activiti.cfg.xml和 activiti-context.xml 文件蹬挤。 對于activiti.cfg.xml文件,流程引擎會使用Activiti的經(jīng)典方式構(gòu)建: ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(inputStream).buildProcessEngine(). 對于activiti-context.xml文件棘幸,流程引擎會使用Spring方法構(gòu)建:先創(chuàng)建一個Spring的環(huán)境焰扳, 然后通過環(huán)境獲得流程引擎。
所有服務(wù)都是無狀態(tài)的误续。這意味著可以在多節(jié)點集群環(huán)境下運行Activiti吨悍,每個節(jié)點都指向同一個數(shù)據(jù)庫, 不用擔(dān)心哪個機器實際執(zhí)行前端的調(diào)用蹋嵌。 無論在哪里執(zhí)行服務(wù)都沒有問題育瓜。
② RepositoryService******:
RepositoryService可能是使用Activiti引擎時最先接觸的服務(wù)。 它提供了管理和控制發(fā)布包和流程定義的操作栽烂。 這里不涉及太多細(xì)節(jié)爆雹,流程定義是BPMN 2.0流程的java實現(xiàn)。 它包含了一個流程每個環(huán)節(jié)的結(jié)構(gòu)和行為愕鼓。 發(fā)布包是Activiti引擎的打包單位。一個發(fā)布包可以包含多個BPMN 2.0 xml文件和其他資源慧起。 開發(fā)者可以自由選擇把任意資源包含到發(fā)布包中菇晃。 既可以把一個單獨的BPMN 2.0 xml文件放到發(fā)布包里,也可以把整個流程和相關(guān)資源都放在一起蚓挤。 (比如磺送,'hr-processes'實例可以包含hr流程相關(guān)的任何資源)。 可以通過RepositoryService來部署這種發(fā)布包灿意。 發(fā)布一個發(fā)布包估灿,意味著把它上傳到引擎中,所有流程都會在保存進(jìn)數(shù)據(jù)庫之前分析解析好缤剧。 從這點來說馅袁,系統(tǒng)知道這個發(fā)布包的存在,發(fā)布包中包含的流程就已經(jīng)可以啟動了荒辕。
除此之外汗销,服務(wù)可以
ü 查詢引擎中的發(fā)布包和流程定義犹褒。
ü 暫停或激活發(fā)布包弛针,對應(yīng)全部和特定流程定義叠骑。 暫停意味著它們不能再執(zhí)行任何操作了,激活是對應(yīng)的反向操作削茁。
ü 獲得多種資源宙枷,像是包含在發(fā)布包里的文件, 或引擎自動生成的流程圖茧跋。
ü 獲得流程定義的pojo版本慰丛, 可以用來通過java解析流程,而不必通過xml厌衔。
③ RuntimeService******:
正如RepositoryService負(fù)責(zé)靜態(tài)信息(比如璧帝,不會改變的數(shù)據(jù),至少是不怎么改變的)富寿, RuntimeService正好是完全相反的睬隶。它負(fù)責(zé)啟動一個流程定義的新實例。 如上所述页徐,流程定義定義了流程各個節(jié)點的結(jié)構(gòu)和行為苏潜。 流程實例就是這樣一個流程定義的實例。對每個流程定義來說变勇,同一時間會有很多實例在執(zhí)行恤左。 RuntimeService也可以用來獲取和保存流程變量。 這些數(shù)據(jù)是特定于某個流程實例的搀绣,并會被很多流程中的節(jié)點使用 (比如飞袋,一個排他網(wǎng)關(guān)常常使用流程變量來決定選擇哪條路徑繼續(xù)流程)。 Runtimeservice也能查詢流程實例和執(zhí)行链患。 執(zhí)行對應(yīng)BPMN 2.0中的'token'巧鸭。基本上執(zhí)行指向流程實例當(dāng)前在哪里麻捻。 最后纲仍,RuntimeService可以在流程實例等待外部觸發(fā)時使用,這時可以用來繼續(xù)流程實例贸毕。 流程實例可以有很多暫停狀態(tài)郑叠,而服務(wù)提供了多種方法來'觸發(fā)'實例, 接受外部觸發(fā)后明棍,流程實例就會繼續(xù)向下執(zhí)行乡革。
④ TaskService******:
任務(wù)是由系統(tǒng)中真實人員執(zhí)行的,它是Activiti這類BPMN引擎的核心功能之一。 所有與任務(wù)有關(guān)的功能都包含在TaskService中:
ü 查詢分配給用戶或組的任務(wù)
ü 創(chuàng)建獨立運行任務(wù)署拟。這些任務(wù)與流程實例無關(guān)婉宰。
ü 手工設(shè)置任務(wù)的執(zhí)行者,或者這些用戶通過何種方式與任務(wù)關(guān)聯(lián)推穷。
ü 認(rèn)領(lǐng)并完成一個任務(wù)心包。認(rèn)領(lǐng)意味著一個人期望成為任務(wù)的執(zhí)行者, 即這個用戶會完成這個任務(wù)馒铃。完成意味著“做這個任務(wù)要求的事情”蟹腾。 通常來說會有很多種處理形式。
⑤ IdentityService******:
IdentityService非常簡單区宇。它可以管理(創(chuàng)建娃殖,更新,刪除,查詢...)群組和用戶。 請注意铝耻, Activiti執(zhí)行時并沒有對用戶進(jìn)行檢查。 例如芬首,任務(wù)可以分配給任何人,但是引擎不會校驗系統(tǒng)中是否存在這個用戶逼裆。 這是Activiti引擎也可以使用外部服務(wù)郁稍,比如ldap,活動目錄胜宇,等等耀怜。
⑥ FormService******:
FormService是一個可選服務(wù)。即使不使用它桐愉,Activiti也可以完美運行财破, 不會損失任何功能。這個服務(wù)提供了啟動表單和任務(wù)表單兩個概念从诲。 啟動表單會在流程實例啟動之前展示給用戶狈究, 任務(wù)表單會在用戶完成任務(wù)時展示。Activiti支持在BPMN 2.0流程定義中設(shè)置這些表單盏求。 這個服務(wù)以一種簡單的方式將數(shù)據(jù)暴露出來。再次重申亿眠,它時可選的碎罚, 表單也不一定要嵌入到流程定義中。
⑦ HistoryService******:
HistoryService提供了Activiti引擎手機的所有歷史數(shù)據(jù)纳像。 在執(zhí)行流程時荆烈,引擎會保存很多數(shù)據(jù)(根據(jù)配置),比如流程實例啟動時間,任務(wù)的參與者憔购, 完成任務(wù)的時間宫峦,每個流程實例的執(zhí)行路徑,等等玫鸟。 這個服務(wù)主要通過查詢功能來獲得這些數(shù)據(jù)导绷。
⑧ ManagementService******:
ManagementService在使用Activiti的定制環(huán)境中基本上不會用到。 它可以查詢數(shù)據(jù)庫的表和表的元數(shù)據(jù)屎飘。另外妥曲,它提供了查詢和管理異步操作的功能。 Activiti的異步操作用途很多钦购,比如定時器檐盟,異步操作, 延遲暫停押桃、激活葵萎,等等。后續(xù)唱凯,會討論這些功能的更多細(xì)節(jié)羡忘。
以下總結(jié)下在開發(fā)工作流引擎動態(tài)表單相關(guān)功能時用到的一些API:
查詢流程列表:
ProcessDefinitionQuery dynamicQuery= repositoryService.createProcessDefinitionQuery()
.orderByDeploymentId().desc();
啟動流程:
identityService.setAuthenticatedUserId(user.getId());
processInstance= formService.submitStartFormData(processDefinitionId, formProperties);
讀取啟動節(jié)點表單數(shù)據(jù):
StartFormDataImpl<u>startFormData</u>* = (StartFormDataImpl)* formService.getStartFormData(processDefinitionId);
任務(wù)列表查詢:
TaskQuery taskQuery= taskService.createTaskQuery()
.taskCandidateOrAssigned(user== null****? "kafeitu":user.getId())
.active().orderByTaskCreateTime().desc();
簽收任務(wù):
taskService.claim(taskId, userId);
辦理任務(wù):
identityService.setAuthenticatedUserId(user.getId());
formService.submitTaskFormData(taskId, formProperties);
讀取Task表單數(shù)據(jù):
TaskFormDataImpltaskFormData* = (TaskFormDataImpl)formService.getTaskFormData(taskId*);
運行中流程列表查詢:
ProcessInstanceQuery dynamicQuery= runtimeService.createProcessInstanceQuery()
.orderByProcessInstanceId().desc();
已結(jié)束流程列表查詢:
HistoricProcessInstanceQuery dynamicQuery= historyService.createHistoricProcessInstanceQuery().finished().orderByProcessInstanceEndTime().desc();
掛起流程:
repositoryService.suspendProcessDefinitionById(processDefinitionId, isCascade, **new**** Date());
激活流程:
repositoryService.activateProcessDefinitionById(processDefinitionId, isCascade, **new**** Date());
刪除流程:
repositoryService.deleteDeployment(deploymentId, isCascade);
掛起流程實例:
runtimeService.suspendProcessInstanceById(processInstanceId);
激活流程實例:
runtimeService.activateProcessInstanceById(processInstanceId);
刪除流程實例:
runtimeService.deleteProcessInstance(processInstanceId, deleteReason);
關(guān)于Activiti的更多詳細(xì)介紹,請參考以下資料:
網(wǎng)址:http://www.mossle.com/docs/activiti/index.html#apiEngine
書籍:Activiti實戰(zhàn)