好文推荐:
jdk8之stream流式编程
2.5万字讲解DDD领域驱动设计(史上最全DDD)
springboot 实现延时队列(超级实用)
2.5万字详解23种设计模式

在这里插入图片描述

1. 前言

目前工作流框架最火的就是Activiti和Flowable,该文章介绍如何使用flowable,如果不想看理论的概念,可以直接跳到第五部分springboot集成flowable,或者文末直接获取代码!!!

2. 背景

  1. Flowable起源于Activiti工作流引擎,由Activiti的主要开发者在2016年创建。它继承了Activiti的众多优点,并在此基础上进行了优化和改进,以提供更加稳定、高效的工作流管理解决方案。Flowable与Activiti有着共同的祖先,即jbpm,并随着技术的发展和需求的变化,逐渐发展成为独立且功能强大的工作流引擎。

  2. 应用场景:Flowable广泛应用于各种需要流程管理的场景,如人力资源管理(如员工入职、离职、请假、绩效评估等)、自动化业务流程(如财务审批、采购流程、销售订单处理等)、任务管理和分配等。在由流程驱动的各种系统中,如OA、CRM、ERP、ECM、BI等,Flowable都能发挥重要作用。
    优势:

  3. 轻量级与高效:Flowable是一个轻量级的引擎,启动快,内存占用小,非常适合在微服务架构中使用。

  4. 全面支持BPMN 2.0标准:允许使用标准化的方式来定义和执行流程,提高了流程的兼容性和可移植性。

  5. 丰富的API和可视化设计工具:降低了与其他系统的集成难度,提高了业务流程的建模和编辑效率。

  6. 良好的社区支持和文档:作为一个活跃的开源项目,Flowable拥有良好的社区支持和不断更新的文档,用户可以在社区中获取帮助和分享经验。

3. flowable相关概念

Flowable是一个功能强大的业务流程管理引擎,支持BPMN 2.0标准。以下是对Flowable相关概念的具体介绍:

3.1 流程定义:

流程定义(Process Definition)是使用BPMN 2.0标准的XML格式描述的,它包含了流程中的节点、连接线和事件等元素。
对应的类:org.flowable.engine.repository.ProcessDefinition

3.2 流程实例:

流程实例(Process Instance)当一个流程定义被启动时,会创建一个流程实例,这个实例将按照定义的流程节点顺序执行。
对应的类:org.flowable.engine.runtime.ProcessInstance

3.3 任务:

任务(Task)是流程实例中的一个执行单元,代表需要由用户或系统自动完成的操作。在Flowable中,任务可以是用户任务、服务任务、脚本任务等多种类型。
对应的类:org.flowable.engine.task.Task

3.4 网关:

网关(Gateway)用于控制流程的执行方向,Flowable支持多种类型的网关,如排他网关和并行网关等。排他网关用于在多个分支中选择一个分支进行执行,而并行网关则用于将流程拆分为多个并行执行的分支。
对应的类:
org.flowable.bpmn.model.ParallelGateway(并行网关)
org.flowable.bpmn.model.ExclusiveGateway(排他网关)

3.5 条件表达式

条件表达式(Conditional Expression):Flowable支持使用条件表达式来控制流程的执行方向。条件表达式使用Java的语法,可以在排他网关等地方使用,根据条件表达式的值来决定流程应该走向哪个分支。

3.6 边界事件

边界事件(Boundary Event):边界事件可以捕获流程中的错误或异常事件,并在事件发生时执行相应的处理逻辑。例如,可以在用户任务中添加一个错误边界事件,当用户任务执行失败时,触发错误边界事件进行错误处理。
对应的类:
org.flowable.bpmn.model.StartEvent(开始事件)
org.flowable.bpmn.model.EndEvent(结束事件)
org.flowable.bpmn.model.BoundaryEvent(边界事件)

总的来说,Flowable作为一个轻量级的业务流程引擎,提供了丰富的流程控制元素和灵活的扩展机制,通过掌握其核心概念和常见问题的解决方案,开发者可以更加高效地构建工作流应用,提高业务流程的自动化水平和执行效率。

4. 配置工作流模型

4.1 请假工作流示意图

员工请假案例:
在这里插入图片描述

4.2 使用idea插件Flowable BPMN visualizer

