1 开始
1.1 状态机
01.状态机
状态机解决流程问题
工作流:就是一个可以处理复杂情况的状态机。
例如,员工请假这个流程:首先员工提交请假申请,假设有项目经理进行审批,审批有两种结果:通过或者拒绝。
实现上面这个需求:
1. 创建一张请假表,表中有员工的 id,请假的天数、请假的理由、项目经理的 id、请假的状态 status。
2. 当员工请假的时候,就自动向这张表中添加一条记录。
3. 然后,当项目经理登录到 OA 的时候,就来这张表中查询自己需要审批的请假申请,查到之后,可以选择批准或者拒绝。
4. 接下来,员工登录之后,就可以查询到自己的请假申请的审批结果。
在这样的实现思路中,请假的流程我们是通过 status 这个字段来控制的。例如:
● status=0 表示待审批
● status=1 表示审批通过
● status=2 表示拒绝
上面这个例子,status 就是状态码,通过这个字段的值来控制流程的状态,这种方式我们可以称之为使用状态机来解决流程问题,但是,这种思路,只能解决非常简单的流程问题。
02.一些复杂的流程
a.报销审批流程
在这个流程中,已经没法使用 status 去描述这个请假走到哪一步了。如果非要用 status,那么 status 可能会有很多取值:
● 0:表示员工提交报销申请
● 1: 表示部门经理审批通过
● 2:表示部门经理审批不通过,员工需要重新提交
● 3:表示大区经理审批通过
● 4:表示大区经理审批不通过
● 。。。
b.笔记本电脑生产流程
这个流程中, 不仅有串行任务,也有并行任务。虽然技术上来说,status 也还能做,
但是,用 status 字段去描述这个流程,会非常非常复杂。
03.三大工作流
三大主流工作流,只要掌握其中一个,另外两个可以非常容易的上手。
最早的工作流是 jBPM,可以目前市面上大部分工作流的共同祖先。
● Activiti:当 jBPM 发展到 jBPM4 这个版本的时候,内部发生了分歧,然后一波人出来单干,基于 jBPM4 开发出来了 Activiti5;留下来的人,继续开发 jBPM5 的时候,几乎完全重写了 jBPM4 的代码。目前 Activiti 的设计侧重于云,即更靠拢 Spring Cloud、Docker、K8s 等。
● Flowable:Activiti5 在发展了一段时间之后,又从中分离出来一个团队,开发出来了 Flowable。Flowable 目前的核心思路还是做一个功能非常非常完善的流程引擎工具。除了常用的最最基本的工作流之外,Flowable 还提供了很多扩展点。
● Camunda:Activiti5 发展没多久,从 Activiti5 中分离中的团队,开发的 Camunda。在这三个主流的流程引擎中,Camunda 是最为轻量级的一个,如果我们的系统,当用户在使用的过程中,需要动态的绘制流程图,那么可以使用 Camunda,这是一个小巧的工具,可以非常的方便的嵌入到我们自己的系统中。Camunda 还提供了一个 bpmn.js 的工具,可以非常方便的实现流程图的绘制。
04.流程图
工作流执行的基础是流程图。
一个完整的流程,要干嘛,先得画出来一个完整的流程图。
上面介绍了三种不同的工作流,那么三种不同的工作流的流程图绘制方式是否一样?
其实,流程图的绘制,有一套统一的标准:BPMN(Business Process Model And Notation),中文译作业务流程模型和标记法。
BPMN 就是一套图形化表示法,用图形来绘制、梳理业务流程模型。就是说,BPMN 其实是一套非常古老的流程图规范,Activiti、Flowable 以及 Camunda 都是支持这个规范的。所以,无论使用哪一个流程图,都可以依照 BPMN 规范去绘制流程图。
虽然 BPMN 大家都支持,但是,在具体的使用细节上,不同的流程引擎还是有差别的。
1.2 核心组成
01.流程分类
事件
连线
任务
网关
02.事件
开始事件/结束事件等等。
这是我们上面用到的事件,实际上,还有很多其他类型的事件。
03.连线
连接各个不同元素之间的线条,就是连线。
注意,线条之上,可能会有条件。
例如,在互斥网关上,满足一定的条件,流程图就继续往下走,不满足条件,流程图就回到之前的某一个位置上。
04.任务
用户任务:需要人工参与完成的工作建模。
服务任务:机器自动完成的事情,例如用户请假,经理审批通过,审批通过之后,想通过企业微信给用户发送一个通知,告诉他请假通过。这样的任务,可以使用服务任务,当流程走到这一步的时候,自动调用某一个 Java Bean,或者某一个远程服务去完成通知的发送,这是自动完成的,不需要人工介入。
活动:活动可以算是一种特殊的任务。活动之中,往往可以在活动中,调用另外一个流程使之作为当前流程的子流程去执行。活动一般又可以继续细分为用户活动、脚本活动等等。。。
接收任务:这个接收任务中,其实并不需要做什么额外的事情,流程到这一步就自动停下来,需要人工去助力一把,去推动流程继续向下走。
发送任务:将消息发送给外部的参与者。
脚本任务:一个自动化的活动,当流程执行到脚本任务的时候,自动执行相应的脚本。齿轮表示这是一个服务任务,也就是系统自动完成的,系统自动完成的方式有很多种,其中一种是提前将自己的业务逻辑在 Java 类中写好,然后这里配置一下类的完整路径即可
业务规则任务:BPMN2.0 中引入的用来对接业务规则的引擎,业务规则主要用于同步执行一个或者多个规则。虽然这里分类比较多,但是实际上,任务主要就两种:用户任务:需要用户介入的任务。服务任务:机器自动完成的任务。发送任务、接收任务、脚本任务等等,这些其实都是服务任务的细分而已。
05.网关
互斥网关:这个可以有多个入口,但是只有一个有效的出口。
并行网关:并行网关一般是成对出现的,当有并行操作的时候,可以使用并行网关。
相容网关:这种网关可能会存在多个有效的出口。
事件网关:通过中间事件驱动的网关,当等待的事件触发之后,才会触发决策。
1.3 实战流程
01.绘制一个报销流程图,大致流程:
1. 启动一个流程。
2. 执行一个用户任务,这个用户任务交给流程的启动人去执行。这个用户任务中,填入报销材料,例如用户名、金额、用途。
3. 系统自动判断一下/或者人工判断报销金额是否大于 1000。
4. 如果报销金额小于等于 1000,那么这个报销任务交给 组长审批:
a. 组长审批通过,则流程结束。
b. 组长审批不通过,则流程回到第 2 步,用户重新去填写报销资料。
5. 如果报销金额大于 1000,那么这个报销任务先交给经理审批:
a. 经理审批通过,则交给 CEO 审批:
ⅰ. CEO 审批通过,流程结束。
ⅱ. CEO 审批不通过,流程回到步骤 2 中。
b. 经理审批不通过,则流程回到步骤 2 中。
02.对于一个 UserTask 而言,任务处理人有四种:
流程发起人,由流程的启动人/发起人来处理这个流程。
单个用户,直接指定某一个具体的用户来处理这个流程,注意这里只能指定一个用户,并且这个用户将来在处理任务的时候,不需要认领,直接就可以处理。
候选用户:可以同时指定多个用户来处理这个 UserTask,将来用户在处理的时候,需要先认领(Claim)任务,然后才能处理。
候选组:可以同时指定多个用户组来处理这个 UserTask,这个处理的时候,也需要先认领,再处理。
03.基本概念
流程定义(ProcessDefinition):我们绘制的流程图、流程的 XML 文件,就是我们的流程定义。
流程(ProcessInstance):一个启动了流程实例就是一个流程,流程可以是已经执行完毕的,也可以是正在执行中的。流程的定义相当于是一个类,而流程则相当于是一个对象。
任务(Task):一个 ProcessInstance 中,需要具体处理的节点就是一个任务。
在 flowable-ui 中,绘制好的流程图,可以直接部署称为一个 App。
04.表单问题
在流程中,传递流程参数有两种方式:流程变量、表单
这两种方式都可以传递参数,区别在于,流程变量是零散的,而表单是整的。
对于通过表单传递的参数,我们也可以按照流程变量的方式去访问单个的表单参数,
例如在上面的流程图中,我们有 ${money <= 1000},这里的 money 实际上是表单中的参数,但是我们可以直接通过 $ 表达式去访问。
还有如 ${managers_approve_or_reject_radio_button=="拒绝"},也是直接访问表单中的变量。
05.注意
在一个流程图中,开始节点必须有且只有一个,结束节点可以有多个。
1.4 附:flowable
01.安装
a.源码地址
https://github.com/flowable/flowable-engine
b.编译的步骤
1.clone 代码: git clone [email protected] :flowable/flowable-engine.gi
2.切换分支 git checkout -b origin/6.7.2切换到 6.7.2 这个版本。
c.先来看下源码的目录结构
LICENSE:开源协议。
README.md:flowable 介绍文档。
distro:主要是保存了不同环境下的信息。
docker:将 flowable 构建成 docker 镜像的脚本。
docs:flowable 的文档。
ide-settings:这是如果想在 Eclipse 或者 IDEA 中快速使用 flowable 时候的配置。
k8s:flowable 支持 k8s 的一些脚本和配置。
modules:flowable 中所有的核心功能代码都在这个里边。
pom.xml:maven 的坐标文件。
qa:提供了很多各种各样的配置模版,例如如果我们需要在传统的 SSM 中配置 flowable,配置文件可以直接参考 qa 中的,但是我们现在主要是 Spring Boot 开发,在 Spring Boot 中,基本上用不到 qa 中的配置模版。
scripts:这个目录下放了常用的脚本文件。
tooling:这个目录中列出来了单元测试的模版。
02.项目编译要点
a.第1步
用 IDEA 打开项目。在 IDEA 中,直接 open 源码即可,不需要 Import Project。
b.第2步
由于 IDEA 无法识别出所有的 Maven 工程,查看是否识别出来 Maven 工程的方式:
pom.xml 文件是蓝色的,或者工程名加粗了,
【重点!!!】如果有 IDEA 未识别出来的 Maven 工程,都需要挨个手动添加,添加方式就是打开项目的 pom.xml 文件,右键单击,选择 Add as Maven Project(添加为Maven项目)
c.第3步
对于 Maven 工程,IDEA 会自动去下载所需要的依赖,但是由于这里需要下载的依赖比较多,所以下载的时候比较费时间,耐心等一下。最终也有可能会下载失败:i:先去本地 Maven 仓库,搜索以 .lastupdated结尾的文件,并删除。
d.注意
如果前面步骤不管用,那么就去 settings.xml 文件中,修改远程仓库地址,切换为 阿里云或者华为云等提供的镜像站,然后再重新导入。
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
03.H2数据库
1.Java 编写的数据库。
2.可以基于内存来使用。
3.也可以基于文件,基于文件,类似于移动端的 Sqlite。
1.5 附:flowable-ui
00.工具
官方:flowable-ui
IDEA:flowable-bpmn-visualizer
Eclipse:Flowable Eclipse Designer
Camunda:bpmn.js
01.安装
a.下载
https://github.com/flowable/flowable-engine/releases/download/flowable-6.7.2/flowable-6.7.2.zip
这个 zip 包下载之后,里边有一个 wars 文件夹,里边包含了 flowable-ui 的 war 包。
然后,就像启动 Spring Boot 一样,直接启动这个 war 包即可:
b.启动
java -jar flowable-ui.war
c.访问
http://localhost:8080/flowable-ui/
admin,test
zs/ls/ww,123456
02.功能模块
a.介绍1
flowable-ui 是完整的 flowable 体验 DEMO,而不仅仅只是一个流程图的绘制工具。所以它里边不仅可以画流程图,还可以运行流程图,既然能够运行流程图,那么就需要身份管理。
1.任务应用程序:我们绘制好的流程图,可以直接将之发布到一个应用中,然后在这里进行部署,这个模块其实就是这些部署的应用程序。
2.建模器应用程序:这个专门用来画流程图的。
3.管理员应用程序:这个主要用来管理应用,一些具有管理员权限的用户,可以通过这个功能模块去查询 BPMN、DMN、FORM 等等信息。
4.身份管理应用程序:这个功能模块,为所有的 flowable-ui 应用程序提供一个单点登录功能,并且还可以为这些用户设置用户组、用户权限等。
b.介绍2
Flowable提供了几个web应用,用于演示及介绍Flowable项目提供的功能:
1.Flowable Task: 运行时任务应用。提供了启动流程实例、编辑任务表单、完成任务,以及查询流程实例与任务的功能。
2.Flowable Modeler: 让具有建模权限的用户可以创建流程模型、表单、选择表与应用定义。
3.Flowable Admin: 管理应用。让具有管理员权限的用户可以查询BPMN、DMN、Form及Content引擎,并提供了许多选项用于修改流程实例、任务、作业等。管理应用通过REST API连接至引擎,并与Flowable Task应用及Flowable REST应用一同部署。
4.Flowable IDM: 身份管理应用。为所有Flowable UI应用提供单点登录认证功能,并且为拥有IDM管理员权限的用户提供了管理用户、组与权限的功能。
c.简单来说
Flowable Task:测试、体验流程
Flowable Modeler:画流程图
Flowable Admin:后台管理
Flowable IDM:创建用户、分配角色
03.创建流程
a.managers_approve_or_reject
managers_approve_or_reject
${form_managers_approve_or_reject_outcome=="同意"}
${form_managers_approve_or_reject_outcome=="拒绝"}
b.ceo_approve_or_reject
${form_ceo_approve_or_reject_outcome=="同意"}
${form_ceo_approve_or_reject_outcome=="拒绝"}
c.leader_approve_or_reject
${form_leader_approve_or_reject_outcome=="同意"}
${form_leader_approve_or_reject_outcome=="拒绝"}
d.managers_approve_or_reject_radio_button
${managers_approve_or_reject_radio_button=="同意"}
${managers_approve_or_reject_radio_button=="拒绝"}
1.6 附:flowable-bpmn-visualizer
01.主要组件
a.开始事件
ID startEvent
Name 开始一个请假流程
Documentation
Form key
Form field validation
Message reference
Escalation reference
Error reference
Signal reference
Form properties Add property
Execution listeners Add execution listeners
b.任务
ID approveRequest
Name 批准或者拒绝请假请求
Documentation
Is for compensation
Asynchronous
Assignee 当前的UserTask由哪个用户处理,这里如果是传递变量需要用 ${} 表达式,如果是字符串,直接写即可,例如,这个节点由一个名为 BNTang 的用户来处理
Candidate Users 当前的UserTask由哪些候选用户处理
Candidate Groups 当前的UserTask由哪个Group来处理,Group相当于我们平时说的角色
Skip expression
Due date
Category
Form key
Form field validation
Priority
Form properties Add property
Execution listeners Add execution listeners
c.网关(Exclusive gateway,排它网关)
ID approveOrRejectGateway
Name
Documentation
Default flow element gateway_to_approve
d.任务
ID sendApproveEmail
Name 发送请假审批通过的邮箱
Documentation
Is for compensation
Asynchronous
Exclusive
Expression
Delegate expression
Class
Skip expression
Is activity triggerable?
Result variable name
Use local scope for result varaible
Failed job retry cycle
Fields Add field
Execution listeners Add execution listeners
-----------------------------------------------------------------------------------------------------
ID sendRejectEmail
Name 发送请假被拒绝的邮箱
Documentation
Is for compensation
Asynchronous
Exclusive
Expression
Delegate expression
Class top.it6666.flowable.Approve
Skip expression
Is activity triggerable?
Result variable name
Use local scope for result varaible
Failed job retry cycle
Fields Add field
Execution listeners Add execution listeners
e.结束任务
ID endEvent
Name
Documentation
Escalation reference
Error reference
Execution listeners Add execution listeners
02.主要组件
a.连接1
ID startEvent_to_approveRequest
Name
Documentation
Source reference startEvent
Target reference approveRequest
Condition expression 条件表达式,用于判断流程的走向
Condition expression type
Execution listeners Add execution listeners
b.连接2
ID approveRequest_to_approveOrRejectGateway
Name
Documentation
Source reference approveRequest
Target reference approveOrRejectGateway
Condition expression
Condition expression type
Execution listeners Add execution listeners
c.排它网关,是
ID gateway_to_approve
Name 从网关到请求同意
Documentation
Source reference approveOrRejectGateway
Target reference sendApproveEmail
Condition expression "${approve}"
Condition expression type tFormalExpression
Default flow element
Execution listeners Add execution listeners
-----------------------------------------------------------------------------------------------------
从排他性网关出来的线条中,有一个 Condition expression,这个表示这个线条执行的条件。
具体来说,就是当用户在审批的时候,本质上其实就是传递一个变量,变量值为 true 或者 false。
下图中的 ${approve} 表示这个变量的名字为 approve。
d.排它网关,否
ID gateway_to_reject
Name 从网关到请求被拒绝
Documentation
Source reference approveOrRejectGateway
Target reference sendRejectEmail
Condition expression "${!approve}"
Condition expression type tFormalExpression
Default flow element
Execution listeners Add execution listeners
-----------------------------------------------------------------------------------------------------
从排他性网关出来的线条中,有一个 Condition expression,这个表示这个线条执行的条件。
具体来说,就是当用户在审批的时候,本质上其实就是传递一个变量,变量值为 true 或者 false。
下图中的 ${approve} 表示这个变量的名字为 approve。
e.连接3
ID sendApproveEmail_to_endEvent
Name
Documentation
Source reference sendApproveEmail
Target reference endEvent
Condition expression
Condition expression type
Execution listeners Add execution listeners
f.连接4
ID sendRejectEmail_to_endEvent
Name
Documentation
Source reference sendRejectEmail
Target reference endEvent
Condition expression
Condition expression type
Execution listeners Add execution listeners
03.示例XML
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
<!--
process:流程定义的根元素,一个BPMN 2.0文档中可以包含多个process元素,每个process元素都是一个独立的流程定义。
-->
<process id="Demo00" name="Demo00" isExecutable="true">
<!--
startEvent:开始事件,流程定义中必须包含一个开始事件,用于标识流程定义的启动点。
-->
<startEvent id="startEvent" name="开始一个请假流程"/>
<!--
userTask:用户任务,流程定义中可以包含多个用户任务,用于标识流程中的一个任务。
-->
<userTask id="approveRequest" name="批准或者拒绝请假请求"/>
<!--
sequenceFlow:顺序流,用于标识流程中的执行顺序。
-->
<sequenceFlow id="startEvent_to_approveRequest" sourceRef="startEvent" targetRef="approveRequest"/>
<!--
exclusiveGateway:排他网关,用于标识流程中的分支和合并。
-->
<exclusiveGateway id="approveOrRejectGateway" default="gateway_to_approve"/>
<!--
sequenceFlow:顺序流,用于标识流程中的执行顺序。
-->
<sequenceFlow id="approveRequest_to_approveOrRejectGateway" sourceRef="approveRequest" targetRef="approveOrRejectGateway"/>
<!--
serviceTask:服务任务,用于标识流程中的一个服务任务。
-->
<serviceTask id="sendApproveEmail" flowable:exclusive="true" name="发送请假审批通过的邮箱"/>
<!--
sequenceFlow:顺序流,用于标识流程中的执行顺序。
-->
<sequenceFlow id="gateway_to_approve" sourceRef="approveOrRejectGateway" targetRef="sendApproveEmail">
<!--
conditionExpression:条件表达式,用于标识顺序流的条件。
-->
<conditionExpression xsi:type="tFormalExpression">${approve}</conditionExpression>
</sequenceFlow>
<!--
serviceTask:服务任务,用于标识流程中的一个服务任务。
-->
<serviceTask id="sendRejectEmail" flowable:exclusive="true" name="发送请假被拒绝的邮箱" flowable:class="top.it6666.flowable.Approve"/>
<!--
sequenceFlow:顺序流,用于标识流程中的执行顺序。
-->
<sequenceFlow id="gateway_to_reject" sourceRef="approveOrRejectGateway" targetRef="sendRejectEmail" name="从网关到请求被拒绝">
<conditionExpression xsi:type="tFormalExpression">${!approve}</conditionExpression>
</sequenceFlow>
<!--
sequenceFlow:顺序流,用于标识流程中的执行顺序。
-->
<sequenceFlow id="sendApproveEmail_to_endEvent" sourceRef="sendApproveEmail" targetRef="endEvent"/>
<sequenceFlow id="sendRejectEmail_to_endEvent" sourceRef="sendRejectEmail" targetRef="endEvent"/>
<endEvent id="endEvent"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_Demo00">
<bpmndi:BPMNPlane bpmnElement="Demo00" id="BPMNPlane_Demo00">
<!--
开始节点
-->
<bpmndi:BPMNShape id="shape-720716bb-7992-4c79-9289-7697c965a250" bpmnElement="startEvent">
<omgdc:Bounds x="-550.0" y="65.0" width="30.0" height="30.0"/>
</bpmndi:BPMNShape>
<!--
用户任务
-->
<bpmndi:BPMNShape id="shape-4bbbd282-b4b4-4ebc-a979-f148f400e7d2" bpmnElement="approveRequest">
<omgdc:Bounds x="-425.0" y="40.0" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<!--
sequenceFlow:顺序流,用于标识流程中的执行顺序。
-->
<bpmndi:BPMNEdge id="edge-c9de96ec-da86-43c6-b84f-c185782ac4ff" bpmnElement="startEvent_to_approveRequest">
<omgdi:waypoint x="-520.0" y="80.0"/>
<omgdi:waypoint x="-425.0" y="80.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-7d5f411f-f2e2-4c4a-ac43-368ef7489c11" bpmnElement="approveOrRejectGateway">
<omgdc:Bounds x="-230.0" y="60.0" width="40.0" height="40.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-20bcfcad-f8b8-4a3c-8f2b-1540b0903b10" bpmnElement="approveRequest_to_approveOrRejectGateway">
<omgdi:waypoint x="-325.0" y="80.0"/>
<omgdi:waypoint x="-275.79172" y="80.0"/>
<omgdi:waypoint x="-230.0" y="80.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-c1f0d7a5-355f-4cc7-857e-ec50ad44e5f9" bpmnElement="sendApproveEmail">
<omgdc:Bounds x="-104.49818" y="40.0" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-7388f00b-1867-4b52-87e2-1c8dbcac5588" bpmnElement="gateway_to_approve">
<omgdi:waypoint x="-190.0" y="80.0"/>
<omgdi:waypoint x="-147.24908" y="80.00001"/>
<omgdi:waypoint x="-104.49818" y="80.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-28a27ae4-28cf-4257-8858-7fd1222f9b58" bpmnElement="sendRejectEmail">
<omgdc:Bounds x="-104.49817" y="165.00002" width="100.0" height="80.0"/>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="edge-c687124b-5335-44b3-9e84-4c08c759e566" bpmnElement="gateway_to_reject">
<omgdi:waypoint x="-210.0" y="100.0"/>
<omgdi:waypoint x="-210.0" y="185.0"/>
<omgdi:waypoint x="-104.49817" y="185.00002"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-cbdabd5a-0320-4beb-8746-8a0b2c3d982b" bpmnElement="sendApproveEmail_to_endEvent">
<omgdi:waypoint x="-4.4981766" y="80.0"/>
<omgdi:waypoint x="125.0" y="80.0"/>
<omgdi:waypoint x="125.0" y="169.99998"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="edge-7ac3bda4-a111-4f1b-a1cd-0e673c8d3ed9" bpmnElement="sendRejectEmail_to_endEvent">
<omgdi:waypoint x="-4.498169" y="185.00002"/>
<omgdi:waypoint x="110.0" y="185.0"/>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="shape-b5991b38-6b94-478d-968a-d137717678f6" bpmnElement="endEvent">
<omgdc:Bounds x="110.0" y="170.0" width="30.0" height="30.0"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
2 主要功能
2.1 涉及表
01.流程定义相关表
ACT_RE_DEPLOYMENT:部署信息表,存储每次流程定义部署的元数据。
ACT_RE_PROCDEF:流程定义表,存储流程定义的详细信息(如定义 ID、版本号、部署 ID 等)。
ACT_RE_MODEL:模型表,用于存储通过 Flowable Modeler 创建的流程模型。
---------------------------------------------------------------------------------------------------------
ACT_RE_DEPLOYMENT 和 ACT_RE_PROCDEF 是一对一的关系。
ACT_RE_DEPLOYMENT 和 ACT_GE_BYTEARRAY 是一对多的关系,一个流程部署 ID 对应两条 ACT_GE_BYTEARRAY 表中的记录(默认)。
02.流程实例相关表
ACT_RU_EXECUTION:流程执行实例表,存储正在运行的流程实例和执行信息。
ACT_RU_EVENT_SUBSCR:事件订阅表,用于存储事件订阅数据(如消息事件和信号事件)。
ACT_RU_VARIABLE运行时变量表,存储流程运行中的变量。
03.任务相关表
ACT_RU_TASK:运行时任务表,存储正在运行的任务实例。
ACT_RU_IDENTITYLINK:任务与用户、组之间的关系表,用于分配用户或角色到特定任务。
ACT_RU_DEADLETTER_JOB:死信任务表,存储无法执行的任务(如失败的定时任务)。
04.历史数据相关表
ACT_HI_PROCINST:历史流程实例表,存储已完成的流程实例记录。
ACT_HI_ACTINST:历史活动实例表,存储每个流程节点的执行历史。
ACT_HI_TASKINST:历史任务实例表,存储已完成的任务记录。
ACT_HI_VARINST:历史变量表,存储流程结束后的变量信息。
ACT_HI_DETAIL:历史详情表,记录流程实例运行期间的详细信息(如变量修改记录)。
05.作业调度相关表
ACT_RU_JOB:定时任务表,用于存储需要执行的作业(如定时器事件)。
ACT_RU_TIMER_JOB:定时器任务表,存储尚未触发的定时任务。
ACT_RU_SUSPENDED_JOB:暂停任务表,存储暂停的定时任务信息。
06.其他辅助表
ACT_ID_USER:用户信息表,存储系统用户数据。
ACT_ID_GROUP:用户组表,存储用户组信息。
ACT_ID_MEMBERSHIP:用户与用户组关系表。
ACT_GE_BYTEARRAY:二进制数据表,存储流程定义的 XML 文件和模型的 JSON 数据。
ACT_GE_PROPERTY:属性表,存储全局配置信息(如数据库 schema 版本等)。
2.2 流程定义、实例
00.概念
a.流程定义
在使用 flowable 的时候,我们首先需要画一个流程图,要在我们的代码中使用流程图,就必须先把流程图部署到项目中。
加载到系统中的流程图,就是流程定义:ProcessDefinition。
b.流程实例
我们启动的每一个具体的流程,就是一个流程实例 ProcessInstance。
ProcessDefinition 相当于 Java 中的类,ProcessInstance 则相当于根据这个类创建出来的对象。
01.流程定义ProcessDefinition
a.自动部署
a.部署位置
在 Spring Boot 中,凡是放在 resources/processes 目录下的流程文件,默认情况下,都会被自动部署。
如:任意绘制一个流程图,放到 resources/processes 目录下:JsonFormatForm.bpmn20.xml
b.自动部署
启动 Spring Boot 项目,启动之后,这个流程就被自动部署了。
ACT_RE_DEPLOYMENT 和 ACT_RE_PROCDEF 分别保存了流程定义相关的信息。
ACT_GE_BYTEARRAY表则保存了刚刚定义的流程的 XML 文件以及根据这个 XML 文件所自动生成的流程图。
c.三张表的关系
● ACT_RE_DEPLOYMENT 和 ACT_RE_PROCDEF 是一对一的关系。
● ACT_RE_DEPLOYMENT 和 ACT_GE_BYTEARRAY 是一对多的关系,一个流程部署 ID 对应两条 ACT_GE_BYTEARRAY 表中的记录(默认)。
d.修改流程
流程部署好之后,如果想要修改,可以直接修改,修改之后,流程会自动升级(数据库中的记录会自动更新)。
举个例子:
假设我们现在修改了流程定义的名字,然后重新启动 SpringBoot 项目,
那么 ACT_RE_DEPLOYMENT 表中会增加一条部署记录,同时 ACT_RE_PROCDEF 表也会增加一条新的流程定义信息,
新的流程信息中,该变的字段会自动变,同时版本号 VERSION_会自增 1。
ACT_GE_BYTEARRAY 表中也会新增两条记录,和最新的版本号的定义相对应。
e.注意
流程图的更新,主要是以流程定义的 id 为依据,
如果流程定义的内容发生变化,但是流程 id 没有变,则流程定义升级;
如果流程图定义的 id 发生变化,则直接重新部署新的流程。
f.在流程的定义中,XML 文件中的 targetNamespace 属性,其实就是流程的分类定义:
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef" exporter="Flowable Open Source Modeler" exporterVersion="6.7.2">
<process id="javaboy_submit_an_expense_account2" name="javaboy的报销流程2" isExecutable="true">
<documentation>javaboy的报销流程</documentation>
<startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-71C33AD7-E892-4037-AFBB-464957E41378" name="填写报销材料" flowable:assignee="$INITIATOR" flowable:formKey="submit_an_expense_account" flowable:formFieldValidation="true">
<extensionElements>
<modeler:activiti-idm-initiator xmlns:modeler="http://flowable.org/modeler"><![CDATA[true]]></modeler:activiti-idm-initiator>
</extensionElements>
</userTask>
如果想要修改流程定义的分类,直接修改该属性即可。
g.Spring Boot 中,关于流程定义的几个重要属性:
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql:///flowable_process?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
logging.level.org.flowable=debug
-------------------------------------------------------------------------------------------------
# 是否在项目启动的时候,自动去检查流程定义目录下是否有对应的流程定义文件,如果这个属性为 true(默认即次),就表示去检查,否则不检查(意味着不会自动部署流程)
flowable.check-process-definitions=true
# 设置流程定义文件的位置,默认位置就是 classpath*:/processes/
flowable.process-definition-location-prefix=classpath*:/javaboy/
# 这个是指定流程定义 XML 文件的后缀,默认后缀有两个:**.bpmn20.xml,**.bpmn
flowable.process-definition-location-suffixes=**.bpmn20.xml,**.bpmn
b.手动部署
a.通过 RepositoryService 部署
@Autowired
private RepositoryService repositoryService;
// 方式1:通过 classpath 路径部署
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/leave.bpmn20.xml")
.name("请假流程")
.category("OA")
.deploy();
// 方式2:通过 InputStream 部署
InputStream inputStream = new FileInputStream("leave.bpmn20.xml");
Deployment deployment = repositoryService.createDeployment()
.addInputStream("leave.bpmn20.xml", inputStream)
.name("请假流程")
.deploy();
// 方式3:通过 ZIP 压缩包部署
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream("processes.zip"));
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("批量部署")
.deploy();
b.查询流程定义
// 查询所有流程定义
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
.orderByProcessDefinitionVersion().desc()
.list();
// 根据流程定义 Key 查询
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("leave")
.latestVersion()
.singleResult();
// 根据部署 ID 查询
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
.deploymentId(deploymentId)
.list();
c.删除流程定义
// 普通删除(如果有运行中的流程实例会报错)
repositoryService.deleteDeployment(deploymentId);
// 级联删除(会删除所有相关的流程实例、任务、历史数据)
repositoryService.deleteDeployment(deploymentId, true);
d.挂起和激活流程定义
// 挂起流程定义(挂起后无法启动新的流程实例)
repositoryService.suspendProcessDefinitionById(processDefinitionId);
// 激活流程定义
repositoryService.activateProcessDefinitionById(processDefinitionId);
// 挂起流程定义,同时挂起所有运行中的流程实例
repositoryService.suspendProcessDefinitionById(processDefinitionId, true, new Date());
03.流程实例ProcessInstance
a.启动流程
a.通过流程定义 Key 启动
@Autowired
private RuntimeService runtimeService;
// 方式1:简单启动
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave");
// 方式2:带业务 Key 启动
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave", "BK001");
// 方式3:带流程变量启动
Map<String, Object> variables = new HashMap<>();
variables.put("days", 3);
variables.put("reason", "事假");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave", variables);
// 方式4:带业务 Key 和流程变量启动
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave", "BK001", variables);
b.通过流程定义 ID 启动
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId);
c.通过消息启动
ProcessInstance processInstance = runtimeService.startProcessInstanceByMessage("messageName");
b.流程变量
a.设置流程变量
// 启动时设置
Map<String, Object> variables = new HashMap<>();
variables.put("days", 3);
runtimeService.startProcessInstanceByKey("leave", variables);
// 运行时设置(全局变量)
runtimeService.setVariable(executionId, "days", 5);
runtimeService.setVariables(executionId, variables);
// 运行时设置(局部变量)
runtimeService.setVariableLocal(executionId, "localVar", "value");
b.获取流程变量
// 获取单个变量
Object days = runtimeService.getVariable(executionId, "days");
// 获取所有变量
Map<String, Object> variables = runtimeService.getVariables(executionId);
// 获取局部变量
Object localVar = runtimeService.getVariableLocal(executionId, "localVar");
c.删除流程变量
runtimeService.removeVariable(executionId, "days");
runtimeService.removeVariables(executionId, Arrays.asList("days", "reason"));
c.查询流程实例
// 查询所有运行中的流程实例
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
.processDefinitionKey("leave")
.list();
// 根据业务 Key 查询
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceBusinessKey("BK001")
.singleResult();
// 根据流程实例 ID 查询
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
// 查询激活状态的流程实例
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
.active()
.list();
d.挂起和激活流程实例
// 挂起流程实例(挂起后无法继续执行任务)
runtimeService.suspendProcessInstanceById(processInstanceId);
// 激活流程实例
runtimeService.activateProcessInstanceById(processInstanceId);
e.删除流程实例
// 删除运行中的流程实例
runtimeService.deleteProcessInstance(processInstanceId, "删除原因");
2.3 任务管理
01.任务查询
a.查询待办任务
@Autowired
private TaskService taskService;
// 查询某个用户的待办任务
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("zhangsan")
.list();
// 查询候选任务(需要认领的任务)
List<Task> tasks = taskService.createTaskQuery()
.taskCandidateUser("zhangsan")
.list();
// 查询候选组任务
List<Task> tasks = taskService.createTaskQuery()
.taskCandidateGroup("manager")
.list();
// 根据流程实例 ID 查询任务
List<Task> tasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.list();
b.任务查询条件
// 根据任务名称查询
.taskName("审批任务")
// 根据任务 Key 查询
.taskDefinitionKey("approveTask")
// 根据流程定义 Key 查询
.processDefinitionKey("leave")
// 排序
.orderByTaskCreateTime().desc()
02.任务认领
a.认领任务
// 候选用户认领任务
taskService.claim(taskId, "zhangsan");
// 取消认领(归还给候选人池)
taskService.unclaim(taskId);
b.设置任务办理人
// 直接设置任务办理人
taskService.setAssignee(taskId, "lisi");
03.任务办理
a.完成任务
// 简单完成
taskService.complete(taskId);
// 带流程变量完成
Map<String, Object> variables = new HashMap<>();
variables.put("approved", true);
taskService.complete(taskId, variables);
// 带局部变量完成
taskService.complete(taskId, variables, true);
b.任务审批
// 审批通过
Map<String, Object> variables = new HashMap<>();
variables.put("approved", true);
variables.put("comment", "同意");
taskService.complete(taskId, variables);
// 审批拒绝
variables.put("approved", false);
variables.put("comment", "不同意");
taskService.complete(taskId, variables);
04.任务委派
a.委派任务
// 将任务委派给其他人处理
taskService.delegateTask(taskId, "wangwu");
// 被委派人处理完后,任务回到原办理人
taskService.resolveTask(taskId);
b.转办任务
// 将任务转办给其他人(不会回到原办理人)
taskService.setAssignee(taskId, "zhaoliu");
05.任务附加功能
a.添加任务备注
// 添加备注
taskService.addComment(taskId, processInstanceId, "同意申请");
// 查询备注
List<Comment> comments = taskService.getTaskComments(taskId);
b.设置任务优先级
taskService.setPriority(taskId, 50);
c.设置任务到期时间
taskService.setDueDate(taskId, new Date());
2.4 历史数据
01.历史流程实例
@Autowired
private HistoryService historyService;
a.查询历史流程实例
// 查询所有历史流程实例
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey("leave")
.list();
// 查询已完成的流程实例
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
.finished()
.list();
// 查询未完成的流程实例
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
.unfinished()
.list();
b.删除历史流程实例
historyService.deleteHistoricProcessInstance(processInstanceId);
02.历史任务
a.查询历史任务
// 查询某个用户的历史任务
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
.taskAssignee("zhangsan")
.list();
// 查询已完成的任务
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
.finished()
.list();
// 查询流程实例的所有历史任务
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceEndTime().desc()
.list();
03.历史活动
a.查询历史活动实例
// 查询流程实例的所有历史活动
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().asc()
.list();
// 查询已完成的活动
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
.finished()
.list();
04.历史变量
a.查询历史变量
// 查询流程实例的所有历史变量
List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.list();
// 查询指定变量名的历史
List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery()
.variableName("days")
.list();
2.5 网关详解
01.排他网关(Exclusive Gateway)
a.概念
排他网关(也叫异或网关)用于在流程中实现决策。
当流程执行到排他网关时,会按照顺序评估每条出口的条件表达式。
只有一条路径会被选中执行。
b.XML 配置
<exclusiveGateway id="gateway1" default="flow3"/>
<sequenceFlow id="flow1" sourceRef="gateway1" targetRef="task1">
<conditionExpression xsi:type="tFormalExpression">
${days <= 3}
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow2" sourceRef="gateway1" targetRef="task2">
<conditionExpression xsi:type="tFormalExpression">
${days > 3 && days <= 7}
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="gateway1" targetRef="task3"/>
c.使用场景
请假天数判断:3天以内组长审批,3-7天经理审批,7天以上总监审批
金额判断:1000元以下直接通过,1000-5000元需要审批,5000元以上需要多级审批
02.并行网关(Parallel Gateway)
a.概念
并行网关用于创建并行流程分支或合并并行分支。
分支时:所有出口都会被执行
合并时:等待所有进入的分支都到达后才继续
b.XML 配置
<!-- 并行分支 -->
<parallelGateway id="fork" name="并行分支"/>
<sequenceFlow sourceRef="fork" targetRef="task1"/>
<sequenceFlow sourceRef="fork" targetRef="task2"/>
<sequenceFlow sourceRef="fork" targetRef="task3"/>
<!-- 并行合并 -->
<parallelGateway id="join" name="并行合并"/>
<sequenceFlow sourceRef="task1" targetRef="join"/>
<sequenceFlow sourceRef="task2" targetRef="join"/>
<sequenceFlow sourceRef="task3" targetRef="join"/>
c.使用场景
采购流程:同时进行供应商询价、财务审核、库存检查
合同审批:法务审核、财务审核、业务审核同时进行
03.包容网关(Inclusive Gateway)
a.概念
包容网关可以看作是排他网关和并行网关的结合。
分支时:所有条件为 true 的分支都会执行
合并时:等待所有被执行的分支都到达
b.XML 配置
<inclusiveGateway id="gateway1"/>
<sequenceFlow sourceRef="gateway1" targetRef="task1">
<conditionExpression>${needManagerApprove}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="gateway1" targetRef="task2">
<conditionExpression>${needFinanceApprove}</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="gateway1" targetRef="task3">
<conditionExpression>${needHRApprove}</conditionExpression>
</sequenceFlow>
c.使用场景
根据条件决定需要哪些部门审批
根据金额决定需要几级审批
04.事件网关(Event Gateway)
a.概念
事件网关用于基于事件做出决策。
流程会等待多个事件中的一个发生,哪个事件先发生就走哪条路径。
b.XML 配置
<eventBasedGateway id="gateway1"/>
<sequenceFlow sourceRef="gateway1" targetRef="timerEvent"/>
<sequenceFlow sourceRef="gateway1" targetRef="messageEvent"/>
<intermediateCatchEvent id="timerEvent">
<timerEventDefinition>
<timeDuration>PT1H</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
<intermediateCatchEvent id="messageEvent">
<messageEventDefinition messageRef="approvalMessage"/>
</intermediateCatchEvent>
c.使用场景
等待审批或超时自动处理
等待用户确认或系统自动取消
3 高级特性
3.1 监听器
01.执行监听器(Execution Listener)
a.概念
执行监听器可以监听流程执行过程中的事件,在特定事件发生时执行自定义逻辑。
可以监听的事件:start(开始)、end(结束)、take(连线)
b.Java 实现
public class MyExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
String eventName = execution.getEventName();
String activityId = execution.getCurrentActivityId();
System.out.println("执行监听器触发:" + eventName + " - " + activityId);
}
}
c.XML 配置
<startEvent id="start">
<extensionElements>
<flowable:executionListener event="start"
class="com.example.MyExecutionListener"/>
</extensionElements>
</startEvent>
<sequenceFlow id="flow1" sourceRef="start" targetRef="task1">
<extensionElements>
<flowable:executionListener event="take"
class="com.example.MyExecutionListener"/>
</extensionElements>
</sequenceFlow>
d.使用场景
流程启动��发送通知
流程结束时记录日志
连线经过时统计数据
02.任务监听器(Task Listener)
a.概念
任务监听器用于监听任务生命周期中的事件。
可以监听的事件:create(创建)、assignment(分配)、complete(完成)、delete(删除)
b.Java 实现
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
String eventName = delegateTask.getEventName();
String taskName = delegateTask.getName();
String assignee = delegateTask.getAssignee();
System.out.println("任务监听器:" + eventName + " - " + taskName + " - " + assignee);
}
}
c.XML 配置
<userTask id="task1" name="审批任务">
<extensionElements>
<flowable:taskListener event="create"
class="com.example.MyTaskListener"/>
<flowable:taskListener event="assignment"
class="com.example.MyTaskListener"/>
<flowable:taskListener event="complete"
class="com.example.MyTaskListener"/>
</extensionElements>
</userTask>
d.使用场景
任务创建时发送待办提醒
任务分配时记录操作日志
任务完成时更新业务数据
03.事件监听器(Event Listener)
a.概念
全局事件监听器,可以监听引擎中所有类型的事件。
b.Java 实现
@Component
public class GlobalEventListener implements FlowableEventListener {
@Override
public void onEvent(FlowableEvent event) {
FlowableEngineEventType type = (FlowableEngineEventType) event.getType();
System.out.println("全局事件:" + type.name());
}
@Override
public boolean isFailOnException() {
return false;
}
@Override
public boolean isFireOnTransactionLifecycleEvent() {
return false;
}
@Override
public String getOnTransaction() {
return null;
}
}
c.Spring Boot 配置
@Configuration
public class FlowableConfig {
@Bean
public ProcessEngineConfigurationConfigurer eventListenerConfigurer() {
return configuration -> {
configuration.setEventListeners(Arrays.asList(new GlobalEventListener()));
};
}
}
d.常见事件类型
ENTITY_CREATED:实体创建
ENTITY_INITIALIZED:实体初始化
ENTITY_UPDATED:实体更新
ENTITY_DELETED:实体删除
TASK_CREATED:任务创建
TASK_ASSIGNED:任务分配
TASK_COMPLETED:任务完成
PROCESS_STARTED:流程启动
PROCESS_COMPLETED:流程完成
3.2 表达式
01.UEL 表达式
a.概念
Flowable 使用 UEL(Unified Expression Language)表达式来访问流程变量和调用方法。
UEL 是 Java EE 标准的一部分,语法类似于 EL 表达式。
b.基本语法
${variable}:获取变量值
#{variable}:延迟计算表达式
${variable == 'value'}:条件判断
${variable > 100}:数值比较
${variable.property}:访问对象属性
${variable.method()}:调用对象方法
02.变量表达式
a.访问流程变量
${days}:获取 days 变量
${user.name}:获取 user 对象的 name 属性
${user.age > 18}:判断 user 的 age 是否大于 18
b.在 XML 中使用
<sequenceFlow sourceRef="gateway1" targetRef="task1">
<conditionExpression>${days <= 3}</conditionExpression>
</sequenceFlow>
<userTask id="task1" flowable:assignee="${assignee}"/>
<serviceTask id="task2" flowable:expression="${myService.doSomething()}"/>
03.方法表达式
a.调用 Spring Bean
<serviceTask id="task1" flowable:expression="${myService.sendEmail(execution)}"/>
@Service("myService")
public class MyService {
public void sendEmail(DelegateExecution execution) {
String email = (String) execution.getVariable("email");
// 发送邮件逻辑
}
}
b.调用静态方法
${T(java.lang.Math).random()}
${T(com.example.Utils).formatDate(date)}
04.条件表达式
a.简单条件
${approved == true}
${amount > 1000}
${status == 'APPROVED'}
b.复合条件
${days > 3 && days <= 7}
${amount > 1000 || level == 'VIP'}
${!(status == 'REJECTED')}
c.三元表达式
${days > 3 ? 'manager' : 'leader'}
${amount > 1000 ? '需要审批' : '自动通过'}
05.内置对象
a.execution
${execution.processInstanceId}:流程实例 ID
${execution.processDefinitionId}:流程定义 ID
${execution.activityId}:当前活动 ID
b.task
${task.id}:任务 ID
${task.name}:任务名称
${task.assignee}:任务办理人
c.authenticatedUserId
${authenticatedUserId}:当前认证用户 ID
3.3 子流程
01.嵌入式子流程(Embedded Subprocess)
a.概念
嵌入式子流程是在父流程内部定义的子流程,与父流程共享流程变量。
子流程可以有自己的开始事件和结束事件。
b.XML 配置
<subProcess id="subprocess1" name="报销审批子流程">
<startEvent id="subStart"/>
<userTask id="subTask1" name="部门经理审批"/>
<userTask id="subTask2" name="财务审批"/>
<endEvent id="subEnd"/>
<sequenceFlow sourceRef="subStart" targetRef="subTask1"/>
<sequenceFlow sourceRef="subTask1" targetRef="subTask2"/>
<sequenceFlow sourceRef="subTask2" targetRef="subEnd"/>
</subProcess>
c.使用场景
将复杂流程分解为多个子流程
对子流程进行边界事件监听
子流程内部的异常处理
02.调用活动(Call Activity)
a.概念
调用活动用于调用外部独立的流程定义,被调用的流程有自己独立的流程实例。
父流程和子流程的变量是隔离的,需要显式传递。
b.XML 配置
<callActivity id="callActivity1" name="调用审批流程"
calledElement="approvalProcess">
<extensionElements>
<!-- 输入参数 -->
<flowable:in source="amount" target="subAmount"/>
<flowable:in source="reason" target="subReason"/>
<!-- 输出参数 -->
<flowable:out source="subResult" target="result"/>
</extensionElements>
</callActivity>
c.Java 代码调用
// 启动父流程
Map<String, Object> variables = new HashMap<>();
variables.put("amount", 5000);
variables.put("reason", "差旅费");
runtimeService.startProcessInstanceByKey("mainProcess", variables);
d.使用场景
流程复用(多个流程调用同一��子流程)
流程解耦(子流程独立维护)
跨流程数据传递
03.事件子流程(Event Subprocess)
a.概念
事件子流程是由事件触发的子流程,不通过正常的顺序流启动。
常用于异常处理、超时处理等场景。
b.XML 配置
<subProcess id="eventSubprocess" triggeredByEvent="true">
<startEvent id="errorStart">
<errorEventDefinition errorRef="error001"/>
</startEvent>
<userTask id="handleError" name="处理异常"/>
<endEvent id="errorEnd"/>
<sequenceFlow sourceRef="errorStart" targetRef="handleError"/>
<sequenceFlow sourceRef="handleError" targetRef="errorEnd"/>
</subProcess>
c.触发方式
错误事件:捕获流程中抛出的错误
定时事件:定时触发子流程
消息事件:接收消息触发子流程
信号事件:接收信号触发子流程
d.使用场景
全局异常处理
超时自动处理
紧急中断处理
3.4 多实例
01.并行多实例(Parallel Multi-Instance)
a.概念
并行多实例用于同时创建多个任务实例,所有实例并行执行。
常用于多人会签场景。
b.XML 配置
<userTask id="task1" name="会签任务">
<multiInstanceLoopCharacteristics isSequential="false">
<!-- 集合变量 -->
<loopCardinality>${approvers.size()}</loopCardinality>
<!-- 或使用集合 -->
<flowable:collection>approvers</flowable:collection>
<!-- 元素变量 -->
<flowable:elementVariable>approver</flowable:elementVariable>
<!-- 完成条件 -->
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
c.内置变量
nrOfInstances:实例总数
nrOfActiveInstances:当前活动实例数
nrOfCompletedInstances:已完成实例数
loopCounter:当前实例的循环计数器
d.使用场景
多人会签(所有人或部分人同意即可)
并行审批(多个部门同时审批)
02.串行多实例(Sequential Multi-Instance)
a.概念
串行多实例按顺序创建任务实例,一个完成后才创建下一个。
常用于多级审批场景。
b.XML 配置
<userTask id="task1" name="多级审批" flowable:assignee="${approver}">
<multiInstanceLoopCharacteristics isSequential="true">
<flowable:collection>approvers</flowable:collection>
<flowable:elementVariable>approver</flowable:elementVariable>
<!-- 任何一人拒绝则结束 -->
<completionCondition>${approved == false}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
c.使用场景
多级审批(组长→经理→总监)
顺序处理(依次处理多个任务)
03.集合变量
a.Java 代码设置
// 设置审批人列表
List<String> approvers = Arrays.asList("zhangsan", "lisi", "wangwu");
Map<String, Object> variables = new HashMap<>();
variables.put("approvers", approvers);
runtimeService.startProcessInstanceByKey("process", variables);
b.动态计算集合
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>${myService.getApprovers().size()}</loopCardinality>
</multiInstanceLoopCharacteristics>
3.5 定时任务
01.定时启动事件(Timer Start Event)
a.概念
定时启动事件用于在指定时间自动启动流程。
b.XML 配置
<!-- 固定时间启动 -->
<startEvent id="start">
<timerEventDefinition>
<timeDate>2024-12-31T23:59:59</timeDate>
</timerEventDefinition>
</startEvent>
<!-- 延迟启动 -->
<startEvent id="start">
<timerEventDefinition>
<timeDuration>PT5M</timeDuration>
</timerEventDefinition>
</startEvent>
<!-- 周期启动 -->
<startEvent id="start">
<timerEventDefinition>
<timeCycle>R3/PT10M</timeCycle>
</timerEventDefinition>
</startEvent>
c.时间格式(ISO 8601)
PT5M:5分钟
PT1H:1小时
P1D:1天
R3/PT10M:每10分钟重复3次
R/P1D:每天重复(无限次)
d.使用场景
定时报表生成
定时数据同步
定时提醒通知
02.定时边界事件(Timer Boundary Event)
a.概念
定时边界事件附加在任务上,用于任务超时处理。
可以是中断型(取消任务)或非中断型(不取消任务)。
b.XML 配置
<!-- 中断型边界事件 -->
<userTask id="task1" name="审批任务"/>
<boundaryEvent id="timer1" attachedToRef="task1" cancelActivity="true">
<timerEventDefinition>
<timeDuration>PT2H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<sequenceFlow sourceRef="timer1" targetRef="timeoutTask"/>
<!-- 非中断型边界事件 -->
<boundaryEvent id="timer2" attachedToRef="task1" cancelActivity="false">
<timerEventDefinition>
<timeDuration>PT1H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<sequenceFlow sourceRef="timer2" targetRef="reminderTask"/>
c.使用场景
任务超时自动处理
任务超时提醒
任务超时升级
03.定时中间事件(Timer Intermediate Event)
a.概念
定时中间事件用于在流程执行过程中等待一段时间。
b.XML 配置
<intermediateCatchEvent id="timer1">
<timerEventDefinition>
<timeDuration>PT30M</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
c.使用场景
流程中的等待时间
延迟执行后续任务
3.6 消息事件
01.消息启动事件(Message Start Event)
a.概念
消息启动事件用于通过接收消息来启动流程。
b.XML 配置
<message id="startMessage" name="orderMessage"/>
<startEvent id="start">
<messageEventDefinition messageRef="startMessage"/>
</startEvent>
c.Java 代码触发
// 启动流程
Map<String, Object> variables = new HashMap<>();
variables.put("orderId", "ORDER001");
runtimeService.startProcessInstanceByMessage("orderMessage", variables);
d.使用场景
接收外部系统消息启动流程
MQ 消息触发流程
API 调用启动流程
02.消息中间事件(Message Intermediate Event)
a.概念
消息中间事件用于在流程执行过程中等待接收消息。
b.XML 配置
<message id="paymentMessage" name="paymentReceived"/>
<intermediateCatchEvent id="waitPayment">
<messageEventDefinition messageRef="paymentMessage"/>
</intermediateCatchEvent>
c.Java 代码触发
// 查询等待消息的执行实例
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(processInstanceId)
.messageEventSubscriptionName("paymentReceived")
.singleResult();
// 发送消息
Map<String, Object> variables = new HashMap<>();
variables.put("paymentAmount", 1000);
runtimeService.messageEventReceived("paymentReceived", execution.getId(), variables);
d.使用场景
等待支付完成
等待外部审批结果
等待第三方系统回调
03.消息边界事件(Message Boundary Event)
a.概念
消息边界事件附加在任务上,用于接收消息中断或不中断任务。
b.XML 配置
<userTask id="task1" name="处理订单"/>
<boundaryEvent id="cancelMsg" attachedToRef="task1" cancelActivity="true">
<messageEventDefinition messageRef="cancelMessage"/>
</boundaryEvent>
<sequenceFlow sourceRef="cancelMsg" targetRef="cancelTask"/>
c.使用场景
订单取消处理
流程紧急中断
外部事件触发
4 Spring Boot 集成
4.1 项目搭建
00.总结
a.说明
Flowable 提供了完整的 Spring Boot 集成方案,通过 Spring Boot Starter 可以快速集成工作流引擎。
Flowable Spring Boot Starter 提供了自动配置功能,简化了开发流程。
支持多种数据库,包括 MySQL、PostgreSQL、Oracle、SQL Server 等。
b.核心依赖
flowable-spring-boot-starter:Flowable Spring Boot 启动器
flowable-spring-boot-starter-rest:REST API 支持
flowable-spring-boot-starter-actuator:健康检查和监控
c.数据库支持
默认使用 H2 内存数据库(开发环境)
生产环境建议使用 MySQL、PostgreSQL 等关系型数据库
自动创建和管理数据库表结构
01.依赖配置
a.Maven 依赖
<!-- Flowable Spring Boot Starter -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
</dependency>
<!-- REST API 支持 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-rest</artifactId>
<version>6.7.2</version>
</dependency>
<!-- 监控支持 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
<version>6.7.2</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
b.Gradle 依赖
implementation 'org.flowable:flowable-spring-boot-starter:6.7.2'
implementation 'org.flowable:flowable-spring-boot-starter-rest:6.7.2'
implementation 'org.flowable:flowable-spring-boot-starter-actuator:6.7.2'
runtimeOnly 'mysql:mysql-connector-java'
c.可选依赖
<!-- 流程设计器 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
<version>6.7.2</version>
</dependency>
<!-- 任务应用 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-task</artifactId>
<version>6.7.2</version>
</dependency>
02.配置文件
a.application.yml 配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/flowable_demo?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Flowable 配置
flowable:
# 检查流程定义
check-process-definitions: true
# 流程定义文件位置
process-definition-location-prefix: classpath*:/processes/
# 流程定义文件后缀
process-definition-location-suffixes: **.bpmn20.xml,**.bpmn
# 数据库更新策略
database-schema-update: true
# 历史级别
history-level: full
# 自动部署
auto-deployment-enabled: true
# 邮件配置
mail:
server:
host: smtp.qq.com
port: 587
username: [email protected]
password: your-password
# REST API 配置
rest:
app:
authentication-mode: verify-privilege
# 日志配置
logging:
level:
org.flowable: debug
org.flowable.task: debug
org.flowable.engine: debug
b.application.properties 配置
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/flowable_demo?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Flowable 核心配置
flowable.check-process-definitions=true
flowable.process-definition-location-prefix=classpath*:/processes/
flowable.process-definition-location-suffixes=**.bpmn20.xml,**.bpmn
flowable.database-schema-update=true
flowable.history-level=full
flowable.auto-deployment-enabled=true
# Flowable 邮件配置
flowable.mail.server.host=smtp.qq.com
flowable.mail.server.port=587
[email protected]
flowable.mail.server.password=your-password
# 流程引擎配置
flowable.process.engine.name=default
flowable.process.engine.configuration.async-executor-activate=true
c.多数据源配置
@Configuration
public class FlowableDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.flowable")
public DataSource flowableDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer() {
return configuration -> {
configuration.setDataSource(flowableDataSource());
};
}
}
03.数据源配置
a.单数据源配置
# 使用默认数据源
spring:
datasource:
url: jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai
username: flowable
password: flowable
driver-class-name: com.mysql.cj.jdbc.Driver
b.多数据源配置
@Configuration
@EnableConfigurationProperties(FlowableProperties.class)
public class MultiDataSourceConfig {
@Primary
@Bean(name = "businessDataSource")
@ConfigurationProperties(prefix = "spring.datasource.business")
public DataSource businessDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "flowableDataSource")
@ConfigurationProperties(prefix = "spring.datasource.flowable")
public DataSource flowableDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer(
@Qualifier("flowableDataSource") DataSource flowableDataSource) {
return configuration -> {
configuration.setDataSource(flowableDataSource);
configuration.setDatabaseSchemaUpdate("true");
configuration.setAsyncExecutorActivate(true);
};
}
}
c.连接池配置
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 300000
connection-timeout: 30000
max-lifetime: 1800000
connection-test-query: SELECT 1
04.自动配置
a.默认自动配置
Flowable 自动配置类会自动配置以下组件:
- ProcessEngine:流程引擎
- RepositoryService:流程定义服务
- RuntimeService:流程运行时服务
- TaskService:任务服务
- HistoryService:历史服务
- ManagementService:管理服务
- IdentityService:身份服务
b.自定义配置
@Configuration
public class FlowableConfig {
@Bean
public ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer() {
return configuration -> {
// 配置事件监听器
configuration.setEventListeners(Arrays.asList(new GlobalEventListener()));
// 配置命令执行器
configuration.setCommandExecutor(new CustomCommandExecutor());
// 配置任务监听器
configuration.setTaskListeners(Arrays.asList(new CustomTaskListener()));
// 配置流程引擎名称
configuration.setProcessEngineName("customEngine");
};
}
@Bean
public SpringProcessEngineConfiguration springProcessEngineConfiguration() {
SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration();
// 设置数据源
configuration.setDataSource(dataSource());
// 设置数据库配置
configuration.setDatabaseSchemaUpdate("true");
configuration.setDatabaseTablePrefix("ACT_");
configuration.setDbIdentityUsed(false);
// 设置历史级别
configuration.setHistoryLevel("full");
// 设置流程定义位置
configuration.setResourceLocations("classpath*:/processes/");
return configuration;
}
}
c.启动配置
@SpringBootApplication
@EnableFlowable
public class FlowableApplication {
public static void main(String[] args) {
SpringApplication.run(FlowableApplication.class, args);
}
// 自定义流程引擎初始化
@Bean
public CommandLineRunner initProcessEngine(ProcessEngine processEngine) {
return args -> {
// 检查流程引擎状态
RepositoryService repositoryService = processEngine.getRepositoryService();
System.out.println("Flowable 流程引擎启动成功!");
System.out.println("已部署流程数:" + repositoryService.createProcessDefinitionQuery().count());
};
}
}
05.代码示例
a.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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>flowable-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>8</java.version>
<flowable.version>6.7.2</flowable.version>
</properties>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Flowable Starters -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-rest</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
<version>${flowable.version}</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
</dependencies>
</project>
b.application.yml 完整配置
server:
port: 8080
servlet:
context-path: /flowable-demo
spring:
application:
name: flowable-demo
datasource:
url: jdbc:mysql://localhost:3306/flowable_demo?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 300000
connection-timeout: 30000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: update
show-sql: true
database-platform: org.hibernate.dialect.MySQL8Dialect
flowable:
# 流程定义配置
check-process-definitions: true
process-definition-location-prefix: classpath*:/processes/
process-definition-location-suffixes: **.bpmn20.xml,**.bpmn
# 数据库配置
database-schema-update: true
database-schema: flowable_demo
# 历史级别: none, activity, audit, full
history-level: full
# 邮件配置
mail:
server:
host: smtp.qq.com
port: 587
username: [email protected]
password: your-password
default-from: [email protected]
# REST API 配置
rest:
app:
authentication-mode: verify-privilege
# 流程引擎配置
process:
engine:
name: defaultFlowableEngine
configuration:
async-executor-activate: true
async-executor-default-async-job-acquire-wait-time: 1000
# 监控配置
management:
endpoints:
web:
exposure:
include: health,info,flowable
endpoint:
health:
show-details: always
# 日志配置
logging:
level:
org.flowable: debug
org.flowable.task: debug
org.flowable.engine: debug
com.example: debug
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
4.2 核心API
00.总结
a.说明
Flowable 提供 6 个核心服务 API,用于管理工作流的生命周期。
所有服务都通过 ProcessEngine 获取,Spring Boot 环境下自动注入。
这些服务覆盖了流程定义、实例、任务、历史等各个方面。
b.核心服务
RepositoryService:流程定义和部署管理
RuntimeService:流程实例和执行管理
TaskService:任务管理
HistoryService:历史数据管理
ManagementService:引擎管理
IdentityService:身份认证管理
c.获取方式
@Autowired 注入
ProcessEngine 获取
Spring Boot 自动配置
01.RepositoryService(流程定义服务)
a.基本介绍
RepositoryService 负责管理流程定义和部署信息。
主要用于部署、查询、删除流程定义。
提供流程定义的版本管理功能。
b.注入方式
@Autowired
private RepositoryService repositoryService;
// 或者通过 ProcessEngine 获取
@Autowired
private ProcessEngine processEngine;
private RepositoryService repositoryService = processEngine.getRepositoryService();
c.常用方法
// 部署流程
Deployment deploy(String name, String... resourceNames)
DeploymentBuilder createDeployment()
void deleteDeployment(String deploymentId, boolean cascade)
// 查询流程定义
ProcessDefinitionQuery createProcessDefinitionQuery()
List<ProcessDefinition> createProcessDefinitionQuery().list()
ProcessDefinitionQuery processDefinitionKey(String processDefinitionKey)
// 获取资源
InputStream getResourceAsStream(String deploymentId, String resourceName)
List<String> getDeploymentResourceNames(String deploymentId)
// 挂起/激活
void suspendProcessDefinitionById(String processDefinitionId)
void activateProcessDefinitionById(String processDefinitionId)
d.代码示例
@Service
public class ProcessDefinitionService {
@Autowired
private RepositoryService repositoryService;
/**
* 部署流程
*/
public Deployment deployProcess(String processName, String bpmnPath) {
try {
return repositoryService.createDeployment()
.addClasspathResource(bpmnPath)
.name(processName)
.deploy();
} catch (Exception e) {
log.error("部署流程失败: {}", e.getMessage(), e);
throw new RuntimeException("部署流程失败", e);
}
}
/**
* 部署流程(带ZIP)
*/
public Deployment deployProcessFromZip(String zipPath) {
try (InputStream inputStream = new FileInputStream(zipPath)) {
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
return repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("批量部署")
.deploy();
} catch (Exception e) {
log.error("批量部署流程失败: {}", e.getMessage(), e);
throw new RuntimeException("批量部署流程失败", e);
}
}
/**
* 查询流程定义
*/
public List<ProcessDefinition> queryProcessDefinitions(String key) {
return repositoryService.createProcessDefinitionQuery()
.processDefinitionKey(key)
.latestVersion()
.active()
.list();
}
/**
* 查询所有流程定义
*/
public List<ProcessDefinition> queryAllProcessDefinitions() {
return repositoryService.createProcessDefinitionQuery()
.orderByProcessDefinitionName().asc()
.list();
}
/**
* 获取流程图
*/
public InputStream getProcessDiagram(String processDefinitionId) {
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.singleResult();
String resourceName = processDefinition.getResourceName();
return repositoryService.getResourceAsStream(
processDefinition.getDeploymentId(), resourceName);
}
/**
* 删除流程定义
*/
public void deleteProcessDefinition(String deploymentId, boolean cascade) {
repositoryService.deleteDeployment(deploymentId, cascade);
}
}
02.RuntimeService(流程运行时服务)
a.基本介绍
RuntimeService 负责管理流程实例和执行对象。
用于启动流程、操作流程实例、管理流程变量等。
提供流程实例的生命周期管理功能。
b.注入方式
@Autowired
private RuntimeService runtimeService;
c.常用方法
// 启动流程
ProcessInstance startProcessInstanceByKey(String processDefinitionKey)
ProcessInstance startProcessInstanceById(String processDefinitionId)
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables)
// 流程实例管理
void suspendProcessInstanceById(String processInstanceId)
void activateProcessInstanceById(String processInstanceId)
void deleteProcessInstance(String processInstanceId, String deleteReason)
// 流程变量管理
void setVariable(String executionId, String variableName, Object value)
Object getVariable(String executionId, String variableName)
Map<String, Object> getVariables(String executionId)
// 执行管理
void trigger(String executionId)
void signal(String executionId)
// 查询
ProcessInstanceQuery createProcessInstanceQuery()
ExecutionQuery createExecutionQuery()
d.代码示例
@Service
public class ProcessInstanceService {
@Autowired
private RuntimeService runtimeService;
/**
* 启动流程(简单方式)
*/
public ProcessInstance startProcess(String processKey) {
return runtimeService.startProcessInstanceByKey(processKey);
}
/**
* 启动流程(带业务Key)
*/
public ProcessInstance startProcessWithBusinessKey(String processKey, String businessKey) {
return runtimeService.startProcessInstanceByKey(processKey, businessKey);
}
/**
* 启动流程(带变量)
*/
public ProcessInstance startProcessWithVariables(String processKey,
Map<String, Object> variables) {
return runtimeService.startProcessInstanceByKey(processKey, variables);
}
/**
* 启动流程(完整参数)
*/
public ProcessInstance startProcess(String processKey, String businessKey,
Map<String, Object> variables, String tenantId) {
return runtimeService.startProcessInstanceByKey(processKey, businessKey,
variables, tenantId);
}
/**
* 查询待办流程实例
*/
public List<ProcessInstance> queryActiveProcessInstances(String processKey) {
return runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processKey)
.active()
.list();
}
/**
* 根据业务Key查询流程实例
*/
public ProcessInstance getProcessInstanceByBusinessKey(String businessKey) {
return runtimeService.createProcessInstanceQuery()
.processInstanceBusinessKey(businessKey)
.singleResult();
}
/**
* 挂起流程实例
*/
public void suspendProcessInstance(String processInstanceId) {
runtimeService.suspendProcessInstanceById(processInstanceId);
}
/**
* 激活流程实例
*/
public void activateProcessInstance(String processInstanceId) {
runtimeService.activateProcessInstanceById(processInstanceId);
}
/**
* 删除流程实例
*/
public void deleteProcessInstance(String processInstanceId, String reason) {
runtimeService.deleteProcessInstance(processInstanceId, reason);
}
/**
* 设置流程变量
*/
public void setProcessVariable(String processInstanceId,
String variableName, Object value) {
runtimeService.setVariable(processInstanceId, variableName, value);
}
/**
* 批量设置流程变量
*/
public void setProcessVariables(String processInstanceId,
Map<String, Object> variables) {
runtimeService.setVariables(processInstanceId, variables);
}
/**
* 获取流程变量
*/
public Object getProcessVariable(String processInstanceId, String variableName) {
return runtimeService.getVariable(processInstanceId, variableName);
}
}
03.TaskService(任务服务)
a.基本介绍
TaskService 负责管理任务的生命周期。
包括用户任务、候选人、任务分配等功能。
提供任务的查询、认领、完成等操作。
b.注入方式
@Autowired
private TaskService taskService;
c.常用方法
// 任务查询
TaskQuery createTaskQuery()
List<Task> list()
Task singleResult()
// 任务操作
void complete(String taskId)
void complete(String taskId, Map<String, Object> variables)
void claim(String taskId, String userId)
void unclaim(String taskId)
// 任务分配
void setAssignee(String taskId, String userId)
void addCandidateUser(String taskId, String userId)
void addCandidateGroup(String taskId, String groupId)
// 任务附件
void createAttachment(String taskId, String attachmentType, String name, InputStream content)
void deleteAttachment(String attachmentId)
// 任务评论
void addComment(String taskId, String processInstanceId, String message)
List<Comment> getTaskComments(String taskId)
d.代码示例
@Service
public class TaskManagementService {
@Autowired
private TaskService taskService;
/**
* 查询用户待办任务
*/
public List<Task> queryUserTasks(String userId) {
return taskService.createTaskQuery()
.taskAssignee(userId)
.active()
.orderByTaskCreateTime().desc()
.list();
}
/**
* 查询用户候选任务
*/
public List<Task> queryCandidateTasks(String userId) {
return taskService.createTaskQuery()
.taskCandidateUser(userId)
.active()
.orderByTaskCreateTime().desc()
.list();
}
/**
* 查询部门候选任务
*/
public List<Task> queryGroupTasks(String groupId) {
return taskService.createTaskQuery()
.taskCandidateGroup(groupId)
.active()
.orderByTaskCreateTime().desc()
.list();
}
/**
* 认领任务
*/
public void claimTask(String taskId, String userId) {
try {
taskService.claim(taskId, userId);
} catch (FlowableException e) {
log.error("认领任务失败: taskId={}, userId={}, error={}",
taskId, userId, e.getMessage());
throw new RuntimeException("认领任务失败", e);
}
}
/**
* 取消认领任务
*/
public void unclaimTask(String taskId) {
taskService.unclaim(taskId);
}
/**
* 分配任务
*/
public void assignTask(String taskId, String userId) {
taskService.setAssignee(taskId, userId);
}
/**
* 完成任务
*/
public void completeTask(String taskId, Map<String, Object> variables) {
try {
taskService.complete(taskId, variables);
} catch (FlowableException e) {
log.error("完成任务失败: taskId={}, error={}", taskId, e.getMessage());
throw new RuntimeException("完成任务失败", e);
}
}
/**
* 完成任务(不带变量)
*/
public void completeTask(String taskId) {
completeTask(taskId, new HashMap<>());
}
/**
* 转办任务
*/
public void transferTask(String taskId, String targetUserId) {
taskService.setAssignee(taskId, targetUserId);
// 记录转办日志
addTaskComment(taskId, "任务已转办给用户: " + targetUserId);
}
/**
* 委派任务
*/
public void delegateTask(String taskId, String targetUserId) {
taskService.delegateTask(taskId, targetUserId);
// 记录委派日志
addTaskComment(taskId, "任务已委派给用户: " + targetUserId);
}
/**
* 添加任务评论
*/
public void addTaskComment(String taskId, String message) {
Task task = taskService.createTaskQuery()
.taskId(taskId)
.singleResult();
if (task != null) {
taskService.addComment(taskId, task.getProcessInstanceId(), message);
}
}
/**
* 获取任务评论
*/
public List<Comment> getTaskComments(String taskId) {
return taskService.getTaskComments(taskId);
}
/**
* 添加任务附件
*/
public void addTaskAttachment(String taskId, String attachmentName,
byte[] content) {
try (InputStream inputStream = new ByteArrayInputStream(content)) {
taskService.createAttachment(taskId, "application/octet-stream",
attachmentName, inputStream);
} catch (Exception e) {
log.error("添加任务附件失败: {}", e.getMessage(), e);
throw new RuntimeException("添加任务附件失败", e);
}
}
}
04.HistoryService(历史服务)
a.基本介绍
HistoryService 负责管理流程的历史数据。
提供流程实例、任务、活动的历史查询功能。
支持不同级别的历史记录(none、activity、audit、full)。
b.注入方式
@Autowired
private HistoryService historyService;
c.常用方法
// 历史流程实例
HistoricProcessInstanceQuery createHistoricProcessInstanceQuery()
void deleteHistoricProcessInstance(String processInstanceId)
// 历史任务
HistoricTaskInstanceQuery createHistoricTaskInstanceQuery()
// 历史活动
HistoricActivityInstanceQuery createHistoricActivityInstanceQuery()
// 历史变量
HistoricVariableInstanceQuery createHistoricVariableInstanceQuery()
// 历史详情
HistoricDetailQuery createHistoricDetailQuery()
// 创建历史报告
List<HistoricProcessInstance> createHistoricProcessInstanceQuery()
.finished()
.processDefinitionKey("leaveProcess")
.list();
d.代码示例
@Service
public class HistoryQueryService {
@Autowired
private HistoryService historyService;
/**
* 查询历史流程实例
*/
public List<HistoricProcessInstance> queryHistoricProcessInstances(String processKey) {
return historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processKey)
.orderByProcessInstanceStartTime().desc()
.list();
}
/**
* 查询已完成流程
*/
public List<HistoricProcessInstance> queryFinishedProcesses(String processKey) {
return historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processKey)
.finished()
.orderByProcessInstanceEndTime().desc()
.list();
}
/**
* 查询用户的流程历史
*/
public List<HistoricProcessInstance> queryUserHistoryProcesses(String userId) {
return historyService.createHistoricProcessInstanceQuery()
.startedBy(userId)
.orderByProcessInstanceStartTime().desc()
.list();
}
/**
* 查询历史任务
*/
public List<HistoricTaskInstance> queryHistoricTasks(String processInstanceId) {
return historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceStartTime().asc()
.list();
}
/**
* 查询用户历史任务
*/
public List<HistoricTaskInstance> queryUserHistoricTasks(String userId) {
return historyService.createHistoricTaskInstanceQuery()
.taskAssignee(userId)
.orderByHistoricTaskInstanceStartTime().desc()
.list();
}
/**
* 查询历史活动
*/
public List<HistoricActivityInstance> queryHistoricActivities(String processInstanceId) {
return historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().asc()
.list();
}
/**
* 查询历史变量
*/
public List<HistoricVariableInstance> queryHistoricVariables(String processInstanceId) {
return historyService.createHistoricVariableInstanceQuery()
.processInstanceId(processInstanceId)
.list();
}
/**
* 获取流程耗时统计
*/
public ProcessDurationStats getProcessDurationStats(String processKey) {
List<HistoricProcessInstance> instances = historyService
.createHistoricProcessInstanceQuery()
.processDefinitionKey(processKey)
.finished()
.list();
ProcessDurationStats stats = new ProcessDurationStats();
stats.setTotalInstances(instances.size());
if (!instances.isEmpty()) {
long totalDuration = instances.stream()
.mapToLong(HistoricProcessInstance::getDurationInMillis)
.sum();
stats.setAverageDuration(totalDuration / instances.size());
stats.setMinDuration(instances.stream()
.mapToLong(HistoricProcessInstance::getDurationInMillis)
.min().orElse(0));
stats.setMaxDuration(instances.stream()
.mapToLong(HistoricProcessInstance::getDurationInMillis)
.max().orElse(0));
}
return stats;
}
/**
* 清理历史数据
*/
public void cleanHistoryData(String processKey, Date beforeDate) {
List<HistoricProcessInstance> instances = historyService
.createHistoricProcessInstanceQuery()
.processDefinitionKey(processKey)
.finished()
.finishedBefore(beforeDate)
.list();
for (HistoricProcessInstance instance : instances) {
historyService.deleteHistoricProcessInstance(instance.getId());
}
}
}
05.ManagementService(管理服务)
a.基本介绍
ManagementService 提供流程引擎的管理功能。
用于执行管理命令、获取引擎信息、管理作业等。
主要用于运维和监控场景。
b.注入方式
@Autowired
private ManagementService managementService;
c.常用方法
// 命令执行
<T> T executeCommand(Command<T> command)
// 作业管理
JobQuery createJobQuery()
void executeJob(String jobId)
void deleteJob(String jobId)
// 表信息
List<String> getTableNames()
TableMetaData getTableMetaData(String tableName)
// 数据库信息
Map<String, Long> getTableCount()
String getDatabaseSchemaVersion()
// 引擎信息
ProcessEngineInfo getProcessEngineInfo()
List<Job> getTimers()
d.代码示例
@Service
public class ManagementService {
@Autowired
private org.flowable.engine.ManagementService managementService;
/**
* 获取引擎信息
*/
public ProcessEngineInfo getEngineInfo() {
return managementService.getProcessEngineInfo();
}
/**
* 获取数据库表信息
*/
public List<String> getTableNames() {
return managementService.getTableNames();
}
/**
* 获取表数据统计
*/
public Map<String, Long> getTableCount() {
return managementService.getTableCount();
}
/**
* 查询定时任务
*/
public List<Job> queryTimerJobs() {
return managementService.createJobQuery()
.timers()
.list();
}
/**
* 查询异步任务
*/
public List<Job> queryAsyncJobs() {
return managementService.createJobQuery()
.messages()
.list();
}
/**
* 执行死信任务
*/
public void executeDeadLetterJob(String jobId) {
try {
managementService.executeJob(jobId);
} catch (Exception e) {
log.error("执行死信任务失败: jobId={}, error={}", jobId, e.getMessage());
throw new RuntimeException("执行死信任务失败", e);
}
}
/**
* 清理死信任务
*/
public void cleanDeadLetterJobs() {
List<Job> deadLetterJobs = managementService.createJobQuery()
.withException()
.list();
for (Job job : deadLetterJobs) {
try {
managementService.deleteJob(job.getId());
log.info("清理死信任务: jobId={}", job.getId());
} catch (Exception e) {
log.error("清理死信任务失败: jobId={}, error={}",
job.getId(), e.getMessage());
}
}
}
/**
* 获取数据库模式版本
*/
public String getDatabaseSchemaVersion() {
return managementService.getDatabaseSchemaVersion();
}
/**
* 执行自定义命令
*/
public <T> T executeCustomCommand(Command<T> command) {
return managementService.executeCommand(command);
}
}
06.完整服务类示例
@Component
public class FlowableService {
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@Autowired
private ManagementService managementService;
/**
* 完整的流程启动和任务处理示例
*/
public void completeWorkflowExample() {
// 1. 部署流程定义
String processKey = "leaveProcess";
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/leave.bpmn20.xml")
.name("请假流程")
.deploy();
// 2. 启动流程实例
Map<String, Object> variables = new HashMap<>();
variables.put("days", 5);
variables.put("reason", "事假");
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey(processKey, variables);
// 3. 查询待办任务
List<Task> tasks = taskService.createTaskQuery()
.processInstanceId(processInstance.getId())
.list();
// 4. 处理任务
if (!tasks.isEmpty()) {
String taskId = tasks.get(0).getId();
// 完成任务
Map<String, Object> taskVariables = new HashMap<>();
taskVariables.put("approved", true);
taskVariables.put("comment", "同意请假");
taskService.complete(taskId, taskVariables);
}
// 5. 查询历史记录
HistoricProcessInstance historicProcess = historyService
.createHistoricProcessInstanceQuery()
.processInstanceId(processInstance.getId())
.singleResult();
log.info("流程完成状态: {}", historicProcess.getEndTime() != null ? "已完成" : "进行中");
}
}
4.3 流程设计器集成
00.总结
a.说明
流程设计器是工作流系统的重要组成部分,用于可视化的流程建模。
BPMN.js 是目前最流行的开源 BPMN 2.0 流程图绘制库。
通过集成流程设计器,用户可以在线设计、编辑和部署流程。
b.主流设计器
Flowable UI Modeler:官方流程设计器
BPMN.js:轻量级 Web 设计器
Camunda Modeler:功能强大的桌面设计器
Eclipse Designer:Eclipse 插件设计器
c.集成方式
Web 端集成:使用 BPMN.js + Vue/React
服务端集成:Flowable UI Modeler
混合集成:前端编辑 + 后端处理
01.BPMN.js 集成
a.简介
BPMN.js 是一个开源的 BPMN 2.0 建模工具包,由 Camunda 开发。
提供完整的 BPMN 2.0 元素支持,包括任务、网关、事件等。
支持流程图的可视化编辑、验证和导出。
b.依赖引入
<!-- BPMN.js 核心库 -->
<script src="https://unpkg.com/[email protected] /dist/bpmn-modeler.development.js"></script>
<!-- 依赖库 -->
<script src="https://unpkg.com/[email protected] /dist/bpmn-js-properties-panel.development.js"></script>
<script src="https://unpkg.com/[email protected] /dist/diagram-js.development.js"></script>
<!-- 样式文件 -->
<link rel="stylesheet" href="https://unpkg.com/[email protected] /dist/assets/diagram-js.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected] /dist/assets/properties-panel.css">
c.NPM 安装
npm install bpmn-js --save
npm install bpmn-js-properties-panel --save
npm install diagram-js --save
02.基本集成示例
a.HTML 结构
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>流程设计器</title>
<style>
#canvas {
height: 500px;
border: 1px solid #ccc;
}
.properties-panel {
width: 300px;
border-left: 1px solid #ccc;
overflow: auto;
}
.toolbar {
padding: 10px;
border-bottom: 1px solid #ccc;
}
</style>
</head>
<body>
<div class="toolbar">
<button id="saveBtn">保存</button>
<button id="downloadBtn">下载</button>
<button id="clearBtn">清空</button>
<input type="file" id="uploadFile" accept=".bpmn,.xml">
</div>
<div class="canvas-container" style="display: flex;">
<div id="canvas" style="flex: 1;"></div>
<div id="properties-panel" class="properties-panel"></div>
</div>
<script src="js/bpmn-designer.js"></script>
</body>
</html>
b.JavaScript 实现
class BpmnDesigner {
constructor(containerId, propertiesPanelId) {
this.container = document.getElementById(containerId);
this.propertiesPanel = document.getElementById(propertiesPanelId);
this.modeler = null;
this.init();
}
init() {
// 初始化 BPMN 建模器
this.modeler = new BpmnJS({
container: this.container,
propertiesPanel: {
parent: this.propertiesPanel
},
keyboard: {
bindTo: window
}
});
// 初始化事件监听
this.initEventListeners();
}
initEventListeners() {
// 保存按钮事件
document.getElementById('saveBtn').addEventListener('click', () => {
this.saveDiagram();
});
// 下载按钮事件
document.getElementById('downloadBtn').addEventListener('click', () => {
this.downloadDiagram();
});
// 清空按钮事件
document.getElementById('clearBtn').addEventListener('click', () => {
this.clearDiagram();
});
// 上传文件事件
document.getElementById('uploadFile').addEventListener('change', (event) => {
this.loadDiagram(event.target.files[0]);
});
// 监听元素选择事件
this.modeler.on('selection.changed', (event) => {
const { newSelection } = event;
console.log('选中元素:', newSelection);
});
// 监听元素变更事件
this.modeler.on('element.changed', (event) => {
const { element } = event;
console.log('元素变更:', element);
});
}
async loadDiagram(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = async (e) => {
try {
const xml = e.target.result;
await this.modeler.importXML(xml);
console.log('流程图加载成功');
} catch (err) {
console.error('加载流程图失败:', err);
alert('加载流程图失败: ' + err.message);
}
};
reader.readAsText(file);
}
async saveDiagram() {
try {
const { xml } = await this.modeler.saveXML({ format: true });
console.log('保存的 XML:', xml);
// 发送到后端保存
await this.saveToServer(xml);
alert('流程图保存成功!');
} catch (err) {
console.error('保存流程图失败:', err);
alert('保存流程图失败: ' + err.message);
}
}
async downloadDiagram() {
try {
const { xml } = await this.modeler.saveXML({ format: true });
const blob = new Blob([xml], { type: 'application/xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'process.bpmn';
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error('下载流程图失败:', err);
alert('下载流程图失败: ' + err.message);
}
}
async clearDiagram() {
if (confirm('确定要清空当前流程图吗?')) {
try {
await this.modeler.importXML(this.getEmptyBpmn());
console.log('流程图已清空');
} catch (err) {
console.error('清空流程图失败:', err);
}
}
}
async saveToServer(xml) {
// 调用后端 API 保存流程图
const response = await fetch('/api/process-design', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
xml: xml,
name: '我的流程',
category: 'OA'
})
});
if (!response.ok) {
throw new Error('保存到服务器失败');
}
}
getEmptyBpmn() {
return `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
targetNamespace="http://bpmn.io/schema/bpmn"
id="Definitions_1">
<process id="Process_1" isExecutable="false">
<startEvent id="StartEvent_1"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>`;
}
}
// 初始化设计器
document.addEventListener('DOMContentLoaded', () => {
const designer = new BpmnDesigner('canvas', 'properties-panel');
});
c.Vue.js 集成
<template>
<div class="process-designer">
<div class="toolbar">
<el-button @click="save" type="primary">保存</el-button>
<el-button @click="download">下载</el-button>
<el-button @click="clear">清空</el-button>
<el-upload
action=""
:show-file-list="false"
:before-upload="handleUpload"
accept=".bpmn,.xml">
<el-button>上传</el-button>
</el-upload>
</div>
<div class="designer-content">
<div ref="canvas" class="canvas"></div>
<div ref="propertiesPanel" class="properties-panel"></div>
</div>
</div>
</template>
<script>
import BpmnModeler from 'bpmn-js/lib/Modeler';
import propertiesPanelModule from 'bpmn-js-properties-panel';
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';
export default {
name: 'ProcessDesigner',
data() {
return {
modeler: null,
processInfo: {
name: '',
category: ''
}
};
},
mounted() {
this.initModeler();
},
methods: {
initModeler() {
this.modeler = new BpmnModeler({
container: this.$refs.canvas,
propertiesPanel: {
parent: this.$refs.propertiesPanel
},
additionalModules: [
propertiesPanelModule,
propertiesProviderModule
],
moddleExtensions: {
camunda: require('camunda-bpmn-moddle/resources/camunda')
}
});
// 加载空模板
this.loadEmptyDiagram();
},
async loadEmptyDiagram() {
try {
await this.modeler.importXML(this.getEmptyTemplate());
} catch (err) {
console.error('加载空模板失败:', err);
}
},
async save() {
try {
const { xml } = await this.modeler.saveXML({ format: true });
// 保存到后端
const response = await this.$http.post('/api/process-design', {
xml: xml,
name: this.processInfo.name,
category: this.processInfo.category
});
if (response.data.success) {
this.$message.success('保存成功');
} else {
this.$message.error('保存失败');
}
} catch (err) {
console.error('保存失败:', err);
this.$message.error('保存失败: ' + err.message);
}
},
async download() {
try {
const { xml } = await this.modeler.saveXML({ format: true });
const blob = new Blob([xml], { type: 'application/xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'process.bpmn';
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error('下载失败:', err);
this.$message.error('下载失败');
}
},
async clear() {
this.$confirm('确定要清空当前流程图吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.loadEmptyDiagram();
this.$message.success('已清空');
});
},
handleUpload(file) {
const reader = new FileReader();
reader.onload = async (e) => {
try {
await this.modeler.importXML(e.target.result);
this.$message.success('上传成功');
} catch (err) {
console.error('上传失败:', err);
this.$message.error('上传失败: ' + err.message);
}
};
reader.readAsText(file);
return false; // 阻止默认上传行为
},
getEmptyTemplate() {
return this.getEmptyBpmn();
},
getEmptyBpmn() {
return `<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
targetNamespace="http://bpmn.io/schema/bpmn">
<process id="Process_1" isExecutable="false">
<startEvent id="StartEvent_1"/>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36"/>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>`;
}
}
};
</script>
<style scoped>
.process-designer {
height: 100vh;
display: flex;
flex-direction: column;
}
.toolbar {
padding: 10px;
border-bottom: 1px solid #e6e6e6;
background: #fff;
}
.designer-content {
flex: 1;
display: flex;
}
.canvas {
flex: 1;
border-right: 1px solid #e6e6e6;
}
.properties-panel {
width: 300px;
background: #f5f5f5;
overflow: auto;
}
</style>
03.流程图绘制
a.创建开始事件
// 创建开始事件
async createStartEvent() {
const elementFactory = this.modeler.get('elementFactory');
const elementRegistry = this.modeler.get('elementRegistry');
const modeling = this.modeler.get('modeling');
const startEvent = elementFactory.createShape({
type: 'bpmn:StartEvent',
x: 100,
y: 100
});
modeling.createShape(startEvent, { x: 100, y: 100 });
}
b.创建用户任务
// 创建用户任务
async createUserTask() {
const elementFactory = this.modeler.get('elementFactory');
const modeling = this.modeler.get('modeling');
const userTask = elementFactory.createShape({
type: 'bpmn:UserTask',
x: 200,
y: 100
});
modeling.createShape(userTask, { x: 200, y: 100 });
}
c.创建网关
// 创建排他网关
async createExclusiveGateway() {
const elementFactory = this.modeler.get('elementFactory');
const modeling = this.modeler.get('modeling');
const gateway = elementFactory.createShape({
type: 'bpmn:ExclusiveGateway',
x: 300,
y: 100
});
modeling.createShape(gateway, { x: 300, y: 100 });
}
d.创建连接线
// 创建连接线
async createConnection(sourceId, targetId) {
const elementFactory = this.modeler.get('elementFactory');
const elementRegistry = this.modeler.get('elementRegistry');
const modeling = this.modeler.get('modeling');
const source = elementRegistry.get(sourceId);
const target = elementRegistry.get(targetId);
const connection = elementFactory.createConnection({
source: source,
target: target
}, {
type: 'bpmn:SequenceFlow'
});
modeling.createConnection(connection);
}
04.流程图预览
a.获取 SVG 预览
async getSvgPreview() {
try {
const { svg } = await this.modeler.saveSVG();
return svg;
} catch (err) {
console.error('获取 SVG 预览失败:', err);
return null;
}
}
b.显示预览
<template>
<div>
<div ref="designerCanvas" class="designer-canvas"></div>
<el-dialog title="流程图预览" :visible.sync="previewVisible" width="80%">
<div v-html="previewSvg" class="preview-container"></div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
modeler: null,
previewVisible: false,
previewSvg: ''
};
},
methods: {
async showPreview() {
this.previewSvg = await this.getSvgPreview();
this.previewVisible = true;
},
async getSvgPreview() {
try {
const { svg } = await this.modeler.saveSVG();
return svg;
} catch (err) {
console.error('获取预览失败:', err);
return '<p>预览失败</p>';
}
}
}
};
</script>
<style scoped>
.designer-canvas {
height: 500px;
border: 1px solid #ccc;
}
.preview-container {
text-align: center;
padding: 20px;
}
</style>
05.流程图导出
a.XML 导出
async exportXml() {
try {
const { xml } = await this.modeler.saveXML({ format: true });
// 创建下载
const blob = new Blob([xml], { type: 'application/xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'process.bpmn';
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error('XML 导出失败:', err);
}
}
b.SVG 导出
async exportSvg() {
try {
const { svg } = await this.modeler.saveSVG();
// 创建下载
const blob = new Blob([svg], { type: 'image/svg+xml' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'process.svg';
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error('SVG 导出失败:', err);
}
}
c.JSON 导出
async exportJson() {
try {
const { xml } = await this.modeler.saveXML();
// 这里可以将 XML 转换为自定义 JSON 格式
const json = await this.convertXmlToJson(xml);
const blob = new Blob([JSON.stringify(json, null, 2)],
{ type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'process.json';
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error('JSON 导出失败:', err);
}
}
06.后端集成
a.流程设计保存接口
@RestController
@RequestMapping("/api/process-design")
public class ProcessDesignController {
@Autowired
private ProcessDesignService processDesignService;
@PostMapping
public ApiResponse<String> saveProcessDesign(@RequestBody ProcessDesignRequest request) {
try {
String deploymentId = processDesignService.saveProcessDesign(
request.getXml(),
request.getName(),
request.getCategory()
);
return ApiResponse.success("保存成功", deploymentId);
} catch (Exception e) {
log.error("保存流程设计失败", e);
return ApiResponse.error("保存失败: " + e.getMessage());
}
}
@GetMapping("/list")
public ApiResponse<List<ProcessDesign>> getProcessDesignList() {
try {
List<ProcessDesign> designs = processDesignService.getAllDesigns();
return ApiResponse.success("查询成功", designs);
} catch (Exception e) {
log.error("查询流程设计失败", e);
return ApiResponse.error("查询失败: " + e.getMessage());
}
}
@GetMapping("/{deploymentId}/xml")
public ResponseEntity<String> getProcessXml(@PathVariable String deploymentId) {
try {
String xml = processDesignService.getProcessXml(deploymentId);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_XML)
.body(xml);
} catch (Exception e) {
log.error("获取流程 XML 失败", e);
return ResponseEntity.notFound().build();
}
}
@DeleteMapping("/{deploymentId}")
public ApiResponse<String> deleteProcessDesign(@PathVariable String deploymentId) {
try {
processDesignService.deleteProcessDesign(deploymentId);
return ApiResponse.success("删除成功");
} catch (Exception e) {
log.error("删除流程设计失败", e);
return ApiResponse.error("删除失败: " + e.getMessage());
}
}
}
b.流程设计服务
@Service
public class ProcessDesignService {
@Autowired
private RepositoryService repositoryService;
@Autowired
private ProcessDesignRepository processDesignRepository;
/**
* 保存流程设计
*/
@Transactional
public String saveProcessDesign(String xml, String name, String category) {
try {
// 部署流程定义
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment()
.addString(name + ".bpmn20.xml", xml)
.name(name);
if (StringUtils.isNotBlank(category)) {
deploymentBuilder.category(category);
}
Deployment deployment = deploymentBuilder.deploy();
// 保存设计记录
ProcessDesign design = new ProcessDesign();
design.setDeploymentId(deployment.getId());
design.setName(name);
design.setCategory(category);
design.setXml(xml);
design.setCreateTime(new Date());
design.setUpdateTime(new Date());
processDesignRepository.save(design);
return deployment.getId();
} catch (Exception e) {
log.error("保存流程设计失败: name={}, category={}", name, category, e);
throw new RuntimeException("保存流程设计失败", e);
}
}
/**
* 获取所有流程设计
*/
public List<ProcessDesign> getAllDesigns() {
return processDesignRepository.findAll();
}
/**
* 获取流程 XML
*/
public String getProcessXml(String deploymentId) {
ProcessDesign design = processDesignRepository.findByDeploymentId(deploymentId);
if (design != null) {
return design.getXml();
}
// 如果没有记录,从部署中获取
ProcessDefinition processDefinition = repositoryService
.createProcessDefinitionQuery()
.deploymentId(deploymentId)
.singleResult();
if (processDefinition != null) {
List<String> resourceNames = repositoryService
.getDeploymentResourceNames(deploymentId);
for (String resourceName : resourceNames) {
if (resourceName.endsWith(".bpmn") || resourceName.endsWith(".bpmn20.xml")) {
InputStream inputStream = repositoryService
.getResourceAsStream(deploymentId, resourceName);
return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
}
}
throw new RuntimeException("未找到流程定义");
}
/**
* 删除流程设计
*/
@Transactional
public void deleteProcessDesign(String deploymentId) {
// 删除数据库记录
ProcessDesign design = processDesignRepository.findByDeploymentId(deploymentId);
if (design != null) {
processDesignRepository.delete(design);
}
// 删除流程部署
repositoryService.deleteDeployment(deploymentId, true);
}
}
c.数据库实体
@Entity
@Table(name = "process_design")
public class ProcessDesign {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "deployment_id", nullable = false, unique = true)
private String deploymentId;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "category")
private String category;
@Column(name = "xml", columnDefinition = "LONGTEXT")
private String xml;
@Column(name = "create_time")
private Date createTime;
@Column(name = "update_time")
private Date updateTime;
@Column(name = "create_by")
private String createBy;
@Column(name = "update_by")
private String updateBy;
// getters and setters
}
4.4 实战案例
00.总结
a.说明
通过具体的业务案例来演示 Flowable 在实际项目中的应用。
包含完整的流程定义、前端页面、后端实现等。
涵盖请假、报销、采购等常见业务场景。
b.案例特点
完整的业务流程设计
前后端分离架构
RESTful API 设计
数据库设计优化
c.技术栈
Spring Boot 2.7
Flowable 6.7.2
Vue.js + Element UI
MySQL 8.0
MyBatis Plus
01.请假流程
a.流程设计
流程名称:员工请假流程
流程说明:员工发起请假申请 → 部门经理审批 → HR 审批 → 完成
流程图:
[开始] → [填写请假申请] → [部门经理审批] → [HR审批] → [结束]
b.BPMN 流程定义
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flowable="http://flowable.org/bpmn"
targetNamespace="http://www.flowable.org/processdef">
<process id="leaveProcess" name="员工请假流程" isExecutable="true">
<startEvent id="startEvent" name="开始"/>
<userTask id="fillLeaveTask" name="填写请假申请"
flowable:assignee="$INITIATOR">
<extensionElements>
<flowable:formProperty id="leaveType" name="请假类型"
type="enum" required="true">
<flowable:value id="sick" name="病假"/>
<flowable:value id="personal" name="事假"/>
<flowable:value id="annual" name="年假"/>
</flowable:formProperty>
<flowable:formProperty id="days" name="请假天数"
type="long" required="true"/>
<flowable:formProperty id="reason" name="请假原因"
type="string" required="true"/>
<flowable:formProperty id="startDate" name="开始日期"
type="date" required="true"/>
<flowable:formProperty id="endDate" name="结束日期"
type="date" required="true"/>
</extensionElements>
</userTask>
<exclusiveGateway id="managerApprovalGateway" name="部门经理审批网关"/>
<userTask id="managerApprovalTask" name="部门经理审批"
flowable:candidateGroups="manager">
<extensionElements>
<flowable:formProperty id="approved" name="审批结果"
type="enum" required="true">
<flowable:value id="true" name="同意"/>
<flowable:value id="false" name="拒绝"/>
</flowable:formProperty>
<flowable:formProperty id="comment" name="审批意见"
type="string"/>
</extensionElements>
</userTask>
<exclusiveGateway id="hrApprovalGateway" name="HR审批网关"/>
<userTask id="hrApprovalTask" name="HR审批"
flowable:candidateGroups="hr">
<extensionElements>
<flowable:formProperty id="approved" name="审批结果"
type="enum" required="true">
<flowable:value id="true" name="同意"/>
<flowable:value id="false" name="拒绝"/>
</flowable:formProperty>
<flowable:formProperty id="comment" name="审批意见"
type="string"/>
</extensionElements>
</userTask>
<endEvent id="approveEndEvent" name="请假通过"/>
<endEvent id="rejectEndEvent" name="请假拒绝"/>
<!-- 连接线 -->
<sequenceFlow id="start_to_fill" sourceRef="startEvent" targetRef="fillLeaveTask"/>
<sequenceFlow id="fill_to_manager" sourceRef="fillLeaveTask"
targetRef="managerApprovalGateway">
<conditionExpression xsi:type="tFormalExpression">
${days > 0}
</conditionExpression>
</sequenceFlow>
<!-- 部门经理审批分支 -->
<sequenceFlow id="gateway_to_manager" sourceRef="managerApprovalGateway"
targetRef="managerApprovalTask">
<conditionExpression xsi:type="tFormalExpression">
${days <= 7 || leaveType == 'sick'}
</conditionExpression>
</sequenceFlow>
<!-- 直接跳到HR审批(请假天数>7天且非病假) -->
<sequenceFlow id="gateway_to_hr_direct" sourceRef="managerApprovalGateway"
targetRef="hrApprovalGateway">
<conditionExpression xsi:type="tFormalExpression">
${days > 7 && leaveType != 'sick'}
</conditionExpression>
</sequenceFlow>
<!-- 部门经理同意 -->
<sequenceFlow id="manager_approve" sourceRef="managerApprovalTask"
targetRef="hrApprovalGateway">
<conditionExpression xsi:type="tFormalExpression">
${approved == true}
</conditionExpression>
</sequenceFlow>
<!-- 部门经理拒绝 -->
<sequenceFlow id="manager_reject" sourceRef="managerApprovalTask"
targetRef="rejectEndEvent">
<conditionExpression xsi:type="tFormalExpression">
${approved == false}
</conditionExpression>
</sequenceFlow>
<!-- HR同意 -->
<sequenceFlow id="hr_approve" sourceRef="hrApprovalTask"
targetRef="approveEndEvent">
<conditionExpression xsi:type="tFormalExpression">
${approved == true}
</conditionExpression>
</sequenceFlow>
<!-- HR拒绝 -->
<sequenceFlow id="hr_reject" sourceRef="hrApprovalTask"
targetRef="rejectEndEvent">
<conditionExpression xsi:type="tFormalExpression">
${approved == false}
</conditionExpression>
</sequenceFlow>
</process>
</definitions>
c.实体类设计
@Data
@TableName("leave_request")
public class LeaveRequest {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("process_instance_id")
private String processInstanceId;
@TableField("user_id")
private Long userId;
@TableField("user_name")
private String userName;
@TableField("department_id")
private Long departmentId;
@TableField("department_name")
private String departmentName;
@TableField("leave_type")
private String leaveType;
@TableField("days")
private Integer days;
@TableField("start_date")
private Date startDate;
@TableField("end_date")
private Date endDate;
@TableField("reason")
private String reason;
@TableField("status")
private String status; // PENDING, APPROVED, REJECTED
@TableField("manager_approval")
private Boolean managerApproval;
@TableField("manager_comment")
private String managerComment;
@TableField("hr_approval")
private Boolean hrApproval;
@TableField("hr_comment")
private String hrComment;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
d.服务实现
@Service
@Transactional
public class LeaveProcessService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
@Autowired
private LeaveRequestMapper leaveRequestMapper;
/**
* 发起请假流程
*/
public ProcessInstance startLeaveProcess(LeaveRequest request) {
// 保存请假记录
leaveRequestMapper.insert(request);
// 设置流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("leaveType", request.getLeaveType());
variables.put("days", request.getDays());
variables.put("reason", request.getReason());
variables.put("startDate", request.getStartDate());
variables.put("endDate", request.getEndDate());
variables.put("requestId", request.getId());
// 启动流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
"leaveProcess",
"LEAVE_" + request.getId(),
variables
);
// 更新请假记录的流程实例ID
request.setProcessInstanceId(processInstance.getId());
leaveRequestMapper.updateById(request);
return processInstance;
}
/**
* 处理请假任务
*/
public void handleLeaveTask(String taskId, LeaveTaskDTO taskDTO) {
// 获取任务信息
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new RuntimeException("任务不存在");
}
// 获取流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId()).singleResult();
// 根据任务名称处理不同审批节点
if ("部门经理审批".equals(task.getName())) {
handleManagerApproval(task, taskDTO);
} else if ("HR审批".equals(task.getName())) {
handleHrApproval(task, taskDTO);
}
// 完成任务
Map<String, Object> variables = new HashMap<>();
variables.put("approved", taskDTO.getApproved());
variables.put("comment", taskDTO.getComment());
taskService.complete(taskId, variables);
}
private void handleManagerApproval(Task task, LeaveTaskDTO taskDTO) {
// 获取请假记录
LeaveRequest request = getLeaveRequestByProcessId(task.getProcessInstanceId());
if (request == null) {
throw new RuntimeException("请假记录不存在");
}
// 更新经理审批信息
request.setManagerApproval(taskDTO.getApproved());
request.setManagerComment(taskDTO.getComment());
if (taskDTO.getApproved()) {
request.setStatus("HR_APPROVING");
} else {
request.setStatus("REJECTED");
}
leaveRequestMapper.updateById(request);
}
private void handleHrApproval(Task task, LeaveTaskDTO taskDTO) {
// 获取请假记录
LeaveRequest request = getLeaveRequestByProcessId(task.getProcessInstanceId());
if (request == null) {
throw new RuntimeException("请假记录不存在");
}
// 更新HR审批信息
request.setHrApproval(taskDTO.getApproved());
request.setHrComment(taskDTO.getComment());
if (taskDTO.getApproved()) {
request.setStatus("APPROVED");
} else {
request.setStatus("REJECTED");
}
leaveRequestMapper.updateById(request);
}
private LeaveRequest getLeaveRequestByProcessId(String processInstanceId) {
return leaveRequestMapper.selectOne(
new QueryWrapper<LeaveRequest>()
.eq("process_instance_id", processInstanceId)
);
}
}
e.前端实现
<template>
<div class="leave-request">
<el-card>
<div slot="header">
<span>请假申请</span>
</div>
<el-form :model="leaveForm" :rules="rules" ref="leaveForm" label-width="120px">
<el-form-item label="请假类型" prop="leaveType">
<el-select v-model="leaveForm.leaveType" placeholder="请选择请假类型">
<el-option label="病假" value="sick"></el-option>
<el-option label="事假" value="personal"></el-option>
<el-option label="年假" value="annual"></el-option>
</el-select>
</el-form-item>
<el-form-item label="请假天数" prop="days">
<el-input-number v-model="leaveForm.days" :min="1" :max="365"></el-input-number>
</el-form-item>
<el-form-item label="开始日期" prop="startDate">
<el-date-picker
v-model="leaveForm.startDate"
type="date"
placeholder="选择开始日期">
</el-date-picker>
</el-form-item>
<el-form-item label="结束日期" prop="endDate">
<el-date-picker
v-model="leaveForm.endDate"
type="date"
placeholder="选择结束日期">
</el-date-picker>
</el-form-item>
<el-form-item label="请假原因" prop="reason">
<el-input
type="textarea"
v-model="leaveForm.reason"
:rows="4"
placeholder="请输入请假原因">
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitLeave">提交申请</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
export default {
name: 'LeaveRequest',
data() {
return {
leaveForm: {
leaveType: '',
days: 1,
startDate: '',
endDate: '',
reason: ''
},
rules: {
leaveType: [
{ required: true, message: '请选择请假类型', trigger: 'change' }
],
days: [
{ required: true, message: '请输入请假天数', trigger: 'blur' }
],
startDate: [
{ required: true, message: '请选择开始日期', trigger: 'change' }
],
endDate: [
{ required: true, message: '请选择结束日期', trigger: 'change' }
],
reason: [
{ required: true, message: '请输入请假原因', trigger: 'blur' }
]
}
};
},
methods: {
submitLeave() {
this.$refs.leaveForm.validate(async (valid) => {
if (valid) {
try {
const response = await this.$http.post('/api/leave/start', this.leaveForm);
if (response.data.success) {
this.$message.success('请假申请提交成功');
this.resetForm();
} else {
this.$message.error(response.data.message);
}
} catch (error) {
this.$message.error('提交失败:' + error.message);
}
}
});
},
resetForm() {
this.$refs.leaveForm.resetFields();
}
}
};
</script>
02.报销流程
a.流程设计
流程名称:员工报销流程
流程说明:员工提交报销申请 → 财务审核 → 经理审批 → 付款完成
流程图:
[开始] → [填写报销单] → [财务审核] → [经理审批] → [付款处理] → [结束]
b.核心代码
@RestController
@RequestMapping("/api/expense")
public class ExpenseController {
@Autowired
private ExpenseProcessService expenseProcessService;
@PostMapping("/start")
public ApiResponse<String> startExpenseProcess(@RequestBody ExpenseRequestDTO requestDTO) {
try {
ProcessInstance processInstance = expenseProcessService.startExpenseProcess(requestDTO);
return ApiResponse.success("报销流程启动成功", processInstance.getId());
} catch (Exception e) {
return ApiResponse.error("启动失败: " + e.getMessage());
}
}
@PostMapping("/task/complete")
public ApiResponse<String> completeTask(@RequestBody TaskCompleteDTO completeDTO) {
try {
expenseProcessService.handleExpenseTask(completeDTO.getTaskId(), completeDTO);
return ApiResponse.success("任务处理完成");
} catch (Exception e) {
return ApiResponse.error("处理失败: " + e.getMessage());
}
}
@GetMapping("/my-tasks")
public ApiResponse<List<TaskDTO>> getMyTasks() {
try {
List<TaskDTO> tasks = expenseProcessService.getMyTasks();
return ApiResponse.success("查询成功", tasks);
} catch (Exception e) {
return ApiResponse.error("查询失败: " + e.getMessage());
}
}
}
03.采购流程
a.流程设计
流程名称:物资采购流程
流程说明:部门申请 → 采购部评估 → 部门经理审批 → 总经理审批 → 执行采购
流程图:
[开始] → [提交采购申请] → [采购评估] → [部门经理审批] → [总经理审批] → [执行采购] → [结束]
b.多级审批实现
@Service
public class PurchaseProcessService {
/**
* 根据金额决定审批级别
*/
private String getApprovalLevel(BigDecimal amount) {
if (amount.compareTo(new BigDecimal("10000")) <= 0) {
return "DEPARTMENT_MANAGER";
} else if (amount.compareTo(new BigDecimal("50000")) <= 0) {
return "GENERAL_MANAGER";
} else {
return "CEO";
}
}
/**
* 动态设置审批人
*/
public void dynamicAssignee(String processInstanceId, BigDecimal amount) {
String approvalLevel = getApprovalLevel(amount);
String assignee = getApprovalUser(approvalLevel);
runtimeService.setVariable(processInstanceId, "approvalLevel", approvalLevel);
runtimeService.setVariable(processInstanceId, "approvalUser", assignee);
}
private String getApprovalUser(String approvalLevel) {
// 根据审批级别获取审批人
// 这里可以实现复杂的审批人选择逻辑
switch (approvalLevel) {
case "DEPARTMENT_MANAGER":
return "manager001";
case "GENERAL_MANAGER":
return "gm001";
case "CEO":
return "ceo001";
default:
return "system";
}
}
}
04.完整代码示例
a.项目结构
src/main/java
├── com.example.flowable
│ ├── config # 配置类
│ ├── controller # 控制器
│ ├── service # 服务类
│ ├── entity # 实体类
│ ├── dto # 数据传输对象
│ └── FlowableApplication.java
└── resources
├── processes # 流程定义文件
│ ├── leave.bpmn20.xml
│ ├── expense.bpmn20.xml
│ └── purchase.bpmn20.xml
├── application.yml # 配置文件
└── mapper # MyBatis 映射文件
b.主配置类
@SpringBootApplication
@EnableFlowable
@MapperScan("com.example.flowable.mapper")
public class FlowableApplication {
public static void main(String[] args) {
SpringApplication.run(FlowableApplication.class, args);
}
}
c.统一响应格式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(true, message, data);
}
public static <T> ApiResponse<T> success(String message) {
return new ApiResponse<>(true, message, null);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(false, message, null);
}
}
5 最佳实践
5.1 设计原则
00.总结
a.说明
流程设计的好坏直接影响系统的可用性和维护性。
遵循良好的设计原则能够提高流程的可读性、可维护性和可扩展性。
设计时需要考虑业务场景的复杂性、性能要求和未来扩展。
b.核心原则
简单性:流程图应该简单易懂,避免过度复杂化
一致性:同类业务的流程设计应该保持一致性
可维护性:流程变更成本要低,便于后期维护
可扩展性:能够适应业务发展需求的变化
性能优先:考虑大数据量情况下的性能表现
01.流程设计规范
a.流程图绘制规范
a.命名规范
流程ID:使用小写字母和下划线,如 leave_process
流程名称:使用中文,描述性名称,如员工请假流程
任务ID:使用驼峰命名,如 fillLeaveInfo
任务名称:使用动词+名词的形式,如填写请假信息
网关ID:使用描述性名称,如 approvalGateway
b.布局规范
从左到右,从上到下的流程走向
避免交叉连线,保持图表整洁
相同类型的节点使用相同的间距
适当使用颜色区分不同类型的节点
c.元素使用规范
开始事件:有且只有一个,使用圆形图标
结束事件:可以有多个,使用粗边框圆形
用户任务:需要人工处理的任务
服务任务:系统自动处理的任务
网关:用于流程分支和合并
b.流程逻辑规范
a.异常处理
每个关键节点都要有异常处理路径
定义超时处理机制
设置补偿事务处理
b.审批链路
审批链路不宜过长,一般不超过5级
相同角色的审批节点可以合并
重要流程需要会签机制
c.并行处理
并行任务要有明确的合并点
避免并行任务之间的依赖关系
考虑并行任务的执行时间差异
02.命名规范
a.流程定义命名
# 命名格式
{业务模块}_{具体业务}_{版本号}
# 示例
hr_leave_process_v1
finance_expense_approval_v2
procurement_purchase_request_v1
# 数据库表命名
表名前缀:biz_
流程记录表:biz_process_record
任务记录表:biz_task_record
审批记录表:biz_approval_record
b.变量命名规范
# 流程变量
使用驼峰命名法
applicant_id:申请人ID
leave_days:请假天数
expense_amount:报销金额
approval_result:审批结果
# 任务变量
task_assignee:任务办理人
task_candidate_users:候选用户
task_candidate_groups:候选组
task_comment:审批意见
c.代码命名规范
# Service 命名
{业务模块}Service
LeaveService、ExpenseService、PurchaseService
# Controller 命名
{业务模块}Controller
LeaveController、ExpenseController
# 实体类命名
{业务模块}Record
LeaveRecord、ExpenseRecord
03.变量管理
a.变量分类
a.流程变量(Process Variables)
生命周期:整个流程实例
作用范围:流程中的所有节点
示例:请假天数、报销金额、申请人信息
b.任务变量(Task Variables)
生命周期:单个任务
作用范围:当前任务
示例:审批意见、操作时间
c.全局变量(Global Variables)
生命周期:系统级别
作用范围:所有流程
示例:系统配置、节假日列表
b.变量使用原则
a.类型安全
使用强类型变量
避免使用 Object 类型
提供变量校验机制
b.作用域控制
最小化变量作用域
及时清理临时变量
避免变量污染
c.命名规范
使用有意义的变量名
避免缩写和拼音
统一命名风格
04.异常处理
a.异常分类
a.业务异常
流程定义异常
流程实例异常
任务执行异常
变量设置异常
b.系统异常
数据库连接异常
网络异常
内存溢出异常
c.集成异常
第三方系统调用异常
消息队列异常
文件操作异常
b.异常处理策略
a.流程级别异常处理
// 全局异常处理器
@Component
public class GlobalFlowableExceptionHandler {
public void handleException(Exception exception, String executionId) {
// 记录异常日志
log.error("流程执行异常", exception);
// 发送告警通知
alertService.sendAlert(exception);
// 设置流程变量
runtimeService.setVariable(executionId, "error_message",
exception.getMessage());
}
}
b.任务级别异常处理
// 任务异常处理
@Component
public class TaskExceptionHandler {
public void handleTaskFailure(DelegateTask task, Exception exception) {
// 记录失败信息
task.setVariable("task_failed", true);
task.setVariable("failure_time", new Date());
// 通知相关人员
notificationService.notifyTaskFailed(task);
}
}
5.2 性能优化
00.总结
a.说明
随着业务数据量增长,工作流系统的性能优化变得尤为重要。
优化涉及数据库、应用代码、流程设计等多个层面。
需要从架构设计角度考虑性能问题,避免出现性能瓶颈。
b.优化目标
响应时间:减少用户操作等待时间
吞吐量:提高系统并发处理能力
资源利用率:合理使用CPU、内存、数据库资源
可扩展性:支持业务量增长
01.数据库优化
a.索引优化
-- 为常用查询字段添加索引
CREATE INDEX idx_process_def_key ON ACT_RE_PROCDEF(PROC_DEF_KEY_);
CREATE INDEX idx_process_instance_id ON ACT_RU_EXECUTION(PROC_INST_ID_);
CREATE INDEX idx_task_assignee ON ACT_RU_TASK(ASSIGNEE_);
CREATE INDEX idx_task_create_time ON ACT_RU_TASK(CREATE_TIME_);
CREATE INDEX idx_historic_proc_inst_id ON ACT_HI_PROCINST(PROC_INST_ID_);
-- 为组合查询添加复合索引
CREATE INDEX idx_task_assignee_status ON ACT_RU_TASK(ASSIGNEE_, SUSPENSION_STATE_);
b.表分区策略
-- 按时间分区历史表
ALTER TABLE ACT_HI_PROCINST PARTITION BY RANGE (START_TIME_) (
PARTITION p2023 VALUES LESS THAN ('2024-01-01'),
PARTITION p2024 VALUES LESS THAN ('2025-01-01'),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
c.数据库连接池优化
# HikariCP 配置优化
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.connection-timeout=20000
# Flowable 批处理配置
flowable.async-executor-activate=true
flowable.async-executor.default-async-job-acquire-wait-time=5000
02.查询优化
a.分页查询优化
@Service
public class TaskQueryService {
public PageResult<TaskInfo> queryTasks(TaskQueryRequest request) {
// 使用原生SQL查询,避免N+1问题
String sql = """
SELECT t.ID_, t.NAME_, t.CREATE_TIME_, t.ASSIGNEE_,
p.PROC_INST_ID_, p.PROC_DEF_KEY_, p.BUSINESS_KEY_
FROM ACT_RU_TASK t
LEFT JOIN ACT_RU_EXECUTION e ON t.PROC_INST_ID_ = e.PROC_INST_ID_
WHERE t.ASSIGNEE_ = ? AND t.SUSPENSION_STATE_ = 1
ORDER BY t.CREATE_TIME_ DESC
LIMIT ? OFFSET ?
""";
List<TaskInfo> tasks = jdbcTemplate.query(sql,
new Object[]{request.getUserId(), request.getPageSize(), request.getPageOffset()},
new TaskRowMapper());
return PageResult.of(tasks, request.getPage(), request.getPageSize());
}
}
03.历史数据清理
a.定时清理策略
@Component
public class HistoryDataCleaner {
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void cleanOldHistoryData() {
LocalDate cutoffDate = LocalDate.now().minusYears(1);
// 清理历史流程实例
historyService.createHistoricProcessInstanceQuery()
.finishedBefore(Date.from(cutoffDate.atStartOfDay()
.atZone(ZoneId.systemDefault()).toInstant()))
.list()
.forEach(this::cleanProcessInstance);
}
private void cleanProcessInstance(HistoricProcessInstance instance) {
try {
historyService.deleteHistoricProcessInstance(instance.getId());
} catch (Exception e) {
log.error("清理历史数据失败: {}", instance.getId(), e);
}
}
}
5.3 安全控制
00.总结
a.说明
工作流系统涉及敏感业务数据,需要完善的安全控制机制。
安全控制包括身份认证、权限管理、数据隔离、审计日志等。
需要建立多层次的安全防护体系。
b.安全层次
应用层:身份认证、会话管理
业务层:权限控制、数据验证
数据层:数据加密、访问控制
审计层:操作日志、行为追踪
01.权限管理
a.基于角色的访问控制(RBAC)
// 权限实体类
@Entity
public class Permission {
private String id;
private String name;
private String code;
private String type; // PROCESS, TASK, ADMIN
}
// 角色实体类
@Entity
public class Role {
private String id;
private String name;
private String code;
@ManyToMany
private Set<Permission> permissions;
}
// 用户实体类
@Entity
public class User {
private String id;
private String username;
private String password;
@ManyToMany
private Set<Role> roles;
}
b.流程权限控制
@Service
public class ProcessPermissionService {
public boolean canStartProcess(String userId, String processDefinitionKey) {
// 检查用户是否有启动流程的权限
User user = userService.findById(userId);
return user.getRoles().stream()
.flatMap(role -> role.getPermissions().stream())
.anyMatch(permission ->
permission.getCode().equals("PROCESS_START") &&
permission.getType().equals("PROCESS"));
}
public boolean canAccessTask(String userId, String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// 检查是否是任务负责人
if (userId.equals(task.getAssignee())) {
return true;
}
// 检查是否是候选人
return taskService.createTaskQuery()
.taskId(taskId)
.taskCandidateUser(userId)
.count() > 0;
}
}
02.数据隔离
a.多租户数据隔离
@Component
public class TenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前租户ID
String tenantId = SecurityUtils.getCurrentTenantId();
// 设置租户上下文
TenantContext.setCurrentTenant(tenantId);
try {
return invocation.proceed();
} finally {
TenantContext.clear();
}
}
}
// 租户上下文
public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setCurrentTenant(String tenantId) {
CURRENT_TENANT.set(tenantId);
}
public static String getCurrentTenant() {
return CURRENT_TENANT.get();
}
public static void clear() {
CURRENT_TENANT.remove();
}
}
b.流程实例隔离
@Service
public class TenantProcessService {
public List<ProcessInstance> getProcessInstances(String userId) {
String tenantId = TenantContext.getCurrentTenant();
return runtimeService.createProcessInstanceQuery()
.processInstanceTenantId(tenantId)
.variableValueEquals("initiator", userId)
.list();
}
public ProcessInstance startProcess(String processDefinitionKey,
Map<String, Object> variables) {
String tenantId = TenantContext.getCurrentTenant();
variables.put("tenantId", tenantId);
return runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
}
}
03.审计日志
a.操作日志记录
@Component
public class AuditLogService {
public void logProcessOperation(String operation, String processInstanceId,
String userId, Map<String, Object> data) {
AuditLog log = AuditLog.builder()
.id(UUID.randomUUID().toString())
.operation(operation)
.resourceType("PROCESS")
.resourceId(processInstanceId)
.userId(userId)
.tenantId(TenantContext.getCurrentTenant())
.operationTime(new Date())
.requestData(data)
.build();
auditLogRepository.save(log);
}
}
5.4 常见问题
00.总结
a.说明
工作流系统在实际使用中会遇到各种问题,需要系统性的解决方案。
常见问题涉及流程设计、系统性能、数据一致性、异常处理等方面。
建立完善的问题诊断和解决机制很重要。
b.问题分类
流程问题:流程挂起、流程无法启动、流程变量丢失
任务问题:任务分配错误、任务无法完成、任务超时
数据问题:数据不一致、历史数据丢失、变量传递失败
性能问题:响应慢、并发处理能力差、资源占用过高
01.流程挂起
a.问题现象
流程实例长时间停留在某个节点不继续执行
用户无法查看后续任务
流程实例状态为挂起
b.解决方案
@Service
public class ProcessRecoveryService {
public void recoverProcess(String processInstanceId) {
// 诊断流程状态
DiagnosticResult diagnostic = diagnoseProcess(processInstanceId);
if (diagnostic.getProcessStatus().equals("SUSPENDED")) {
// 激活流程
runtimeService.activateProcessInstanceById(processInstanceId);
log.info("流程实例已激活: {}", processInstanceId);
}
// 检查等待事件
checkPendingEvents(processInstanceId);
// 发送恢复通知
notificationService.sendRecoveryNotification(processInstanceId);
}
}
02.任务丢失
a.问题现象
用户应该有任务但在任务列表中看不到
流程卡在某个节点不继续
任务分配给错误的用户
b.解决方案
@Service
public class TaskRecoveryService {
public void recoverLostTask(String processInstanceId, String userId) {
// 检查未分配的任务
List<Task> unassignedTasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.taskAssignee(null)
.list();
for (Task task : unassignedTasks) {
if (shouldAssignToUser(task, userId)) {
taskService.setAssignee(task.getId(), userId);
log.info("任务已重新分配: {} -> {}", task.getId(), userId);
}
}
}
}
03.变量传递
a.问题现象
流程变量在节点间传递失败
变量值被意外修改或丢失
变量类型转换错误
b.解决方案
@Service
public class VariableManagementService {
public void fixVariableIssues(String processInstanceId) {
// 获取当前变量
Map<String, Object> currentVariables = runtimeService.getVariables(processInstanceId);
// 检查变量类型
for (Map.Entry<String, Object> entry : currentVariables.entrySet()) {
String variableName = entry.getKey();
Object value = entry.getValue();
if (value == null) {
// 尝试从历史变量中恢复
Object historicValue = getHistoricVariable(processInstanceId, variableName);
if (historicValue != null) {
runtimeService.setVariable(processInstanceId, variableName, historicValue);
}
}
}
}
}
04.版本升级
a.问题现象
流程版本升级后历史数据不可见
新版本流程定义无法正确处理运行中的实例
版本回滚时数据丢失
b.解决方案
@Service
public class ProcessVersionService {
public void upgradeProcessVersion(String processDefinitionKey, String newBpmnXml) {
try {
// 备份当前数据
backupProcessData(processDefinitionKey);
// 部署新版本
Deployment deployment = repositoryService.createDeployment()
.addString(newBpmnXml, processDefinitionKey + "_v" + System.currentTimeMillis() + ".bpmn20.xml")
.deploy();
// 处理运行中的实例
handleRunningInstances(processDefinitionKey);
} catch (Exception e) {
log.error("流程版本升级失败: {}", processDefinitionKey, e);
rollbackProcessUpgrade(processDefinitionKey);
throw new RuntimeException("版本升级失败", e);
}
}
}