文章标题:
Java开发中借助SpringBoot整合Flowable快速构建工作流的窍门
文章内容:目录
1、流程引擎相关介绍
2、项目创建步骤
3、流程图绘制
4、接口开发
4.1 Java类梳理
ProcessDefinition
ProcessInstance
Activity
Execution
Task
4.2 查看流程图
4.3 启动一个流程
4.4 将请求提交给组长
4.5 组长进行审批
4.6 经理进行审批
4.7 拒绝流程
1、流程引擎相关介绍
Flowable是一款用Java编写的轻量级业务流程引擎。它能够部署遵循BPMN2.0标准的流程定义(这是定义流程的行业XML规范),还能创建这些流程定义的实例,并且可以进行查询,获取运行中或者历史的流程实例以及相关数据等。在Java领域里还有另一个流程引擎叫Activiti,实际上掌握了其中一个,对另一个的使用也不会有太大障碍。不多废话了,上代码。
2、项目创建步骤
首先创建一个Spring Boot项目,引入Web和MySQL驱动这两个依赖,如图所示:

项目创建完成后,引入flowable依赖,如下:
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
</dependency>
该依赖会进行一些自动化配置,默认情况下,位于resources/processes目录下的流程会被自动部署。接着在application.yaml中配置数据库连接信息,项目启动时会自动初始化数据库,流程引擎运行时的数据会自动持久化到数据库中。
spring:
datasource:
username: root
password: 123
url: jdbc:mysql:///flowable?serverTimezone=Asia/Shanghai&useSSL=false
配置完成后就可以启动项目了。项目启动成功后,flowable数据库会自动创建若干表,流程引擎相关的数据都会保存到这些表中。默认的表数量较多,截图只是其中一部分。

3、流程图绘制
绘制流程图是较为关键的一步,也是使用流程引擎的重要环节。官方提供了一些流程引擎绘制工具,感兴趣的小伙伴可以自行去体验;IDEA自带了一个流程可视化工具,但使用起来不太方便。这里介绍一下常用的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文件来解释Flowable中的组件:
<process>
:代表一个完整的工作流程。<startEvent>
:工作流的起始位置,也就是图中的绿色按钮。<endEvent>
:工作流的结束位置,即图中的红色按钮。<userTask>
:表示一个任务审核节点(像组长、经理等角色),该节点上的flowable:assignee
属性指定了处理这个节点任务的人员,在Java代码调用时,需要指定对应的处理人的唯一标识。<serviceTask>
:属于服务任务,在具体实现中,这个任务可以执行任何操作。<exclusiveGateway>
:是逻辑判断节点,相当于流程图中的菱形框。<sequenceFlow>
:用于连接各个节点的线条,sourceRef属性表示线条的起始节点,targetRef属性表示线条指向的节点,图中的线条都属于这类。
流程图初看比较复杂,但只要理清其中各个属性,很快就能理解其原理。
4、接口开发
接下来编写几个接口来体验流程引擎。在正式体验之前,先熟悉几个类,写代码时会用到这些类。
4.1 Java类梳理
ProcessDefinition
这是流程的定义,就好比是一个规范,每个ProcessDefinition都有一个对应的id。
ProcessInstance
是流程的一个实例。简单来说,ProcessDefinition就像是类,而ProcessInstance则像是根据这个类创建出来的对象。
Activity
是BPMN2.0规范中所定义的流程步骤,流程中的每一个步骤都是一个Activity。
Execution
表示流程的执行路径,通过Execution能够知道当前ProcessInstance当前执行到了哪个Activity。
Task
就是当前需要处理的工作。这里先弄一个简单的示例,上面提到的这些知识点暂时够用。
4.2 查看流程图
在正式开始之前,先准备一个接口,用来查看流程图的实时执行情况,这样方便了解流程到底执行到了哪一步。具体的代码如下:
@RestController
public class HelloController {
@Autowired
RuntimeService runtimeService;
@Autowired
TaskService taskService;
@Autowired
RepositoryService repositoryService;
@Autowired
ProcessEngine processEngine;
@GetMapping("/pic")
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();
}
}
}
}
这只是一个工具,大家在看完后面的代码后,再回过头来看这个接口,很多地方就会清晰明了。
4.3 启动一个流程
为了方便,接下来的代码都在单元测试中完成。首先来启动一个流程,代码如下:
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去访问4.2小节中的接口,结果如下:

可以看到,请假步骤被红色框标注出来了,这表明当前流程走到了这一步。
4.4 将请求提交给组长
接下来,需要将这个请假流程向后推进一个步骤,把请假事务提交给组长,代码如下:
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。提交完成后,再去查看流程图片,如下:

可以看到,流程图走到了组长审批这一步。
4.5 组长审批
组长有两种选择,同意或者拒绝,同意的代码如下:
/**
* 组长审批-批准
*/
@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);
}
}
拒绝的代码如下:
/**
* 组长审批-拒绝
*/
@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);
}
}
假设这里执行了同意操作,那么流程图如下:

4.6 经理审批
经理审批和组长审批类似,只不过经理这里是最后一步了,不需要再指定下一位处理人了,同意的代码如下:
/**
* 经理审批自己的任务-批准
*/
@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);
}
}
4.7 拒绝流程
如果组长拒绝了或者经理拒绝了,也有相应的处理方案,首先在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("请假失败。。。");
}
}
也就是请假失败会进入到这个方法中,在这里就