Flowable BPMN visualizer是一款为IntelliJ IDEA系列IDE设计的插件,它提供了一个强大的BPMN(Business Process Model and Notation,即业务流程模型和表示法)模型编辑工具.

  1. 安装idea插件Flowable BPMN visualizer
    在这里插入图片描述
  2. 在resources文件夹下创建文件夹processes
  3. 然后创建文件leave.bpmn20.xml
  4. 选中leave.bpmn20.xml文件右键选择View BPMN (Flowable) Diagram
    在这里插入图片描述
  5. 然后就可以开始制作流程图了,右键会有各种工具
    在这里插入图片描述
    重要的工具已标出

4.3 插件里面的工具介绍

右键会出现各种工具
在这里插入图片描述

  1. Start Events(开始事件)
    Start Event: 流程的起点,手动触发。(重要)
    Start Conditional Event: 条件满足时自动触发。
    Start Message Event: 接收到特定消息时自动触发。
    Start Error Event: 发生指定错误时自动触发。
    Start Escalation Event: 需要升级或转交给更高级别人员时自动触发。
    Start Signal Event: 接收到特定信号时自动触发。
    Start Timer Event: 经过特定时间后自动触发。
  2. Activities(活动)
    Task: 用户执行的任务,如审批、填写表单等。
    Service Task: 系统自动执行的任务,如调用外部服务。
    User Task: 分配给用户执行的任务。(重要)
    Script Task: 使用脚本语言(如Groovy、JavaScript)编写的自定义任务。
    Business Rule Task: 基于业务规则引擎执行的任务。
    Manual Task: 需要人工干预的任务。
  3. Structural(结构)
    Subprocess: 子流程,表示一个嵌套的流程。
    Transaction: 事务,确保一组操作要么全部成功,要么全部失败。
    Ad-hoc Subprocess: 临时子流程,可以在运行时动态创建。
    Event Subprocess: 事件子流程,与特定事件相关联。
  4. Gateways(网关)
    Exclusive Gateway: 排他网关,根据条件选择一条路径执行。(重要)
    Parallel Gateway: 并行网关,将流程分成多个并行分支。
    Inclusive Gateway: 包容网关,允许多条路径同时执行。
    Complex Gateway: 复杂网关,结合了排他网关和并行网关的特性。
  5. Boundary Events(边界事件)
    Boundary Timer Event: 定时边界事件,当到达指定时间点时触发。
    Boundary Error Event: 错误边界事件,当发生指定错误时触发。
    Boundary Signal Event: 信号边界事件,当接收到指定信号时触发。
    Boundary Message Event: 消息边界事件,当接收到指定消息时触发。
    Boundary Cancel Event: 取消边界事件,当流程被取消时触发。
    Boundary Compensation Event: 补偿边界事件,用于处理补偿逻辑。
  6. Intermediate Catching Events(中间捕获事件)
    Intermediate Message Event: 中间消息事件,等待接收到特定消息时触发。
    Intermediate Timer Event: 中间定时事件,等待到达指定时间点时触发。
    Intermediate Signal Event: 中间信号事件,等待接收到特定信号时触发。
    Intermediate Conditional Event: 中间条件事件,等待满足特定条件时触发。
  7. Intermediate Throwing Events(中间抛出事件)
    Intermediate Message Throw Event: 中间消息抛出事件,发送消息给其他流程或系统。
    Intermediate Signal Throw Event: 中间信号抛出事件,发送信号给其他流程或系统。
    Intermediate Escalation Event: 中间升级事件,将问题升级或转交给更高级别的人员或系统。
    Intermediate Link Event: 中间链接事件,用于跨流程的通信。
  8. End Events(结束事件)
    End Event: 流程的正常结束点。(重要)
    Error End Event: 流程的错误结束点。
    Escalation End Event: 流程的升级结束点。
    Message End Event: 流程的消息结束点,发送消息给其他流程或系统。
    Signal End Event: 流程的信号结束点,发送信号给其他流程或系统。
    Terminate End Event: 流程的终止结束点,强制终止流程。
  9. Save to PNG
    Save to PNG: 将当前BPMN模型保存为PNG格式的图片文件,便于分享和展示。

5. springboot集成flowable

5.1 pom依赖文件

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.2</version>
    <relativePath/>
  </parent>
  
  <dependencies>
    <!-- 工作流flowable jar包 -->
    <dependency>
      <groupId>org.flowable</groupId>
      <artifactId>flowable-spring-boot-starter</artifactId>
      <version>6.7.2</version>
    </dependency>
    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>
    <!--Mybatis-Plus-->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus</artifactId>
      <version>3.5.2</version>
    </dependency>
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.8</version>
    </dependency>
  </dependencies>

