Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

文章标题:

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驱动这两个依赖,如图所示:

Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

项目创建完成后,引入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数据库会自动创建若干表,流程引擎相关的数据都会保存到这些表中。默认的表数量较多,截图只是其中一部分。

Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

3、流程图绘制

绘制流程图是较为关键的一步,也是使用流程引擎的重要环节。官方提供了一些流程引擎绘制工具,感兴趣的小伙伴可以自行去体验;IDEA自带了一个流程可视化工具,但使用起来不太方便。这里介绍一下常用的IDEA插件Flowable BPMN visualizer,如图:

Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

安装好插件后,在resources目录下新建processes目录,这个目录下的流程文件会被自动部署。随后在processes目录下新建一个BPMN文件(插件安装好后就有相应选项),如图:

Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

画一个请假流程,命名为ask_for_leave.bpmn20.xml,注意后缀是固定的.bpmn20.xml。文件创建好后,右键选择View BPMN(Flowable) Diagram就可以打开可视化界面进行流程图的绘制了。请假流程绘制出来是这样的:

Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

员工发起一个请假流程,首先由组长进行审核,组长审核通过后进入经理审核环节,经理审核通过则流程结束;要是组长或者经理审核不通过,流程会给员工发送请假失败的通知并结束。来看这个流程对应的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小节中的接口,结果如下:

Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

可以看到,请假步骤被红色框标注出来了,这表明当前流程走到了这一步。

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。提交完成后,再去查看流程图片,如下:

Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

可以看到,流程图走到了组长审批这一步。

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);
    }
}

假设这里执行了同意操作,那么流程图如下:

Java开发中利用SpringBoot整合Flowable快速构建工作流的妙招

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("请假失败。。。");
    }
}

也就是请假失败会进入到这个方法中,在这里就

相关文章

暂无评论

暂无评论...