我的项目引入工作流引擎计划阐明
一、工作流引擎选型
1、flowable
flowable基于activiti6衍生进去的版本,flowable目前最新版本是v6.6.0,开发团队是从activiti中决裂进去的,修复了一众activiti6的bug,并在其根底上研发了DMN反对,BPEL反对等等,绝对开源版,其商业版的性能会更弱小。以flowable6.4.1版本为分水岭,大力发展其商业版产品,*开源版本保护不及时,局部性能曾经不再开源版公布,比方表单生成器(表单引擎)、历史数据同步至其余数据源、ES等*。Flowable 是一个应用 Java 编写的轻量级业务流程引擎,应用 Apache V2 license 协定开源。2016 年 10 月,Activiti 工作流引擎的次要开发者来到 Alfresco 公司并在 Activiti 分支根底上开启了 Flowable 开源我的项目。基于 Activiti v6 beta4 公布的第一个 Flowable release 版本为6.0。Flowable 我的项目中包含 BPMN(Business Process Model and Notation)引擎、CMMN(Case Management Model and Notation)引擎、DMN(Decision Model and Notation)引擎、表单引擎(Form Engine)等模块。官方网站:https://flowable.com/open-sou…
2、Camunda
Camunda基于activiti5,所以其保留了PVM,最新版本Camunda7.15,放弃每年公布2个小版本的节奏,开发团队也是从activiti中决裂进去的,倒退轨迹与flowable类似,同时也提供了商业版,不过对于个别企业应用,开源版本也足够了,具体见:https://blog.csdn.net/wxz258/…。官方网站:https://docs.camunda.org/manu…。笔者强烈推荐camunda流程引擎,并在云程低代码平台中应用了camunda,性能和性能体现稳固。
抉择camunda的理由:
(1)通过压力测试验证Camunda BPMN引擎性能和稳定性更好。具体见:https://blog.csdn.net/wxz258/…
(2)性能比较完善,除了BPMN,Camunda还反对企业和社区版本中的CMMN(案例治理)和DMN(决策自动化)。Camunda不仅带有引擎,还带有十分弱小的工具,用于建模,工作治理,操作监控和用户治理,所有这些都是开源的。具体见:https://blog.csdn.net/wxz258/…
3、Osworkflow
Osworkflow是一个轻量化的流程引擎,基于状态机机制,数据库表很少,Osworkflow提供的工作流形成元素有:步骤(step)、条件(conditions)、循环(loops)、分支(spilts)、合并(joins)等,*但不反对会签、跳转、退回、加签等这些操作*,须要本人扩大开发,有肯定难度,如果流程比较简单,osworkflow是很号的抉择,笔者在2008年给某大型国企团体开发OA零碎,就是基于Osworkflow,至今仍稳固运行,性能也很高。官方网站:http://www.opensymphony.com/o…
4、JBPM
JBPM由JBoss公司开发,目前最高版本JPBM7,不过从JBPM5开始曾经跟之前不是同一个产品了,JBPM5的代码根底不是JBPM4,而是从Drools Flow从新开始,*基于Drools Flow技术在国内市场上用的很少,所有不倡议抉择jBPM5当前版本*,jBPM4诞生的比拟早,起初JBPM4创建者Tom Baeyens来到JBoss后,退出Alfresco后很快推出了新的基于jBPM4的开源工作流零碎Activiti, *另外JBPM以hibernate作为数据长久化ORM也已不是支流技术*。笔者在2012年开发某团体BPM平台时,抉择的就是JBPM4.4版本,也是4系列的最初一个版本,进行了大量的扩大开发,才实现中国特色的流程需要。当初工夫节点抉择流程引擎,JBPM不是最佳抉择。官方网站:https://www.jbpm.org/
5、Activiti
activiti由Alfresco软件开发,目前最高版本activiti 7。activiti的版本比较复杂,有activiti5、activiti6、activiti7几个支流版本,选型时让人昏头昏脑,有必要先理解一下activiti这几个版本的倒退历史。activiti5和activiti6的外围leader是Tijs Rademakers,因为团队外部一致,在2017年时Tijs Rademakers来到团队,创立了起初的*flowable*, activiti6以及activiti5代码曾经交接给了 Salaboy团队, *activiti6以及activiti5的代码官网曾经暂停保护了*, *Salaboy团队目前在开发activiti7框架,activiti7内核应用的还是activiti6,并没有为引擎注入更多的新个性,只是在activiti之外的下层封装了一些利用。论断是activiti审慎抉择*。官方网站:https://www.activiti.org/
二、camunda工作流平台搭建
Springboot2.5.4+jdk1.8+camunda7.16.0+mysql5.7
1、创立springboot我的项目,批改pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>loan-approval-spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<camunda.spring-boot.version>7.16.0</camunda.spring-boot.version>
<spring-boot.version>2.5.4</spring-boot.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<mysql.version>8.0.19</mysql.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 流程引擎 -->
<!-- <dependency>-->
<!-- <groupId>org.camunda.bpm.springboot</groupId>-->
<!-- <artifactId>camunda-bpm-spring-boot-starter</artifactId>-->
<!-- <version>${camunda.spring-boot.version}</version>-->
<!-- </dependency>-->
<!-- Rest服务接口,会主动加载camunda-bpm-spring-boot-starter,所以下面的引入能够不要 -->
<dependency>
<groupId>org.camunda.bpm.springboot</groupId>
<artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
<version>${camunda.spring-boot.version}</version>
</dependency>
<!-- web界面模块,会主动加载camunda-bpm-spring-boot-starter,所以下面的引入能够不要 -->
<!-- web界面模块不必须,如果只是提供引擎服务能够不引入 -->
<dependency>
<groupId>org.camunda.bpm.springboot</groupId>
<artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
<version>${camunda.spring-boot.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.5</version>
</dependency>
<dependency>
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine-plugin-spin</artifactId>
<version>${camunda.spring-boot.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.camunda.spin/camunda-spin-dataformat-all -->
<dependency>
<groupId>org.camunda.spin</groupId>
<artifactId>camunda-spin-dataformat-all</artifactId>
<version>1.13.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2、创立或批改application.yaml文件
server:
port: 8080
camunda:
bpm:
database:
type: mysql
admin-user:
id: admin
password: admin
first-name: zhou
last-name: lei
filter:
create: All tasks
#禁止主动部署resources上面的bpmn文件
auto-deployment-enabled: false
#禁止index跳转到Camunda自带的治理界面,默认true
# webapp:
# ndex-redirect-enabled: false
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/camunda?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
3、创立我的项目启动类
package com.sxvbd;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author ZL
* @version 1.0
* @date 2022/1/18 16:59
*/
@SpringBootApplication
public class WebappExampleProcessApplication {
public static void main(String... args) {
SpringApplication.run(WebappExampleProcessApplication.class, args);
}
}
4、我的项目拜访门路
4.1 web模块拜访门路
http://localhost:8080
4.2 RestAPI申请门路
http://localhost:8080/engine-…接口门路
例如:http://localhost:8080/engine-…
三、camunda modeler搭建及应用
3.1、装置
关上下载地址 https://camunda.com/download/modeler/
下载对应零碎的版本,并解压到任意地位
执行 camunda-modeler.exe
(Windows), camunda-modeler.app
(Mac), or camunda-modeler.sh
(Linux),即可启动Camunda Modeler
3.2 汉化
1、首先Camunda Modeler的装置目录,camunda-modeler-4.12.0-win-x64\resources\plugins
2、git clone https://gitee.com/skay463/cam…
3、npm install
4、npm run build
5、在工具栏右侧抉择简体中文,重启Camunda Modeler失效
3.3 编辑流程
新建BPMN流程
点击 File > New File > BPMN Diagram ,创立一个新的流程设计文件
编辑一个简略的流程
- 双击 开始 节点编辑标签,输出“付款申请”
标签能够换行,须要应用Shift +回车
- 点击右面显示的方框,增加一个新的流动
能够看到一个新的流动显示到画布上,双击将它命名为“刷卡付款”
- 点击取信用卡节点右面的扳手能够批改流动类型,这里咱们批改为Service Task(服务类型)
- 新增一个完结节点,并命名为“收到付款”
配置“刷卡付款”节点
服务类型有很多执行的办法,这次咱们应用“external(内部)”工作模式
- 点击“取信用卡”节点,在右侧的面板中批改Implementation(实现)为
External
,批改Topic为charge-card
配置流程参数
- 点击画布的空白处,右侧的面板会显示以后流程自身的参数
这里咱们批改id为payment-retrieval,id是辨别流程的标识
而后批改Name 为“付款流程”
最初确保 Executable 是勾选的,只有Executable被勾选,流程能力执行
- 点击 File > Save File As.. 或者间接点击工具栏中的保留按钮,将流程保留到你喜爱的地位,命名为
payment.bpmn
到此第一局部完结,如果想间接获取到当初为止的进度,能够应用如下命令
git checkout -f Step-1
应用 Camunda Modeler 部署流程
点击工具栏中的部署按钮能够将以后流程部署到流程引擎,点击部署按钮,输出Deployment Name
为 “Payment” ,输出下方REST Endpoint
为http://localhost:8080/engine-rest
(camunda平台门路),而后点击右下角Deploy部署
如果收到胜利提醒,示意部署胜利
四、流程部署及工作启动
步骤:
1、按第三章编辑流程步骤制作流程文件,将生成的bpmn文件到camunda平台
2、创立流程实例并启动流程
3、事务处理人解决以后节点工作。
4、流程进度查问
5、历史信息查问
五、camunda-rest API阐明
Response Codes
Code | Media type | Description |
---|---|---|
200 | application/json | Request successful. |
400 | application/json | In case one of the bpmn resources cannot be parsed. See the Introduction for the error response format. |
5.1、部署
5.1.1 部署bpmn文件
办法:POST /deployment/create
参数:
参数 | 类型 | 阐明 |
---|---|---|
upload | File | bpmn流程文件 |
响应后果:
Name | Type | Description |
---|---|---|
links | List | Link to the newly created deployment with method , href and rel . |
id | String | The id of the deployment. |
name | String | The name of the deployment. |
source | String | The source of the deployment. |
tenantId | String | The tenant id of the deployment. |
deploymentTime | String | The time when the deployment was created. |
deployedProcessDefinitions | Object | A JSON Object containing a property for each of the process definitions, which are successfully deployed with that deployment. The key is the process definition id, the value is a JSON Object corresponding to the process definition, which is defined in the Process Definition resource. |
deployedCaseDefinitions | Object | A JSON Object containing a property for each of the case definitions, which are successfully deployed with that deployment. The key is the case definition id, the value is a JSON Object corresponding to the case definition, which is defined in the Case Definition resource. |
deployedDecisionDefinitions | Object | A JSON Object containing a property for each of the decision definitions, which are successfully deployed with that deployment. The key is the decision definition id, the value is a JSON Object corresponding to the decision definition, which is defined in the Decision Definition resource. |
deployedDecisionRequirementsDefinitions | Object | A JSON Object containing a property for each of the decision requirements definitions, which are successfully deployed with that deployment. The key is the decision requirements definition id, the value is a JSON Object corresponding to the decision requirements definition, which is defined in the Decision Requirements Definition resource. |
测试用例:POST /deployment/create
胜利
{
"links": [
{
"method": "GET",
"href": "http://localhost:38080/rest-test/deployment/aDeploymentId",
"rel": "self"
}
],
"id": "aDeploymentId",
"name": "aName",
"source": "process application",
"deploymentTime": "2013-01-23T13:59:43.000+0200",
"tenantId": null,
"deployedProcessDefinitions": {
"aProcDefId": {
"id": "aProcDefId",
"key": "aKey",
"category": "aCategory",
"description": "aDescription",
"name": "aName",
"version": 42,
"resource": "aResourceName",
"deploymentId": "aDeploymentId",
"diagram": "aResourceName.png",
"suspended": true,
"tenantId": null,
"versionTag": null
}
},
"deployedCaseDefinitions": null,
"deployedDecisionDefinitions": null,
"deployedDecisionRequirementsDefinitions": null
}
失败
{
"type": "ParseException",
"message": "ENGINE-09005 Could not parse BPMN process. Errors: Exclusive Gateway 'ExclusiveGateway_1' has outgoing sequence flow 'SequenceFlow_0' without condition which is not the default flow.",
"details": {
"invoice.bpmn": {
"errors": [
{
"message": "Exclusive Gateway 'ExclusiveGateway_1' has outgoing sequence flow 'SequenceFlow_0' without condition which is not the default flow.",
"line": 77,
"column": 15,
"mainBpmnElementId": "ExclusiveGateway_1",
"bpmnElementIds": [
"ExclusiveGateway_1",
"SequenceFlow_0"
]
}
],
"warnings": [
{
"message": "It is not recommended to use a cancelling boundary timer event with a time cycle.",
"line": 87,
"column": 20,
"mainBpmnElementId": "BoundaryEvent_1",
"bpmnElementIds": [
"BoundaryEvent_1"
]
}
]
}
}
5.1.2 部署信息List查问
办法:GET /deployment
参数:
Name | Description |
---|---|
id | Filter by deployment id. |
name | Filter by the deployment name. Exact match. |
nameLike | Filter by the deployment name that the parameter is a substring of. The parameter can include the wildcard % to express like-strategy such as: starts with (% name), ends with (name% ) or contains (% name% ). |
source | Filter by the deployment source. |
withoutSource | Filter by the deployment source whereby source is equal to null . |
tenantIdIn | Filter by a comma-separated list of tenant ids. A deployment must have one of the given tenant ids. |
withoutTenantId | Only include deployments which belong to no tenant. Value may only be true , as false is the default behavior. |
includeDeploymentsWithoutTenantId | Include deployments which belong to no tenant. Can be used in combination with tenantIdIn . Value may only be true , as false is the default behavior. |
after | Restricts to all deployments after the given date. By default*, the date must have the format yyyy-MM-dd'T'HH:mm:ss.SSSZ , e.g., 2013-01-23T14:42:45.000+0200 . |
before | Restricts to all deployments before the given date. By default*, the date must have the format yyyy-MM-dd'T'HH:mm:ss.SSSZ , e.g., 2013-01-23T14:42:45.000+0200 . |
sortBy | Sort the results lexicographically by a given criterion. Valid values are id , name , deploymentTime and tenantId . Must be used in conjunction with the sortOrder parameter. |
sortOrder | Sort the results in a given order. Values may be asc for ascending order or desc for descending order. Must be used in conjunction with the sortBy parameter. |
firstResult | Pagination of results. Specifies the index of the first result to return. |
maxResults | Pagination of results. Specifies the maximum number of results to return. Will return less results if there are no more results left. |
响应后果:
ame | Type | Description |
---|---|---|
id | String | The id of the deployment. |
name | String | The name of the deployment. |
source | String | The source of the deployment. |
tenantId | String | The tenant id of the deployment. |
deploymentTime | Date | The date and time of the deployment. |
测试用例:GET /deployment?name=deploymentName
胜利
[
{
"id": "someId",
"name": "deploymentName",
"source": "process application",
"tenantId": null,
"deploymentTime": "2013-04-23T13:42:43.000+0200"
}
]
5.1.3 部署信息List count查问
办法:GET /deployment
参数:
Name | Description |
---|---|
id | Filter by deployment id. |
name | Filter by the deployment name. Exact match. |
nameLike | Filter by the deployment name that the parameter is a substring of. The parameter can include the wildcard % to express like-strategy such as: starts with (% name), ends with (name% ) or contains (% name% ). |
source | Filter by the deployment source. |
withoutSource | Filter by the deployment source whereby source is equal to null . |
tenantIdIn | Filter by a comma-separated list of tenant ids. A deployment must have one of the given tenant ids. |
withoutTenantId | Only include deployments which belong to no tenant. Value may only be true , as false is the default behavior. |
includeDeploymentsWithoutTenantId | Include deployments which belong to no tenant. Can be used in combination with tenantIdIn . Value may only be true , as false is the default behavior. |
after | Restricts to all deployments after the given date. By default*, the date must have the format yyyy-MM-dd'T'HH:mm:ss.SSSZ , e.g., 2013-01-23T14:42:45.000+0200 . |
before | Restricts to all deployments before the given date. By default*, the date must have the format yyyy-MM-dd'T'HH:mm:ss.SSSZ , e.g., 2013-01-23T14:42:45.000+0200 . |
sortBy | Sort the results lexicographically by a given criterion. Valid values are id , name , deploymentTime and tenantId . Must be used in conjunction with the sortOrder parameter. |
sortOrder | Sort the results in a given order. Values may be asc for ascending order or desc for descending order. Must be used in conjunction with the sortBy parameter. |
firstResult | Pagination of results. Specifies the index of the first result to return. |
maxResults | Pagination of results. Specifies the maximum number of results to return. Will return less results if there are no more results left. |
响应后果:
ame | Type | Description |
---|---|---|
id | String | The id of the deployment. |
name | String | The name of the deployment. |
source | String | The source of the deployment. |
tenantId | String | The tenant id of the deployment. |
deploymentTime | Date | The date and time of the deployment. |
测试用例:GET /deployment?name=deploymentName
胜利
[
{
"id": "someId",
"name": "deploymentName",
"source": "process application",
"tenantId": null,
"deploymentTime": "2013-04-23T13:42:43.000+0200"
}
]
5.1.4 删除部署信息
5.1.5 重新部署redeploy
5.1.6 依据ID查问部署信息
5.1.7 部署信息资源列表查问
5.1.8 依据ID查问部署资源信息
5.1.9 依据ID查问部署资源信息(binary)
5.2、流程设计
const modeling = this.bpmnModeler.get('modeling');
const elementRegistry = this.bpmnModeler.get('elementRegistry');
let todo = data.filter(item => item.state == 1) // 已实现
let notStart = data.filter(item => item.state == 0) // 未开始
todo.forEach(item =>{
let node = elementRegistry.filter(item_ => item_.id == item.key)
modeling.setColor(node[0], {
stroke: 'green',
});
})
notStart.forEach(item =>{
let node = elementRegistry.filter(item_ => item_.id == item.key)
modeling.setColor(node[0], {
stroke: 'red',
});
})
5.3、工作发动解决
5.6、自定义事件
entryNode.appendChild(html)
// TODO JASON
if (entryNode.querySelector('input')) {
if (entry.id === 'formKey') {
entryNode.querySelector('input').addEventListener('click', function() {
console.log(entry)
//回显
let val={};val[entry.id] = "";
self.applyChanges(entry, val, entryNode)
// self._modeling.updateProperties(self._current.element,{'flowable:candidateUsers': 'userId_123'})
// console.log(entry)
v.$dialogFormKey.show(self)
})
}
// if (entry.id === 'candidateUsers' || entry.id === 'candidateStarterUsers') {
if (entry.id === 'camunda:Approving' || entry.id === 'candidateStarterUsers') {
entryNode.querySelector('input').addEventListener('click', function() {
//回显
let val={};val[entry.id] = "";
self.applyChanges(entry, val, entryNode)
// self._modeling.updateProperties(self._current.element,{'flowable:candidateUsers': 'userId_123'})
// console.log(entry)
v.$dialog.show(self)
})
}
}
发表回复