3.2 application.yml

# 工作流 Flowable 配置
flowable:
  check-process-definitions: true # 设置为 false,禁用 /resources/processes 自动部署 BPMN XML 流程
  history-level: full # full:保存历史数据的最高级别,可保存全部流程相关细节,包括流程流转各节点参数
  #关闭定时任务JOB
  async-executor-activate: false

5.3 flowable 配置文件

import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;

/**
 * flowable配置
 */
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {

    @Override
    public void configure(SpringProcessEngineConfiguration engineConfiguration) {
        engineConfiguration.setActivityFontName("宋体");
        engineConfiguration.setLabelFontName("宋体");
        engineConfiguration.setAnnotationFontName("宋体");
    }


}

5.4 流程任务实体

import lombok.Data;
import java.util.Map;

/**
 * @author wdyin
 * @date 2024/11/15
 **/
@Data
public class TaskVo {
    /**
     * 任务id
     */
    private String taskId;
    /**
     * 任务名称
     */
    private String taskName;
    /**
     * 流程实例id
     */
    private String processInstanceId;
    /**
     * 流程变量
     */
    Map<String, Object> processVariables;
}

5.5 bpmn20.xml文件

leave.bpmn20.xml,工作流模型配置请看第四部分使用idea插件可视化制作工作流模型

