Activiti入門學(xué)習(xí)

原始博文鏈接

什么是工作流

首先還是要把專業(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)備工作

  1. 首先Activiti的數(shù)據(jù)基于數(shù)據(jù)庫,所以首先需要一個(gè)數(shù)據(jù)庫胳岂,基本主流的數(shù)據(jù)庫Activiti都支持编整,選用mysql就ok。
  2. 去官網(wǎng)下載Activiti乳丰,里面不僅包含了需要的jar包掌测,還有文檔、sql文件产园、war包等赏半。
  3. 配置IDE,雖然說eclipse插件是Activiti的一個(gè)優(yōu)點(diǎn)淆两,但是目前IDEA的流行程度更高一些断箫,還是選用IDEA作為開發(fā)測(cè)試工具,IDEA也有Activiti插件名為actiBPM秋冰,但是個(gè)人覺得不太好用仲义,截止目前這個(gè)插件最后更新時(shí)間是2014年,看起來好像不在維護(hù)了剑勾,比較尷尬埃撵。
  4. 框架集成,Activiti絕大多數(shù)情況還是作為一個(gè)組件集成到后臺(tái)框架中虽另,為了方便暂刘,減少其他因素干擾,選用SpringBoot捂刺。不過很可惜在練習(xí)的時(shí)候想當(dāng)然地選了SpringBoot2.0谣拣,集成起來發(fā)生了問題浪費(fèi)了很多時(shí)間募寨。
  5. 簡(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)單的流程圖,流程的目的是員工加入公司的審批坟奥。

exampleprocess.jpg

對(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ǔ)的兩種:

  1. UserTask:人工任務(wù)清女,用來描述認(rèn)為參與的操作钱烟,流程執(zhí)行到此節(jié)點(diǎn)時(shí)須人工響應(yīng)后才能繼續(xù)向下流轉(zhuǎn)。人工任務(wù)最終要的屬性就是辦理任務(wù)的執(zhí)行者嫡丙,有assignee(單一執(zhí)行人)拴袭、candidateUsers(多個(gè)候選執(zhí)行人)、candidateGroups(候選執(zhí)行組)這幾種屬性可選曙博。
  2. 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ù)

  1. RepositoryService:倉庫服務(wù)芒粹。業(yè)務(wù)流程的定義都需要使用一些定義文件兄纺,定義了流程需要將其部署,部署之后可能需要查詢部署的信息和相關(guān)的文件化漆,這些需求就由RepositoryService提供估脆。
  2. RuntimeService:流程執(zhí)行服務(wù)。在Activiti中座云,每當(dāng)一個(gè)流程定義被啟動(dòng)一次之后疙赠,都會(huì)生成一個(gè)相應(yīng)的流程對(duì)象實(shí)例付材。RuntimeService提供了啟動(dòng)流程、查詢流程實(shí)例圃阳、設(shè)置獲取流程實(shí)例變量等功能厌衔。
  3. TaskService:任務(wù)服務(wù)。它提供了運(yùn)行時(shí)任務(wù)查詢捍岳、領(lǐng)取富寿、完成、刪除以及變量設(shè)置等功能祟同。
  4. HistoryService:歷史服務(wù)作喘。流程、任務(wù)執(zhí)行完成之后可能需要追溯晕城、查詢歷史泞坦,這就需要用到HistoryService。
  5. IdentityService:身份服務(wù)砖顷。Activiti中內(nèi)置了用戶以及組管理的功能贰锁,必須使用這些用戶和組的信息才能獲取到相應(yīng)的Task。IdentityService提供了對(duì)Activiti 系統(tǒng)中的用戶和組的管理功能滤蝠。當(dāng)然用戶和組也可以自定義提供豌熄,但是這些內(nèi)容不在本文討論范圍之內(nèi)
  6. ManagementService: 管理服務(wù)。它提供了對(duì)Activiti流程引擎的管理和維護(hù)功能物咳,這些功能不在工作流驅(qū)動(dòng)的應(yīng)用程序中使用锣险,主要用于Activiti系統(tǒng)的日常維護(hù)。

對(duì)象

除了服務(wù)览闰,還有以接口形式表達(dá)的幾個(gè)關(guān)鍵對(duì)象:

  1. Deployment:流程部署對(duì)象芯肤,是對(duì)一個(gè)部署的抽象化表達(dá)。
  2. ProcessDefinition:流程定義压鉴,部署成功后自動(dòng)創(chuàng)建崖咨。
  3. ProcessInstance:代表流程實(shí)例,啟動(dòng)流程時(shí)創(chuàng)建油吭。
  4. Execution:執(zhí)行計(jì)劃击蹲,流程實(shí)例和流程執(zhí)行中的所有節(jié)點(diǎn)都是Execution。當(dāng)流程單線執(zhí)行時(shí)ProcessInstance與Execution內(nèi)容保持一致婉宰,并發(fā)流程中歌豺,總線路為ProcessInstance,分線路中每個(gè)活動(dòng)由Execution表達(dá)心包。
  5. 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)操作榕暇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蓬衡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子彤枢,更是在濱河造成了極大的恐慌狰晚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缴啡,死亡現(xiàn)場(chǎng)離奇詭異壁晒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)业栅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門秒咐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碘裕,你說我怎么就攤上這事携取。” “怎么了帮孔?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵雷滋,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我文兢,道長(zhǎng)晤斩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任姆坚,我火速辦了婚禮澳泵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兼呵。我一直安慰自己兔辅,他們只是感情好腊敲,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幢妄,像睡著了一般兔仰。 火紅的嫁衣襯著肌膚如雪茫负。 梳的紋絲不亂的頭發(fā)上蕉鸳,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音忍法,去河邊找鬼潮尝。 笑死,一個(gè)胖子當(dāng)著我的面吹牛饿序,可吹牛的內(nèi)容都是我干的勉失。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼原探,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼乱凿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咽弦,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤徒蟆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后型型,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體段审,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年闹蒜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了寺枉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绷落,死狀恐怖姥闪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砌烁,我是刑警寧澤甘畅,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站往弓,受9級(jí)特大地震影響疏唾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜函似,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一槐脏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撇寞,春花似錦顿天、人聲如沸堂氯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咽白。三九已至,卻和暖如春鸟缕,著一層夾襖步出監(jiān)牢的瞬間晶框,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國打工懂从, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留授段,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓番甩,卻偏偏與公主長(zhǎng)得像侵贵,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缘薛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容