首先搭建流程設(shè)計(jì)器
官網(wǎng)下載activiti6.0,下載下來(lái)之后解壓后找到activiti-app.war文件放入本地tomcat中運(yùn)行,訪問(wèn)路徑為(localhost:8080/activiti-app),賬號(hào):admin 密碼:test
修改設(shè)計(jì)器數(shù)據(jù)庫(kù),設(shè)計(jì)器默認(rèn)的使用的是h2數(shù)據(jù)庫(kù),需要修改tomcat中項(xiàng)目\activiti-app\WEB-INF\classes\META-INF\activiti-app\activiti-app.properties,選擇需要的數(shù)據(jù)庫(kù)設(shè)置
#datasource.driver=org.h2.Driver
#datasource.url=jdbc:h2:mem:activiti;DB_CLOSE_DELAY=-1
datasource.driver=com.mysql.jdbc.Driver
datasource.url=jdbc:mysql://**********:3306/****?characterEncoding=UTF-8
datasource.username=****
datasource.password=****
搭建activiti項(xiàng)目(該項(xiàng)目使用springboot,所以和常規(guī)搭建可能稍有不同)
- 引入pom
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0.RC1</version>
</dependency>
- 關(guān)閉activiti自動(dòng)部署(因?yàn)槲覀兪褂昧鞒淘O(shè)計(jì)器部署,不使用具體文件訪問(wèn)方式),在application.properties中加入以下代碼
spring.activiti.check-process-definitions=false
-
設(shè)計(jì)流程
遇到問(wèn)題:
在設(shè)計(jì)流程中我們發(fā)現(xiàn)在定義審批人時(shí)遇到了一個(gè)無(wú)法忽略的問(wèn)題————無(wú)法找到指定審批人,意思是設(shè)置了一個(gè)審批人的參數(shù),沒(méi)辦法根據(jù)申請(qǐng)人獲取到相應(yīng)的審批人,或許你會(huì)覺(jué)得根據(jù)申請(qǐng)人的部門查找不就行了嗎,但是像財(cái)務(wù)、董事長(zhǎng)之類的在部門之上的的部門和人,就沒(méi)辦法找對(duì)并且獲取了。
解決:
暫時(shí)的辦法是將每個(gè)部門的所有流程全部分開(kāi),每個(gè)審批人使用角色定義,這就解決了獲取不到對(duì)應(yīng)審批人的問(wèn)題,但是緊接著又有一個(gè)問(wèn)題,申請(qǐng)人如何才能申請(qǐng)到自己部門的對(duì)應(yīng)流程呢,又是一個(gè)大問(wèn)題,問(wèn)題好像又回到了原點(diǎn),但是有何開(kāi)始的問(wèn)題有一些不同,從同一個(gè)流程獲得不同的用戶角色,變成了從一個(gè)部門獲取相應(yīng)部門流程,那就簡(jiǎn)單了,只需要?jiǎng)?chuàng)建一個(gè)表將他們對(duì)應(yīng)起來(lái)就可以了,但是這涉及到三個(gè)字段,一個(gè)是部門id,一個(gè)是流程id,還有一個(gè)類型id(就是這個(gè)流程是干什么的,借款、報(bào)銷。。。),三個(gè)信息組成一條對(duì)應(yīng)流程片,好像有點(diǎn)復(fù)雜,管理頁(yè)面也有點(diǎn)難做。
舉個(gè)例子:

