SpringBoot集成Flowable工作流
作者:mmseoamin日期:2024-04-27

文章目录

  • 一、了解Flowable
    • 1. 什么是Flowable
    • 2. Flowable基本流程
    • 3. Flowable主要几张表介绍
    • 二、SpringBoot集成Flowable
      • 1. 在idea中安装Flowable插件
      • 2. SpringBoot集成Flowable
      • 3. SpringBoot集成Flowable前端页面
      • 三、创建流程模版(以请假为例)

        提示:以下是本篇文章正文内容,下面案例可供参考

        一、了解Flowable

        1. 什么是Flowable

        Flowable下载

        Flowable源码GitHub下载

        Flowable用户使用手册

        Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。

        具体介绍请看Flowable介绍

        2. Flowable基本流程

        参考知乎Flowable工作流引擎

        对于业务建模,我们需要一种通用的语言来描绘,这样在沟通上和实现上会降低难度,就像中文、英文一样,BPMN2.0便是一种国际通用的建模语言,他能让自然人轻松阅读,更能被计算机所解析。

        协议中元素的主要分类为,事件-任务-连线-网关

        一个流程必须包含一个事件(如:开始事件)和至少一个结束(事件)。其中网关的作用是流程流转逻辑的控制。任务则分很多类型,他们各司其职,所有节点均由连线联系起来。

        BPMN2.0协议

        SpringBoot集成Flowable工作流,在这里插入图片描述,第1张

        不同的图标代表不同的含义

        1. 网关

          SpringBoot集成Flowable工作流,在这里插入图片描述,第2张

        互斥网关(Exclusive Gateway) 又称排他网关,他有且仅有一个有效出口,可以理解为if…else if… else if…else,就和我们平时写代码的一样。

        SpringBoot集成Flowable工作流,在这里插入图片描述,第3张

        并行网关(Parallel Gateway) 他的所有出口都会被执行,可以理解为开多线程同时执行多个任务。

        SpringBoot集成Flowable工作流,在这里插入图片描述,第4张

        包容性网关(Inclusive Gateway)

        只要满足条件的出口都会执行,可以理解为 if(…) do, if (…) do, if (…)

        do,所有的条件判断都是同级别的。

        SpringBoot集成Flowable工作流,在这里插入图片描述,第5张

        1. 任务

          SpringBoot集成Flowable工作流,在这里插入图片描述,第6张

        人工任务(User Task)

        它是使用得做多的一种任务类型,他自带有一些人工任务的变量,例如签收人(Assignee),签收人就代表该任务交由谁处理,我们也可以通过某个特定或一系列特定的签收人来查找待办任务。利用上面的行为解释便是,当到达User

        Task节点的时候,节点设置Assignee变量或等待设置Assignee变量,当任务被完成的时候,我们使用Trigger来要求流程引擎退出该任务,继续流转。

        SpringBoot集成Flowable工作流,在这里插入图片描述,第7张

        服务任务(Service Task)

        该任务会在到达的时候执行一段自动的逻辑并自动流转。从“到达自动执行一段逻辑”这里我们就可以发现,服务任务的想象空间就可以非常大,我们可以执行一段计算,执行发送邮件,执行RPC调用,而使用最广泛的则为HTTP调用,因为HTTP是使用最广泛的协议之一,它可以解决大部分第三方调用问题,在我们的使用中,HTTP服务任务也被我们单独剥离出来作为一个特殊任务节点。

        SpringBoot集成Flowable工作流,在这里插入图片描述,第8张

        接受任务(Receive Task) 该任务的名字让人费解,但它又是最简单的一种任务,当该任务到达的时候,它不做任何逻辑,而是被动地等待Trigger,它的适用场景往往是一些不明确的阻塞,比如:一个复杂的计算需要等待很多条件,这些条件是需要人为来判断是否可以执行,而不是直接执行,这个时候,工作人员如果判断可以继续了,那么就Trigger一下使其流转。

        SpringBoot集成Flowable工作流,在这里插入图片描述,第9张

        1. 结构

          SpringBoot集成Flowable工作流,在这里插入图片描述,第10张

        调用活动(Call Activity)

        调用活动可以理解为函数调用,它会引用另外一个流程使之作为子流程运行,调用活动跟函数调用的功能一样,使流程模块化,增加复用的可能性。

        SpringBoot集成Flowable工作流,在这里插入图片描述,第11张

        3. Flowable主要几张表介绍

        Flowable在项目启动的时候会自动创建表,以下是主要几张表介绍

        • ACT_RU_TASK: 每次启动的流程都会再这张表中,表示代办项, 流程结束会删除该流程数据
        • ACT_RU_EXECUTION: 流程执行过程表, 会存该流程正在执行的过程数据, 流程结束会删除该流程数据
        • ACT_RU_VARIABLE: 流程变量表, 流程中传的参数都会再该表存储, 流程结束会删除该流程数据
        • ACT_HI_PROCINST: 历史运行流程, 当流程处理完了, 在ACT_RU_* 表中就不会有数据, 可以在该表中查询历史
        • ACT_HI_TASKINST: 历史运行的task信息,
        • ACT_RE_PROCDEF: 流程模板记录,同一个key多次发布version_字段会递增
        • ACT_RE_DEPLOYMENT: 部署的流程模板, 可以启动流程使用的

          二、SpringBoot集成Flowable

          1. 在idea中安装Flowable插件

          File-->settings 搜索:flowable

          SpringBoot集成Flowable工作流,在这里插入图片描述,第12张

          安装完成,重启idea

          SpringBoot集成Flowable工作流,在这里插入图片描述,第13张

          2. SpringBoot集成Flowable

          1. 添加依赖
               	/**
               	  * 引入数据库依赖,Flowable在项目启动的时候会自动创建表
              	  */
                  
                      org.springframework.boot
                      spring-boot-starter-jdbc
                  
                  
                      mysql
                      mysql-connector-java
                      runtime
                  
                   /**
               	  * lombok
              	  */
                   
                      org.projectlombok
                      lombok
                      1.16.16
                      compile
                  
                   /**
               	  * 添加flowable工作流依赖
              	  */
                  
                      org.flowable
                      flowable-spring-boot-starter
                      6.7.2
                  
                   /**
               	  * 添加flowable工作流前端页面依赖,如果项目没有集成页面,无需添加此依赖
              	  */
                  
                      org.flowable
                      flowable-ui-modeler-rest
                      6.7.2
                  
                   /**
               	  * 添加flowable-ui-modeler配置依赖项
              	  */
                  
                      org.flowable
                      flowable-ui-modeler-conf
                      6.7.2
                  
          
          1. 设置application.yml
          server:
            port: 8080  #tomcat端口
            servlet:
              context-path: /
          spring:
            #数据库链接配置
            datasource:
              url: jdbc:mysql://127.0.0.1:3306/flowable?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF8
              driver-class-name: com.mysql.cj.jdbc.Driver
              username: root
              password: root
          flowable:
            #异步执行
            async-executor-activate: true
            #自动更新数据库
            database-schema-update: true
            #校验流程文件,默认校验resources下的processes文件夹里的流程文件
            process-definition-location-prefix: classpath*:/processes/
            process-definition-location-suffixes: "**.bpmn20.xml, **.bpmn"
            
            #该配置只是防止页面报错,没有实际意义
            common:
              app:
                idm-admin:
                  password: test
                  user: test
                #没有实际意义
                idm-url: http://localhost:8080/flowable-demo
          
          1. 创建 【*.bpmn20.xml】

            通过【application.yml】可知,需要在resources下的processes文件夹里的创建流程文件

            这是ask_for_leave.bpmn20.xml我自己的流程文件

            SpringBoot集成Flowable工作流,在这里插入图片描述,第14张

            ask_for_leave.bpmn20.xml

          
          
            
              
              
                员工提交申请
              
              
              
              
              
              
              
              
                ${outcome=='驳回'}
              
              
              
                ${outcome=='通过'}
              
              
              
              
              
                ${outcome=='驳回'}
              
              
              
                ${outcome=='通过'}
              
            
            
              
                
                  
                
                
                  
                
                
                  
                  
                
                
                  
                
                
                  
                  
                
                
                  
                
                
                  
                  
                
                
                  
                
                
                  
                  
                
                
                  
                
                
                  
                  
                
                
                  
                
                
                  
                  
                
                
                  
                
                
                  
                  
                
                
                  
                
                
                  
                  
                
              
            
          
          
          1. 设置配置文件
          import org.flowable.spring.SpringProcessEngineConfiguration;
          import org.flowable.spring.boot.EngineConfigurationConfigurer;
          import org.springframework.context.annotation.Configuration;
          @Configuration
          public class FlowableConfig implements EngineConfigurationConfigurer {
              /**
               * 防止生成的流程图中文乱码
               * @param springProcessEngineConfiguration
               */
              @Override
              public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {
                  springProcessEngineConfiguration.setActivityFontName("宋体");
                  springProcessEngineConfiguration.setLabelFontName("宋体");
                  springProcessEngineConfiguration.setAnnotationFontName("宋体");
              }
          }
          

          如果醒目没有集成页面,【SecurityConfiguration.java】无需配置

          参考简书springboot集成flowable

          import org.flowable.ui.common.security.SecurityConstants;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.core.annotation.Order;
          import org.springframework.security.config.annotation.web.builders.HttpSecurity;
          import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
          /**
           * 绕过flowable的登录验证
           */
          @Configuration
          public class SecurityConfiguration {
              @Configuration(proxyBeanMethods = false)
              //Order配置说明
              // 这个地方相同会报错
              //这个地方如果大于则该配置在FlowableUiSecurityAutoConfiguratio中对应项后加载,不能起到绕过授权作用
              //所以这个地方-1让该配置项在FlowableUiSecurityAutoConfiguratio中对应配置项前加载,以跳过授权
              @Order(SecurityConstants.FORM_LOGIN_SECURITY_ORDER - 1)
              public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
                  @Override
                  protected void configure(HttpSecurity http) throws Exception {
                      http.headers().frameOptions().disable();
                      http
                              //必须要将csrf设置为disable,不然后面发送POST请求时会报403错误
                              .csrf().disable()
                              //为了简单起见,简单粗暴方式直接放行modeler下面所有请求
                              .authorizeRequests().antMatchers("/modeler/**").permitAll();
                  }
              }
          }
          
          1. 书写逻辑
          import lombok.extern.slf4j.Slf4j;
          import org.flowable.bpmn.model.BpmnModel;
          import org.flowable.engine.*;
          import org.flowable.engine.runtime.Execution;
          import org.flowable.engine.runtime.ProcessInstance;
          import org.flowable.image.ProcessDiagramGenerator;
          import org.flowable.task.api.Task;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.web.bind.annotation.GetMapping;
          import org.springframework.web.bind.annotation.RequestMapping;
          import org.springframework.web.bind.annotation.RequestParam;
          import org.springframework.web.bind.annotation.RestController;
          import javax.servlet.http.HttpServletResponse;
          import java.io.InputStream;
          import java.io.OutputStream;
          import java.util.*;
          @Slf4j
          @RestController
          @RequestMapping("askForLeave")
          public class AskForLeaveFlowableController {
              @Autowired
              private RuntimeService runtimeService;
              @Autowired
              private TaskService taskService;
              @Autowired
              private RepositoryService repositoryService;
              @Autowired
              private ProcessEngine processEngine;
              /**
               * 员工提交请假申请
               *
               * @param employeeNo 员工工号
               * @param name       姓名
               * @param reason     原因
               * @param days       天数
               * @return
               */
              @GetMapping("employeeSubmit")
              public String employeeSubmitAskForLeave(
                      @RequestParam(value = "employeeNo") String employeeNo,
                      @RequestParam(value = "name") String name,
                      @RequestParam(value = "reason") String reason,
                      @RequestParam(value = "days") Integer days) {
                  HashMap map = new HashMap<>();
                  /**
                   * 员工编号字段来自于配置文件
                   */
                  map.put("employeeNo", employeeNo);
                  map.put("name", name);
                  map.put("reason", reason);
                  map.put("days", days);
                  /**
                   *      key:配置文件中的下个处理流程id
                   *      value:默认领导工号为002
                   */
                  map.put("leaderNo", "002");
                  /**
                   * askForLeave:为开启流程的id  与配置文件中的一致
                   */
                  ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("askForLeave", map);
                  log.info("{},提交请假申请,流程id:{}", name, processInstance.getId());
                  return "提交成功,流程id:"+processInstance.getId();
              }
              /**
               * 领导审核通过
               * @param employeeNo  员工工号
               * @return
               */
              @GetMapping("leaderExaminePass")
              public String leaderExamine(@RequestParam(value = "employeeNo") String employeeNo) {
                  List taskList = taskService.createTaskQuery().taskAssignee(employeeNo).orderByTaskId().desc().list();
                  if (null == taskList) {
                      throw  new RuntimeException("当前员工没有任何申请");
                  }
                  for (Task task : taskList) {
                      if (task == null) {
                          log.info("任务不存在 ID:{};", task.getId());
                          continue;
                      }
                      log.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());
                      Map map = new HashMap<>();
                      /**
                       *      key:配置文件中的下个处理流程id
                       *      value:默认老板工号为001
                       */
                      map.put("bossNo", "001");
                      /**
                       *      key:指定配置文件中的条件判断id
                       *      value:指定配置文件中的审核条件
                       */
                      map.put("outcome", "通过");
                      taskService.complete(task.getId(), map);
                  }
                  return "领导审核通过";
              }
              /**
               * 老板审核通过
               * @param leaderNo  领导工号
               * @return
               */
              @GetMapping("bossExaminePass")
              public String bossExamine(@RequestParam(value = "leaderNo") String leaderNo) {
                  List taskList = taskService.createTaskQuery().taskAssignee(leaderNo).orderByTaskId().desc().list();
                  if (null == taskList) {
                      throw  new RuntimeException("当前员工没有任何申请");
                  }
                  for (Task task : taskList) {
                      if (task == null) {
                          log.info("任务不存在 ID:{};", task.getId());
                          continue;
                      }
                      log.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());
                      Map map = new HashMap<>();
                      /**
                       *     老板是最后的审批人   无需指定下个流程
                       */
          //            map.put("boss", "001");
                      /**
                       *      key:指定配置文件中的条件判断id
                       *      value:指定配置文件中的审核条件
                       */
                      map.put("outcome", "通过");
                      taskService.complete(task.getId(), map);
                  }
                  return "领导审核通过";
              }
              /**
               * 驳回
               *
               * @param employeeNo  员工工号
               * @return
               */
              @GetMapping("reject")
              public String reject(@RequestParam(value = "employeeNo") String employeeNo) {
                  List taskList = taskService.createTaskQuery().taskAssignee(employeeNo).orderByTaskId().desc().list();
                  if (null == taskList) {
                      throw  new RuntimeException("当前员工没有任何申请");
                  }
                  for (Task task : taskList) {
                      if (task == null) {
                          log.info("任务不存在 ID:{};", task.getId());
                          continue;
                      }
                      log.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());
                      Map map = new HashMap<>();
                      /**
                       *      key:指定配置文件中的领导id
                       *      value:指定配置文件中的审核条件
                       */
                      map.put("outcome", "驳回");
                      taskService.complete(task.getId(), map);
                  }
                  return "申请被驳回";
              }
              /**
               * 生成流程图
               *
               * @param processId 任务ID
               */
              @GetMapping(value = "processDiagram")
              public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
                  ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
                  //流程走完的不显示图
                  if (pi == null) {
                      return;
                  }
                  Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
                  //使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
                  String InstanceId = task.getProcessInstanceId();
                  List executions = runtimeService
                          .createExecutionQuery()
                          .processInstanceId(InstanceId)
                          .list();
                  //得到正在执行的Activity的Id
                  List activityIds = new ArrayList<>();
                  List flows = new ArrayList<>();
                  for (Execution exe : executions) {
                      List 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, true);
                  OutputStream out = null;
                  byte[] buf = new byte[1024];
                  int legth = 0;
                  try {
                      out = httpServletResponse.getOutputStream();
                      while ((legth = in.read(buf)) != -1) {
                          out.write(buf, 0, legth);
                      }
                  } finally {
                      if (in != null) {
                          in.close();
                      }
                      if (out != null) {
                          out.close();
                      }
                  }
              }
          }
          
          1. 测试
          • 员工提交请假申请,如下图:

            http://localhost:8080/askForLeave/employeeSubmit?name=Jerry&reason=去旅游&days=30&employeeNo=006

            SpringBoot集成Flowable工作流,在这里插入图片描述,第15张

          • 查看员工提交申请的流程图

            http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e

            SpringBoot集成Flowable工作流,在这里插入图片描述,第16张

          • 领导进行审批

            http://localhost:8080/askForLeave/leaderExaminePass?employeeNo=006

            SpringBoot集成Flowable工作流,在这里插入图片描述,第17张

          • 查看领导审批后的流程图

            http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e

            SpringBoot集成Flowable工作流,在这里插入图片描述,第18张

          • 老板审批

            http://localhost:8080/askForLeave/bossExaminePass?leaderNo=002

            SpringBoot集成Flowable工作流,在这里插入图片描述,第19张

          • 查看老板审批后的流程图

            http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e

            SpringBoot集成Flowable工作流,在这里插入图片描述,第20张

            3. SpringBoot集成Flowable前端页面

            参考简书毛于晏

            1. 下载源码包,此处使用的是6.7.2版本的

              源码下载地址: https://github.com/flowable/flowable-engine/tree/flowable-6.7.2

              链接:https://pan.baidu.com/s/1w-iuynskMuREBlYcpbDFAg

              提取码:9gs7

            2. 将以下路径的代码复制到自己工程

              /flowable-6.7.2/modules/flowable-ui/flowable-ui-modeler-frontend/src/main/resources/static/modeler

              SpringBoot集成Flowable工作流,在这里插入图片描述,第21张

            3. 跳过验证

            import org.flowable.ui.common.security.SecurityConstants;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.core.annotation.Order;
            import org.springframework.security.config.annotation.web.builders.HttpSecurity;
            import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
            /**
             * 绕过flowable的登录验证
             */
            @Configuration
            public class SecurityConfiguration {
                @Configuration(proxyBeanMethods = false)
                //Order配置说明
                // 这个地方相同会报错
                //这个地方如果大于则该配置在FlowableUiSecurityAutoConfiguratio中对应项后加载,不能起到绕过授权作用
                //所以这个地方-1让该配置项在FlowableUiSecurityAutoConfiguratio中对应配置项前加载,以跳过授权
                @Order(SecurityConstants.FORM_LOGIN_SECURITY_ORDER - 1)
                public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
                    @Override
                    protected void configure(HttpSecurity http) throws Exception {
                        http.headers().frameOptions().disable();
                        http
                                //必须要将csrf设置为disable,不然后面发送POST请求时会报403错误
                                .csrf().disable()
                                //为了简单起见,简单粗暴方式直接放行modeler下面所有请求
                                .authorizeRequests().antMatchers("/modeler/**").permitAll();
                    }
                }
            }
            
            1. 启动项目,访问:http://localhost:8080/modeler/#/processes

              SpringBoot集成Flowable工作流,在这里插入图片描述,第22张

            三、创建流程模版(以请假为例)

            1. 确保已在idea中安装Flowable插件

            2. 在resources下创建processes文件夹,右键,创建一个【*.bpmn20.xml】文件

              SpringBoot集成Flowable工作流,在这里插入图片描述,第23张

            3. 选中创建好的【*.bpmn20.xml】文件,右键;SpringBoot集成Flowable工作流,在这里插入图片描述,第24张

            4. 开始画流程图 任意位置右键–>启动一个流程

              SpringBoot集成Flowable工作流,在这里插入图片描述,第25张

              SpringBoot集成Flowable工作流,在这里插入图片描述,第26张

            5. 设置员工提交申请

              SpringBoot集成Flowable工作流,在这里插入图片描述,第27张

              SpringBoot集成Flowable工作流,在这里插入图片描述,第28张

            6. 设置领导

              SpringBoot集成Flowable工作流,在这里插入图片描述,第29张

            7. 设置网关,用来判断领导是否同意

              SpringBoot集成Flowable工作流,在这里插入图片描述,第30张

              SpringBoot集成Flowable工作流,在这里插入图片描述,第31张

            8. 网关条件判断条件–驳回

              SpringBoot集成Flowable工作流,在这里插入图片描述,第32张

            9. 网关条件判断条件–通过

              SpringBoot集成Flowable工作流,在这里插入图片描述,第33张

            10. 设置老板

              SpringBoot集成Flowable工作流,在这里插入图片描述,第34张

            11. 设置网关,用来判断老板是否同意

              SpringBoot集成Flowable工作流,在这里插入图片描述,第35张

            12. 网关条件判断条件–驳回

              SpringBoot集成Flowable工作流,在这里插入图片描述,第36张

            13. 网关条件判断条件–通过

              SpringBoot集成Flowable工作流,在这里插入图片描述,第37张

            14. 当我们画好图后,【*.bpmn20.xml】流程文件也已经创建成功了

              SpringBoot集成Flowable工作流,在这里插入图片描述,第38张

            15. 可以书写针对流程的逻辑了