相关推荐recommended
Flowable工作流入门&完整SpringBoot案例
作者:mmseoamin日期:2023-12-11

文章目录

    • 一 、Flowable 的出现是为了什么
    • 二、Flowable 的优势
    • 三、常见的Java类/实例
      • 3.1 ProcessEngine
      • 3.2 RepositoryService
      • 3.3 ProcessDefinition
      • 3.4 Deployment
      • 3.5 RuntimeService
      • 3.6 ProcessInstance
      • 3.7 TaskService
      • 3.8 JavaDelegate
      • 3.9 其他
      • 四、核心数据库表
        • 4.1 数据库
        • 4.2 通用数据表(2个)
        • 4.3 历史表(8个,HistoryService接口操作的表)
        • 4.4 用户相关表(4个,IdentityService接口操作的表)
        • 4.5 流程定义、流程模板相关表(3个,RepositoryService接口操作的表)
        • 4.6 流程运行时表(6个,RuntimeService接口操作的表)
        • 五、在SpringBoot中简单使用
          • 5.1 Flowable入门之表的生成
          • 5.2 请假审批案例的实现
            • 5.2.1 项目结构
            • 5.2.2 application.yml
            • 5.2.3 holiday-request.bpmn20.xml
            • 5.2.4 HolidayRequestDTO
            • 5.2.5 HolidayRequestProcessService
            • 5.2.6 HolidayRequestProcessServiceImpl
            • 5.2.7 CallExternalSystemDelegate
            • 5.2.8 SendRejectionMail
            • 5.2.9 HolidayRequestProcessController
            • 5.2.10 运行&结果展示
            • 附录
              • 参考文章
              • 项目地址

                一 、Flowable 的出现是为了什么

                工作流(Workflow),是指对于一项业务,按照规定的流程,逐级传递、申请、执行等,并且受到了严格控制的一种业务过程。

                BPM(Business Process Management)是指对于某项业务的整个生命周期进行全面管理的一种模式,最核心的内容包括了工作流、决策、交互等。在这些管理过程中,人员、系统等资源都是可以被自动调度的,以致达到更高效、完善的管理目的。

                Flowable工作流是一款基于Java的轻量级开源工作流引擎,它支持BPMN2.0规范、CMMN规范,同时也提供REST API以及JavaAPI,支持Spring,Spring Boot等框架。借助Flowable工作流,企业可以快速构建出符合自己实际业务的工作流。

                二、Flowable 的优势

                1、轻量级:Flowable工作流是一款轻量级的工作流引擎,启动快、体积小,且可以嵌入Java应用中使用。

                2、开放源代码:Flowable工作流是一个开源的工作流引擎,保证企业在使用过程中不会受到第三方的监控与限制。

                3、商用友好:Flowable工作流允许企业在商业环境下进行使用,并且提供Flowable Task一类的额外服务。

                三、常见的Java类/实例

                3.1 ProcessEngine

                流程引擎实例,线程安全,一般一个工作流只需要初始化一次。

                代码示例见本文的 5.1 小节。

                3.2 RepositoryService

                可以通过流程引擎(ProcessEngine)获取到。使用RepositoryService可以根据xml文件路径创建一个新的部署(Deployment),并调用 Deployment#deploy() 实际执行。

                代码示例如下(其中xml是放在src/main/resources中):

                RepositoryService repositoryService = processEngine.getRepositoryService();
                Deployment deployment = repositoryService.createDeployment()
                 .addClasspathResource("holiday-request.bpmn20.xml")
                 .deploy();
                

                3.3 ProcessDefinition

                流程定义实例,包含了流程的基本信息。通过RepositoryService获取,可以根据部署ID(Deployment)查询到对应的流程的基本信息,如流程的名字。首先获取到ProcessDefinitionQuery实例,再根据这个Query实例去查询1或多个流程定义实例。

                代码示例如下,查询了1个流程定义实例:

                ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                 .deploymentId(deployment.getId())
                 .singleResult();
                System.out.println("Found process definition : " + processDefinition.getName());
                

                3.4 Deployment

                部署实例,通过RepositoryService获取。

                部署之后可以查询流程定义,也可以启动流程实例。

                部署的代码示例见 3.2 小节。

                PS:当xml文件放在 src/main/resources/processes/ 里时,会被自动部署。

                3.5 RuntimeService

                运行时服务实例,用来启动一个部署了的流程实例。

                通过流程引擎获取,启动时可以传递参数。

                代码示例如下:

                RuntimeService runtimeService = processEngine.getRuntimeService();
                Map variables = new HashMap<>();
                variables.put("employee", employee);
                variables.put("nrOfHolidays", nrOfHolidays);
                variables.put("description", description);
                ProcessInstance processInstance =
                 
                runtimeService.startProcessInstanceByKey("holidayRequest", variables);
                

                3.6 ProcessInstance

                流程实例,通过 RuntimeService 启动一个流程获取。

                3.7 TaskService

                任务服务实例,常用于查询、完成任务。

                3.8 JavaDelegate

                用于定义任务执行后自动执行到的一个逻辑。这是一个服务任务。

                3.9 其他

                1、IdentityService 用于管理(创建,更新,删除,查询……)组与用户。

                2、FormService是可选服务。也就是说Flowable没有它也能很好地运行,而不必牺牲任何功能。

                3、HistoryService暴露Flowable引擎收集的所有历史数据。要提供查询历史数据的能力。

                4、ManagementService通常在用Flowable编写用户应用时不需要使用。它可以读取数据库表与表原始数据的信息,也提供了对作业(job)的查询与管理操作。

                5、DynamicBpmnService可用于修改流程定义中的部分内容,而不需要重新部署它。例如可以修改流程定义中一个用户任务的办理人设置,或者修改一个服务任务中的类名。

                四、核心数据库表

                4.1 数据库

                1、Flowable的所有数据库表都以ACT_开头。第二部分是说明表用途的两字符标示符。服务API的命名也大略符合这个规则。

                2、ACT_RE_: 'RE’代表repository。带有这个前缀的表包含“静态”信息,例如流程定义与流程资源(图片、规则等)。

                3、ACT_RU_: 'RU’代表runtime。这些表存储运行时信息,例如流程实例(process instance)、用户任务(user task)、变量(variable)、作业(job)等。Flowable只在流程实例运行中保存运行时数据,并在流程实例结束时删除记录。这样保证运行时表小和快。

                4、ACT_HI_: 'HI’代表history。这些表存储历史数据,例如已完成的流程实例、变量、任务等。

                5、ACT_GE_: 通用数据。在多处使用。

                4.2 通用数据表(2个)

                act_ge_bytearray:二进制数据表,如流程定义、流程模板、流程图的字节流文件;

                act_ge_property:属性数据表(不常用);

                4.3 历史表(8个,HistoryService接口操作的表)

                act_hi_actinst:历史节点表,存放流程实例运转的各个节点信息(包含开始、结束等非任务节点);

                act_hi_attachment:历史附件表,存放历史节点上传的附件信息(不常用);

                act_hi_comment:历史意见表;

                act_hi_detail:历史详情表,存储节点运转的一些信息(不常用);

                act_hi_identitylink:历史流程人员表,存储流程各节点候选、办理人员信息,常用于查询某人或部门的已办任务;

                act_hi_procinst:历史流程实例表,存储流程实例历史数据(包含正在运行的流程实例);

                act_hi_taskinst:历史流程任务表,存储历史任务节点;

                act_hi_varinst:流程历史变量表,存储流程历史节点的变量信息;

                4.4 用户相关表(4个,IdentityService接口操作的表)

                act_id_group:用户组信息表,对应节点选定候选组信息;

                act_id_info:用户扩展信息表,存储用户扩展信息;

                act_id_membership:用户与用户组关系表;

                act_id_user:用户信息表,对应节点选定办理人或候选人信息;

                4.5 流程定义、流程模板相关表(3个,RepositoryService接口操作的表)

                act_re_deployment:部属信息表,存储流程定义、模板部署信息;

                act_re_procdef:流程定义信息表,存储流程定义相关描述信息,但其真正内容存储在act_ge_bytearray表中,以字节形式存储;

                act_re_model:流程模板信息表,存储流程模板相关描述信息,但其真正内容存储在act_ge_bytearray表中,以字节形式存储;

                4.6 流程运行时表(6个,RuntimeService接口操作的表)

                act_ru_task:运行时流程任务节点表,存储运行中流程的任务节点信息,重要,常用于查询人员或部门的待办任务时使用;

                act_ru_event_subscr:监听信息表,不常用;

                act_ru_execution:运行时流程执行实例表,记录运行中流程运行的各个分支信息(当没有子流程时,其数据与act_ru_task表数据是一一对应的);

                act_ru_identitylink:运行时流程人员表,重要,常用于查询人员或部门的待办任务时使用;

                act_ru_job:运行时定时任务数据表,存储流程的定时任务信息;

                act_ru_variable:运行时流程变量数据表,存储运行中的流程各节点的变量信息;

                五、在SpringBoot中简单使用

                以下以Mysql数据库为例,java版本为11,开发工具使用Idea。

                5.1 Flowable入门之表的生成

                首先创建一个普通的Maven项目,引入依赖如下:

                
                    org.flowable
                    flowable-spring-boot-starter
                    6.8.0
                
                
                
                    mysql
                    mysql-connector-java
                    8.0.28
                
                
                
                    org.slf4j
                    slf4j-api
                    1.7.21
                
                
                    org.slf4j
                    slf4j-log4j12
                    1.7.21
                
                

                随后定义 log4j.properties文件:

                log4j.rootLogger=DEBUG, CA
                log4j.appender.CA=org.apache.log4j.ConsoleAppender
                log4j.appender.CA.layout=org.apache.log4j.PatternLayout
                log4j.appender.CA.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n
                

                然后是一个普通的Java类,定义Main方法(这里只打印了执行引擎实例):

                package org.feng.flowable;
                import org.flowable.engine.ProcessEngine;
                import org.flowable.engine.ProcessEngineConfiguration;
                import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;
                public class MainFlowableClient {
                    /**
                     * 执行引擎(现成安全的对象,在一个应用中只需要初始化一次)
                     */
                    private static final ProcessEngine processEngine;
                    static {
                        // 单机处理引擎配置信息实例
                        StandaloneProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration();
                        // 连接数据库的信息
                        cfg.setJdbcUrl("jdbc:mysql://localhost:3306/flowable?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC");
                        cfg.setJdbcUsername("root");
                        cfg.setJdbcPassword("123456");
                        cfg.setJdbcDriver("com.mysql.cj.jdbc.Driver");
                        // 设置了true,确保在JDBC参数连接的数据库中,数据库表结构不存在时,会创建相应的表结构。
                        cfg.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
                        // 通过配置获取执行引擎
                        processEngine = cfg.buildProcessEngine();
                    }
                    public static void main(String[] args) {
                        System.out.println(processEngine);
                    }
                }
                

                启动main方法后的效果是,会连接到指定的数据库,如果库中没有Flowable对应数据库,或结构发生变化,会自动创建或更新表。

                5.2 请假审批案例的实现

                在官方手册中有一个请假审批的案例,讲的比较详细。

                Flowable工作流入门&完整SpringBoot案例,在这里插入图片描述,第1张

                大致流程是这样的:发起一个开始事件,随后一个工作流引用到用户任务(Approve or reject request),分配给到经理,经理需要决定通过请求还是拒绝请求。然后使用一个排他网关,将流程实例路由到批准或者驳回。如果批准,则需要将申请注册至某个外部系统,并跟着另一个用户任务,将经理的决定通知给申请人。当然也可以改为发送邮件。如果驳回,则为雇员发送一封邮件通知他。

                依据以上示例编写Java代码实现。

                5.2.1 项目结构

                项目中对oneTask任务、请假流程(holidayRequest)任务做了处理。本小节只关注请假流程。

                并且数据表前面已经创建好了。

                Flowable工作流入门&完整SpringBoot案例,在这里插入图片描述,第2张

                项目依赖使用 springboot 2.7.12 版本:

                		
                			org.flowable
                			flowable-spring-boot-starter
                			6.8.0
                		
                		
                			org.springframework.boot
                			spring-boot-starter-web
                		
                		
                			mysql
                			mysql-connector-java
                			8.0.28
                		
                
                5.2.2 application.yml
                spring:
                  datasource:
                    driverClassName: com.mysql.cj.jdbc.Driver
                    type: com.zaxxer.hikari.HikariDataSource
                    url: jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai
                    username: root
                    password: 123456
                    hikari:
                      read-only: false
                      #客户端等待连接池连接的最大毫秒数
                      connection-timeout: 60000
                      #允许连接在连接池中空闲的最长时间(以毫秒为单位)
                      idle-timeout: 60000
                      #连接将被测试活动的最大时间量
                      validation-timeout: 3000
                      #池中连接关闭后的最长生命周期
                      max-lifetime: 60000
                      #最大池大小
                      maximum-pool-size: 60
                      #连接池中维护的最小空闲连接数
                      minimum-idle: 10
                      #从池返回的连接的默认自动提交行为。默认值为true
                      auto-commit: true
                      #如果您的驱动程序支持JDBC4,我们强烈建议您不要设置此属性
                      connection-test-query: SELECT 1
                      #自定义连接池名称
                      pool-name: myHikarCp
                flowable:
                  # 设置了true,确保在JDBC参数连接的数据库中,数据库表结构不存在时,会创建相应的表结构。
                  database-schema-update: false
                  #关闭定时任务JOB
                  async-executor-activate: false
                
                5.2.3 holiday-request.bpmn20.xml
                
                
                    
                    
                        
                        
                        
                        
                        
                        
                            
                                
                            
                        
                        
                            
                                
                            
                        
                        
                        
                        
                        
                        
                        
                        
                        
                    
                
                
                5.2.4 HolidayRequestDTO
                package org.feng.dto;
                public class HolidayRequestDTO {
                    /**
                     * 员工名
                     */
                    private String employee;
                    /**
                     * 休假天数
                     */
                    private Integer nrOfHolidays;
                    /**
                     * 备注
                     */
                    private String description;
                    // 篇幅原因省略get/set方法,自己写的时候要加上
                }
                
                5.2.5 HolidayRequestProcessService
                package org.feng.service;
                import org.feng.dto.HolidayRequestDTO;
                public interface HolidayRequestProcessService {
                    void holidayRequest(HolidayRequestDTO holidayRequestDTO);
                }
                
                5.2.6 HolidayRequestProcessServiceImpl
                package org.feng.service.impl;
                import org.feng.dto.HolidayRequestDTO;
                import org.feng.service.HolidayRequestProcessService;
                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.springframework.stereotype.Service;
                import org.springframework.util.CollectionUtils;
                import javax.annotation.PostConstruct;
                import javax.annotation.Resource;
                import java.util.HashMap;
                import java.util.List;
                import java.util.Map;
                import java.util.concurrent.ThreadLocalRandom;
                @Service
                public class HolidayRequestProcessServiceImpl implements HolidayRequestProcessService {
                    @Resource
                    private RepositoryService repositoryService;
                    @Resource
                    private RuntimeService runtimeService;
                    @Resource
                    private TaskService taskService;
                    @Resource
                    private HistoryService historyService;
                    @Override
                    public void holidayRequest(HolidayRequestDTO holidayRequestDTO) {
                        // 组装参数
                        Map variables = new HashMap<>(8);
                        variables.put("employee", holidayRequestDTO.getEmployee());
                        variables.put("nrOfHolidays", holidayRequestDTO.getNrOfHolidays());
                        variables.put("description", holidayRequestDTO.getDescription());
                        // 启动流程实例
                        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holidayRequest", variables);
                        // 查询任务,组是“managers”
                        List tasks = taskService.createTaskQuery().taskCandidateGroup("managers").list();
                        // 打印查询到的任务信息
                        System.out.println("You have " + tasks.size() + " tasks:");
                        for (int i = 0; i < tasks.size(); i++) {
                            System.out.println((i + 1) + ") " + tasks.get(i).getName());
                        }
                        // 获取要执行的任务:查询到的最后一个task
                        if (!CollectionUtils.isEmpty(tasks)) {
                            Task task = tasks.get(tasks.size() - 1);
                            Map processVariables = taskService.getVariables(task.getId());
                            System.out.println(processVariables.get("employee") + " wants " +
                                    processVariables.get("nrOfHolidays") + " of holidays. Do you approve this?");
                            // 生成随机数,从而决定请求是通过还是拒绝
                            boolean approved = ThreadLocalRandom.current().nextInt(1, 10) > 5;
                            System.out.println(approved ? "do approved" : "do rejected");
                            variables = new HashMap<>();
                            variables.put("approved", approved);
                            // 执行完成
                            taskService.complete(task.getId(), variables);
                        }
                        // 历史数据
                        List activities =
                                historyService.createHistoricActivityInstanceQuery()
                                        .processInstanceId(processInstance.getId())
                                        .finished()
                                        .orderByHistoricActivityInstanceEndTime().asc()
                                        .list();
                        for (HistoricActivityInstance activity : activities) {
                            System.out.println(activity.getActivityId() + " took "
                                    + activity.getDurationInMillis() + " milliseconds");
                        }
                    }
                    @PostConstruct
                    private void deploy() {
                        // 手动部署
                        Deployment deployment = repositoryService.createDeployment()
                                // 从xml文件读取流程
                                .addClasspathResource("holiday-request.bpmn20.xml")
                                // 执行部署
                                .deploy();
                        // 根据部署ID获取流程定义
                        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                                .deploymentId(deployment.getId())
                                .singleResult();
                        // 输出流程名
                        System.out.println("Found process definition : " + processDefinition.getName());
                    }
                }
                
                5.2.7 CallExternalSystemDelegate
                package org.feng.service.task;
                import org.flowable.engine.delegate.DelegateExecution;
                import org.flowable.engine.delegate.JavaDelegate;
                public class CallExternalSystemDelegate implements JavaDelegate {
                    @Override
                    public void execute(DelegateExecution execution) {
                        System.out.println("Calling the external system for employee "
                                + execution.getVariable("employee"));
                    }
                }
                
                5.2.8 SendRejectionMail
                package org.feng.service.task;
                import org.flowable.engine.delegate.DelegateExecution;
                import org.flowable.engine.delegate.JavaDelegate;
                public class SendRejectionMail implements JavaDelegate {
                    @Override
                    public void execute(DelegateExecution execution) {
                        System.out.println("Send Rejection Email ");
                    }
                }
                
                5.2.9 HolidayRequestProcessController
                package org.feng.controller;
                import org.feng.dto.HolidayRequestDTO;
                import org.feng.service.HolidayRequestProcessService;
                import org.springframework.web.bind.annotation.PostMapping;
                import org.springframework.web.bind.annotation.RequestBody;
                import org.springframework.web.bind.annotation.RestController;
                import javax.annotation.Resource;
                @RestController
                public class HolidayRequestProcessController {
                    @Resource
                    private HolidayRequestProcessService holidayRequestProcessService;
                    @PostMapping("/holidayRequest")
                    public void holidayRequest(@RequestBody HolidayRequestDTO holidayRequestDTO) {
                        holidayRequestProcessService.holidayRequest(holidayRequestDTO);
                    }
                }
                
                5.2.10 运行&结果展示

                启动SpringBoot项目,随后访问rest接口:

                POST http://localhost:8080/holidayRequest
                Content-Type: application/json
                {
                  "employee": "bob",
                  "nrOfHolidays": 3,
                  "description": "就请假而已"
                }
                

                多次调整参数,可以看到不同的结果。

                部分结果展示:

                请求通过时:

                You have 1 tasks:
                1) Approve or reject holiday request
                bob wants 3 of holidays. Do you approve this?
                do approved
                Calling the external system for employee bob
                startEvent took 3 milliseconds
                _flow_startEvent__approveTask took 0 milliseconds
                approveTask took 1122 milliseconds
                _flow_approveTask__decision took 0 milliseconds
                _flow_decision__externalSystemCall took 0 milliseconds
                decision took 1 milliseconds
                externalSystemCall took 1 milliseconds
                _flow_externalSystemCall__holidayApprovedTask took 0 milliseconds
                

                请求拒绝时:

                You have 1 tasks:
                1) Approve or reject holiday request
                bob wants 3 of holidays. Do you approve this?
                do rejected
                Send Rejection Email 
                _flow_startEvent__approveTask took 0 milliseconds
                startEvent took 0 milliseconds
                approveTask took 927 milliseconds
                _flow_approveTask__decision took 0 milliseconds
                _flow_decision__sendRejectionMail took 0 milliseconds
                decision took 2 milliseconds
                _flow_sendRejectionMail__rejectEnd took 0 milliseconds
                sendRejectionMail took 1 milliseconds
                rejectEnd took 1 milliseconds
                

                附录

                参考文章

                Flowable工作流入门看这篇就够了-腾讯云开发者社区-腾讯云

                Flowable工作流详解_笔记大全_设计学院

                Flowable官网

                Flowable官方用户手册

                Flowable流程引擎入门

                项目地址

                https://gitee.com/fengsoshuai/flowable-demo