一.简介
对于flowable是什么以及关于此框架的具体信息可以参看此项目的官方文档:https://www.flowable.org/docs/userguide/index.html
Flowable is a light-weight business process engine written in Java.这是官网文档对此框架的完美解释:Flowable是一个用java语言写的轻量级工作流引擎。
流程引擎,也算是一个比较常见的工具了,我们在日常的很多开发中都会用到,当然用的最多的就是 OA 系统了,但是在一些非 OA 系统中,我们也会涉及到,比如一个 CRM 中,可能会有合同管理的需求,合同的审批,也是需要流程引擎的。
在简单了解flowable后与activiti框架相比的第一感觉就是开发方便快速,易与springBoot等各种框架快速整合。今天便写一个简单的例子,来感受下流程引擎到底是个啥。
二.springboot整合flowable
1.使用版本
springBoot版本:2.3.2.RELEASE
flowable版本:6.5.0
2.创建项目
创建一个 SpringBoot 项目,引入 Web、和 MySQL 驱动两个依赖,截图如下:
3.项目依赖pom.xml
springboot 版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<scope>compile</scope>
<version>6.5.0</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-basic</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-bpmn-layout</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
4.项目配置application.yml
数据库和flowable的配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/flowable?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
flowable:
database-schema-update: true
# flase: 默认值。activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常。(生产环境常用)
# true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建。(开发时常用)
# create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)。(单元测试常用)
# drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)。
process:
definition-cache-limit: -1
activity-font-name: 宋体
label-font-name: 宋体
annotation-font-name: 宋体
flowable的配置,再默认情况下,位于 resources/processes 的流程都会被自动部署。
配置完成后,就可以启动项目了。项目启动成功之后,flowable 数据库中就会自动创建如下这些表,会自动初始化数据库,将来流程引擎运行时候的数据会被自动持久化到数据库中。。数据库表的截图如下:
三.画流程图
下载IDEA 插件 Flowable BPMN visualizer,如下图:
装好插件之后,我们在 resources 目录下新建 processes 目录,这个目录下的流程文件将来会被自动部署。
接下来我们在 processes 目录下,新建一个 BPMN 文件(插件装好了就有这个选项了),如下:
我们来画个请假的流程,就叫做 ask_for_leave.bpmn20.xml,注意最后面的 .bpmn20.xml 是固定后缀。
文件创建出来之后,右键单击,选择 View BPMN(Flowable) Diagram,就打开了可视化页面了,我们就可以来绘制自己的流程图了。
请假流程画出来是这样:
业务流程是这样的:
员工发起一个请假流程,首先是组长审核,组长审核通过了,就进入到经理审核,经理审核通过了,这个流程就结束了,如果组长审核未通过或者经理审核未通过,则流程给员工发送一个请假失败的通知,流程结束。
这个流程对应的 XML 文件,一些流程细节会在 XML 文件中体现出来,如下:
<process id="ask_for_leave" name="ask_for_leave" isExecutable="true">
<userTask id="leaveTask" name="请假" flowable:assignee="#{leaveTask}"/>
<userTask id="zuzhangTask" name="组长审核" flowable:assignee="#{zuzhangTask}"/>
<userTask id="managerTask" name="经理审核" flowable:assignee="#{managerTask}"/>
<exclusiveGateway id="managerJudgeTask"/>
<exclusiveGateway id="zuzhangJudeTask"/>
<endEvent id="endLeave" name="结束"/>
<startEvent id="startLeave" name="开始"/>
<sequenceFlow id="flowStart" sourceRef="startLeave" targetRef="leaveTask"/>
<sequenceFlow id="modeFlow" sourceRef="leaveTask" targetRef="zuzhangTask"/>
<sequenceFlow id="zuzhang_go" sourceRef="zuzhangJudeTask" targetRef="managerTask" name="通过">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='通过'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="zuzhang_reject" sourceRef="zuzhangJudeTask" targetRef="sendMail" name="拒绝">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='拒绝'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="jugdeFlow" sourceRef="managerTask" targetRef="managerJudgeTask"/>
<sequenceFlow id="flowEnd" name="通过" sourceRef="managerJudgeTask" targetRef="endLeave">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='通过'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="rejectFlow" name="拒绝" sourceRef="managerJudgeTask" targetRef="sendMail">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='拒绝'}]]></conditionExpression>
</sequenceFlow>
<serviceTask id="sendMail" flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
<sequenceFlow id="endFlow" sourceRef="sendMail" targetRef="askForLeaveFail"/>
<endEvent id="askForLeaveFail" name="请假失败"/>
<sequenceFlow id="zuzhangTask_zuzhangJudeTask" sourceRef="zuzhangTask" targetRef="zuzhangJudeTask"/>
</process>
XML 文件里面标签的释义,如下:
process:表示一个完整的工作流程。
startEvent:工作流中起点位置,也就是图中的绿色按钮。
endEvent:工作流中结束位置,也就是图中的红色按钮。
userTask:代表一个任务审核节点(组长、经理等角色),这个节点上有一个 flowable:assignee 属性,这表示这个节点该由谁来处理,将来在 Java 代码中调用的时候,我们需要指定对应的处理人的 ID 或者其他唯一标记。
serviceTask:这是服务任务,在具体的实现中,这个任务可以做任何事情。
exclusiveGateway:逻辑判断节点,相当于流程图中的菱形框。
sequenceFlow:链接各个节点的线条,sourceRef 属性表示线的起始节点,targetRef 属性表示线指向的节点,我们图中的线条都属于这种。
四.demo例子涉及的Java 类梳理
ProcessDefinition
这个最好理解,就是流程的定义,也就相当于规范,每个 ProcessDefinition 都会有一个 id。
ProcessInstance
这个就是流程的一个实例。简单来说,ProcessDefinition 相当于是类,而 ProcessInstance 则相当于是根据类 new 出来的对象。
Activity
Activity 是流程标准规范 BPMN2.0 里面的规范,流程中的每一个步骤都是一个 Activity。
Execution
Execution 的含义是流程的执行线路,通过 Execution 可以获得当前 ProcessInstance 当前执行到哪个 Activity了。
Task
Task 就是当前要做的工作。
五.代码
1.查看流程图接口
写一个接口,用来查看流程图的实时执行情况,这样方便我们查看流程到底执行到哪一步了。
代码如下:
@RestController
public class TestController {
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
@Autowired
RepositoryService repositoryService;
@Autowired
ProcessEngine processEngine;
@GetMapping("/createPic")
public void showPic(HttpServletResponse resp, String processId) throws Exception {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
if (pi == null) {
return;
}
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(processId)
.list();
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (Execution exe : executions) {
List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
activityIds.addAll(ids);
}
/**
* 生成流程图
*/
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(), engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, false);
OutputStream out = null;
byte[] buf = new byte[1024];
int legth = 0;
try {
out = resp.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
1.开启一个新流程接口
开启一个新流程,代码如下:
String staffId = "1000";
/**
* 开启一个流程
*/
@Test
void askForLeave() {
HashMap<String, Object> map = new HashMap<>();
map.put("leaveTask", staffId);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("ask_for_leave", map);
runtimeService.setVariable(processInstance.getId(), "name", "javaboy");
runtimeService.setVariable(processInstance.getId(), "reason", "休息一下");
runtimeService.setVariable(processInstance.getId(), "days", 10);
logger.info("创建请假流程 processId:{}", processInstance.getId());
}
首先由员工发起一个请假流程,map 中存放的 leaveTask 是我们在 XML 流程文件中提前定义好的,提前定义好当前这个任务创建之后,该由谁来处理,这里我们是假设由工号为 1000 的员工来发起这样一个请假流程。同时,我们还设置了一些额外信息。ask_for_leave 是我们在 XML 文件中定义的一个 process 的名称。
执行这个单元测试方法,执行完成后,控制台会打印出当前这个流程的 id,我们拿着这个 id 去访问 createPic的接口,结果如下:
可以看到,请假用红色的框框起来了,说明当前流程走到了这一步。
2.将请求提交给组长
接下来将这个请假流程向后推进一步,将请假事务提交给组长,代码如下:
String zuzhangId = "90";
/**
* 提交给组长审批
*/
@Test
void submitToZuzhang() {
//员工查找到自己的任务,然后提交给组长审批
List<Task> list = taskService.createTaskQuery().taskAssignee(staffId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());
Map<String, Object> map = new HashMap<>();
//提交给组长的时候,需要指定组长的 id
map.put("zuzhangTask", zuzhangId);
taskService.complete(task.getId(), map);
}
}
首先我们利用 staffId 查找到当前员工的 id,进而找到当前员工需要执行的任务,遍历这个任务,调用 taskService.complete 方法将任务提交给组长,注意在 map 中指定组长的 id。
提交完成后,我们再去看流程图片,如下:
可以看到,流程图走到组长审批了。
3.组长审批
组长现在有两种选择,同意或者拒绝,同意的代码如下:
/**
* 组长审批-批准
*/
@Test
void zuZhangApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("组长 {} 在审批 {} 任务", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
//组长审批的时候,如果是同意,需要指定经理的 id
map.put("managerTask", managerId);
map.put("checkResult", "通过");
taskService.complete(task.getId(), map);
}
}
通过组长的 id 查询组长的任务,同意的话,需要指定经理,也就是这个流程下一步该由谁来处理。
拒绝的代码如下:
/**
* 组长审批-拒绝
*/
@Test
void zuZhangReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("组长 {} 在审批 {} 任务", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
//组长审批的时候,如果是拒绝,就不需要指定经理的 id
map.put("checkResult", "拒绝");
taskService.complete(task.getId(), map);
}
}
拒绝的话,就没那么多事了,直接设置 checkResult 为拒绝即可。
假设这里执行了同意,那么流程图如下:
4.经理审批
经理审批和组长审批差不多,只不过经理这里是最后一步了,不需要再指定下一位处理人了,同意的代码如下:
/**
* 经理审批自己的任务-批准
*/
@Test
void managerApprove() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("经理 {} 在审批 {} 任务", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "通过");
taskService.complete(task.getId(), map);
}
}
拒绝代码如下:
/**
* 经理审批自己的任务-拒绝
*/
@Test
void managerReject() {
List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
for (Task task : list) {
logger.info("经理 {} 在审批 {} 任务", task.getAssignee(), task.getId());
Map<String, Object> map = new HashMap<>();
map.put("checkResult", "拒绝");
taskService.complete(task.getId(), map);
}
}
5.拒绝流程
如果组长拒绝了或者经理拒绝了,我们也有相应的处理方案,首先在 XML 流程文件定义时,如下:
<serviceTask id="sendMail" flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
如果请假被拒绝,会进入到这个 serviceTask,serviceTask 对应的处理类是 org.javaboy.flowable.AskForLeaveFail,该类的代码如下:
public class AskForLeaveFail implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("请假失败。。。");
}
}
也就是请假失败会进入到这个方法中,现在我们就可以在这个方法中写失败的业务逻辑