项目引入工作流引擎方案说明

一、工作流引擎选型

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

image-20220120171611854

4.2 RestAPI请求路径

http://localhost:8080/engine-...接口路径

例如:http://localhost:8080/engine-...

image-20220120171949371

三、camunda modeler搭建及使用

3.1、安装

打开下载地址 https://camunda.com/download/modeler/

img

下载对应系统的版本,并解压到任意位置

img

执行 camunda-modeler.exe (Windows), camunda-modeler.app (Mac), or camunda-modeler.sh(Linux),即可启动Camunda Modeler

img

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 ,创建一个新的流程设计文件

img

编辑一个简单的流程

img

  1. 双击 开始 节点编辑标签,输入“付款请求”
标签可以换行,需要使用Shift +回车
  1. 点击右面显示的方框,添加一个新的活动

可以看到一个新的活动显示到画布上,双击将它命名为“刷卡付款”

  1. 点击取信用卡节点右面的扳手可以修改活动类型,这里我们修改为Service Task(服务类型)

img

  1. 新增一个结束节点,并命名为“收到付款”

img

配置“刷卡付款”节点

服务类型有很多执行的方法,这次我们使用“external(外部)”任务模式

  1. 点击“取信用卡”节点,在右侧的面板中修改Implementation(实现)为 External ,修改Topic为 charge-card

img

配置流程参数

  1. 点击画布的空白处,右侧的面板会显示当前流程本身的参数

这里我们修改id为payment-retrieval,id是区分流程的标识

然后修改Name 为“付款流程”

最后确保 Executable 是勾选的,只有Executable被勾选,流程才能执行

img

  1. 点击 File > Save File As.. 或者直接点击工具栏中的保存按钮,将流程保存到你喜欢的位置,命名为 payment.bpmn
到此第一部分结束,如果想直接获取到现在为止的进度,可以使用如下命令
git checkout -f Step-1

使用 Camunda Modeler 部署流程

点击工具栏中的部署按钮可以将当前流程部署到流程引擎,点击部署按钮,输入Deployment Name 为 “Payment” ,输入下方REST Endpointhttp://localhost:8080/engine-rest (camunda平台路径),然后点击右下角Deploy部署

img

如果收到成功提示,表示部署成功

img

四、流程部署及任务启动

步骤:

1、按第三章编辑流程步骤制作流程文件,将生成的bpmn文件到camunda平台

2、创建流程实例并启动流程

3、事务处理人处理当前节点任务。

4、流程进度查询

5、历史信息查询

五、camunda-rest API说明

Response Codes
CodeMedia typeDescription
200application/jsonRequest successful.
400application/jsonIn 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

参数:

参数类型说明
uploadFilebpmn流程文件

响应结果:

NameTypeDescription
linksListLink to the newly created deployment with method, href and rel.
idStringThe id of the deployment.
nameStringThe name of the deployment.
sourceStringThe source of the deployment.
tenantIdStringThe tenant id of the deployment.
deploymentTimeStringThe time when the deployment was created.
deployedProcessDefinitionsObjectA 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.
deployedCaseDefinitionsObjectA 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.
deployedDecisionDefinitionsObjectA 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.
deployedDecisionRequirementsDefinitionsObjectA 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

参数:

NameDescription
idFilter by deployment id.
nameFilter by the deployment name. Exact match.
nameLikeFilter 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%).
sourceFilter by the deployment source.
withoutSourceFilter by the deployment source whereby source is equal to null.
tenantIdInFilter by a comma-separated list of tenant ids. A deployment must have one of the given tenant ids.
withoutTenantIdOnly include deployments which belong to no tenant. Value may only be true, as false is the default behavior.
includeDeploymentsWithoutTenantIdInclude deployments which belong to no tenant. Can be used in combination with tenantIdIn. Value may only be true, as false is the default behavior.
afterRestricts 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.
beforeRestricts 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.
sortBySort the results lexicographically by a given criterion. Valid values are id, name, deploymentTime and tenantId. Must be used in conjunction with the sortOrderparameter.
sortOrderSort 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.
firstResultPagination of results. Specifies the index of the first result to return.
maxResultsPagination of results. Specifies the maximum number of results to return. Will return less results if there are no more results left.

响应结果:

ameTypeDescription
idStringThe id of the deployment.
nameStringThe name of the deployment.
sourceStringThe source of the deployment.
tenantIdStringThe tenant id of the deployment.
deploymentTimeDateThe 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

参数:

NameDescription
idFilter by deployment id.
nameFilter by the deployment name. Exact match.
nameLikeFilter 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%).
sourceFilter by the deployment source.
withoutSourceFilter by the deployment source whereby source is equal to null.
tenantIdInFilter by a comma-separated list of tenant ids. A deployment must have one of the given tenant ids.
withoutTenantIdOnly include deployments which belong to no tenant. Value may only be true, as false is the default behavior.
includeDeploymentsWithoutTenantIdInclude deployments which belong to no tenant. Can be used in combination with tenantIdIn. Value may only be true, as false is the default behavior.
afterRestricts 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.
beforeRestricts 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.
sortBySort the results lexicographically by a given criterion. Valid values are id, name, deploymentTime and tenantId. Must be used in conjunction with the sortOrderparameter.
sortOrderSort 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.
firstResultPagination of results. Specifies the index of the first result to return.
maxResultsPagination of results. Specifies the maximum number of results to return. Will return less results if there are no more results left.

响应结果:

ameTypeDescription
idStringThe id of the deployment.
nameStringThe name of the deployment.
sourceStringThe source of the deployment.
tenantIdStringThe tenant id of the deployment.
deploymentTimeDateThe 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',
            });
          })
            

image.png

5.3、任务发起处理

image.png

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)
            })
          }
        }
5.5、权限对接
5.6、历史流程跟踪
5.7、第三方对接

西安小哥
1.3k 声望88 粉丝

thinking、doing、do better、do much better than today。exchange 、sharing、improve as quickly as possible。