小白学流程引擎-FLowable(二) — 从零搭建自己的FLowable服务 — 搭建流程服务-FLowable的新手指南

一、介绍

纵览Gitee搜索Flowable开源项目,大多都是已开发好的项目,而笔者从零开始搭建属于自己的Flowable引擎,并且是可以拿到生产上使用的。这里搭建一个Springboot + Flowable6.7.2的开箱即用的流程引擎开源软件,代码仓库上传到Gitee,想要的文章末尾拿链接。
当前Flowable最新版本是6.7.2,学习肯定用最新稳定版本啦。

二、软件架构

Springboot + Flowable + modeler + idm + Mysql
SrpingBoot version:2.7.5
Flowable version:6.7.2
Mysql version:8.0.26
JDK:8

三、SpringBoot整合Flowable

3.1 使用spring initializer快速创建spring Boot项目

步骤:
1,打开idea,创建工程:file -> newproject ,选择spring initializer;
2,然后填写项目命名后,选择JDK8,Maven;
3,选择功能模块,选Web和lombok即可;
4,点Finish,就能够自动创建工程,并能导入相应依赖了;
5,Maven加载依赖后,运行主程序类看是否成功。

3.2 pom.xml文件引入依赖配置

引入Flowable依赖,这里选择Springboot-starter,毕竟是springboot项目。
添加必要的依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

   <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.79</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.flowable/flowable-spring-boot-starter -->
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter</artifactId>
        <version>6.7.2</version>
    </dependency>

</dependencies>

3.3 application.yml配置

在application.yml文件中加入以下配置。

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spider-flowable?charset=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8
    username: root
    password: 12345678

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true

flowable:
  #关闭定时任务JOB
  async-executor-activate: false
  #将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
  database-schema-update: true

3.4 运行项目

项目启动的时候检查如果数据库对应的表结构没有创建,会帮助我们先创建对应的表结构。到此Springboot整合Flowable就搞定了,
项目启动后,刷新数据库,你会发现多了79张表,都是以ACT和FLW为前缀的表名。
知道为什么Flowable自带的数据库的表名为啥是以ACT为前缀吗?
因为Flowable是基于Activiti6衍生出来的版本。

数据库结构如图:
在这里插入图片描述

四、定义流程文件

FLowable流程的运行,其实是用一个满足BPMN格式的XML文件来执行的,至于XML内容格式是怎么样的后续在学习。

在项目中的resources下新建一个processes文件夹,processes目录下的任何BPMN 2.0流程定义都会被自动部署。

1,在processes文件夹下新建holiday-request.bpmn20.xml文件
2,文件内容复制如下:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
             xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
             xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
             xmlns:flowable="http://flowable.org/bpmn"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             expressionLanguage="http://www.w3.org/1999/XPath"
             targetNamespace="http://www.flowable.org/processdef">

    <process id="holidayRequest" name="请假流程" isExecutable="true">
        <!--开始事件-->
        <startEvent id="startEvent"/>
        <!--顺序流,从 startEvent 到 approveTask-->
        <sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>
        <!--用户任务,需要 assignee 来审批-->
        <userTask id="approveTask" name="同意或驳回请求" flowable:candidateGroups="managers" flowable:assignee="admin"/>
        <!--顺序流,从 approveTask 到 decision -->
        <sequenceFlow sourceRef="approveTask" targetRef="decision"/>
        <!--排他网关-->
        <exclusiveGateway id="decision"/>
        <!--顺序流,从 decision 到 successCall-->
        <sequenceFlow sourceRef="decision" targetRef="successCall">
            <conditionExpression xsi:type="tFormalExpression">
                <!--approved 为 true 时,该顺序流生效-->
                <![CDATA[
          ${approved}
        ]]>
            </conditionExpression>
        </sequenceFlow>
        <!--顺序流,从 decision 到 failedCall-->
        <sequenceFlow sourceRef="decision" targetRef="failedCall">
            <conditionExpression xsi:type="tFormalExpression">
                <!--approved 为 false 时,该顺序流生效-->
                <![CDATA[
          ${!approved}
        ]]>
            </conditionExpression>
        </sequenceFlow>
        <!--服务任务,审批通过 -->
        <serviceTask id="successCall" name="审批通过调用服务"
                     flowable:class="com.example.spiderFlowable.core.delegate.ApprovalSuccessDelegate"/>
        <sequenceFlow sourceRef="successCall" targetRef="holidayApprovedTask"/>
        <userTask id="holidayApprovedTask" name="审批通过"/>
        <sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>
        <!--服务任务,审批驳回 -->
        <serviceTask id="failedCall" name="审批驳回调用服务"
                     flowable:class="com.example.spiderFlowable.core.delegate.ApprovalFailDelegate"/>
        <sequenceFlow sourceRef="failedCall" targetRef="rejectEnd"/>
        <!--结束事件-->
        <endEvent id="approveEnd"/>
        <endEvent id="rejectEnd"/>
    </process>