從這張圖上能清楚的看出我們面臨的問(wèn)題,因?yàn)槊總€(gè)部門都有同樣的流程類型,但是又不同的流程具體實(shí)現(xiàn),如何才能從三個(gè)選擇減少成兩個(gè)甚至一個(gè)呢?
鑒于這一點(diǎn),我們只能在流程id上做手腳了,將流程id根據(jù)不同的類型使用不同的名字前綴,這樣就從三個(gè)字段變成了兩個(gè),例如:流通部門國(guó)內(nèi)借款流程,我們將id設(shè)為 gnjk_ltjk ,這樣我們就能根據(jù)id的前綴gnjk查到該流程為國(guó)內(nèi)借款類型。可能有人會(huì)問(wèn)為什么不直接將部門也放在流程id里面,這樣就不用創(chuàng)建表了,也想過(guò)這個(gè)問(wèn)題,而且也可以這樣做,但未免太死板了,完全不給以后融合流程機(jī)會(huì)了,畢竟現(xiàn)在一個(gè)部門就要n條流程還是有弊端的,就是重復(fù)流程過(guò)多的問(wèn)題,所以先暫時(shí)這樣吧
-
表設(shè)計(jì)
在正式走流程之前還有一個(gè)要做,就是業(yè)務(wù)表的設(shè)計(jì),盡管activiti自帶了大量的表,但是還是要設(shè)計(jì)關(guān)于自己項(xiàng)目的業(yè)務(wù)表才行。具體的業(yè)務(wù)表就不詳細(xì)說(shuō)了,無(wú)非是報(bào)銷、借款。。。之類的,但是有一個(gè)小技巧(前人經(jīng)驗(yàn)),因?yàn)榫唧w業(yè)務(wù)的不同,所有會(huì)有多個(gè)表,在審批時(shí)查找起來(lái)會(huì)相當(dāng)?shù)穆闊?,而且也不易于列表展示,所以提出一個(gè)共有的審核主表,該主表有流程id、業(yè)務(wù)id和具體業(yè)務(wù)的簡(jiǎn)要信息,這樣才查找審批列表時(shí)直接來(lái)一個(gè)表中查找就可以了,而在審批需要查看詳情時(shí),又可以根據(jù)不同的類型去對(duì)應(yīng)的表中查找審批,相當(dāng)方便。
CREATE TABLE `s_auditor_core` (
`id` varchar(50) NOT NULL COMMENT '使用nextval函數(shù)獲取',
`business_id` varchar(50) DEFAULT NULL COMMENT '業(yè)務(wù)id',
`process_id` varchar(64) DEFAULT NULL COMMENT '流程id',
`create_user` bigint(10) DEFAULT NULL COMMENT '用戶id',
`create_date` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
`sketch` varchar(255) DEFAULT NULL COMMENT '備注',
`apply_type` varchar(255) DEFAULT NULL COMMENT '申請(qǐng)類型(和權(quán)限表中的權(quán)限一致)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-
啟動(dòng)流程
國(guó)內(nèi)借款_流通部門因?yàn)橛玫氖莝pringboot,所以不需要手動(dòng)的創(chuàng)建xml文件,然后配置一大堆東西,只需要引包,然后再需要使用的地方聲明接口就好了,非常的方便。
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
然后在獲得對(duì)應(yīng)的流程id后,將業(yè)務(wù)詳細(xì)信息,流程,審批主表一次執(zhí)行保存,一次完整的流程啟動(dòng)就完成了。
*
* @param bGnjk 國(guó)內(nèi)借款業(yè)務(wù)參數(shù)
* @param htmlSign 模塊標(biāo)記,這個(gè)為借款、報(bào)銷。。。類型
* @return
* @throws Exception
*/
public CommonDomain<String> gnjkProcessStart(BGnjk bGnjk,String htmlSign)throws Exception {
CommonDomain<String> commonDomain = new CommonDomain<>();
//配置activiti需要參數(shù)
Map m = new HashMap();
m.put(ACTKeyTool.MONEY,bGnjk.getMoney());
List<SUser> process = sUserMapper.getProcessByUser(bGnjk.getCreateUser().intValue(),htmlSign);
if(process==null||process.size()!=1){
throw new Exception("流程啟動(dòng)獲得多條流程");
}
//啟動(dòng)activiti
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(process.get(0).getProcess_key(),m);
//保存業(yè)務(wù)數(shù)據(jù)
bGnjkMapper.insertSelective(bGnjk);
//保存審批主表
SAuditorCore sAuditorCore = new SAuditorCore();
sAuditorCore.setApplyType(htmlSign);
sAuditorCore.setBusinessId(bGnjk.getId());
sAuditorCore.setCreateDate(new Date());
sAuditorCore.setCreateUser(bGnjk.getCreateUser());
sAuditorCore.setProcessId(processInstance.getId());
sAuditorCore.setSketch(bGnjk.getDescribe());
SAuditorCoreMapper.insertSelective(sAuditorCore);
//返回結(jié)果封裝
commonDomain.setStatuscode(PublicMsg.ResultCode.SUCCESS.value());
commonDomain.setMsg(PublicMsg.SUCCESS_INFO);
return commonDomain;
}
當(dāng)完成這一步的時(shí)候,業(yè)務(wù)詳細(xì)表中有了業(yè)務(wù)的數(shù)據(jù),act_ru_task中有個(gè)流程信息,并指向第一個(gè)usertask節(jié)點(diǎn)--流通部門經(jīng)理,審批主表中有業(yè)務(wù)詳細(xì)表和流程信息表的唯一id,并將它們聯(lián)系起來(lái),現(xiàn)在開(kāi)始,流程就正式啟動(dòng)了
-
審批流程
不推薦使用activiti的taskService原生api,因?yàn)橐话忝總€(gè)項(xiàng)目都會(huì)有自己的用戶表,雖然activiti支持替換用戶表,但是配置起來(lái)貌似有些麻煩,所以根本沒(méi)有鳥(niǎo)他。下面是一些taskService常用的查詢方法源碼
query.taskCandidateGroup 源碼:
SELECT DISTINCT
RES.*
FROM
ACT_RU_TASK RES
INNER JOIN ACT_RU_IDENTITYLINK I
ON I.TASK_ID_ = RES.ID_
WHERE RES.ASSIGNEE_ IS NULL
AND I.TYPE_ = 'candidate'
AND (I.GROUP_ID_ IN (?))
query.taskCandidateOrAssigned 源碼:
select distinct RES.* from ACT_RU_TASK RES
left join ACT_RU_IDENTITYLINK I on I.TASK_ID_ = RES.ID_
WHERE (
RES.ASSIGNEE_ = ?
or (
RES.ASSIGNEE_ is null
and (
I.USER_ID_ = ?
or
I.GROUP_ID_ IN (
select g.GROUP_ID_ from ACT_ID_MEMBERSHIP g where g.USER_ID_ = ?
)
)
)
)
在源碼中可以看到基本上查的是ACT_RU_TASK和ACT_RU_IDENTITYLINK表,因?yàn)槲覀兏緵](méi)有用activit的用戶表,所以如果使用它的api會(huì)讓sql查詢變得奇怪起來(lái),因?yàn)槲覀冊(cè)谠O(shè)計(jì)流程的時(shí)候已經(jīng)指定了會(huì)使用角色來(lái)充當(dāng)審批人,那么這個(gè)角色只會(huì)出現(xiàn)在ACT_RU_IDENTITYLINK表的USER_ID_字段上,下面是我根據(jù)我們的實(shí)際業(yè)務(wù)編寫(xiě)的sql
SELECT
s.`name`apply_type, -- 類型名稱
a.`business_id`, -- 業(yè)務(wù)詳細(xì)表id
a.`create_date`, -- 申請(qǐng)時(shí)間
u.`name` user, -- 申請(qǐng)人
a.`id`, -- 審批主表d
a.`process_id`, -- 流程id
a.`sketch`, -- 簡(jiǎn)介
i.`TASK_ID_` task_id -- 任務(wù)id
FROM
`act_ru_task` t
JOIN `act_ru_identitylink` i
ON t.`ID_` = i.`TASK_ID_`
JOIN `s_auditor_core` a ON a.process_id=t.PROC_INST_ID_
JOIN `s_permission` s ON a.`apply_type`=s.`permission`
JOIN `s_user` u ON u.`id`=a.`create_user`
WHERE FIND_IN_SET(i.`USER_ID_`, #{roles})
order by a.`create_date` desc
其中s_auditor_core為審批主表,s_permission申請(qǐng)類型(客串,其實(shí)是權(quán)限表),s_user用戶表,act_ru_task任務(wù)表,act_ru_identitylink任務(wù)關(guān)聯(lián)人員表,而傳進(jìn)來(lái)的#{roles}也是一個(gè)登錄人的所有角色集合
接下來(lái)就是審批的具體實(shí)施了,因?yàn)橛X(jué)得不需要再有一個(gè)審批意見(jiàn)表,所以使用act_hi_comment來(lái)代替:
設(shè)置/獲得 審批人和意見(jiàn)
設(shè)置:
審批人一般為登錄人
Authentication.setAuthenticatedUserId("審批人");
taskService.addComment(task.getId(), task.getProcessInstanceId(), "批注");
獲得:
List<HistoricActivityInstance> hais = historyService
.createHistoricActivityInstanceQuery()
.processInstanceId(id)
.activityType("userTask").list();
for (HistoricActivityInstance hai : hais) {
String historytaskId = hai.getTaskId();
List<Comment> comments = taskService.getTaskComments(historytaskId);
for (Comment comment : comments) {
System.out.println(comment.getFullMessage()+"ren"+comment.getUserId());
}
在同意時(shí)使用正常的審批就ok了:
taskService.complete(task.getId());
但是在拒絕時(shí)需要特殊操作:
刪除正在運(yùn)行的流程,主要用于拒絕操作
runtimeService.deleteProcessInstance(流程實(shí)力id,刪除原因(可為空))
為什么要這樣做:
因?yàn)閍ctivit并不支持突然的拒絕操作,如果想要實(shí)現(xiàn)這樣的效果,就要在每條審批中設(shè)置一條拒絕的流程線并指向結(jié)束節(jié)點(diǎn),而且還必須要用到排他網(wǎng)管,不然流程就不會(huì)結(jié)束,所以,鑒于這個(gè)情況的出現(xiàn),我們采用直接刪除流程,但流程的歷史信息不回被刪除,而且流程也會(huì)被置為結(jié)束,只需要在審批前填寫(xiě)流程審批意見(jiàn)就行了
遺留問(wèn)題
- 將各部門流程融合成一個(gè),現(xiàn)在重復(fù)太多不便管理
- 獲取下一個(gè)審批人,因?yàn)閾Q成activiti6.0后,pvm包被整體移除,所以原來(lái)的方式好像行不通了
- BpmnModel 操作