<?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="leave" name="leave" isExecutable="true">
    <startEvent id="sid-7271f156-c78c-403a-9d19-73ccbfdd9881" name="开始请假流程">
      <documentation>员工开始请假流程</documentation>
    </startEvent>
    <userTask id="sid-bd605c32-03cb-4e04-86cb-fa93d7e5ef83" name="请假申请" flowable:assignee="${assignee}">
      <documentation>员工请假申请</documentation>
    </userTask>
    <sequenceFlow id="sid-ae7fd113-b1b7-4dc8-a276-f7355ef22d5c" sourceRef="sid-7271f156-c78c-403a-9d19-73ccbfdd9881" targetRef="sid-bd605c32-03cb-4e04-86cb-fa93d7e5ef83" name="流程开始">
      <documentation>流程开始</documentation>
    </sequenceFlow>
    <userTask id="sid-617c05f7-8e8d-4734-b831-f601443701df" name="领导审批" flowable:assignee="${assignee}">
      <documentation>领导审批</documentation>
    </userTask>
    <sequenceFlow id="sid-53772e79-4146-49d9-a04e-fefefccfe21d" sourceRef="sid-bd605c32-03cb-4e04-86cb-fa93d7e5ef83" targetRef="sid-617c05f7-8e8d-4734-b831-f601443701df" name="申请流程"/>
    <sequenceFlow id="sid-5b9c0f8c-594b-4dac-a07d-0ef68f5b0b54" sourceRef="sid-617c05f7-8e8d-4734-b831-f601443701df" targetRef="sid-bd605c32-03cb-4e04-86cb-fa93d7e5ef83" name="领导审批驳回">
      <conditionExpression xsi:type="tFormalExpression">${result==false}</conditionExpression>
    </sequenceFlow>
    <exclusiveGateway id="sid-cb1bce6f-ecef-48aa-b4cb-7dee1f76fdb6"/>
    <sequenceFlow id="sid-f433236a-6e6f-46ba-9799-b240cf151d76" sourceRef="sid-617c05f7-8e8d-4734-b831-f601443701df" targetRef="sid-cb1bce6f-ecef-48aa-b4cb-7dee1f76fdb6" name="领导审批通过">
      <conditionExpression xsi:type="tFormalExpression">${result==true}</conditionExpression>
    </sequenceFlow>
    <endEvent id="sid-68b72d73-e005-4403-b454-5c8f5d99745d"/>
    <sequenceFlow id="sid-575778a8-b18b-4542-b11a-dd2965b09a30" sourceRef="sid-cb1bce6f-ecef-48aa-b4cb-7dee1f76fdb6" targetRef="sid-68b72d73-e005-4403-b454-5c8f5d99745d" name="请假小于两天">
      <conditionExpression xsi:type="tFormalExpression">${day&lt;2}</conditionExpression>
    </sequenceFlow>
    <userTask id="sid-afa9dbd8-3aaf-44a5-8ae0-c2035cb2a698" name="老板审批" flowable:assignee="${assignee}">
      <documentation>老板审批</documentation>
    </userTask>
    <sequenceFlow id="sid-30725bf0-64b7-45b5-a14b-914c27462e2d" sourceRef="sid-cb1bce6f-ecef-48aa-b4cb-7dee1f76fdb6" targetRef="sid-afa9dbd8-3aaf-44a5-8ae0-c2035cb2a698" name="请假大于等于两天">
      <conditionExpression xsi:type="tFormalExpression">${day&gt;=2}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-9b841925-57c1-4e6b-8ec8-ac4a58d2b80c" sourceRef="sid-afa9dbd8-3aaf-44a5-8ae0-c2035cb2a698" targetRef="sid-68b72d73-e005-4403-b454-5c8f5d99745d" name="老板审批通过">
      <documentation>老板不同意</documentation>
      <conditionExpression xsi:type="tFormalExpression">${result==true}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="sid-0d2f0355-dded-45a9-8474-84893afae40d" sourceRef="sid-afa9dbd8-3aaf-44a5-8ae0-c2035cb2a698" targetRef="sid-7271f156-c78c-403a-9d19-73ccbfdd9881" name="老板审批驳回">
      <conditionExpression xsi:type="tFormalExpression">${result==false}</conditionExpression>
    </sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_leave">
    <bpmndi:BPMNPlane bpmnElement="leave" id="BPMNPlane_leave">
      <bpmndi:BPMNShape id="shape-bc3b6696-4835-4f6e-9458-e77ba9666cac" bpmnElement="sid-7271f156-c78c-403a-9d19-73ccbfdd9881">
        <omgdc:Bounds x="-330.0" y="-170.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-2ccaef2e-ca0c-43d9-9965-d0eda82b2633" bpmnElement="sid-bd605c32-03cb-4e04-86cb-fa93d7e5ef83">
        <omgdc:Bounds x="-275.0" y="-180.0" width="55.0" height="50.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-9f7f9b61-5efe-4203-9010-822a74a103ce" bpmnElement="sid-ae7fd113-b1b7-4dc8-a276-f7355ef22d5c">
        <omgdi:waypoint x="-300.0" y="-155.0"/>
        <omgdi:waypoint x="-275.0" y="-155.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="sid-d4255997-664d-4c6d-903b-38c48a453d09" bpmnElement="sid-617c05f7-8e8d-4734-b831-f601443701df">
        <omgdc:Bounds x="-178.5" y="-180.0" width="55.0" height="50.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-a0b9bc01-a881-4f1f-87e0-363c0d904811" bpmnElement="sid-53772e79-4146-49d9-a04e-fefefccfe21d">
        <omgdi:waypoint x="-220.0" y="-155.0"/>
        <omgdi:waypoint x="-178.5" y="-155.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-1cd38e63-1d71-4aea-b340-5ca4886333b3" bpmnElement="sid-5b9c0f8c-594b-4dac-a07d-0ef68f5b0b54">
        <omgdi:waypoint x="-151.0" y="-180.0"/>
        <omgdi:waypoint x="-151.0" y="-222.5"/>
        <omgdi:waypoint x="-247.5" y="-222.5"/>
        <omgdi:waypoint x="-247.5" y="-180.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-8f3a8a11-80b2-43cc-b754-ee73faf7c91c" bpmnElement="sid-cb1bce6f-ecef-48aa-b4cb-7dee1f76fdb6">
        <omgdc:Bounds x="-75.0" y="-175.0" width="40.0" height="40.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-2459964a-c3e5-49a9-b182-0a1a9f1576b2" bpmnElement="sid-f433236a-6e6f-46ba-9799-b240cf151d76">
        <omgdi:waypoint x="-123.5" y="-155.0"/>
        <omgdi:waypoint x="-75.0" y="-155.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="shape-b4874158-c2df-4d2a-8467-21cc5488e5f6" bpmnElement="sid-68b72d73-e005-4403-b454-5c8f5d99745d">
        <omgdc:Bounds x="10.0" y="-170.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-793e0ef5-d447-41b5-8aa7-ebbfd9ea0ce8" bpmnElement="sid-575778a8-b18b-4542-b11a-dd2965b09a30">
        <omgdi:waypoint x="-35.0" y="-155.0"/>
        <omgdi:waypoint x="10.0" y="-155.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNShape id="sid-1a7cb41a-8eb2-4db9-9237-7ab7d1d802ce" bpmnElement="sid-afa9dbd8-3aaf-44a5-8ae0-c2035cb2a698">
        <omgdc:Bounds x="-82.5" y="-82.25" width="55.0" height="50.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-781d3fc5-dea4-4ec3-8741-1adcc1a03995" bpmnElement="sid-30725bf0-64b7-45b5-a14b-914c27462e2d">
        <omgdi:waypoint x="-55.0" y="-135.0"/>
        <omgdi:waypoint x="-55.0" y="-108.625"/>
        <omgdi:waypoint x="-55.0" y="-82.25"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-2dbcc4cc-2d70-41f1-9afe-b0e45bf598f4" bpmnElement="sid-9b841925-57c1-4e6b-8ec8-ac4a58d2b80c">
        <omgdi:waypoint x="-27.5" y="-57.25"/>
        <omgdi:waypoint x="25.0" y="-57.25"/>
        <omgdi:waypoint x="25.0" y="-140.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-18d2af23-bed9-4368-8f47-021d2fa60d01" bpmnElement="sid-0d2f0355-dded-45a9-8474-84893afae40d">
        <omgdi:waypoint x="-82.5" y="-57.25"/>
        <omgdi:waypoint x="-315.0" y="-57.25"/>
        <omgdi:waypoint x="-315.0" y="-140.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