</definitions>

五、运行项目并测试

咱来测试一下功能,小试牛刀,更多功能在后面文章再细谈。
在test文件夹下新建测试类SpiderFlowableTest,代码如下。

扫描二维码关注公众号,回复: 14784629 查看本文章
package com.example.spiderFlowable;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author xiong.bo
 * @version 1.0
 * @date 2022/11/27 10:41 上午
 */

@SpringBootTest
public class SpiderFlowableTest {

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private HistoryService historyService;

    /**
     * 模拟-部署流程
     * 说明:
     * 流程定义有版本的概念,bpmn文件有改动,需要部署后才生效
     */
    @Test
    void createDeployment() {
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("processes/holiday-request.bpmn20.xml")
                .deploy();
        System.out.println(deployment.getId());
        System.out.println(JSON.toJSONString(deployment));
    }

    /**
     * 获取流程定义
     */
    @Test
    void getProcessDefinition() {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId("17504")
                .singleResult();
        System.out.println("definitionId:" + processDefinition.getId());
        System.out.println("definition:" + JSONUtil.toJsonStr(processDefinition));
    }

    /**
     * 启动流程
     * 模拟用户发起一个请假流程
     */
    @Test
    void startProcessDefinition() {
        Map<String, Object> variables = new HashMap<>();
        variables.put("employee", "李四");
        variables.put("nrOfHolidays", "4");
        variables.put("description", "外出请假");
        //启动流程实例有多个方法,这里调用流程key的方法来启动
        ProcessInstance holidayProcessInstance = runtimeService.startProcessInstanceByKey("holidayRequest", variables);
        System.out.println("processInstanceId:" + holidayProcessInstance.getProcessInstanceId());
    }

    /**
     * 获取任务
     * 用户发起流程后,相关的人员能够查询该任务
     *
     */
    @Test
    void getTask(){
        List<Task> tasks = taskService.createTaskQuery()
                .active()
                .includeProcessVariables()
                //值为admin是管理员可以查看所有的,测试
                .taskCandidateOrAssigned("admin")
                .list();
        System.out.println("You have " + tasks.size() + " tasks:");
        for (int i = 0; i < tasks.size(); i++) {
            Task task = tasks.get(i);
            System.out.println((i + 1) + ") " + "taskId: " + task.getId() + ", taskName: " + task.getName());
        }
    }

    /**
     * 审批任务
     * 说明:
     * 1,变量approved同流程定义文件里面顺序流定义的变量
     * 2,taskId是上一个获取用户任务的taskId值,也就是要指定哪一个用户任务往下执行
     */
    @Test
    void completeTask(){
        Map<String, Object> variables = new HashMap<>();
        variables.put("approved", true);
        String taskId = "20008";
        taskService.complete(taskId,variables);
    }

    /**
     * 查看历史任务
     * 说明:
     * taskAssignee: 分配人
     * finished:已完成状态的
     *
     */
    @Test
    void historyTask(){
        List<HistoricActivityInstance> hisList = historyService.createHistoricActivityInstanceQuery()
                .taskAssignee("admin")
                .finished()
                .list();

        hisList.stream().forEach(e -> System.out.println(JSONUtil.toJsonStr(e)));
    }
}

1,部署流程

	 /**
     * 模拟-部署流程
     * 说明:
     * 流程定义有版本的概念,bpmn文件有改动,需要部署后才生效
     */
    @Test
    void createDeployment() {
    
    
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("processes/holiday-request.bpmn20.xml")
                .deploy();
        System.out.println(deployment.getId());
        System.out.println(JSON.toJSONString(deployment));
    }

运行结果:

27501
ps: System.out.println(JSON.toJSONString(deployment));输出内容太多这里不贴上了

2,获取流程定义

	/**
     * 获取流程定义
     */
    @Test
    void getProcessDefinition() {
    
    
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId("17504")
                .singleResult();
        System.out.println("definitionId:" + processDefinition.getId());
        System.out.println("definition:" + JSONUtil.toJsonStr(processDefinition));
    }

运行结果:

definitionId:holidayRequest:5:17506
definition:{
    
    "name":"请假流程","key":"holidayRequest","version":5,"category":"http://www.flowable.org/processdef","deploymentId":"17504","resourceName":"processes/holiday-request.bpmn20.xml","tenantId":"","isGraphicalNotationDefined":false,"hasStartFormKey":false,"suspensionState":1,"derivedVersion":0,"id":"holidayRequest:5:17506","revision":1,"isInserted":false,"isUpdated":false,"isDeleted":false,"originalPersistentState":{
    
    "suspensionState":1,"category":"http://www.flowable.org/processdef"}}

