什么是工作流/工作引擎?
Georgakopoulos給出的工作流定義
是:工作流是將一組任務(wù)組織起來以完成某個(gè)經(jīng)營過程:定義了任務(wù)的觸發(fā)順序和觸發(fā)條件绸狐,每個(gè)任務(wù)可以由一個(gè)或多個(gè)軟件系統(tǒng)完成闻书,也可以由一個(gè)或一組人完成搏予,還可以由一個(gè)或多個(gè)人與軟件系統(tǒng)協(xié)作完罩息。它主要解決的是“使在多個(gè)參與者之間按照某種預(yù)定義的規(guī)則傳遞文檔、信息或任務(wù)的過程自動(dòng)進(jìn)行妈踊,從而實(shí)現(xiàn)某個(gè)預(yù)期的業(yè)務(wù)目標(biāo),或者促使此目標(biāo)的實(shí)現(xiàn)”泪漂。工作流管理系統(tǒng)的目標(biāo):管理工作的流程以確保工作在正確的時(shí)間被期望的人員所執(zhí)行——在自動(dòng)化進(jìn)行的業(yè)務(wù)過程中插入任何的執(zhí)行和干預(yù)廊营。所謂工作流引擎
是指workflow作為應(yīng)用系統(tǒng)的一部分,并為之提供對(duì)各應(yīng)用系統(tǒng)有決定作用的根據(jù)角色萝勤、分工和條件的不同決定信息傳遞路由露筒、內(nèi)容等級(jí)等核心解決方案。例如開發(fā)一個(gè)系統(tǒng)最關(guān)鍵的部分不是系統(tǒng)的界面敌卓,也不是和數(shù)據(jù)庫之間的信息交換慎式,而是如何根據(jù)業(yè)務(wù)邏輯開發(fā)出符合實(shí)際需要的程序邏輯并確保其穩(wěn)定性、易維護(hù)性和彈性趟径。比如你的系統(tǒng)中有一個(gè)任務(wù)流程瘪吏,一般情況下這個(gè)任務(wù)的代碼邏輯、流程你都要自己來編寫蜗巧。實(shí)現(xiàn)它是沒有問題的掌眠。但是誰能保證邏輯編寫的毫無紕漏?經(jīng)過無數(shù)次的測(cè)試與改進(jìn)幕屹,這個(gè)流程沒有任何漏洞也是可以實(shí)現(xiàn)的蓝丙,但是明顯就會(huì)拖慢整個(gè)項(xiàng)目的進(jìn)度级遭。
BPMN2.0規(guī)范
BPMN(Business Process Model and Notation)--業(yè)務(wù)流程模型與符號(hào)。
BPMN是一套流程建模的標(biāo)準(zhǔn)渺尘,主要目標(biāo)是被所有業(yè)務(wù)用戶容易理解的符號(hào)挫鸽,支持從創(chuàng)建流程輪廓的業(yè)務(wù)分析到這些流程的最終實(shí)現(xiàn),知道最終用戶的管理監(jiān)控沧烈。
通俗一點(diǎn)其實(shí)就是一套規(guī)范掠兄,畫流程模型的規(guī)范。流程模型包括:流程圖锌雀、協(xié)作圖蚂夕、編排圖、會(huì)話圖腋逆。
什么是
Activiti
婿牍?
Activiti的創(chuàng)始人也就是JBPM(也是一個(gè)優(yōu)秀的BPM引擎)的創(chuàng)始人,從Jboss離職后開發(fā)了一個(gè)新的BPM引擎:Activiti惩歉。所以等脂,Activiti有很多地方都有JBPM的影子。Activiti是一個(gè)開源的工作流引擎撑蚌,它實(shí)現(xiàn)了BPMN 2.0規(guī)范上遥,可以發(fā)布設(shè)計(jì)好的流程定義,并通過api進(jìn)行流程調(diào)度争涌。Activiti 作為一個(gè)遵從 Apache 許可的工作流和業(yè)務(wù)流程管理開源平臺(tái)粉楚,其核心是基于 Java 的超快速、超穩(wěn)定的 BPMN2.0 流程引擎亮垫,強(qiáng)調(diào)流程服務(wù)的可嵌入性和可擴(kuò)展性模软,同時(shí)更加強(qiáng)調(diào)面向業(yè)務(wù)人員。Activiti 流程引擎重點(diǎn)關(guān)注在系統(tǒng)開發(fā)的易用性和輕量性上饮潦。每一項(xiàng) BPM 業(yè)務(wù)功能 Activiti 流程引擎都以服務(wù)的形式提供給開發(fā)人員燃异。通過使用這些服務(wù),開發(fā)人員能夠構(gòu)建出功能豐富继蜡、輕便且高效的 BPM 應(yīng)用程序回俐。ProcessEngine
對(duì)象,這是Activiti工作的核心稀并。負(fù)責(zé)生成流程運(yùn)行時(shí)的各種實(shí)例及數(shù)據(jù)鲫剿、監(jiān)控和管理流程的運(yùn)行。所有的操作都是從獲取引擎開始的稻轨,所以一般會(huì)把引擎作為全局變量灵莲。
ProcessEngine processEngine = ProcessEngine.getDefaultProcessEngine();
為什么要用工作流引擎殴俱?
簡(jiǎn)單來說政冻,就是為了統(tǒng)一管理流程業(yè)務(wù)枚抵。想想看,如果要設(shè)計(jì)一個(gè)流程的程序明场,通常需要在數(shù)據(jù)庫中存各種狀態(tài)值汽摹,比如一個(gè)訂單程序,要標(biāo)記訂單是未付款苦锨、已付款逼泣、已出庫等等狀態(tài),而這些各種各樣的狀態(tài)參雜在程序中舟舒,邏輯自然就變得復(fù)雜了拉庶。而將這些狀態(tài)對(duì)應(yīng)流程里的一個(gè)個(gè)步驟,交由流程引擎去管理秃励,這樣不僅簡(jiǎn)化了業(yè)務(wù)邏輯代碼氏仗,而且,還有很強(qiáng)的擴(kuò)展性夺鲜。我可以修改我的流程皆尔,我可以添加一些步驟而不用改我的數(shù)據(jù)庫表結(jié)構(gòu),不用改我的業(yè)務(wù)邏輯币励。
在Eclipse中使用Activiti
推薦使用離線安裝
:斷網(wǎng)
通過網(wǎng)盤:https://pan.baidu.com/s/15jZrf5LzlrgG82gPAh3cng 密碼:4kxd 慷蠕,下載在離線安裝包。
將下載好的jars文件夾里的3個(gè)jar文件復(fù)制到eclipse安裝目錄的plugins目錄下食呻。
刪除eclipse安裝目錄下流炕,configuration文件夾里的org.eclipse.update文件夾,重啟eclipse搁进。
打開eclipse浪感,在Help->Install New Software后的彈出窗點(diǎn)擊Available Software Sites昔头,刪除設(shè)置過的Activiti資源信息饼问,add確認(rèn)安裝。
在IDEA中使用Activiti
- 點(diǎn)擊菜單【File】-->【Settings...】打開【Settings】窗口揭斧。
點(diǎn)擊左側(cè)【Plugins】按鈕莱革,在右側(cè)輸出"actiBPM",點(diǎn)擊下面的【Search in repositories】鏈接會(huì)打開【Browse Repositories】窗口
進(jìn)入【Browse Repositories】窗口讹开,選中左側(cè)的【actiBPM】盅视,點(diǎn)擊右側(cè)的【Install】按鈕,開始安裝旦万。
安裝完成后闹击,會(huì)提示【Restart IntelliJ IDEA】,重啟IDEA即可完成安裝成艘。
如何更優(yōu)雅地使用Activiti
隨著微服務(wù)的普及赏半,Rest風(fēng)格的編碼規(guī)范越來被推崇贺归,因而我們使用Activiti Rest模塊進(jìn)行Activiti統(tǒng)一Rest調(diào)用。
1. Activiti REST模塊介紹
Activiti-rest是Activiti提供的一組可以直接操作工作流引擎的REST API断箫,使用者可以在自己應(yīng)用中直接調(diào)用Activiti-rest接口拂酣。
1.1 使用REST的好處
簡(jiǎn)單化:利用現(xiàn)有模塊(activiti-rest.war)代替直接API調(diào)用
標(biāo)準(zhǔn)化:各個(gè)系統(tǒng)根據(jù)rest模塊的接口規(guī)范訪問REST資源,統(tǒng)一處理仲义;對(duì)于工作流平臺(tái)來說此特性尤為突出
擴(kuò)展性:如果官方提供的REST接口還不能滿足可以繼續(xù)在其基礎(chǔ)上進(jìn)行擴(kuò)展以滿足業(yè)務(wù)系統(tǒng)(平臺(tái))的需求
1.2 不適合使用REST的場(chǎng)景
業(yè)務(wù)數(shù)據(jù)與流程數(shù)據(jù)分離:就像kft-activiti-demo中普通表單的演示一樣婶熬,業(yè)務(wù)數(shù)據(jù)保存在一張單獨(dú)設(shè)計(jì)的表中,而不是把表單數(shù)據(jù)保存在引擎的變量表中埃撵,所以對(duì)于這樣的場(chǎng)景中需要聯(lián)合事務(wù)管理的就不能使用REST了赵颅,例如:?jiǎn)?dòng)流程、任務(wù)完成盯另、業(yè)務(wù)與流程數(shù)據(jù)聯(lián)合查詢性含。
1.3 部署Rest模塊
從5.11版本開始不再使用ant腳本的方式啟動(dòng)demo,并且把a(bǔ)ctiviti-explorer和activiti-rest分離并分別提供一個(gè)war包鸳惯,在wars目錄可以找到它商蕴。
把a(bǔ)ctiviti-rest.war解壓到Web服務(wù)器的應(yīng)用部署目錄(例如tomcat的webapps),根據(jù)實(shí)際需求修改activiti-rest/WEB-INF/classes/db.properties里面的數(shù)據(jù)庫配置后啟動(dòng)應(yīng)用芝发。
可以通過REST工具測(cè)試是否部署成功可以正常的提供服務(wù)绪商,例如Chrome的插件REST Console,或者通過Spring MVC提供的RestTemplate辅鲸。
2. 訪問REST資源
對(duì)于REST模塊提供的接口可以參考用戶手冊(cè)的REST API章節(jié)格郁,有著詳細(xì)的介紹(包括URL和參數(shù)含義)。
2.1 身份認(rèn)證
REST接口的大部分功能都需要驗(yàn)證独悴,默認(rèn)使用Basic Access Authentication(基本連接認(rèn)證)例书,所以在訪問資源時(shí)要在header中添加驗(yàn)證信息,當(dāng)然為了安全期間把用戶名和密碼進(jìn)行base 64位加密刻炒。
可以在用戶登陸之后把用戶名和密碼進(jìn)行加密并設(shè)置到session中决采,這樣在前端就可以直接通過Ajax方式獲取資源了:
import jodd.util.Base64;
String base64Code = "Basic " + Base64.encodeToString(user.getId() + ":" + user.getPassword());
session.setAttribute("BASE_64_CODE", base64Code);
2.2 通過Ajax方式讀取資源
下面通過kft-activiti-demo中的代碼片段介紹:
$.ajax({
type: "get",
url: REST_URL + 'process-definition/' + processDefinitionId + '/form',
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', BASE_64_CODE);
},
dataType: 'html',
success: function(form) {
// 獲取的form是字符行,html格式直接顯示在對(duì)話框內(nèi)就可以了坟奥,然后用form包裹起來
$(dialog).html(form).wrap("
在第5行處設(shè)置了ajax請(qǐng)求的header信息树瞭,這樣REST模塊就可以通過header的信息進(jìn)行身份認(rèn)證,通過之后就可以執(zhí)行資源請(qǐng)求并返回處理結(jié)果爱谁。
2.3 通過Java方式讀取資源
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
public class TestLogin {
public static void main(String[] args) throws Exception, IOException {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost postRequest = new HttpPost("http://localhost:8080/activiti-rest/service/login");
StringEntity input = new StringEntity("{\"userId\":\"kermit\",\"password\":\"kermit\"}");
input.setContentType("application/json");
postRequest.setEntity(input);
HttpResponse response = httpClient.execute(postRequest);
BufferedReader br = new BufferedReader(new InputStreamReader((response.getEntity().getContent())));
String output;
System.out.println("Output from Server .... \n");
while ((output = br.readLine()) != null) {
System.out.println(output);
}
httpClient.getConnectionManager().shutdown();
}
}
3. Ajax跨域問題解決辦法
把REST模塊和應(yīng)用部署在同一個(gè)Web服務(wù)器中(廢話……)
等待官方提供JSONP的支持晒喷,JIRA Issue:ACT-1534
利用后臺(tái)代理方式,把請(qǐng)求的URL發(fā)送給后臺(tái)代理服務(wù)器访敌,獲取數(shù)據(jù)之后再把結(jié)果返回給前臺(tái)
在WEB界面繪制流程圖
Activiti Modeler 組件凉敲,是一套支持WEB界面實(shí)現(xiàn)流程繪制的服務(wù)組件,允許使用方自定義流程。
1.下載 Activiti-activiti-5.22.0 包
下載地址:https://github.com/Activiti/Activiti/tree/activiti-5.22.0/
- 1
提取 Activiti-activiti-5.22.0 包內(nèi)容爷抓,放置到項(xiàng)目中
1.1.1 從 Activiti-activiti-5.22.0>modules>activiti-webapp-explorer2>src>main>webapp 下提取diagram-viewer雨效,editor-app,modeler.html 放置到 resource/static 中1.1.2 從Activiti-activiti-5.22.0>modules>activiti-webapp-explorer2>src>main>resources 下提取stencilset.json 放置到 resource/static 中1.1.3 從Activiti-activiti-5.22.0>modules>activiti-modeler>src>main>java>org>activiti>rest>editor 下提取 ModelEditorJsonRestResource.java废赞,ModelSaveRestResource.java 徽龟,StencilsetRestResource.java放置到項(xiàng)目中1.1.4 修改ModelEditorJsonRestResource.java
@RestController
@RequestMapping(value = "/service") //添加
1.1.5 修改ModelSaveRestResource.java
@RestController
@RequestMapping("/service")
public class ModelSaveRestResource implements ModelDataJsonConstants {
protected static final Logger LOGGER = LoggerFactory.getLogger(ModelSaveRestResource.class);
@Autowired
private RepositoryService repositoryService;
@Autowired
private ObjectMapper objectMapper;
/**
* 保存模型
* @param modelId
* @param json_xml
* @param svg_xml
* @param name
* @param description
*/
@RequestMapping(value = "/model/{modelId}/save", method = RequestMethod.PUT)
public void saveModel(@PathVariable String modelId, @RequestParam String json_xml,
@RequestParam String svg_xml,@RequestParam String name, @RequestParam String description) {
try {
// 獲取模型信息并更新模型信息
ObjectMapper objectMapper = new ObjectMapper();
Model model = repositoryService.getModel(modelId);
ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
modelJson.put(MODEL_NAME, name);
modelJson.put(MODEL_DESCRIPTION, description);
model.setMetaInfo(modelJson.toString());
model.setName(name);
repositoryService.saveModel(model);
repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes("utf-8"));
// 基于模型信息做流程部署
InputStream svgStream = new ByteArrayInputStream(svg_xml.getBytes("utf-8"));
TranscoderInput input = new TranscoderInput(svgStream);
PNGTranscoder transcoder = new PNGTranscoder();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
TranscoderOutput output = new TranscoderOutput(outStream);
transcoder.transcode(input, output);
final byte[] result = outStream.toByteArray();
repositoryService.addModelEditorSourceExtra(model.getId(), result);
outStream.close();
Model modelData = repositoryService.getModel(modelId);
ObjectNode modelNode = (ObjectNode) objectMapper.readTree(repositoryService.getModelEditorSource(modelData.getId()));
byte[] bpmnBytes = null;
BpmnModel model2 = new BpmnJsonConverter().convertToBpmnModel(modelNode);
bpmnBytes = new BpmnXMLConverter().convertToXML(model2);
String processName = modelData.getName() + ".bpmn";
Deployment deployment = repositoryService.createDeployment()
.name(modelData.getName())
.addString(processName, StringUtils.toEncodedString(bpmnBytes, Charset.forName("UTF-8")))
.deploy();
} catch (Exception e) {
LOGGER.error("模型保存失敗", e);
throw new ActivitiException("模型保存失敗", e);
}
}
}
1.1.6 修改StencilsetRestResource.java
@RestController
@RequestMapping(value = "/service")
public class StencilsetRestResource {
/**
* 加載模板集配置
* @return
*/
@RequestMapping(value="/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")
public @ResponseBody String getStencilset() {
try {
InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("static/stencilset-cn.json");
return IOUtils.toString(stencilsetStream, "utf-8");
} catch (Exception e) {
throw new ActivitiException("加載模板集配置失敗", e);
}
}
}
1.1.7 修改app-cfg.js
ACTIVITI.CONFIG = {
// 'contextRoot' : '/activiti-explorer/service',
'contextRoot' : '/activiti/service',
}
- 2
activiti 繪制器的漢化
使用漢化的stencilset.json替換resource/static 下的stencilset.json
- 3
實(shí)現(xiàn)控制類,用于模型的構(gòu)建
創(chuàng)建ActivitiModelController.java
@RestController
public class ActivitiModelController {
public static Logger logger = Logger.getLogger(ActivitiModelController.class);
@Autowired
private ProcessEngine processEngine; // 定義流程引擎
@Autowired
private ObjectMapper objectMapper;
private final static Integer MODEL_REVISION = 1; // 模型的版本
private final static String ACTIVITI_REDIRECT_MODELER_INDEX = "/activitiService/modeler.html?modelId="; // modeler.html頁面地址
private final static String ACTIVITI_NAMESPACE_VALUE = "http://b3mn.org/stencilset/bpmn2.0#"; // 默認(rèn)的空間值
private final static String ACTIVITI_ID_VALUE = "canvas"; // 默認(rèn)ID值
/**
* 新建一個(gè)模型
* @param response
* @throws IOException
*/
@RequestMapping("/activiti-ui.html")
public void newModel(HttpServletResponse response) throws IOException {
RepositoryService repositoryService = processEngine.getRepositoryService();
// 初始化空的流程資源模型唉地,填充信息并持久化模型
Model model = repositoryService.newModel();
String uuid = UUUID.randomUUID().toString().replaceAll("-", "");
ObjectNode modelNode = objectMapper.createObjectNode();
modelNode.put(ModelDataJsonConstants.MODEL_NAME, uuid);
modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, "");
modelNode.put(ModelDataJsonConstants.MODEL_REVISION, MODEL_REVISION);
model.setName(uuid);
model.setKey(uuid);
model.setMetaInfo(modelNode.toString());
repositoryService.saveModel(model);
// 將 editorNode 數(shù)據(jù)填充到模型中, 并做頁面的重定向
ObjectNode editorNode = objectMapper.createObjectNode();
editorNode.put("id", ACTIVITI_ID_VALUE);
editorNode.put("resourceId", ACTIVITI_ID_VALUE);
ObjectNode stencilSetNode = objectMapper.createObjectNode();
stencilSetNode.put("namespace",ACTIVITI_NAMESPACE_VALUE);
editorNode.put("stencilset", stencilSetNode);
repositoryService.addModelEditorSource(model.getId(),editorNode.toString().getBytes("utf-8"));
response.sendRedirect(ACTIVITI_REDIRECT_MODELER_INDEX + model.getId());
}
}
- 4
驗(yàn)證 activiti modeler
http://ip:port/activiti/activiti-ui.html
搭建工作流平臺(tái)
參考開源社區(qū):