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

一、了解Flowable

1. 什么是Flowable

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

具体介绍请看Flowable介绍

2. Flowable基本流程

参考知乎Flowable工作流引擎

对于业务建模,我们需要一种通用的语言来描绘,这样在沟通上和实现上会降低难度,就像中文、英文一样,BPMN2.0便是一种国际通用的建模语言,他能让自然人轻松阅读,更能被计算机所解析。
协议中元素的主要分类为,事件-任务-连线-网关
一个流程必须包含一个事件(如:开始事件)和至少一个结束(事件)。其中网关的作用是流程流转逻辑的控制。任务则分很多类型,他们各司其职,所有节点均由连线联系起来。
BPMN2.0协议
在这里插入图片描述
不同的图标代表不同的含义

  1. 网关
    在这里插入图片描述

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

并行网关(Parallel Gateway) 他的所有出口都会被执行,可以理解为开多线程同时执行多个任务。
在这里插入图片描述

包容性网关(Inclusive Gateway)
只要满足条件的出口都会执行,可以理解为 if(…) do, if (…) do, if (…)
do,所有的条件判断都是同级别的。
在这里插入图片描述

  1. 任务
    在这里插入图片描述

人工任务(User Task)
它是使用得做多的一种任务类型,他自带有一些人工任务的变量,例如签收人(Assignee),签收人就代表该任务交由谁处理,我们也可以通过某个特定或一系列特定的签收人来查找待办任务。利用上面的行为解释便是,当到达User
Task节点的时候,节点设置Assignee变量或等待设置Assignee变量,当任务被完成的时候,我们使用Trigger来要求流程引擎退出该任务,继续流转。
在这里插入图片描述

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

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

  1. 结构
    在这里插入图片描述

调用活动(Call Activity)
调用活动可以理解为函数调用,它会引用另外一个流程使之作为子流程运行,调用活动跟函数调用的功能一样,使流程模块化,增加复用的可能性。
在这里插入图片描述

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
在这里插入图片描述
安装完成,重启idea
在这里插入图片描述

2. SpringBoot集成Flowable

  1. 添加依赖
     	/**
     	  * 引入数据库依赖,Flowable在项目启动的时候会自动创建表
    	  */
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
         /**
     	  * lombok
    	  */
         <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.16</version>
            <scope>compile</scope>
        </dependency>
         /**
     	  * 添加flowable工作流依赖
    	  */
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter</artifactId>
            <version>6.7.2</version>
        </dependency>
         /**
     	  * 添加flowable工作流前端页面依赖,如果项目没有集成页面,无需添加此依赖
    	  */
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-ui-modeler-rest</artifactId>
            <version>6.7.2</version>
        </dependency>
         /**
     	  * 添加flowable-ui-modeler配置依赖项
    	  */
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-ui-modeler-conf</artifactId>
            <version>6.7.2</version>
        </dependency>
  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我自己的流程文件
    在这里插入图片描述
    ask_for_leave.bpmn20.xml
