- 开发成本的挑战:
在传统的手动审批系统中,开发人员需要从头开始设计和实现整个审批流程,包括流程逻辑、状态跟踪、任务分配等。这可能需要大量的时间和精力,尤其是在涉及到复杂的业务场景时。
使用工作流引擎,开发人员可以利用其强大的流程建模和执行功能,快速搭建审批系统。工作流引擎提供了图形化的流程设计工具,让流程建模变得简单易懂。这样,开发人员可以专注于业务逻辑的实现,大大降低了开发成本和周期。
- 业务复杂度的挑战:
- 在实际的审批流程中,往往涉及到多个步骤、多个角色、多个条件等复杂情况。手动处理这些复杂的业务流程可能会导致流程不一致、流程控制不清晰等问题,增加了业务的复杂度和错误的可能性。
- 工作流引擎提供了灵活的条件判断和分支网关功能,可以轻松处理复杂的审批流程。不同的业务场景可以在流程图中得到清晰的表达,从而确保流程的逻辑一致性和可控性。同时,Flowable引擎支持动态变量和表单的设置,使得审批系统能够满足不同业务场景的灵活需求。
通过引入工作流引擎,我们可以克服手动处理审批流程所带来的开发成本高和业务复杂度的挑战。它使得开发人员能够更专注于业务逻辑的实现,快速构建符合实际需求的审批系统。同时,流程图的可视化设计和条件判断的支持,使得业务流程更加清晰、透明,降低了错误发生的概率,提高了审批过程的效率和准确性。这将为企业带来更高的效益和竞争力。
图片
二.技术选型
目前市面上比较主流的几个工作流引擎包括Activiti、Flowable、Camunda等,体系较为成熟,使用最为广泛的是activiti,flowable跟activiti本质上没什么区别,是由activiti改版而来,但是两者后续发展路线则不一样,Activiti后续发展重心是在商业版与云上面,并未对核心功能与性能优化上有过多的跟进优化,而flowable当下重心则是针对功能性、扩展性、性能上进行迭代优化。
图片
当然,具体选型还是要看业务实际的需要,目前activiti迭代的方向并不是当下我们业务所需要的,我们更重视性能、扩展性这一块,而flowbale与Camunda之间为什么选择了flowable呢,则是因为flowable社区较为活跃,遇到并处理问题上更为效率,并且面对多节点审批时,异步任务是提升吞吐率的优质之选。
图片
下面我将简单介绍下flowable的一些功能以及模块。
三.Flowable引擎介绍
3.1 基础元素
- 流程定义(Process Definition):
- 流程定义是业务流程的抽象表示,它描述了流程中各个环节的顺序和执行逻辑。
在Flowable中,流程定义通常使用BPMN 2.0标准(Business Process Model and Notation)进行建模,可以通过Flowable Modeler进行图形化设计。
- 流程实例(Process Instance):
- 流程实例是流程定义的一个具体执行实例。每当一个流程启动时,就会创建一个新的流程实例,这个实例将遵循流程定义中规定的执行逻辑。
- 每个流程实例都有一个唯一的标识符,用于跟踪和管理流程的执行状态。
- 任务(Task):
- 任务表示流程中的具体工作单元,需要由参与者(用户或系统)完成。
- 在Flowable中,任务可以是用户任务(Human Task)、服务任务(Service Task)、脚本任务(Script Task)等类型。
- 网关(Gateway):
- 网关用于在流程中进行条件分支和合并操作,根据一定的条件来控制流程的走向。
- Flowable提供了多种类型的网关,如排他网关(Exclusive Gateway)、并行网关(Parallel Gateway)等。
- 执行(Execution):
- 执行代表了流程中的一个具体执行状态,包括当前的节点、变量值等信息。
- 当流程实例执行时,会产生一系列执行对象,它们共同组成了流程的执行路径。
- 变量(Variable):
- 变量用于在流程中传递和保存数据,可以在流程的不同节点间进行传递和共享。
- Flowable允许在流程实例中动态添加、修改和删除变量。
- 监听器(Listener):
- 监听器用于在流程的不同生命周期阶段监听事件,例如流程启动、流程结束、任务分配等事件。
- Flowable支持多种类型的监听器,开发者可以根据需求自定义监听逻辑。
图片
这些是Flowable中的一些基础元素,它们构成了流程引擎的核心。通过这些元素,我们可以灵活地设计和管理复杂的业务流程,使得流程执行更加高效、透明和可控。
3.2 API说明
在调用开始之前,我们简单说明一下各个api的作用以及应用场景:
- RepositoryService:
- RepositoryService用于管理流程和流程部署相关的操作。
- 可以通过该Service部署、删除、查询流程定义,以及获取流程定义的资源文件等。
- RuntimeService:
- RuntimeService用于管理流程实例的启动、挂起、终止等操作。
- 可以通过该Service启动流程实例,查询流程实例状态,设置流程变量等。
- TaskService:
- TaskService用于管理任务的创建、完成、分配等操作。
- 可以通过该Service查询用户任务、完成任务,指定任务的办理人等。
- IdentityService:
- IdentityService用于管理用户、组和用户组的操作。
- 可以通过该Service创建、删除、查询用户和组,将用户加入到用户组等。
- HistoryService:
- HistoryService用于查询流程实例和任务的历史数据。
- 可以通过该Service查询已完成的流程实例、已完成的任务,以及历史的流程变量值等。
- ManagementService:
- ManagementService用于执行一些底层的管理操作,如数据库表的创建、删除,作业的执行,引擎的配置等。
- 一般情况下,开发人员不需要直接使用ManagementService,因为它执行的操作较为底层。
3.3系统数据表介绍
启动后会自动生成一些内置的系统表(如果不想自动生成可以通过配置关闭),这里简单介绍一下表的含义:
ACT_HI_*: 历史数据表,例如:
表名 | 含义 |
ACT_HI_PROCINST | 历史流程实例表,存储已完成的流程实例信息,包括流程开始时间、结束时间等。 |
ACT_HI_TASKINST | 历史任务实例表,存储已完成的任务实例信息,包括任务开始时间、结束时间、办理人等。 |
ACT_HI_VARIABLE | 历史流程变量表,用于存储在流程运行时设置的变量信息。 |
ACT_RU_*: 运行时的数据表,节点结束时清除,例如:
表名 | 含义 |
ACT_RU_EXECUTION | 运行时流程执行实例表,存储当前正在执行的流程实例信息。 |
ACT_RU_TASK | 运行时任务表,存储当前正在执行的任务信息。 |
ACT_RU_VARIABLE | 运行时流程变量表,用于存储在流程运行时设置的变量信息。 |
另外,flowable支持定期清理历史数据,业务侧可以定义一个时间范围,超过时间范围可以认为数据可以被清理,系统内部会自行将数据物理删除,为历史表做瘦身操作。
不过我们业务侧接入的时候,往往业务需要创建几个符合我们具体业务场景的表,例如工单表、任务表虽然框架提供了,但是并没有记录详细的状态,这些状态是我们业务自己定义的,所以需要额外创建业务工单表、审批任务(节点)表、流水表等来进行数据上的冗余来满足实际的业务场景。
四.流程图建模绘制
4.1 BPMN 2.0协议
我们可以通过可视化后台绘制流程建模,生成BPMN格式的文件,那么BMPN又是什么呢?
BPMN(Business Process Model and Notation)是一种用于建模业务流程的标准化符号和语法,用于描述业务流程的各个环节和活动。BPMN的最新版本是BPMN 2.0,它是业务流程建模领域的国际标准,由OMG(Object Management Group)制定和发布。
BPMN 2.0的主要目标是提供一种统一的标准,使得不同人员和组织能够使用相同的符号和语法来建模和理解业务流程,从而增加流程的可视化和可读性。
它具有以下特点和优势:
- 图形化表示:
- BPMN 2.0使用图形化符号来表示业务流程,包括开始事件、结束事件、任务、网关等。这些符号使得流程的设计和阅读更加直观和易懂。
- 丰富的元素:
- BPMN 2.0定义了丰富的元素和连接方式,可以描述复杂的业务流程,包括顺序流、消息流、时间流等。
- 流程分支和合并:
- BPMN 2.0支持多种类型的网关元素,如排他网关、并行网关等,用于实现条件分支和合并,使得流程的走向更加灵活。
- 子流程支持:
- BPMN 2.0允许将复杂的业务流程分解为多个子流程,使得整体流程更易于管理和维护。
- 扩展性:
- BPMN 2.0支持自定义属性和扩展元素,使得业务流程模型能够满足特定业务需求。
- 与执行引擎的结合:
- BPMN 2.0是与执行引擎(如Flowable)无缝集成的,这使得通过工作流引擎执行流程变得更加高效和自动化。
4.2 绘制
我们可以从Flowable官网下载flowable-ui部署到本地来启动可视化后台,可以使用在线的Flowable Modeler来绘制,这里我们使用官网的flowable-ui来绘制。
具体下载启动过程我就不过多赘述了,flowable-ui是基于 springboot2.0开发的,可以直接以下方式来启动:
java -jar flowable-ui.war
启动成功后如图所示:
图片
我们打开建模器应用程序并点击【创建流程】按钮,我们可以给模型进行一个简单的定义.
图片
如图绘制了一个最简单的流程建模
图片
我们也可以给用户任务分配一些基础属性,这里我们配置一个固定用户, 当然这里也支持占位符来动态控制
图片
Image.png
图片
这样一个最简单的流程建模(不含复杂节点、网关等)就绘制完毕了,绘制完成后会生成一个xml文件,
接下来我会从导入到执行来执行执行一遍该流程。
五.快速开始
5.1 导入starter
org.flowable
flowable-spring-boot-starter
6.6.0
5.2 导入建模
@Test
public void createConfig() throws Exception{
File file = new File("/Users/xxx/Documents/flowbale/model/testModel.bpmn20.xml");
final FileInputStream fileInputStream = new FileInputStream(file);
final Deployment deploy = repositoryService.createDeployment()
.addInputStream("testModel.bpmn20.xml", fileInputStream).tenantId("类似于业务线的id,可以做数据隔离用")
.name("testModel").deploy();
System.out.println("id=" + deploy.getId());
}
5.3 查询流程建模的基本信息
@Test
public void getProcessDefinition() {
final ProcessDefinition rst =
repositoryService.createProcessDefinitionQuery().processDefinitionKey("testModel").orderByProcessDefinitionVersion().desc().list().get(0);
System.out.println(rst.toString());
}
5.4 创建审批工单
// processDefinitionKey 流程建模的key
// businessId 具有业务属性的id
// variableMap变量的map 结果会存入 ACT_RU_VARIABLE表中,如果审批人传入的是变量就需要再节点执行前将变量传入到系统中
Map variableMap = new HashMap();
variableMap.put("testUserList", Lists.newArrayList("nick","jack","tony"));
String processDefinitionKey = "testModel";
Long businessId = 1L;
String tenantId = "1001";
runtimeService.startProcessInstanceByKeyAndTenantId(processDefinitionKey, businessId + "", variableMap, tenantId);
5.5 获取待审批工单
final List taskList = taskService.createTaskQuery().taskAssignee("jack").list()
5.6 审批工单
Map variableMap = new HashMap();
// 我们自己定义了一个内部标准化字段,flag代表着节点是通过还是驳回,在互斥网关判断通过驳回的时候会用到。
variableMap.put("flag", 1);
taskService.complete(taskId), variableMap);
5.7 事件监听
我们通过上述用例了解了一个工单是如何操作流转的,但是一个完整的审批服务是应该有消息推送的,当审批节点到达某个人需要提醒他进行审批操作,或者审批工单结束了需要通知发起人,我们应该如何操作呢?Flowable提供了便携的事件监听器,不需要我们额外编写代码来判断是否到达相应的节点,我们这里常用的就是节点创建、节点完成、流程结束等。
Flowable支持的事件有几十种,具体大家请自行去官网参考,下面仅展示“流程结束事件”监听器的代码用例:
package com.zhuanzhuan.workflow_engine.config;
import com.zhuanzhuan.workflow_engine.listener.*;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
import org.flowable.common.engine.api.delegate.event.FlowableEventDispatcher;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
@Configuration
@Slf4j
public class FlowableListenerConfiguration implements ApplicationListener {
@Autowired
private SpringProcessEngineConfiguration configuration;
@Autowired
private ProcessEndListener processEndListener;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
FlowableEventDispatcher dispatcher = configuration.getEventDispatcher();
dispatcher.addEventListener(processEndListener, FlowableEngineEventType.PROCESS_COMPLETED);
}
}
package com.zhuanzhuan.workflow_engine.listener;
import com.bj58.zhuanzhuan.zzarch.common.util.JsonUtil;
import com.zhuanzhuan.workflow_engine.entity.dto.WorkflowOrderDTO;
import com.zhuanzhuan.workflow_engine.enums.CurrentFlagEnum;
import com.zhuanzhuan.workflow_engine.enums.WorkflowStateEnum;
import com.zhuanzhuan.workflow_engine.mq.MqConstant;
import com.zhuanzhuan.workflow_engine.mq.body.ProcessExchangeMsgBody;
import com.zhuanzhuan.workflow_engine.mq.producer.ProducerHandler;
import com.zhuanzhuan.workflow_engine.service.WorkflowOrderBizService;
import com.zhuanzhuan.workflow_engine.wrapper.FlowableWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.common.engine.api.delegate.event.FlowableEvent;
import org.flowable.engine.delegate.event.impl.FlowableEntityEventImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
@Slf4j
public class ProcessEndListener implements FlowableEventListener {
@Override
public void onEvent(FlowableEvent flowableEvent) {
// 流程结束的监听器,发送mq、给发起人发送消息在此进行
}
@Override
public boolean isFailOnException() {
// 抛异常, 不姑息
return true;
}
@Override
public boolean isFireOnTransactionLifecycleEvent() {
// event触发时机跟着 getOnTransaction走
return true;
}
@Override
public String getOnTransaction() {
// 提交事务后触发
return TransactionState.COMMITTED.name();
}
}
六.总结
以上就是Flowable工作流引擎的简介以及基本用法,我们实际生产建设过程中,还需要基于框架进行拓展开发来满足我们项目的实际需要,例如是否需要引入用户组、权限管理的模块、是否存在性能问题从而引入异步任务的模块、是否需要冗余业务表来满足定制化查询的需求,这些都属于扩展玩法,需要我们根据实际需求按需接入。毕竟没有最好的架构,只有最适合的架构,选择适合自己的才是最重要的。
七.参考资料
flowable官方文档 https://www.flowable.org/documentation.html
2.技术选型参考 https://www.zhihu.com/question/59274016/answer/2398240513
关于作者
王锐刚,线上回收业务后端开发工程师