5.6 核心功能类

实现功能如下

  1. 查询流程定义列表
  2. 创建请假审批流程
  3. 审批流程列表
  4. 提交审批流程
  5. 历史流程列表
  6. 删除流程
  7. 领导待办任务
  8. 领导已办任务
  9. 领导批准
  10. 领导拒绝
  11. 老板待办任务
  12. 老板批准
  13. 老板拒绝
  14. 员工再次申请请假
  15. 生成流程图
    代码如下
import com.wander.flowable.vo.TaskVo;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
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.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wdyin
 * @date 2024/11/13
 **/
@RestController
@RequestMapping("/flowable/test")
@Slf4j
public class FlowableTestController {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ProcessEngine processEngine;
    /**
     * 对应leave.bpmn20.xml文件中process标签的id属性
     */
    private String processKey = "leave";

    /**
     * 1. 查询流程定义列表
     */
    @GetMapping("/processDefinitionList")
    public void processDefinitionList() {
        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery().list();
        for (ProcessDefinition processDefinition : processDefinitions) {
            log.info("部署id:{},流程定义id:{},流程定义名称:{}", processDefinition.getDeploymentId(), processDefinition.getId(), processDefinition.getName());
        }
    }

    /**
     * 2. 创建请假审批流程
     *
     * @param day        请假天数
     * @param employeeId 员工id
     * @return
     */
    @PostMapping("/start/{day}/{employeeId}")
    public void start(@PathVariable("day") Integer day, @PathVariable("employeeId") String employeeId) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("day", day);
        variables.put("employeeId", employeeId);
        //assignee表示流程的办理人
        variables.put("assignee", employeeId);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processKey, variables);
        runtimeService.updateBusinessStatus(processInstance.getId(), "未审批");
        log.info("流程实例ID:{}", processInstance.getId());
    }

    /**
     * 3. 审批流程列表
     *
     * @return
     */
    @DeleteMapping("/processList")
    public void processList() {
        List<ProcessInstance> processInstances = runtimeService.createProcessInstanceQuery().list();
        for (ProcessInstance processInstance : processInstances) {
            log.info("流程实例ID:{}, 流程状态:{}", processInstance.getId(), processInstance.getBusinessStatus());
        }
    }

    /**
     * 4. 提交审批流程
     *
     * @param processInstanceId 流程实例ID
     * @param employeeId        员工ID
     * @param leaderId          领导ID
     */
    @PostMapping("/submit/{processInstanceId}/{employeeId}/{leaderId}")
    public void submit(@PathVariable("processInstanceId") String processInstanceId, @PathVariable("employeeId") String employeeId, @PathVariable("leaderId") String leaderId) {
        log.info("流程实例ID:" + processInstanceId);
        //查询自己的审批流程
        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).taskAssignee(employeeId).singleResult();
        Map<String, Object> variables = new HashMap<>();
        //交给哪位领导审批
        variables.put("assignee", leaderId);
        //提交请假审批流程
        taskService.complete(task.getId(), variables);
        //修改请假流程的状态
        runtimeService.updateBusinessStatus(processInstanceId, "审批中");
        log.info("流程id:{},任务id:{},", processInstanceId, task.getId(), "审批中");
    }

    /**
     * 5. 历史流程列表
     *
     * @return
     */
    @DeleteMapping("/historicProcessInstanceList")
    public void historicProcessInstanceList() {
        HistoricProcessInstanceQuery query = processEngine.getHistoryService().createHistoricProcessInstanceQuery();
        List<HistoricProcessInstance> historicProcessInstances = query.list();
        historicProcessInstances.forEach(historicProcessInstance -> {
            log.info("历史流程列表ID:{},流程状态:{}", historicProcessInstance.getId(), historicProcessInstance.getBusinessStatus());
        });
    }

    /**
     * 6. 删除流程
     *
     * @param id 流程id
     * @return
     */
    @DeleteMapping("/process/delete/{id}")
    public void processDelete(@PathVariable("id") String id) {
        runtimeService.deleteProcessInstance(id, "删除流程");
        log.info("删除该流程{}", id);
    }

    /**
     * 7. 领导待办任务
     *
     * @return
     */
    @GetMapping("/leaderTodoList/{leaderId}")
    public void leaderTodoList(@PathVariable("leaderId") String leaderId) {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(leaderId).list();
        List<TaskVo> taskVoList = tasks.stream().map(task -> {
            TaskVo taskVo = new TaskVo();
            taskVo.setTaskId(task.getId());
            taskVo.setTaskName(task.getName());
            taskVo.setProcessInstanceId(task.getProcessInstanceId());
            Map<String, Object> processVariables = processEngine.getHistoryService()
                    .createHistoricVariableInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .list()
                    .stream()
                    .collect(Collectors.toMap(var -> var.getVariableName(), var -> var.getValue()));
            taskVo.setProcessVariables(processVariables);
            return taskVo;
        }).collect(Collectors.toList());
        log.info("领导任务列表:" + taskVoList);
    }

    /**
     * 8. 领导已办任务
     */
    @GetMapping("/leaderDoneList/{leaderId}")
    public void leaderDoneList(@PathVariable("leaderId") String leaderId) {
        List<HistoricTaskInstance> historicTaskInstanceList = processEngine.getHistoryService().createHistoricTaskInstanceQuery().taskAssignee(leaderId).list();
        List<TaskVo> taskVoList = historicTaskInstanceList.stream().map(task -> {
            TaskVo taskVo = new TaskVo();
            taskVo.setTaskId(task.getId());
            taskVo.setTaskName(task.getName());

            Map<String, Object> processVariables = processEngine.getHistoryService()
                    .createHistoricVariableInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .list()
                    .stream()
                    .collect(Collectors.toMap(var -> var.getVariableName(), var -> var.getValue()));
            taskVo.setProcessVariables(processVariables);
            return taskVo;
        }).collect(Collectors.toList());
        log.info("已办任务:" + taskVoList);
    }

    /**
     * 9. 领导批准
     *
     * @param taskId 任务ID,非流程id
     */
    @GetMapping("/leaderApply/{taskId}/{bossId}")
    public void leaderApply(@PathVariable("taskId") String taskId, @PathVariable("bossId") String bossId) {
        Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee("2000").singleResult();
        Map<String, Object> variables = new HashMap<>();
        variables.put("assignee", bossId);
        variables.put("result", true);
        taskService.complete(task.getId(), variables);
        runtimeService.updateBusinessStatus(task.getProcessInstanceId(), "领导审批完成");

        log.info("领导审批成功任务{}", taskId);
    }

    /**
     * 10. 领导拒绝
     *
     * @param taskId 任务ID
     */
    @GetMapping("/leaderRefuse/{taskId}")
    public void leaderRefuse(@PathVariable("taskId") String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee("2000").singleResult();
        //通过审核
        HashMap<String, Object> variables = new HashMap<>();
        variables.put("result", false);
        taskService.complete(task.getId(), variables);
        runtimeService.updateBusinessStatus(task.getProcessInstanceId(), "领导审批拒绝");

        log.info("领导审批拒绝任务{}", taskId);
    }

    /**
     * 11. 老板待办任务
     */
    @GetMapping("/bossTodoList")
    public void bossTodoList() {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee("3000").list();
        List<TaskVo> taskVoList = tasks.stream().map(task -> {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            TaskVo taskVO = new TaskVo();
            taskVO.setTaskId(task.getId());
            taskVO.setTaskName(task.getName());
            taskVO.setProcessVariables(variables);
            return taskVO;
        }).collect(Collectors.toList());
        log.info("老板任务列表:" + taskVoList);
    }

    /**
     * 12. 老板批准
     *
     * @param taskId 任务ID,非流程id
     * @return
     */
    @GetMapping("/bossApply/{taskId}")
    public void apply(@PathVariable("taskId") String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).taskAssignee("3000").singleResult();
        if (task == null) {
            log.info("老板没有任务");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("result", true);
        String processInstanceId = task.getProcessInstanceId();
        runtimeService.updateBusinessStatus(processInstanceId, "老板审批完成");
        taskService.complete(task.getId(), map);

    }

    /**
     * 13. 老板拒绝
     *
     * @param taskId 任务ID
     * @return
     */
    @GetMapping("/bossRefuse/{taskId}")
    public void bossRefuse(@PathVariable("taskId") String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).taskCandidateGroupIn(Arrays.asList("boss")).singleResult();
        if (task == null) {
            log.info("老板没有任务");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("result", "驳回");
        taskService.complete(task.getId(), map);
        runtimeService.updateBusinessStatus(task.getProcessInstanceId(), "老板审批拒绝");
        log.info("领导审批拒绝任务{}", taskId);
    }

    /**
     * 14. 员工再次申请请假
     *
     * @param processId 流程id
     * @param day
     * @return
     */
    @GetMapping("/applyAgain/{processId}/{day}/{employeeId}/{leaderId}")
    public void applyAgain(@PathVariable("processId") String processId, @PathVariable("day") Integer day, @PathVariable("employeeId") String employeeId, @PathVariable("leaderId") String leaderId) {
        Task task = taskService.createTaskQuery().processInstanceId(processId).singleResult();
        if (task == null) {
            log.info("员工没有任务");
        }
        // 提交请假申请
        Map<String, Object> map = new HashMap<>();
        map.put("day", day);
        map.put("employeeId", employeeId);
        map.put("leaderId", leaderId);
        map.put("groups", Arrays.asList("leader"));
        taskService.complete(task.getId(), map);
        runtimeService.updateBusinessStatus(task.getProcessInstanceId(), "审批中");
        log.info("员工再次请假申请,任务{}", task.getId());
    }

    /**
     * 15. 生成流程图
     *
     * @param processId 流程ID
     */
    @GetMapping("/processDiagram/{processId}")
    public void genProcessDiagram(HttpServletResponse httpServletResponse, @PathVariable("processId") String processId) throws Exception {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();

        if (pi == null) {
            return;
        }
        Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
        String InstanceId = task.getProcessInstanceId();
        List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(InstanceId).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 inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", activityIds, flows, engconf.getActivityFontName(),
                engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, true);
        OutputStream os = null;
        try {
            BufferedImage image = ImageIO.read(inputStream);
            httpServletResponse.setContentType("image/png");
            os = httpServletResponse.getOutputStream();
            if (image != null) {
                ImageIO.write(image, "png", os);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.flush();
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

6. 多租户flowable数据隔离

多租户Flowable是指在一个单一的应用实例中支持多个租户(客户)的业务流程管理。每个租户拥有独立的数据和流程定义,但共享同一个应用实例。这种架构在SaaS(软件即服务)应用中非常常见,因为它能够有效地利用资源并降低运营成本。有关flowable多租户的完整代码在文末获取!

7. 总结

flowable工作流对于提高效率、优化流程、降低成本、提高质量等方面都有重要作用,是现代企业管理中不可或缺的工具,老铁们用起来吧!

如果看到这里,说明你喜欢这篇文章,请转发,点赞。

  1. 关注公众号老板来一杯java
  2. 加群即可获取flowable多租户代码,并赠送DDD领域驱动设计实战落地解惑PDF一份!
  3. 公众号回复java即可获取java基础经典面试一份!

好文推荐:
推荐一款基于AI编程的代码自动生成工具Cursor,替代VSCode
2.5万字讲解DDD领域驱动设计(史上最全DDD)
python vs java,从java转python一键简简单单入门,轻轻松松上手,抓紧收藏起来吧

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