什么是工作流
首先還是要把專業(yè)術(shù)語給搬出來:工作流(Workflow),就是“業(yè)務(wù)過程的部分或整體在計(jì)算機(jī)應(yīng)用環(huán)境下的自動(dòng)化”,它主要解決的是“使在多個(gè)參與者之間按照某種預(yù)定義的規(guī)則傳遞文檔秦忿、信息或任務(wù)的過程自動(dòng)進(jìn)行凝赛,從而實(shí)現(xiàn)某個(gè)預(yù)期的業(yè)務(wù)目標(biāo)玩般,或者促使此目標(biāo)的實(shí)現(xiàn)”琐脏。說的通俗一點(diǎn)就是通過計(jì)算機(jī)應(yīng)用幫助我們“走流程”坚嗜,各種證件的辦理携狭、資格的審批在生活中無處不在继蜡,工作流就在我們身邊。
工作流這個(gè)名詞概念在計(jì)算機(jī)領(lǐng)域中逛腿,起源于生產(chǎn)組織和辦公自動(dòng)化領(lǐng)域稀并,是針對(duì)日常工作中具有固定程序活動(dòng)而提出的一個(gè)概念,目的是通過將工作分解成定義良好的任務(wù)或角色单默,按照一定的規(guī)則和過程來執(zhí)行這些任務(wù)并對(duì)其進(jìn)行監(jiān)控碘举,達(dá)到提高工作效率、更好的控制過程搁廓、增強(qiáng)對(duì)客戶的服務(wù)引颈、有效管理業(yè)務(wù)流程等目的。
要走“走流程”的人是痛苦的境蜕,慢蝙场、麻煩相信是很多人對(duì)走流程的印象,而處理“走流程”的人也并不開心粱年,復(fù)雜的過程令人心力憔悴售滤。而在這個(gè)計(jì)算機(jī)互聯(lián)網(wǎng)時(shí)代,流程也都逐漸開始“上網(wǎng)”台诗,構(gòu)成所謂的信息化的一個(gè)重要組成部分完箩。這就催生了后臺(tái)工作流框架,比較著名的有jBPM和Activiti拉队,本文的主角就是Activiti嗜憔。
Activiti簡(jiǎn)介
本文以Activiti 5.22作為示例和分析的基礎(chǔ)。還是先上官話:Activiti5是由Alfresco軟件在2010年5月17日發(fā)布的業(yè)務(wù)流程管理(BPM)框架氏仗,它是覆蓋了業(yè)務(wù)流程管理吉捶、工作流夺鲜、服務(wù)協(xié)作等領(lǐng)域的一個(gè)開源的、靈活的呐舔、易擴(kuò)展的可執(zhí)行流程語言框架币励。
Activiti實(shí)現(xiàn)了BPMN 2.0規(guī)范,可以發(fā)布設(shè)計(jì)好的流程定義珊拼,并通過api進(jìn)行流程調(diào)度食呻。Activiti流程引擎重點(diǎn)關(guān)注在系統(tǒng)開發(fā)的易用性和輕量性上。每一項(xiàng) BPM 業(yè)務(wù)功能 Activiti 流程引擎都以服務(wù)的形式提供給開發(fā)人員澎现。通過使用這些服務(wù)牡整,開發(fā)人員能夠構(gòu)建出功能豐富、輕便且高效的 BPM 應(yīng)用程序塔淤。
本文目標(biāo)
記錄上手過程流礁,快速理解、入門Actitviti妹蔽,并不深挖以及使用騷操作椎眯。
上手過程
準(zhǔn)備工作
- 首先Activiti的數(shù)據(jù)基于數(shù)據(jù)庫,所以首先需要一個(gè)數(shù)據(jù)庫胳岂,基本主流的數(shù)據(jù)庫Activiti都支持编整,選用mysql就ok。
- 去官網(wǎng)下載Activiti乳丰,里面不僅包含了需要的jar包掌测,還有文檔、sql文件产园、war包等赏半。
- 配置IDE,雖然說eclipse插件是Activiti的一個(gè)優(yōu)點(diǎn)淆两,但是目前IDEA的流行程度更高一些断箫,還是選用IDEA作為開發(fā)測(cè)試工具,IDEA也有Activiti插件名為actiBPM秋冰,但是個(gè)人覺得不太好用仲义,截止目前這個(gè)插件最后更新時(shí)間是2014年,看起來好像不在維護(hù)了剑勾,比較尷尬埃撵。
- 框架集成,Activiti絕大多數(shù)情況還是作為一個(gè)組件集成到后臺(tái)框架中虽另,為了方便暂刘,減少其他因素干擾,選用SpringBoot捂刺。不過很可惜在練習(xí)的時(shí)候想當(dāng)然地選了SpringBoot2.0谣拣,集成起來發(fā)生了問題浪費(fèi)了很多時(shí)間募寨。
- 簡(jiǎn)單了解下BPMN,業(yè)務(wù)流程建模與標(biāo)注(Business Process Model and Notation森缠,BPMN)拔鹰,定義了流程圖的內(nèi)容和表達(dá),最新的是BPMN 2.0規(guī)范贵涵。流程內(nèi)容一般采用XML格式來承載表達(dá)列肢,通過圖形化工具來快速、直觀地設(shè)計(jì)流程宾茂。
流程圖圖解
很多教程喜歡上來就說API瓷马、說數(shù)據(jù)庫表,但我認(rèn)為還是該從最直觀的流程圖來作為切入點(diǎn)跨晴,一步一步引出各個(gè)概念欧聘。下圖是一個(gè)最簡(jiǎn)單的流程圖,流程的目的是員工加入公司的審批坟奥。
對(duì)應(yīng)的XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
xmlns:tns="http://www.activiti.org/test"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
expressionLanguage="http://www.w3.org/1999/XPath"
id="m1534942345607" name=""
targetNamespace="http://www.activiti.org/test"
typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="joinProcess" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_2" name="Join process">
<extensionElements>
<activiti:formProperty id="personId" name="person Id" required="true" type="long"/>
<activiti:formProperty id="compId" name="company Id" required="true" type="long"/>
</extensionElements>
</startEvent>
<userTask activiti:exclusive="true" id="_3" name="主管審批"
activiti:candidateUsers="${joinService.findUsers(execution)}" isForCompensation="true">
<extensionElements>
<activiti:formProperty id="joinApproved" name="Join Approved" type="enum">
<activiti:value id="true" name="Approve" />
<activiti:value id="false" name="Reject" />
</activiti:formProperty>
</extensionElements>
</userTask>
<endEvent id="_4" name="EndEvent"/>
<serviceTask activiti:exclusive="true" id="_5" name="審批處理" activiti:expression="${joinService.joinGroup(execution)}" />
<sequenceFlow id="_6" sourceRef="_2" targetRef="_3"/>
<sequenceFlow id="_7" sourceRef="_3" targetRef="_5"/>
<sequenceFlow id="_8" sourceRef="_5" targetRef="_4"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;
width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="myProcess_1">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="70.0" y="181.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="55.0" width="85.0" x="160.0" y="170.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="32.0" width="32.0" x="455.0" y="181.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="55.0" width="85.0" x="310.0" y="170.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_6" id="BPMNEdge__6" sourceElement="_2" targetElement="_3">
<omgdi:waypoint x="102.0" y="197.5"/>
<omgdi:waypoint x="160.0" y="197.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_3" targetElement="_5">
<omgdi:waypoint x="245.0" y="197.5"/>
<omgdi:waypoint x="310.0" y="197.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_5" targetElement="_4">
<omgdi:waypoint x="395.0" y="197.5"/>
<omgdi:waypoint x="455.0" y="197.5"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
圖中內(nèi)容相當(dāng)簡(jiǎn)單,兩個(gè)開始和結(jié)束的圓圈拇厢,兩個(gè)流程的步驟爱谁。
先用直白的文字描述這個(gè)流程:人事提交員工加入公司的審批 -> 主管審批 -> 結(jié)束,通過后要生成一條入職數(shù)據(jù)孝偎。
而流程圖中表現(xiàn)的是:開始流程 -> 主管審批 -> 審批處理 -> 結(jié)束访敌。
可見還是有一定的差別,根據(jù)我個(gè)人的感覺衣盾,BPMN流程圖的表達(dá)和直白的思維是有一定的區(qū)別的寺旺,這也是我一開始上手時(shí)感到困惑的點(diǎn)。其一是Activiti流程開始后并不會(huì)在開始節(jié)點(diǎn)停留势决,所以提交審批這個(gè)流程在圖中沒有直接的體現(xiàn)阻塑,其二是對(duì)于一個(gè)審批任務(wù)來說通俗思維會(huì)認(rèn)為審批和處理屬于同一步操作,而對(duì)于Activiti每一步操作是一個(gè)任務(wù)果复,任務(wù)區(qū)分明確陈莽,其中尤以人為的操作(UserTask)和程序處理(ServiceTask)這兩個(gè)為主,因此主管審批是一步虽抄,而審批處理生成數(shù)據(jù)是另外一步走搁。
圖中bpmndi:BPMNDiagram
節(jié)點(diǎn)下的內(nèi)容代表的是流程圖各個(gè)節(jié)點(diǎn)的位置信息,以圖的左上角為原點(diǎn)迈窟,用xy坐標(biāo)值表示流程節(jié)點(diǎn)在圖中的位置私植。
主要概念
任務(wù)
很自然地引申出任務(wù)這個(gè)點(diǎn),更準(zhǔn)確地說應(yīng)該是執(zhí)行計(jì)劃(excution)车酣。Task用來描述業(yè)務(wù)過程中所有可能發(fā)生工時(shí)的行為曲稼,它主要包括UserTask索绪、ServiceTask、ScriptTask躯肌、ReceiveTask者春、MailTask等等。詳細(xì)說明一下其中最基礎(chǔ)的兩種:
- UserTask:人工任務(wù)清女,用來描述認(rèn)為參與的操作钱烟,流程執(zhí)行到此節(jié)點(diǎn)時(shí)須人工響應(yīng)后才能繼續(xù)向下流轉(zhuǎn)。人工任務(wù)最終要的屬性就是辦理任務(wù)的執(zhí)行者嫡丙,有assignee(單一執(zhí)行人)拴袭、candidateUsers(多個(gè)候選執(zhí)行人)、candidateGroups(候選執(zhí)行組)這幾種屬性可選曙博。
- ServiceTask:Java服務(wù)任務(wù)拥刻,允許指定一個(gè)實(shí)現(xiàn)了指定接口的java類,或者執(zhí)行一個(gè)表達(dá)式父泳。與User Task不同般哼,流程引擎流經(jīng)此節(jié)點(diǎn)會(huì)自動(dòng)調(diào)用Java類中定義的方法,方法執(zhí)行完畢自動(dòng)向下一流程節(jié)點(diǎn)流轉(zhuǎn)惠窄。
順序流
有了任務(wù)節(jié)點(diǎn)就自然需要把他們連接起來蒸眠,圖中的箭頭連線稱為順序流(sequenceFlow),用于描述節(jié)點(diǎn)的流轉(zhuǎn)方向和順序杆融。主要屬性有sourceRef(起始節(jié)點(diǎn)Id)楞卡、targetRef(指向節(jié)點(diǎn)Id)、conditionExpression(條件限制的表達(dá))脾歇。
網(wǎng)關(guān)
網(wǎng)關(guān)(gateways)在示例的圖中沒有表現(xiàn)蒋腮,但是它是流程定義重要的要素之一。真實(shí)的流程并不會(huì)如此簡(jiǎn)單藕各,很多時(shí)候流程需要分叉池摧、并行等等,而網(wǎng)關(guān)就是用來決定流程流轉(zhuǎn)指向的激况,可能會(huì)被用作條件分支或聚合险绘,也可以被用作并行執(zhí)行或基于事件的排它性條件判斷。常用的網(wǎng)關(guān)有互斥關(guān)口(exclusiveGateway誉碴,流程經(jīng)過時(shí)只能走其中一個(gè)順序流)和并行關(guān)口(parallelGateway宦棺,經(jīng)過關(guān)口后會(huì)同時(shí)經(jīng)過所有順序流,所有流程完成后會(huì)一起通過指向的并行關(guān)口)黔帕。
事件
說完上述的還有兩個(gè)圓圈沒有講到代咸,在BPMN規(guī)范下圓圈一般表示事件。啟動(dòng)成黄、結(jié)束呐芥、邊界條件以及每個(gè)活動(dòng)的創(chuàng)建逻杖、開始、流轉(zhuǎn)等都是流程事件思瘟,利用事件機(jī)制荸百,可以通過事件控制器為系統(tǒng)增加輔助功能。開始和結(jié)束是每個(gè)流程都必須包含的事件節(jié)點(diǎn)滨攻。
任務(wù)和順序流以及未介紹的子流程可以同稱為活動(dòng)(Activities)够话,加上網(wǎng)關(guān)和事件就構(gòu)成了BPMN2.0對(duì)流程執(zhí)行語義定義的三類基本要素。
流程變量
流程變量在整個(gè)工作流中用于傳遞業(yè)務(wù)變量光绕,流程變量的作用域范圍是只對(duì)應(yīng)一個(gè)流程實(shí)例女嘲,在開啟流程和完成任務(wù)時(shí)都可以設(shè)置變量,直接設(shè)置變量時(shí)诞帐,分setVariable和setVariableLocal欣尼,local表示綁定當(dāng)前任務(wù),流程繼續(xù)執(zhí)行時(shí)下個(gè)任務(wù)獲取不到這個(gè)流程變量停蕉,涉及的數(shù)據(jù)庫表為act_ru_variable愕鼓、act_hi_varinst(變量歷史記錄)。
注意慧起,流程變量需要實(shí)現(xiàn)Serializable接口且要求過程中屬性不能變化(反序列化)菇晃,需要添加序列化ID,如果是JavaBean作為流程變量完慧,序列化的bean會(huì)存放到act_ge_bytearray這張表中谋旦。
流程可以定義需要哪些變量剩失,可以查看xml中activiti:formProperty相關(guān)的信息屈尼。
核心API
了解了流程圖之后,介紹Activiti的核心API拴孤,快速形成對(duì)于Activiti的基本概念脾歧。Activiti最核心的類是流程引擎(ProcessEngine),其他所有類都通過引擎來獲取演熟。引擎可以獲取各種各種各樣的服務(wù)(Service)對(duì)應(yīng)處理各種需求鞭执,所有流程的具體操作都對(duì)應(yīng)執(zhí)行某一項(xiàng)服務(wù)提供的方法。
服務(wù)
- RepositoryService:倉庫服務(wù)芒粹。業(yè)務(wù)流程的定義都需要使用一些定義文件兄纺,定義了流程需要將其部署,部署之后可能需要查詢部署的信息和相關(guān)的文件化漆,這些需求就由RepositoryService提供估脆。
- RuntimeService:流程執(zhí)行服務(wù)。在Activiti中座云,每當(dāng)一個(gè)流程定義被啟動(dòng)一次之后疙赠,都會(huì)生成一個(gè)相應(yīng)的流程對(duì)象實(shí)例付材。RuntimeService提供了啟動(dòng)流程、查詢流程實(shí)例圃阳、設(shè)置獲取流程實(shí)例變量等功能厌衔。
- TaskService:任務(wù)服務(wù)。它提供了運(yùn)行時(shí)任務(wù)查詢捍岳、領(lǐng)取富寿、完成、刪除以及變量設(shè)置等功能祟同。
- HistoryService:歷史服務(wù)作喘。流程、任務(wù)執(zhí)行完成之后可能需要追溯晕城、查詢歷史泞坦,這就需要用到HistoryService。
- IdentityService:身份服務(wù)砖顷。Activiti中內(nèi)置了用戶以及組管理的功能贰锁,必須使用這些用戶和組的信息才能獲取到相應(yīng)的Task。IdentityService提供了對(duì)Activiti 系統(tǒng)中的用戶和組的管理功能滤蝠。當(dāng)然用戶和組也可以自定義提供豌熄,但是這些內(nèi)容不在本文討論范圍之內(nèi)
- ManagementService: 管理服務(wù)。它提供了對(duì)Activiti流程引擎的管理和維護(hù)功能物咳,這些功能不在工作流驅(qū)動(dòng)的應(yīng)用程序中使用锣险,主要用于Activiti系統(tǒng)的日常維護(hù)。
對(duì)象
除了服務(wù)览闰,還有以接口形式表達(dá)的幾個(gè)關(guān)鍵對(duì)象:
- Deployment:流程部署對(duì)象芯肤,是對(duì)一個(gè)部署的抽象化表達(dá)。
- ProcessDefinition:流程定義压鉴,部署成功后自動(dòng)創(chuàng)建崖咨。
- ProcessInstance:代表流程實(shí)例,啟動(dòng)流程時(shí)創(chuàng)建油吭。
- Execution:執(zhí)行計(jì)劃击蹲,流程實(shí)例和流程執(zhí)行中的所有節(jié)點(diǎn)都是Execution。當(dāng)流程單線執(zhí)行時(shí)ProcessInstance與Execution內(nèi)容保持一致婉宰,并發(fā)流程中歌豺,總線路為ProcessInstance,分線路中每個(gè)活動(dòng)由Execution表達(dá)心包。
- Task:任務(wù)类咧,在Activiti中的Task僅指有角色參與的任務(wù),包括上述的UserTask以及MannualTask。
入門示例
說完了概念性的東西轮听,接下來就需要完整地走一遍流程骗露。示例的同時(shí)介紹相關(guān)的數(shù)據(jù)表,Activiti所有的內(nèi)容信息都以數(shù)據(jù)庫作為基礎(chǔ)血巍,各種服務(wù)提供的查詢本質(zhì)上都是對(duì)數(shù)據(jù)庫的查詢萧锉。
還是以上述的簡(jiǎn)單流程作為示例來進(jìn)行演示。
工程搭建
打開IDEA創(chuàng)建一個(gè)SpringBoot項(xiàng)目(可以選2.0述寡,有坑但是會(huì)加以說明解決)柿隙,勾選組件:Web、JPA(想用其他的ORM也可以)鲫凶、MySQL即可禀崖,項(xiàng)目構(gòu)建采用Maven。工程創(chuàng)建成功后螟炫,在POM文件中增加Activiti的依賴波附。
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>5.22.0</version>
</dependency>
這里有個(gè)整合的坑點(diǎn),Activiti目前的版本落后于SpringBoot和SpringSecurity昼钻,如果直接就這么運(yùn)行工程會(huì)產(chǎn)生缺少SpringSecurity依賴的錯(cuò)誤掸屡,但是可以在POM中可以看到SpringSecurity的依賴的Optional屬性為True,而且安全相關(guān)的自動(dòng)配置添加了@AutoConfigureBefore
然评,確保在SpringSecurity自動(dòng)配置完成后執(zhí)行仅财。嘗試加入SpringSecurity的依賴依然報(bào)錯(cuò),可以發(fā)現(xiàn)Activiti里依賴的一個(gè)類在SpringSecurity5.0版本之后已經(jīng)移動(dòng)了位置碗淌。因此必須強(qiáng)制排除產(chǎn)生錯(cuò)誤的Activiti的自動(dòng)配置類盏求,修改主類Application如下:
@EnableAutoConfiguration(exclude = {
org.activiti.spring.boot.SecurityAutoConfiguration.class
})
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
數(shù)據(jù)準(zhǔn)備與流程部署
創(chuàng)建Person和Comp兩個(gè)實(shí)體類以及相關(guān)的JpaRepository,用于表示員工和公司以及表現(xiàn)生成數(shù)據(jù)的操作亿眠。然后保存幾個(gè)實(shí)例以供使用碎罚。
@Entity
public class Person {
@Id
@GeneratedValue
private Long personId;
private String personName;
@ManyToOne
private Comp comp;
public Person(String personName) {
this.personName = personName;
}
// Getter & Setter
// ...
}
@Entity
public class Comp {
@Id
@GeneratedValue
private Long compId;
private String compName;
@OneToMany(mappedBy = "comp")
private List<Person> people;
public Comp(String compName) {
this.compName = compName;
}
// Getter & Setter
// ...
}
在測(cè)試類中寫入:
@Autowired
private PersonRepository personRepository;
@Autowired
private CompRepository compRepository;
@Before
public void contextLoads() {
// 準(zhǔn)備數(shù)據(jù)
if (personRepository.findAll().size() == 0) {
personRepository.save(new Person("wtr"));
personRepository.save(new Person("wyf"));
personRepository.save(new Person("admin"));
}
if (compRepository.findAll().size() == 0) {
Comp group = new Comp("great company");
compRepository.save(group);
Person admin = personRepository.findByPersonName("admin");
Person wtr = personRepository.findByPersonName("wtr");
admin.setComp(group); wtr.setComp(group);
personRepository.save(admin); personRepository.save(wtr);
}
}
接下來將上面的流程XML代碼拷貝進(jìn)入一個(gè)xml文件,在resource目錄下新建一個(gè)process文件夾缕探,將xml文件放入這個(gè)文件夾魂莫,復(fù)制文件更改后綴為bpmn还蹲,這么做是因?yàn)镮DEA的插件編輯器無法編輯某些屬性爹耗,而且重新打開bpmn文件會(huì)發(fā)現(xiàn)配置的屬性看不到,所以會(huì)把xml放在同一個(gè)目錄下進(jìn)行參考谜喊。設(shè)計(jì)流程時(shí)可以先用插件畫圖潭兽,然后更改為xml后綴進(jìn)行屬性編輯,編輯完成后復(fù)制一份并更改為bpmn后綴斗遏。
由于和springboot集成山卦,process目錄下的流程會(huì)自動(dòng)部署,省去了部署這件事诵次,不過還是把普通的部署代碼貼出來账蓉。
@Test
public void showDeploy(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
Deployment deploy = processEngine.getRepositoryService()
.createDeployment()
.name("testDeploy")
.addClasspathResource("processes/example.bpmn")
.deploy();
System.out.println(deploy.getId() + " " + deploy.getName());
}
與部署相關(guān)的數(shù)據(jù)表以ACT_RE開頭枚碗,在執(zhí)行以上步驟或者自動(dòng)部署后,可以在相關(guān)表中查看到新生成的部署信息以及流程定義的信息铸本。另外流程文件(包括bpmn肮雨、png、bpmn20.xml等)會(huì)直接以二進(jìn)制形式存入act_ge_bytearray表箱玷,ACT_GE開頭的表存放通用數(shù)據(jù)怨规。
同時(shí),還需要準(zhǔn)備身份信息锡足,也就是添加執(zhí)行人以及執(zhí)行組波丰,但是這一步并不是必要的,原因同樣可以從數(shù)據(jù)庫中窺得一二舶得。上文提到了Activiti提供了一個(gè)默認(rèn)的身份服務(wù)掰烟,相關(guān)的數(shù)據(jù)表以ACT_ID開頭,很明顯act_id_user存放的就是用戶的信息沐批,不過任務(wù)相關(guān)的表并沒有和用戶表有外鍵約束媚赖,所以任務(wù)執(zhí)行的時(shí)候即使用戶表中沒有相關(guān)信息,相關(guān)的任務(wù)表依然可以存儲(chǔ)用戶的ID標(biāo)識(shí)珠插,也可以以ID來查詢惧磺。同樣還是放出一段添加用戶和組的代碼,以供了解參考捻撑,示例工程為了方便就不添加用戶和組了磨隘。
@Test
public void addUserAndGroup(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
identityService.saveGroup(new GroupEntity("主管"));//建立組
identityService.saveGroup(new GroupEntity("經(jīng)理"));
identityService.saveUser(new UserEntity("小明"));// 建立用戶
identityService.saveUser(new UserEntity("小紅"));
UserEntity littileMount = new UserEntity("小山");
littileMount.setEmail("xxx@fake.com"); // 設(shè)置用戶相關(guān)詳細(xì)信息
identityService.saveUser(littileMount);
identityService.createMembership("小明", "主管");//建立組和用戶關(guān)系
identityService.createMembership("小紅", "主管");
identityService.createMembership("小山", "經(jīng)理");
}
開啟流程實(shí)例
以下相關(guān)操作為了方便都寫在測(cè)試方法中,更標(biāo)準(zhǔn)地做法是寫幾個(gè)Controller和Service來進(jìn)行請(qǐng)求顾患,不過實(shí)際生效的代碼是一致的番捂。不要立馬運(yùn)行這些代碼,因?yàn)楹竺孢€需要寫一些東西江解,看完一遍以后再動(dòng)手練習(xí)设预。
準(zhǔn)備妥當(dāng)之后,首先就是開啟一個(gè)流程犁河。每開啟一個(gè)定義好的流程就會(huì)產(chǎn)生一個(gè)流程實(shí)例鳖枕,相關(guān)的數(shù)據(jù)表為act_ru_execution。
@Autowired
private RuntimeService runtimeService;
@Test
public void startProcess() {
runtimeService.startProcessInstanceByKey("joinProcess");
}
由于和Spring整合桨螺,Activiti的引擎和服務(wù)都在Spring中以供裝配宾符。開啟流程只有一句話,很簡(jiǎn)單灭翔,給出的這個(gè)參數(shù)值可以在定義流程的xml中找到魏烫,找到了自然就理解了。不過仔細(xì)想想,其實(shí)還漏了一些東西哄褒,開始流程的時(shí)候需要提供這個(gè)審批過程需要的信息稀蟋,就好比去辦手續(xù)總是要你提交身份證復(fù)印件一樣。對(duì)于這個(gè)審批員工加入公司的示例流程來說呐赡,最基本的信息就是兩個(gè):?jiǎn)T工id和公司id糊治,需要在流程開啟時(shí)傳入,這就是流程變量的概念罚舱。所以完整的開啟流程代碼應(yīng)該如下所示:
@Autowired
private RuntimeService runtimeService;
@Test
public void startProcess() {
Person wyf = personRepository.findByPersonName("wyf");
Comp comp = compRepository.findAll().get(0);
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("personId", wyf.getId()); // 員工id
variables.put("compId", comp.getId()); // 公司id
runtimeService.startProcessInstanceByKey("joinProcess",variables);
}
完成人工任務(wù)
如之前所說井辜,流程開啟后不會(huì)在開始節(jié)點(diǎn)停留,那么很顯然目前流程位于主管審批這個(gè)節(jié)點(diǎn)管闷。人工任務(wù)需要響應(yīng)后流程才會(huì)繼續(xù)粥脚,這一步就是抽象化的主管審批動(dòng)作。
查詢?nèi)蝿?wù)
首先包个,Activiti需要知道由誰來執(zhí)行某個(gè)任務(wù)刷允,查看流程xml文件可以發(fā)現(xiàn)userTask節(jié)點(diǎn)有一個(gè)屬性:activiti:candidateUsers,這就是在指定執(zhí)行任務(wù)的候選人碧囊。它的值是一個(gè)表達(dá)式(也可以直接填入候選人的id树灶,指定執(zhí)行者有多種方法),可以理解為執(zhí)行某個(gè)java方法來獲取候選人的id糯而。創(chuàng)建一個(gè)類JoinService天通,并且將其交由Spring管理。
@Service
public class JoinService {
//獲取符合條件的審批人熄驼,演示方便這里寫死像寒,使用時(shí)應(yīng)用實(shí)際代碼
public List<String> findUsers(DelegateExecution execution) {
return Arrays.asList("admin", "wtr");
}
}
這就對(duì)應(yīng)了表達(dá)式:"${joinService.findUsers(execution)}"
。這里指定了admin和wtr可以執(zhí)行這個(gè)審批瓜贾。但是實(shí)際應(yīng)用過程中任務(wù)不會(huì)直接擺在審批人面前诺祸,我們需要查詢?nèi)蝿?wù)。查詢?nèi)蝿?wù)的方式有許多種祭芦,這里就介紹最基本的按照用戶id查詢筷笨。
@Autowired
private ActivitiService activitiService;
@Test
public void query() {
List<Task> tasks = activitiService.getTasks("admin");
System.out.println("----------------- task size : "+tasks.size());
for (Task task : tasks){
System.out.println("Id : "+task.getId()+" Name : "+task.getName());
}
return ;
}
執(zhí)行完成
查詢到這個(gè)任務(wù)后就可以將其完成。代碼也很少龟劲,不過別忘了輸入代表審批結(jié)果的參數(shù)胃夏。執(zhí)行完這個(gè)方法后,人工任務(wù)就完成了咸灿,流程將進(jìn)入下一個(gè)節(jié)點(diǎn)构订。
@Autowired
private TaskService taskService;
@Test
public void completeTasks() {
Map<String, Object> taskVariables = new HashMap<String, Object>();
taskVariables.put("joinApproved", true); // 這里直接給予true表示通過
String taskId = "1"; // 更改成上個(gè)方法查詢出的任務(wù)Id
taskService.complete(taskId, taskVariables);
}
服務(wù)任務(wù)的執(zhí)行
可以在圖中看到接下來的節(jié)點(diǎn)是一個(gè)服務(wù)任務(wù)侮叮。如上文所述避矢,與人工任務(wù)不同,服務(wù)任務(wù)會(huì)在執(zhí)行完設(shè)置的方法或者表達(dá)式之后自動(dòng)結(jié)束進(jìn)入下一步。那么很顯然最重要的就是如何設(shè)置執(zhí)行的方法审胸,查看流程定義xml文件中的serviceTask節(jié)點(diǎn)亥宿,很明顯就是它的activiti:expression屬性指定了執(zhí)行的方法。根據(jù)這個(gè)屬性值砂沛,需要在之前創(chuàng)建的JoinService類中添加一個(gè)名為joinGroup的方法烫扼,完整的JoinService類如下。
@Service
public class JoinService {
@Autowired
PersonRepository personRepository;
@Autowired
private CompRepository compRepository;
//加入公司操作碍庵,可從DelegateExecution獲取流程中的變量
public void joinGroup(DelegateExecution execution) {
Boolean bool = execution.getVariable("joinApproved",Boolean.class);
if (bool) {
Long personId = execution.getVariable("personId", Long.class);
Long compId = execution.getVariable("compId",Long.class);
Comp comp = compRepository.findById(compId).get();
Person person = personRepository.findById(personId).get();
person.setComp(comp);
personRepository.save(person);
System.out.println("加入組織成功");
} else {
System.out.println("加入組織失敗");
}
}
//獲取符合條件的審批人映企,演示方便這里寫死,使用時(shí)應(yīng)用實(shí)際代碼
public List<String> findUsers(DelegateExecution execution) {
return Arrays.asList("admin", "wtr");
}
}
在joinGroup方法中可以通過入?yún)elegateExcution來獲取流程中的參數(shù)静浴。如果審批通過則在數(shù)據(jù)庫中增加Person和Comp的關(guān)聯(lián)堰氓,表示員工加入了公司。
查詢歷史記錄
到上一步其實(shí)可以發(fā)現(xiàn)整個(gè)示例流程已經(jīng)結(jié)束苹享。但是還有一個(gè)很重要的需求就是歷史記錄的查詢双絮,流程實(shí)例結(jié)束之后可以查詢相關(guān)的歷史記錄,主要包括歷史流程實(shí)例查詢得问、歷史活動(dòng)實(shí)例查詢囤攀、歷史任務(wù)實(shí)例查詢和歷史流程變量查詢等,這些方法都來自HistoryService宫纬。歷史相關(guān)的數(shù)據(jù)庫表以ACT_HI開頭焚挠,一共有8張歷史記錄相關(guān)的表。
值得一提的是正在運(yùn)行中的流程相關(guān)信息由RuntimeService提供查詢漓骚,而結(jié)束的流程信息才能夠通過HistoryService獲取到宣蔚,信息存儲(chǔ)在不同的數(shù)據(jù)表,這是為了減少單表數(shù)據(jù)量提高效率认境,大部分的操作主要是對(duì)執(zhí)行中流程的查詢胚委。因此在人工任務(wù)等待期間可以在act_ru_execution表中查到相關(guān)數(shù)據(jù),而流程結(jié)束之后可以發(fā)現(xiàn)相關(guān)信息已經(jīng)從表中刪除叉信。
總結(jié)
看完上述內(nèi)容亩冬,并且實(shí)踐過示例流程后,相信已經(jīng)建立了對(duì)于Activiti的基本理解硼身,對(duì)于創(chuàng)建硅急、處理簡(jiǎn)單的流程應(yīng)該不成問題,當(dāng)然實(shí)際項(xiàng)目的應(yīng)用需要處理的問題一定不限于此佳遂,Activiti框架本身也不止于此营袜。本文只是入門,想進(jìn)一步了解還是應(yīng)該查看官方文檔丑罪、源碼以及高階的教程荚板。
另外凤壁,官網(wǎng)下載的Activiti文件中包含了一個(gè)演示項(xiàng)目的war包:activiti-explorer.war,部署后可以在線編輯流程圖跪另,作為一個(gè)官方Demo也有助于快速入門拧抖。部署也比較簡(jiǎn)單,在mysql中創(chuàng)建一個(gè)數(shù)據(jù)庫免绿,使用給予的sql文件創(chuàng)建Activiti相關(guān)的表唧席,然后將war包解壓置入tomcat,找到activiti-explorer\WEB-INF\classes下的db.properties配置文件并打開嘲驾,修改數(shù)據(jù)庫連接信息如下淌哟。
db=mysql
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/activiti #更改為創(chuàng)建的數(shù)據(jù)庫名
jdbc.username=root #數(shù)據(jù)庫用戶
jdbc.password=123456 #用戶密碼
復(fù)制mysql連接的jar包mysql-connector-java-xxx.jar(沒有的話先下載)到WEB-INF\lib目錄下即可。啟動(dòng)tomcat辽故,不出問題打開http://localhost:8080/activiti-explorer绞绒,使用用戶kermit,密碼kermit登錄即可開始體驗(yàn)操作榕暇。