3,启动流程

	/**
     * 启动流程
     * 模拟用户发起一个请假流程
     */
    @Test
    void startProcessDefinition() {
    
    
        Map<String, Object> variables = new HashMap<>();
        variables.put("employee", "李四");
        variables.put("nrOfHolidays", "4");
        variables.put("description", "外出请假");
        //启动流程实例有多个方法,这里调用流程key的方法来启动
        ProcessInstance holidayProcessInstance = runtimeService.startProcessInstanceByKey("holidayRequest", variables);
        System.out.println("processInstanceId:" + holidayProcessInstance.getProcessInstanceId());
    }

运行结果:

processInstanceId:30001

4,获取用户任务

	/**
     * 获取任务
     * 用户发起流程后,相关的人员能够查询该任务
     *
     */
    @Test
    void getTask(){
    
    
        List<Task> tasks = taskService.createTaskQuery()
                .active()
                .includeProcessVariables()
                //值为admin是管理员可以查看所有的,测试
                .taskCandidateOrAssigned("admin")
                .list();
        System.out.println("You have " + tasks.size() + " tasks:");
        for (int i = 0; i < tasks.size(); i++) {
    
    
            Task task = tasks.get(i);
            System.out.println((i + 1) + ") " + "taskId: " + task.getId() + ", taskName: " + task.getName());
        }
    }

运行结果:

You have 4 tasks:
1) taskId: 10008, taskName: 同意或驳回请求
2) taskId: 12508, taskName: 同意或驳回请求
3) taskId: 20008, taskName: 同意或驳回请求
4) taskId: 7508, taskName: 同意或驳回请求

5,审批任务

	/**
     * 审批任务
     * 说明:
     * 1,变量approved同流程定义文件里面顺序流定义的变量
     * 2,taskId是上一个获取用户任务的taskId值,也就是要指定哪一个用户任务往下执行
     */
    @Test
    void completeTask(){
    
    
        Map<String, Object> variables = new HashMap<>();
        variables.put("approved", true);
        String taskId = "20008";
        taskService.complete(taskId,variables);
    }

运行结果:
结果回调了 ApprovalSuccessDelegate ,打印出"审批通过了"

6,查看历史任务

	/**
     * 查看历史任务
     * 说明:
     * taskAssignee: 分配人
     * finished:已完成状态的
     *
     */
    @Test
    void historyTask(){
    
    
        List<HistoricActivityInstance> hisList = historyService.createHistoricActivityInstanceQuery()
                .taskAssignee("admin")
                .finished()
                .list();

        hisList.stream().forEach(e -> System.out.println(JSONUtil.toJsonStr(e)));

    }

运行结果:

{
    
    "activityId":"approveTask","activityName":"同意或驳回请求","activityType":"userTask","executionId":"20005","assignee":"admin","taskId":"20008","tenantId":"","processInstanceId":"20001","processDefinitionId":"holidayRequest:5:17506","startTime":1669521326282,"endTime":1669521596820,"durationInMillis":270538,"id":"20007","revision":2,"isInserted":false,"isUpdated":false,"isDeleted":false,"originalPersistentState":{
    
    "executionId":"20005","durationInMillis":270538,"endTime":1669521596820,"assignee":"admin","taskId":"20008"}}
{
    
    "activityId":"approveTask","activityName":"同意或驳回请求","activityType":"userTask","executionId":"30005","assignee":"admin","taskId":"30008","tenantId":"","processInstanceId":"30001","processDefinitionId":"holidayRequest:6:27503","startTime":1669643905412,"endTime":1669644033366,"durationInMillis":127954,"id":"30007","revision":2,"isInserted":false,"isUpdated":false,"isDeleted":false,"originalPersistentState":{
    
    "executionId":"30005","durationInMillis":127954,"endTime":1669644033366,"assignee":"admin","taskId":"30008"}}

最基本的完成一个流程的核心功能在上面列举了,先部署流程,再启动流程,再获取任务,再完成某一个任务。

六、总结

其实你会发现,流程引擎并没有那么神秘,它强大的功能,就是通过满足BPM2.0规范的xml文件和数据库进行流转的,后面文章在学习BPM2.0规范和Flowable数据库各个表的大致作用等。

以上代码已放到笔者的Gitee仓库地址:https://gitee.com/xiongbomy/spider-flowable.git
欢迎大家star和fork。
建议大家fork此项目到你个人仓库,方便你测试或者基于此开发属于你的流程引擎,当然,基于此进行二次扩展,再拿到公司当内部系统也是可以的,只要不商用拿去卖钱~

题外话,笔者在公司主要负责公司内部的流程引擎系统的开发,使用的是Flowable,后续会记录学习Flowable的笔记,一是让自己的知识形成体系化,二是让更多想要学习Flowable的同学们得到一点点帮助也好。

猜你喜欢

转载自blog.csdn.net/weixin_44143114/article/details/128088024