<?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:flowable="http://flowable.org/bpmn" 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" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
  <process id="askForLeave" name="ask_for_leave_process" isExecutable="true">
    <startEvent id="sid-b3fcb298-124a-4dc3-b603-1397b392e1a5"/>
    <userTask id="employee" name="员工" flowable:assignee="#{employeeNo}" flowable:formFieldValidation="true">
      <documentation>员工提交申请</documentation>
    </userTask>
    <sequenceFlow id="sid-9a4f7da0-7df8-422c-8d93-fcfe6eed6454" sourceRef="sid-b3fcb298-124a-4dc3-b603-1397b392e1a5" targetRef="employee"/>
    <userTask id="leader" name="领导" flowable:assignee="#{leaderNo}" flowable:formFieldValidation="true"/>
    <sequenceFlow id="sid-cda53600-1fdd-4556-b1f4-434cdef4b44c" sourceRef="employee" targetRef="leader"/>
    <exclusiveGateway id="sid-53b678ee-c126-46be-9bb7-70efe235451c"/>
    <sequenceFlow id="sid-c18a730f-6932-4036-b105-a840204bbd1f" sourceRef="leader" targetRef="sid-53b678ee-c126-46be-9bb7-70efe235451c"/>
    <endEvent id="sid-dc057597-34a0-4835-bbbb-1b12f0e8e407"/>
    <sequenceFlow id="leaderExamine" sourceRef="sid-53b678ee-c126-46be-9bb7-70efe235451c" targetRef="sid-dc057597-34a0-4835-bbbb-1b12f0e8e407" name="领导审核不通过">
      <conditionExpression xsi:type="tFormalExpression">${outcome=='驳回'}</conditionExpression>
    </sequenceFlow>
    <userTask id="boss" name="老板" flowable:formFieldValidation="true" flowable:assignee="#{bossNo}"/>
    <sequenceFlow id="leaderExaminePass" sourceRef="sid-53b678ee-c126-46be-9bb7-70efe235451c" targetRef="boss" name="领导审核通过">
      <conditionExpression xsi:type="tFormalExpression">${outcome=='通过'}</conditionExpression>
    </sequenceFlow>
    <exclusiveGateway id="sid-17d3919b-99f0-4a2e-8914-3dfb7bc74046"/>
    <sequenceFlow id="sid-e4dd8e02-acc7-4e51-95d4-5a1ade5eb2fd" sourceRef="boss" targetRef="sid-17d3919b-99f0-4a2e-8914-3dfb7bc74046"/>
    <endEvent id="sid-311b83fa-5c04-48af-8491-4e2f9417c49c"/>
    <sequenceFlow id="bossExamine" sourceRef="sid-17d3919b-99f0-4a2e-8914-3dfb7bc74046" targetRef="sid-311b83fa-5c04-48af-8491-4e2f9417c49c" name="老板审核不通过">
      <conditionExpression xsi:type="tFormalExpression">${outcome=='驳回'}</conditionExpression>
    </sequenceFlow>
    <endEvent id="sid-3f6150ad-4b8c-47e9-a726-39f5dffd5e0a"/>
    <sequenceFlow id="bossExaminePass" sourceRef="sid-17d3919b-99f0-4a2e-8914-3dfb7bc74046" targetRef="sid-3f6150ad-4b8c-47e9-a726-39f5dffd5e0a" name="老板审核通过">
      <conditionExpression xsi:type="tFormalExpression">${outcome=='通过'}</conditionExpression>
    </sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_ask_for_leave">
    <bpmndi:BPMNPlane bpmnElement="askForLeave" id="BPMNPlane_ask_for_leave">
      <bpmndi:BPMNShape id="shape-c5da47a1-57a1-465c-a7f8-2aad63d19b47" bpmnElement="sid-b3fcb298-124a-4dc3-b603-1397b392e1a5">
        <omgdc:Bounds x="-203.0" y="-10.75" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-71f351aa-e8f4-4656-9fa0-7979ddc1d916" bpmnElement="employee">
        <omgdc:Bounds x="-140.0" y="-10.5" width="61.0" height="29.5"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-a0cd4bce-af0a-40ce-8128-97629a5f4a23" bpmnElement="sid-9a4f7da0-7df8-422c-8d93-fcfe6eed6454">
        <omgdi:waypoint x="-173.0" y="4.25"/>
        <omgdi:waypoint x="-140.0" y="4.25"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-6a814a53-e6e2-4453-bb4a-43a1167e5559" bpmnElement="leader">
        <omgdc:Bounds x="-46.5" y="-11.0" width="63.0" height="30.5"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-3a2944ef-43d6-4409-920d-9d3d1acd422f" bpmnElement="sid-cda53600-1fdd-4556-b1f4-434cdef4b44c">
        <omgdi:waypoint x="-79.0" y="4.25"/>
        <omgdi:waypoint x="-46.5" y="4.25"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-bef28649-6416-4428-a3bd-43edb5c1b9c6" bpmnElement="sid-53b678ee-c126-46be-9bb7-70efe235451c">
        <omgdc:Bounds x="42.36" y="-15.75" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-fa69c266-5b3c-4e26-a4e2-3f58c271ebb4" bpmnElement="sid-c18a730f-6932-4036-b105-a840204bbd1f">
        <omgdi:waypoint x="16.5" y="4.25"/>
        <omgdi:waypoint x="42.36" y="4.25"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-9d577a21-ca61-4e33-bf3e-c892557f1638" bpmnElement="sid-dc057597-34a0-4835-bbbb-1b12f0e8e407">
        <omgdc:Bounds x="47.36" y="54.97" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-5651dbf2-4118-4668-bbd6-0259282cc084" bpmnElement="leaderExamine">
        <omgdi:waypoint x="62.36" y="24.25"/>
        <omgdi:waypoint x="62.36" y="54.97"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-1398abfb-fefc-4a16-881c-84a6c4b7b7f7" bpmnElement="boss">
        <omgdc:Bounds x="108.86" y="-12.75" width="68.0" height="34.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-3f5df081-8086-492f-a2c7-90aba1c1e4d7" bpmnElement="leaderExaminePass">
        <omgdi:waypoint x="82.36" y="4.25"/>
        <omgdi:waypoint x="108.86" y="4.25"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-4932221b-736b-4e85-a319-0859dbeb3e6b" bpmnElement="sid-17d3919b-99f0-4a2e-8914-3dfb7bc74046">
        <omgdc:Bounds x="203.35999" y="-15.75" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-3daa1ef4-119f-4b98-a40e-0b9cb3b205ce" bpmnElement="sid-e4dd8e02-acc7-4e51-95d4-5a1ade5eb2fd">
        <omgdi:waypoint x="176.86" y="4.25"/>
        <omgdi:waypoint x="203.35999" y="4.25"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-6becf929-e7e7-4153-a1d1-6175677742ca" bpmnElement="sid-311b83fa-5c04-48af-8491-4e2f9417c49c">
        <omgdc:Bounds x="208.35999" y="54.97" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-1f1a1414-f4f9-442a-97af-676878899415" bpmnElement="bossExamine">
        <omgdi:waypoint x="223.35999" y="24.25"/>
        <omgdi:waypoint x="223.35999" y="54.97"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-919e70dd-5004-4066-8103-427901b5c1fd" bpmnElement="sid-3f6150ad-4b8c-47e9-a726-39f5dffd5e0a">
        <omgdc:Bounds x="275.86" y="-10.75" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-542631d7-42d7-4b2a-b467-da8fdba298a1" bpmnElement="bossExaminePass">
        <omgdi:waypoint x="243.35999" y="4.25"/>
        <omgdi:waypoint x="275.86" y="4.25"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

  1. 设置配置文件
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
    /**
     * 防止生成的流程图中文乱码
     * @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<String, Object> 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<Task> 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<String, Object> 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<Task> 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<String, Object> 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<Task> 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<String, Object> 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<Execution> executions = runtimeService
                .createExecutionQuery()
                .processInstanceId(InstanceId)
                .list();

        //得到正在执行的Activity的Id
        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, 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
    在这里插入图片描述

  • 查看员工提交申请的流程图
    http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e
    在这里插入图片描述

  • 领导进行审批
    http://localhost:8080/askForLeave/leaderExaminePass?employeeNo=006
    在这里插入图片描述

  • 查看领导审批后的流程图
    http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e
    在这里插入图片描述

  • 老板审批
    http://localhost:8080/askForLeave/bossExaminePass?leaderNo=002
    在这里插入图片描述

  • 查看老板审批后的流程图
    http://localhost:8080/askForLeave/processDiagram?processId=2cd67ca3-c32e-11ee-a344-d41b812ff10e
    在这里插入图片描述

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
    在这里插入图片描述

  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
    在这里插入图片描述

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

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

  2. 在resources下创建processes文件夹,右键,创建一个【*.bpmn20.xml】文件
    在这里插入图片描述

  3. 选中创建好的【*.bpmn20.xml】文件,右键;在这里插入图片描述

  4. 开始画流程图 任意位置右键–>启动一个流程
    在这里插入图片描述
    在这里插入图片描述

  5. 设置员工提交申请
    在这里插入图片描述
    在这里插入图片描述

  6. 设置领导
    在这里插入图片描述

  7. 设置网关,用来判断领导是否同意
    在这里插入图片描述
    在这里插入图片描述

  8. 网关条件判断条件–驳回
    在这里插入图片描述

  9. 网关条件判断条件–通过
    在这里插入图片描述

  10. 设置老板
    在这里插入图片描述

  11. 设置网关,用来判断老板是否同意
    在这里插入图片描述

  12. 网关条件判断条件–驳回
    在这里插入图片描述

  13. 网关条件判断条件–通过
    在这里插入图片描述

  14. 当我们画好图后,【*.bpmn20.xml】流程文件也已经创建成功了
    在这里插入图片描述

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

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