写在前面
最近几个月做了一些工作流相关的需求,也看了不少视频和文档资料。给我的感觉是,概念太多,网上也缺少一些对复杂组件的案例使用。
其实几个需求做下来,我发现常用的组件就几个:开始事件、结束事件、顺序流、互斥网关。最多再加上服务任务、任务监听器和执行监听器。
也是因为我处理的工作流程比较简单,并没有涉及到太复杂应用,诸如:边界事件、中间事件、子流程等。
基于此原则,才有了这个最简教程。即,使用常用的组件来实现一个流程,避免复杂的概念讲解。
如果刚好你的需求和我相似,又或者你仅仅是想了解一下工作流引擎,那么本文很适合你。
本文旨在怎么使用工作流引擎进行流程设计、启动、流转等,省略了工作流引擎前后端集成环节,默认你已经集成了。如果你想把工作流引擎集成进自己系统,可以参考其他资料
工作流引擎是什么
对应用层面来说,就是:数据表 + api实现的流程流转服务,其他暂时不需要关注。
怎么用
这里包含几个步骤:
- 设计流程
- 部署流程
- 启动流程实例
- 操作流程实例
不好理解的话,拿面向对象来类比,分别对应
- 设计类
- 创建类
Class A {}
- 创建类实例
A a = new A()
- 使用类实例
a.getData()、a.setData()
下面,一步步来说
1、设计流程

这种图,大家想必很熟悉了,没接触过应该也可以看懂。其实工作流引擎的流程,也和这个相似
转化过来,就是下面这个样子

这样看,也没有什么大的不同,就是换了换组件

这两个圆圈就是:开始事件、结束事件
标志着流程的开始 与结束

这种正菱形 的,叫做网关
里面一个X 的,是互斥网关 ,表示要么走左边,要么走右边

一个矩形 ,称为任务
左上角有个人的图标 的,叫做用户任务
对了,还有带箭头的线 ,叫做顺序流,用于连接各个组件
掌握了这些,就能开始进行流程设计了
我们拿老生常谈的请假流程举例子吧
- 员工发起请假,启动流程
- 请假不超过3天,组长审批;不超过3天,经理审批
- 审批同意,考勤人员归档,结束流程;不同意,直接结束流程
上面提到的流程绘制工具bpmn.js 一般需要集成进系统,我们直接使用集成好的bpmn.js
生产环境一般会集成bpmn.js 进行流程设计,可以参考实现 RuoYi-flowable: 基于RuoYi-vue + flowable 6.x 的工作流管理平台

流程图画好了,但还没有完全画好。问题在于:工作流引擎怎么知道要走哪条路的?
通过:互斥网关 + 条件流转路径

我们给互斥网关 出发的顺序流 ,设置为条件流转路径,即满足条件才走这条路
并且设置表达式为${agree == false}
,当agree
为false
时,才满足条件
${}
是变量表达式写法agree
称为流程变量
那么问题来了,要是我不设置,或者设置成一样的,会是什么结果?
当然是报错,正如一个人不能同时向左转和向右转
所以要求我们,各个分叉路口,都得写好条件表达式,并且覆盖全面
流程设计的正确与否,直接影响流程的运行结果,部署前请仔细检查
还有个问题:用户任务指定给了谁?

我们选中用户任务 ,就可以配置任务执行人 ,也叫做候选人
生产环境中,一般有以下几种类型:
- 指定人
- 指定角色
- 指定部门
- 变量表达式
分配到人,活才有人干不是吗?
至此,请假流程 设计好了,这里对流程设计的步骤做个小结
- 放置开始事件、结束事件
- 有条件分支,放置流程网关,设置其流出顺序流为条件流转路径
- 放置用户任务,配置候选人
- 保存流程
2、部署流程
可设计为:保存时自动部署,或者点击部署。这个与工作流引擎的集成方式有关
3、启动流程实例
集成好的工作流引擎,一般支持点击发起流程 ,即创建流程实例
还是拿上面的请假流程 举例:小明需要请假,填写了请假单并保存
流程实例的启动可以多种方法:
- 前端实现:保存请假单 接口调用成功,调用发起流程接口
- 后端实现:保存请假单 业务后,同步或者异步使用工作流引擎API,发起流程
前端实现异常情况下,会有事务问题 ,而后端实现,我们可以通过事务 、分布式事务等方式来避免
可参考
java
@Transactional(rollbackFor = Exception.class)
public String startInstance(
String processDefinitionKey,
Map<String, Object> variables)
{
UserDetail user = SecurityUser.getUser();
// 启动流程用户
identityService.setAuthenticatedUserId(user.getId().toString());
// 流程参数
variables.put("nextUserId", user.getId().toString());
String businessKey = (String) variables.get("businessKey");
// 获取key对应的最新版本流程id
String processDefinitionId = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey).latestVersion().singleResult().getId();
// 启动流程
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, businessKey, variables);
return processInstance.getProcessInstanceId();
}
启动前,可以将请假天数,写入流程变量variables
中
流程实例启动后,我们需要保存实例ID到业务数据中,方便进行后续的调用
4、 使用流程实例
拿到实例ID 后,我们便能操作流程实例的流转 和结束
流程的流转
java
public boolean auditByInstanceId(FlowProcessInstanceAuditDTO req){
// 查找任务
Task task = taskService.createTaskQuery()
.processInstanceId(req.getProcessInstanceId())
.singleResult();
if (task == null) {
log.error("任务不存在");
return false;
}
//添加意见
if (StringUtils.isNotBlank(req.getComment())) {
taskService.addComment(task.getId(), task.getProcessInstanceId(), req.getComment());
}
//流程变量
Map<String, Object> variables = new HashMap<>();
// 控制流程分支
variables.put("agree", req.getApprove());
variables.put("nextUserId", req.getNextUserId());
// 签收任务
taskService.claim(task.getId(), SecurityUser.getUserId().toString());
//提交任务
taskService.complete(task.getId(), variables);
return true;
}
流程的结束
java
public void endByInstanceId(String instanceId) {
// 根据流程实例ID获取流程实例
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
.processInstanceId(instanceId)
.singleResult();
if (instance == null) {
throw new RenException("流程实例不存在");
}
runtimeService.deleteProcessInstance(instanceId, "流程终止");
}
可以看到,通过表达式方式引入的条件流程路径 和用户任务候选人 ,我们均可以使用流程变量进行填入,即
java
variables.put("agree", req.getApprove());
variables.put("nextUserId", req.getNextUserId());
使用前端集成好的流程表单填入也可,但是缺乏灵活性
更多
如果你需要学习更多工作流引擎的知识,可以使用如下资料
- flowable.me/
- 三大工作流引擎技术Activiti、Flowable、Camunda指南_哔哩哔哩_bilibili
- 《深入Flowable流程引擎:核心原理与高阶实战 》