项目引入工作流引擎方案说明
一、工作流引擎选型
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模块访问路径
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)
})
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。