怎么用文件做網(wǎng)站快速優(yōu)化網(wǎng)站排名軟件
記錄開發(fā)最核心的部分,理論結(jié)合業(yè)務(wù)實(shí)操減少?gòu)U話,從未接觸工作流快速帶入開發(fā)。假設(shè)你是后端的同學(xué)學(xué)過JAVA和流程圖,則可以繼續(xù)向后看,否則先把基礎(chǔ)課程書準(zhǔn)備好先翻翻。
為什么要工作流
比起直接使用狀態(tài)字段,工作流有以下幾個(gè)好處:
- 直觀管理:可以直接通過維護(hù)流程圖設(shè)計(jì)來實(shí)現(xiàn)狀態(tài)流轉(zhuǎn)。流程變化可直接維護(hù)BMP流程圖發(fā)布來實(shí)現(xiàn)變更
- 復(fù)雜流程:會(huì)簽、序簽、指定時(shí)間半數(shù)以上通過等等
- 低代碼:基于表單的審批數(shù)據(jù)模式性很強(qiáng),可以比較方便的實(shí)現(xiàn)低代碼,高效開發(fā)
其它理由不用多說,光這3項(xiàng)的場(chǎng)景就有充分使用的理由。如果模式非常簡(jiǎn)單,還是狀態(tài)字段來的直接。
從流程圖說起
我們知道,一個(gè)流程圖包括開始狀態(tài)、結(jié)束狀態(tài)、任務(wù)節(jié)點(diǎn)、流程條件。嗯,看起來像這樣
對(duì)應(yīng)Activiti的概念:
實(shí)例:開始一個(gè)任務(wù)后,則產(chǎn)生一個(gè)流程實(shí)例,即ProcessInstance
任務(wù):當(dāng)走到一個(gè)任務(wù)節(jié)點(diǎn)時(shí),則產(chǎn)生任務(wù),任務(wù)有單個(gè)和多個(gè)兩種模式,多個(gè)任務(wù)有順序和并行模式?
執(zhí)行:通常一個(gè)任務(wù)節(jié)點(diǎn)會(huì)產(chǎn)生一個(gè)執(zhí)行,但是并行節(jié)點(diǎn)或分支任務(wù)會(huì)產(chǎn)生多個(gè)執(zhí)行,一個(gè)進(jìn)行中的任務(wù)對(duì)應(yīng)一個(gè)執(zhí)行,每個(gè)執(zhí)行可以有多個(gè)子執(zhí)行(多個(gè)任務(wù)節(jié)點(diǎn)時(shí))。這里的執(zhí)行,相當(dāng)于有多少個(gè)“待辦任務(wù)”在進(jìn)行。當(dāng)對(duì)應(yīng)的任務(wù)完成時(shí),執(zhí)行表的對(duì)應(yīng)記錄也會(huì)被刪除
變量:流程的數(shù)據(jù)容器,在整個(gè)流轉(zhuǎn)過程中。變量分為兩種,一種是實(shí)例級(jí)別的變量,另一種是執(zhí)行級(jí)別的變量。對(duì)應(yīng)task.setVariable和execution.setVariable,區(qū)別是execution variable只在當(dāng)前的執(zhí)行節(jié)點(diǎn)有效,而task variable是針對(duì)任務(wù)的變量。
變量生命周期:對(duì)于task和execution,都有setVariable和setLocalVariable兩種。變量的setVariable設(shè)置的值針對(duì)整個(gè)流程實(shí)例生命周期有效,而setLocalVariable設(shè)置的值針對(duì)當(dāng)前任務(wù)節(jié)點(diǎn)有效??梢岳斫鉃槭冀K使用還是一次性使用(針對(duì)網(wǎng)關(guān)使用一次后作廢)。
邊界事件:定時(shí)、信號(hào)等事件,在事件網(wǎng)關(guān)時(shí),可以根據(jù)事件來定義。實(shí)現(xiàn)時(shí)間或信號(hào)特性的約束,常見的場(chǎng)景例如客服1小時(shí)處理不完投訴則交給主管處理。
約束條件與網(wǎng)關(guān)
以前面的流程圖為例,超過3天和不超過3天為根據(jù)約束條件產(chǎn)生的兩種不同的操作任務(wù)。在Activiti7里用網(wǎng)關(guān)(gateway)來表示。網(wǎng)關(guān)有表達(dá)式(SPEL)、腳本和Java類3中處理。常用的為表達(dá)式模式。當(dāng)前一個(gè)任務(wù)完成(complete)時(shí),根據(jù)表達(dá)式不同走向不同的節(jié)點(diǎn)
網(wǎng)關(guān)有4種類型:
排他網(wǎng)關(guān)(Exclusive Gateway)
會(huì)按照給定的條件,產(chǎn)生一個(gè)任務(wù)實(shí)例
蛋疼理論:如果多個(gè)分支條件都滿足,或者一個(gè)分支都不滿足,怎么辦?
如果都滿足,流程會(huì)按照id值升序,處理第一條分支(先定義的分支id通常值較小,但不絕對(duì))
如果都不滿足,則會(huì)拋出異常
并行網(wǎng)關(guān)(ParallelGateway)
會(huì)按照給定的條件,產(chǎn)生多個(gè)任務(wù)實(shí)例。并行網(wǎng)關(guān)可以并行作業(yè),提高業(yè)務(wù)流程速度。
包容網(wǎng)關(guān)(Inclusive Gateway)
相當(dāng)于排他和并行的綜合體 ,同時(shí)執(zhí)行時(shí)可以指定條件,例如某些需要補(bǔ)充材料的情況
事件網(wǎng)關(guān)(Event Gateway)
用于根據(jù)事件的觸發(fā)選擇分支路徑。當(dāng)指定的事件觸發(fā)時(shí),流程會(huì)選擇對(duì)應(yīng)的分支執(zhí)行。
多實(shí)例與子執(zhí)行
有那么一種業(yè)務(wù),流程圖可能表示為多個(gè)人參與同一個(gè)活動(dòng)。例如你可能想到的流程圖:
像這種活動(dòng),在BPM里,可以作為單個(gè)任務(wù)節(jié)點(diǎn)來實(shí)現(xiàn)。叫做多實(shí)例任務(wù)。
我們可以通過節(jié)點(diǎn)的MultiInstance來指定多實(shí)例,當(dāng)指定多實(shí)例時(shí),需要設(shè)置Collection的參數(shù)來決定進(jìn)入任務(wù)時(shí)產(chǎn)生多少個(gè)子執(zhí)行。Collection是一組java.util.Collection接口的數(shù)據(jù)。進(jìn)入任務(wù)時(shí),將產(chǎn)生一個(gè)對(duì)應(yīng)的主執(zhí)行,和多個(gè)子執(zhí)行,這些子任務(wù)將分別指派給Collection的每一個(gè)人員。
對(duì)應(yīng)的業(yè)務(wù)場(chǎng)景通常有會(huì)簽和序簽。序簽用的較少,這里主要講會(huì)簽。
會(huì)簽?zāi)J?/h3>
會(huì)員的模式主要有:
按數(shù)量通過:達(dá)到一定數(shù)量的通過表決后,會(huì)簽通過。
按比例通過:達(dá)到一定比例的通過表決后,會(huì)簽通過。
一票否決:只要有一個(gè)表決是否定的,會(huì)簽否決。
一票通過:只要有一個(gè)表決通過的,會(huì)簽通過。
我們可以在Task的MultiInstance配置多實(shí)例的信息。可以配置的有:
模式參數(shù)
順序(sequential ):執(zhí)行順序,必選項(xiàng)。true:多實(shí)例順序執(zhí)行。false:多實(shí)例并行。
并行(parallel):多個(gè)實(shí)例會(huì)同時(shí)并行發(fā)放給處理人
loop cardinality:循環(huán)基數(shù)(實(shí)例數(shù)量),可選項(xiàng)??商钫麛?shù),表示會(huì)簽的人數(shù)。
Collection:集合,可選項(xiàng)。會(huì)簽人數(shù)的集合list,與loop cardinality二選一。
Element variable:元素變量。選擇Collection時(shí)必選,為collection集合每次遍歷的元素。
Completion condition:完成條件,可選項(xiàng)。
完成條件
會(huì)簽配置關(guān)鍵就是Completion condition,可以填寫一個(gè)UEL(類似SPEL)。在流程流轉(zhuǎn)時(shí),對(duì)于多實(shí)例節(jié)點(diǎn),會(huì)內(nèi)置下面幾個(gè)參數(shù):
nrOfInstances:創(chuàng)建的實(shí)例總數(shù),在進(jìn)入任務(wù)節(jié)點(diǎn)時(shí)則設(shè)置好,一般等于Collection的數(shù)量
nrOfActiveInstances:當(dāng)前活動(dòng)的實(shí)例數(shù),針對(duì)順序類型的多實(shí)例,該變量值等于1;對(duì)于并行的類型,進(jìn)入任務(wù)時(shí)為Collection數(shù)量,每完成一個(gè)執(zhí)行,則活動(dòng)任務(wù)數(shù)減1。
nrOfCompletedInstances:已執(zhí)行實(shí)例數(shù)。每完成一個(gè)執(zhí)行,則加1。
loopCounter:表示多實(shí)例流程循環(huán)的下標(biāo),順序執(zhí)行時(shí)記錄執(zhí)行順序。
條件實(shí)現(xiàn)
理解上面的內(nèi)容這樣實(shí)現(xiàn)的方式就很明確了:
假設(shè)我們?yōu)闀?huì)簽設(shè)置了2個(gè)投票池(說白了就是變量)approveCount和denyCount。操作時(shí),每點(diǎn)擊"同意"或"拒絕"(先不考慮棄權(quán)情況)則變量+1
1)按數(shù)量通過:2人通過則通過#{approveCount > 2}
2)按比例通過:常見的過半數(shù)同意則通過?#{agreeCount/nrOfInstance > 0.5}
3)一票否決:#{denyCount== 1}
4)一票通過:#{approveCount== 1}
序簽:較少使用。需要每個(gè)人員逐一完成任務(wù)。
搶單與代辦
接下來講兩個(gè)常見業(yè)務(wù)場(chǎng)景,第一個(gè)是搶單。搶單的需求為我們可以將任務(wù)指定給一個(gè)處理小組,處理小組某個(gè)人點(diǎn)擊“開始處理”后,其它人將無(wú)法再點(diǎn)擊處理按鈕或點(diǎn)擊處理按鈕提示已被搶單。
核心業(yè)務(wù)實(shí)現(xiàn):
1)任務(wù)的CandidateUser屬性可以指定一組處理人員,Task的Candidata Users屬性可以通過SPEL指定一個(gè)列表。當(dāng)指派列表后,任務(wù)流轉(zhuǎn)到節(jié)點(diǎn)時(shí)所有的Candidata Users都會(huì)收到一個(gè)代辦任務(wù)。
2)操作員點(diǎn)擊開始處理時(shí),通過taskService.setAssignee(taskId, assignee);指派給某個(gè)人員,指派后,其它操作員將再無(wú)法看到這個(gè)任務(wù)
3)當(dāng)其他操作員點(diǎn)擊操作時(shí),需要檢查操作列表,如果已經(jīng)被指派給非當(dāng)前操作員 ,需要返回前端提醒用戶該任務(wù)已指派。否則直接使用Activity引擎來處理指派,將會(huì)導(dǎo)致任務(wù)重新分配(和后面講的代辦邏輯一樣)
代辦的模式和搶單的一樣,當(dāng)某任務(wù)已經(jīng)指定給某個(gè)操作員后??梢酝ㄟ^setAssignee實(shí)現(xiàn)操作的重新指派。從而實(shí)現(xiàn)代辦指派。當(dāng)然這只是流程級(jí)別的操作,對(duì)于業(yè)務(wù)的具體流轉(zhuǎn)記錄,我們還時(shí)得借助日志的實(shí)現(xiàn)。
Activiti之歷史記錄
對(duì)于工作流業(yè)務(wù),僅有流程狀態(tài)支撐顯然不夠。我們還需要對(duì)歷史數(shù)據(jù)進(jìn)行查詢和展示。Activiti提供了一組歷史記錄表,以ACT_HI_開頭,記錄了流程信息、流程任務(wù)信息、以及流程的變量信息。能夠滿足一些常見的業(yè)務(wù)場(chǎng)景需求,例如:
審批歷史
在審批的過程,我們通常需要添加批示信息。一個(gè)流程的審批歷史類似:
張三:提交請(qǐng)假申請(qǐng)
組長(zhǎng):同意請(qǐng)假?
經(jīng)理:同意請(qǐng)假
人事:請(qǐng)假申請(qǐng)?zhí)幚硗戤?br />
如果不借助額外的數(shù)據(jù)表,我們可以使用流程變量。定義為一個(gè)String數(shù)組或者JSON,每經(jīng)過一個(gè)處理節(jié)點(diǎn)添加一行記錄。流程進(jìn)行或完成時(shí),將該記錄拿出來展示,該信息記錄在表act_hi_taskinst
注意如果使用流程實(shí)例變量記錄審批過程JSON,需要留意String變量大小的限制(4000字符),以免在審批過程中超過大小。以每個(gè)節(jié)點(diǎn)審批信息256字符為例,我們可以推算最少大概15個(gè)節(jié)點(diǎn)審批信息填滿會(huì)導(dǎo)致審批記錄滿。
流轉(zhuǎn)節(jié)點(diǎn)
在任務(wù)記錄表act_hi_taskinst里,記錄了流程產(chǎn)生的任務(wù)以及經(jīng)過的節(jié)點(diǎn),以及對(duì)于流程圖文件的任務(wù)節(jié)點(diǎn)的KEY。這樣我們可以通過act_hi_taskinst,在BPMN流程定義圖上繪制詳細(xì)的節(jié)點(diǎn)流轉(zhuǎn)標(biāo)記進(jìn)行展示
理論了很多,下一步來點(diǎn)干貨實(shí)操
環(huán)境搭建
以springboot+Activiti7為例,開始環(huán)境搭建。
Activiti依賴的支持有:
- spring:提供了一組starter,啟動(dòng)后可以通過對(duì)應(yīng)的Bean完成流程處理
- spring-security:activiti7.X默認(rèn)整合了springsecurity,使用sa-token或Shiro等其他權(quán)限控制需要?jiǎng)冸x對(duì)spring-security的依賴,否則會(huì)報(bào)錯(cuò)。對(duì)springsecurity的依賴主要包含群組權(quán)限的部分,我們不需要完全可以剝離它
- mybatis:數(shù)據(jù)存儲(chǔ)層使用的是mybatis來進(jìn)行控制
Activiti最新的版本
如果從sonatype公共的倉(cāng)庫(kù)去拿,僅能拿到4年前的版本7.1.0.M6。你可能滿腦袋的問號(hào):Activiti開源死掉了嗎?不再支持了嗎?
如果你去Activiti官網(wǎng),會(huì)發(fā)現(xiàn)Activiti仍舊有維護(hù),甚至發(fā)展到8.0版本。無(wú)法獲得是因?yàn)槲覀儧]有配置Activiti官網(wǎng)倉(cāng)庫(kù)。以gradle為例,我們只需要添加這個(gè)倉(cāng)庫(kù)即可
maven { url 'https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases' } // activiti最新版?zhèn)}庫(kù)
然后我們可以使用最新版本了,為了避免表結(jié)構(gòu)出現(xiàn)不兼容,還是選擇7.X的最新版本:
implementation("org.activiti:activiti-spring-boot-starter:7.11.0") implementation("org.activiti:activiti-core-dependencies:7.11.0")
升級(jí)注意
JDK版本依賴:Activiti自身倉(cāng)庫(kù)的版本都是使用JDK11編譯,如果是使用JDK1.8編譯會(huì)出現(xiàn)報(bào)錯(cuò)。解決辦法1是升級(jí)JDK到11,2是重新使用JDK1.8編譯整個(gè)Activity源(比較麻煩)。我選擇的是升級(jí)到JDK11
剝離springsecurity依賴
剝離springsecurity依賴主要是方式是禁用Activiti里與springsecurity相關(guān)的starter
YAML配置的方式:
?
spring:autoconfigure:exclude:- org.activiti.spring.boot.ActivitiMethodSecurityAutoConfiguration- org.activiti.core.common.spring.identity.config.ActivitiSpringIdentityAutoConfiguration
代碼配置方式:
@SpringBootApplication(exclude = {ActivitiMethodSecurityAutoConfiguration.class, ActivitiSpringIdentityAutoConfiguration.class}
)
然后啟動(dòng)你會(huì)發(fā)現(xiàn),還會(huì)有Bean UserGroupManager報(bào)錯(cuò),這個(gè)Bean是依賴spring security的群組功能。不使用群組的話,我們可以直接new個(gè)匿名類讓spring裝配不報(bào)錯(cuò)。
我的ActivitiConfiguration如下
package org.ccframe.app;import org.activiti.api.runtime.shared.identity.UserGroupManager;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;@Configuration
public class ActivitiConfiguration {@Autowiredprivate DataSource dataSource;@Autowiredprivate PlatformTransactionManager transactionManager;//通過@Bean注解將SpringProcessEngineConfiguration實(shí)例聲明為Spring Bean,使其可供其他組件注入和使用@Beanpublic SpringProcessEngineConfiguration springProcessEngineConfiguration() {SpringProcessEngineConfiguration spec = new SpringProcessEngineConfiguration();//設(shè)置數(shù)據(jù)源,將注入的數(shù)據(jù)源設(shè)置到SpringProcessEngineConfiguration實(shí)例中spec.setDataSource(this.dataSource);//設(shè)置事務(wù)管理器將注入的事務(wù)管理器設(shè)置到SpringProcessEngineConfiguration實(shí)例中spec.setTransactionManager(this.transactionManager);//設(shè)置數(shù)據(jù)庫(kù)模式更新策略 true表示在啟動(dòng)時(shí)自動(dòng)創(chuàng)建或更新Activiti引擎所需的數(shù)據(jù)庫(kù)表結(jié)構(gòu)spec.setDatabaseSchemaUpdate("true");Resource[] resources = null;//配置流程部署資源//使用PathMatchingResourcePatternResolver從classpath中的bpmn目錄下加載所有以.bpmn為擴(kuò)展名的文件作為流程定義資源,// 并將它們?cè)O(shè)置到SpringProcessEngineConfiguration實(shí)例中。try {resources = (new PathMatchingResourcePatternResolver()).getResources("classpath*:bpmn/*.bpmn");} catch (IOException var4) {var4.printStackTrace();}spec.setDeploymentResources(resources);return spec;}@Beanpublic UserGroupManager userGroupManager(){return new UserGroupManager(){@Overridepublic List<String> getUserGroups(String username) {return new ArrayList<>();}@Overridepublic List<String> getUserRoles(String username) {return new ArrayList<>();}@Overridepublic List<String> getGroups() {return new ArrayList<>();}@Overridepublic List<String> getUsers() {return new ArrayList<>();}};}
}
這里只是不使用group的功能,當(dāng)然你可以使用自己的權(quán)限系統(tǒng)來實(shí)現(xiàn)group的功能,將group信息對(duì)應(yīng)到自己權(quán)限系統(tǒng)的用戶列表。
可控流轉(zhuǎn)的實(shí)現(xiàn)
<編寫中>