Bootstrap

Spring Boot整合流程引擎Flowable

Spring Boot整合流程引擎Flowable

在这里插入图片描述

一.工作流介绍

官方Flowable网站:Introducing Flowable Open Source · Flowable Open Source Documentation

中文文档:Flowable BPMN 用户手册 (v 6.3.0) (tkjohn.github.io)

1.1 为什么使用工作流

在程序员工作中,或多或少都会遇见审批流程类型的业务需求。一个审批流程可能包含开始->申请->领导审批->老板审批->结束等多个阶段,如果我们用字段去定义每一个流程阶段(0->开始 1->申请 2->领导审批 3->老板审批 4->结束),虽然可以实现流程运行逻辑,但这样业务代码逻辑复杂。如果审批流程还有驳回操作,则还需要加一个是否驳回字段,显然这样实现,成本太大,且不利于维护。

为了解决上述用代码逻辑硬写审批流程而导致的成本大,不利用维护的缺点,工作流因此而生。

1.2 工作流是什么

工作流,是把业务之间的各个步骤以及规则进行抽象和概括性的描述。使用特定的语言为业务流程建模,让其运行在计算机上,并让计算机进行计算和推动。工作流是复杂版本的状态机。

简单状态

在这里插入图片描述

上图为工作流退化为基础状态机的例子,路人乙的状态非常简单,站起来->走起来->跑起来->慢下来->站起来,无限循环,如果让我们实现路人乙的状态切换,那么我们只需要用一个字段来记录路人乙当前的状态就好了。

而对于复杂的状态或者状态维度增加且状态流转的条件极为复杂,可能单纯用字段记录状态的实现方式就会不那么理想。如下图:
复杂状态
在这里插入图片描述

现在交给路人甲的选择就多了起来,当路人甲获发完工资后的时候,他会根据余额的大小来判断接下来该如何行动,如果数额小于等于5000,那么他决定买一个平板,如果数额小于等于10万,那么路人甲就决定去学习一下购买理财产品,但如果路人甲获得的余额数量超过了30万,他就决定购买一辆宝马,但购买宝马的流程是复杂的,路人甲决定同时完成交首付和贷款的手续。
其实这个流程还不算特别复杂,但到目前为止,单纯用一个字段来表明状态已经无法满足要求了。

工作流解决的痛点在于,解除业务宏观流程和微观逻辑的耦合,让熟悉宏观业务流程的人去制定整套流转逻辑,而让专业的人只需要关心他们应当关心的流程节点,就好比大家要一起修建一座超级体育场,路人甲只需要关心他身边的这一堆砖是怎么堆砌而非整座建筑。

1.3 工作流不能解决的问题
工作流无法解决毫无关系(没有前后关联)的事务。

在这里插入图片描述

工作流是一个固定好的框架,大家就按照这个框架来执行流程就行了,但某些情况他本身没有流转顺序的。
比如:路人丙每天需要学习,谈恋爱以及玩游戏,它们之间没有关联性无法建立流程,但可以根据每一项完成的状态决定今天的任务是否完结,这种情况我们需要使用CMMN来建模,它就是专门针对这种情况而设计的,但今天我们不讲这个,而是讲讲BPMN协议。

二.BPMN2.0协议

2.1 什么是BPMN2.0协议

在这里插入图片描述

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

2.2 BPMN2.0协议元素介绍

协议中元素的主要分类为,事件-任务-连线-网关
一个流程必须包含一个事件(如:开始事件)和至少一个结束(事件)。其中网关的作用是流程流转逻辑的控制。任务则分很多类型,他们各司其职,所有节点均由连线联系起来。
下面我就以每种类型的节点简单地概括一下其作用。

在这里插入图片描述

  • 事件(event)通常用于为流程生命周期中发生的事情建模,图里是【开始、结束】两个圈。
  • 顺序流(sequence flow)是流程中两个元素间的连接器。图里是【箭头线段】。
  • 网关(gateway)用于控制执行的流向。图里是【菱形(中间有X)】
  • 用户任务(user task)用于对需要人工执行的任务进行建模。图里是【矩形】。

**事件(event definition)😗*通常用于为流程生命周期中发生的事情建模。事件总是图形化为圆圈。在BPMN 2.0中,有两种主要的事件分类:*捕获(catching)抛出(throwing)*事件。

**顺序流(sequence flow)😗*是流程中两个元素间的连接器。在流程执行过程中,一个元素被访问后,会沿着其所有出口顺序流继续执行。这意味着BPMN 2.0的默认是并行执行的:两个出口顺序流就会创建两个独立的、并行的执行路径。

网关:
互斥网关(Exclusive Gateway),又称排他网关,他有且仅有一个有效出口,可以理解为if…else if… else if…else,就和我们平时写代码的一样。
并行网关(Parallel Gateway),他的所有出口都会被执行,可以理解为开多线程同时执行多个任务。
包容性网关(Inclusive Gateway),只要满足条件的出口都会执行,可以理解为 if(…) do, if (…) do, if (…) do,所有的条件判断都是同级别的。

**用户任务(user task)😗*用于对需要人工执行的任务进行建模。当流程执行到达用户任务时,会为指派至该任务的用户或组的任务列表创建一个新任务。

2.3 如何使用BPMN2.0协议

首先,从用户的角度来看,使用者其实只需要关心三件事

  • 我如何把我的业务逻辑转化为流程图-即容易理解的绘图工具。

  • 我如何使流程流转-即开箱即用的API。

  • 我需要引擎告诉我,我现在该处理什么节点-即丰富且鲜明的事件机制。

图中是流程图的整个生命周期,从画图到部署,然后启动流程,流程经过人工或自动的方式流转,最后结束。

在这里插入图片描述

三.Flowable简介

3.1 Flowable是什么

Flowable是BPMN2.0协议的一种Java版本的实现,是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。
Flowable可以嵌入Java应用程序中运行,也可以作为服务器、集群运行,更可以提供云服务。

3.2 提供的接口API

flowable提供了7个常见的api,所以只要掌握这些api就可以完成审批业务的开发了

在这里插入图片描述

1.FormService 表单数据的管理。

2.RepositoryService 提供了在编辑和发布审批流程的api。主要是模型管理和流程定义的业务api。

3.RuntimeService 处理正在运行的流程。

4.HistoryService 在用户发起审批后,会生成流程实例。historyService为处理流程实例的api,但是其中包括了已经完成的和未完成的流程实例。

5.TaskService 对流程实例的各个节点的审批处理。

6.ManagementService 主要是执行自定义命令。

7.ldentityService 用于身份信息获取和保存,这里主要是获取身份信息。

四.Flowable实战

假设有一个淮师大计科院学生请假需求,需要你设计一个学生请假流程的功能。你该如何设计?

  • 我会用flowable来实现
  • 创建springboot项目,该项目是启动flowable-ui网站,在本地进行流程画图设计。安装flowable-ui,启动服务进入folwable-ui网站画图,导出图对应的xml文件 (画图)
  • 创建springboot项目(flowable),该项目是启动流程应用,将对应的xml文件复制到resources下的processes文件夹中(processes文件夹需自己创建) (部署)
  • 启动springboot项目 (启动)
  • 通过接口调用,启动流程 (流转)
4.1 搭建项目目录

在这里插入图片描述

4.2编写启动类,和相关配置文件
在pom.xml引入依赖
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <!-- 集中定义依赖版本号 -->
    <properties>
        <flowable.version>6.7.2</flowable.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!--knife4j(swagger) -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>

        <!--swagger-->
<!--        <dependency>-->
<!--            <groupId>io.springfox</groupId>-->
<!--            <artifactId>springfox-swagger2</artifactId>-->
<!--            <version>2.9.2</version>-->
<!--            &lt;!&ndash;排除swagger2的annotations和models依赖,然后再引入1.5.21版本的annotations和models依赖&ndash;&gt;-->
<!--            <exclusions>-->
<!--                <exclusion>-->
<!--                    <groupId>io.swagger</groupId>-->
<!--                    <artifactId>swagger-annotations</artifactId>-->
<!--                </exclusion>-->
<!--                <exclusion>-->
<!--                    <groupId>io.swagger</groupId>-->
<!--                    <artifactId>swagger-models</artifactId>-->
<!--                </exclusion>-->
<!--            </exclusions>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>io.springfox</groupId>-->
<!--            <artifactId>springfox-swagger-ui</artifactId>-->
<!--            <version>2.9.2</version>-->
<!--        </dependency>-->
<!--        &lt;!&ndash;引入1.5.21版本的annotations和models依赖&ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>io.swagger</groupId>-->
<!--            <artifactId>swagger-annotations</artifactId>-->
<!--            <version>1.5.21</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>io.swagger</groupId>-->
<!--            <artifactId>swagger-models</artifactId>-->
<!--            <version>1.5.21</version>-->
<!--        </dependency>-->
        <!--flowable工作流依赖-->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter</artifactId>
            <version>${flowable.version}</version>
        </dependency>
        <!--idm依赖提供身份认证-->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter-ui-idm</artifactId>
            <version>${flowable.version}</version>
        </dependency>
        <!-- modeler绘制流程图 -->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
            <version>${flowable.version}</version>
        </dependency>
<!--        mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
application.yml文件
server:
  port: 8080

flowable:
  idm:
    app:
      admin:
        # 登录的用户名
        user-id: admin
        # 登录的密码
        password: admin
        # 用户的名字
        first-name: w
        last-name: j
  database-schema-update: true  # 没有数据库表的时候生成数据库表  建表后可关闭,下次启动不会再次建表
  async-executor-activate: false # 关闭定时任务JOB
spring:
  # mysql连接信息
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    #&nullCatalogMeansCurrent=true 这个一定要加在url后,不然flowable自动创建表失败
    url: jdbc:mysql://localhost:3306/flowable4?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=Asia/Shanghai&nullCatalogMeansCurrent=true
    username: root
    password: root
    # 默认会启动liquibase, 可关闭
    liquibase:
      enabled: false
#  swagger2:
#    enabled: true
#  mvc:
#    pathmatch:
#      matching-strategy: ant_path_matcher

注意:

  • &nullCatalogMeansCurrent=true 这个一定要加在url后,不然flowable自动创建表失败
  • 在数据库中创建flowable数据库,启动项目时,flowable服务会自动创建对应的表
  • 启动项目后 网站的登录用户 密码 user-id: admin password: admin

在这里插入图片描述

配置类
防止流程图乱码FlowableConfig
package com.igeek.boot.config;


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

/**
 *防止流程图乱码配置
 */

@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {


    @Override
    public void configure(SpringProcessEngineConfiguration engineConfiguration) {
        engineConfiguration.setActivityFontName("宋体");
        engineConfiguration.setLabelFontName("宋体");
        engineConfiguration.setAnnotationFontName("宋体");
    }
}
返回结果Result
package com.igeek.boot.config;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * TODO
 * @since 2023/12/16
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {

    private boolean flag;
    private String message;
    private Object data;

    public Result(boolean flag , String message){
        this.flag = flag;
        this.message = message;
    }
}
swagger2测试
package com.igeek.boot.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Description Swagger配置类
 * @Author chenmin
 * @Version 1.0
 * @Date 2023/3/15 15:17
 *
 * http://localhost:8080/swagger-ui.html
 */
@Configuration
//开启Swagger2
@EnableSwagger2
public class SwaggerConfig {
    @Value("${spring.swagger2.enabled}")
    private Boolean enabled;

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(enabled)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.igeek.boot"))
                .paths(PathSelectors.any())
                .build();
    }

    //Swagger文档信息
    public ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("springboot利用swagger2构建api接口文档 "+"\t"+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now()))
                .description("有问题给管理员邮件:[email protected]")
                .version("1.0")
                .termsOfServiceUrl("https://www.igeekhome.com/")
                .build();
    }
}
TaskVO封装审批列表查询结果:
package com.igeek.boot.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 2023/12/26 1:27
 * @description 审批列表查询结果
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TaskVO {
    private String id;
    private String day;
    private String name;
}

controller请假流程接口:
package com.igeek.boot.controller;



import com.igeek.boot.config.Result;
import com.igeek.boot.vo.TaskVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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.*;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/leave")
@Slf4j
@Api(tags = "请假流程接口")
public class LeaveController {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ProcessEngine processEngine;

    @ApiOperation("学生提交请假申请")
    @PostMapping( "add/{day}/{studentUser}")
    public Result sub(@ApiParam("请假天数") @PathVariable("day") Integer day , @ApiParam("学生用户名")@PathVariable("studentUser") String studentUser) {
        // 学生提交请假申请
        Map<String, Object> map = new HashMap<>();
        map.put("day", day);
        map.put("studentName", studentUser);
        // leave为学生请假流程xml文件中的id
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("go", map);
        log.info("流程实例ID:" + processInstance.getId());
        //完成申请任务
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
        taskService.complete(task.getId());
        return new Result(true,"提交成功.流程Id为:" + processInstance.getId()) ;
    }


    @ApiOperation("辅导员获取审批管理列表")
    @GetMapping("teacherList")
    public Result teacherList() {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("a").list();
        List<TaskVO> taskVOList = tasks.stream().map(task -> {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            return new TaskVO(task.getId(), variables.get("day").toString(), variables.get("studentName").toString());
        }).collect(Collectors.toList());
        log.info("任务列表:" + tasks);
        if (tasks == null || tasks.size() == 0) {
            return new Result(false,"没有任务");
        }
        return new Result(true,"获取成功",taskVOList);
    }

    /**
     * 辅导员批准
     *
     * @param taskId 任务ID
     */
    @ApiOperation("辅导员批准")
    @GetMapping("teacherApply/{taskId}")
    public Result teacherApply(@ApiParam("任务id")@PathVariable("taskId") String taskId) {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("a").list();
        //Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (tasks == null) {
            return new Result(false, "没有任务");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "通过");
        for (Task task : tasks) {
            taskService.complete(task.getId(), map);
        }
        return new Result(true,"审批成功");
    }

    /**
     * 辅导员拒绝
     */
    @ApiOperation("辅导员拒绝")
    @GetMapping( "teacherReject/{taskId}")
    public Result teacherReject(@ApiParam("任务id")@PathVariable("taskId") String taskId) {
        Task task1 = taskService.createTaskQuery().taskCandidateGroup("a").taskId(taskId).singleResult();
        //Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task1 == null) {
            return new Result(false, "没有任务");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "驳回");
        taskService.complete(task1.getId(), map);
        return new Result(true,"审批失败");
    }
    /**
     * 院长获取审批管理列表
     */
    @ApiOperation("院长获取审批管理列表")
    @GetMapping("deanList")
    public Result deanList() {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("b").list();
        List<TaskVO> taskVOList = tasks.stream().map(task -> {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            return new TaskVO(task.getId(), variables.get("day").toString(), variables.get("studentName").toString());
        }).collect(Collectors.toList());
        if (tasks == null || tasks.size() == 0) {
            return new Result(false,"没有任务");
        }
        return new Result(true,"获取成功",taskVOList);
    }

    /**
     * 院长批准
     * @param taskId
     * @return
     */
    @ApiOperation("院长批准")
    @GetMapping("deanApply/{taskId}")
    public Result apply(@ApiParam("任务id")@PathVariable("taskId") String taskId) {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("b").list();
        //Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (tasks == null) {
            return new Result(false, "没有任务");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "通过");
        for (Task task : tasks) {
            taskService.complete(task.getId(), map);
        }
        return new Result(true,"审批成功");
    }

    /**
     * 院长拒绝
     * @param taskId
     * @return
     */
    @ApiOperation("院长拒绝")
    @GetMapping("deanReject/{taskId}")
    public Result deanReject(@ApiParam("任务id")@PathVariable("taskId") String taskId) {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("b").list();
        //Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (tasks == null) {
            return new Result(false, "没有任务");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "通过");
        for (Task task : tasks) {
            taskService.complete(task.getId(), map);
        }
        return new Result(true,"审批成功");
    }

    /**
     * 生成流程图
     *
     * @param taskId 任务ID
     */
    @ApiOperation("生成流程图")
    @GetMapping( "processDiagram/{taskId}")
    public void genProcessDiagram(HttpServletResponse httpServletResponse,@ApiParam("任务id")@PathVariable("taskId") String taskId) throws Exception {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(taskId).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 ,false);
        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();
            }
        }
    }
}
## 插件安装

BPMN绘图可视化工具

> Flowable BPMN visualizer
4.3画图接口

引入相关依赖

<!--idm依赖提供身份认证-->
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-spring-boot-starter-ui-idm</artifactId>
    <version>${flowable.version}</version>
</dependency>
<!-- modeler绘制流程图 -->
<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
    <version>${flowable.version}</version>
</dependency>

配置yml文件:

flowable:
  idm:
    app:
      admin:
        # 登录的用户名
        user-id: admin
        # 登录的密码
        password: admin
        # 用户的名字
        first-name: w
        last-name: j

启动项目

@SpringBootApplication
public class BootFlowableApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootFlowableApplication.class, args);
    }
}

在这里插入图片描述

访问http://localhost:8080/

在这里插入图片描述

这个时候就可以开始设计员工请假流程图 我先画一个完整的出来 将每个步骤对应的信息都截下来 供大家参考

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

学生请假流程.bpmn20.xml 在resources下创建processes文件夹 复制xml文件到该目录即可

安装Flowable BPMN visualize 插件后会减少xml文件的很多爆红,但爆红不影响对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" exporter="Flowable Open Source Modeler" exporterVersion="6.7.1">
  <process id="go" name="学生请假流程" isExecutable="true">
    <documentation>学生请假流程</documentation>
    <startEvent id="start" name="开始" flowable:formFieldValidation="true"></startEvent>
    <userTask id="apply" name="请假申请" flowable:assignee="${studentName}" flowable:formFieldValidation="true">
      <extensionElements>
        <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
      </extensionElements>
    </userTask>
    <userTask id="teacherPass" name="辅导员审批" flowable:candidateGroups="a" flowable:formFieldValidation="true"></userTask>
    <exclusiveGateway id="judgeTask" name="判断是否大于2天"></exclusiveGateway>
    <endEvent id="over" name="结束"></endEvent>
    <userTask id="bossPass" name="院长审批" flowable:candidateGroups="b" flowable:formFieldValidation="true"></userTask>
    <sequenceFlow id="sid-7D98C541-9EF4-46A3-81B5-3AE06CE17F05" name="申请流程" sourceRef="apply" targetRef="teacherPass"></sequenceFlow>
    <sequenceFlow id="sid-60427D00-F713-4C79-9918-837BE087D8D3" name="流程开始" sourceRef="start" targetRef="apply"></sequenceFlow>
    <sequenceFlow id="judgeLess" name="小于2天" sourceRef="judgeTask" targetRef="over">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${day<=2}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="teacherNotPassFlow" name="驳回" sourceRef="teacherPass" targetRef="apply">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=="驳回"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="bossPassFlow" name="通过" sourceRef="bossPass" targetRef="over">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=="通过"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="judgeMore" name="大于2天" sourceRef="judgeTask" targetRef="bossPass">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${day>2}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="teacherPassFlow" name="通过" sourceRef="teacherPass" targetRef="judgeTask">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=="通过"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="bossNotPassFlow" name="驳回" sourceRef="bossPass" targetRef="apply">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=="驳回"}]]></conditionExpression>
    </sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_go">
    <bpmndi:BPMNPlane bpmnElement="go" id="BPMNPlane_go">
      <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
        <omgdc:Bounds height="30.0" width="30.0" x="134.99999798834327" y="134.99999798834327"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="apply" id="BPMNShape_apply">
        <omgdc:Bounds height="80.0" width="100.0" x="314.9999953061343" y="109.99999471008795"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="teacherPass" id="BPMNShape_teacherPass">
        <omgdc:Bounds height="80.0" width="100.0" x="539.9999919533731" y="109.99999634921559"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask">
        <omgdc:Bounds height="40.0" width="40.0" x="809.9999758601194" y="129.99999605119237"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="over" id="BPMNShape_over">
        <omgdc:Bounds height="28.0" width="28.0" x="1019.9999848008158" y="135.9999959617854"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="bossPass" id="BPMNShape_bossPass">
        <omgdc:Bounds height="80.0" width="100.0" x="779.9999646842491" y="314.9999953061343"></omgdc:Bounds>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="sid-60427D00-F713-4C79-9918-837BE087D8D3" id="BPMNEdge_sid-60427D00-F713-4C79-9918-837BE087D8D3" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="1.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="164.94999732515438" y="149.99999769211536"></omgdi:waypoint>
        <omgdi:waypoint x="314.999994585362" y="149.99999472884903"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="sid-7D98C541-9EF4-46A3-81B5-3AE06CE17F05" id="BPMNEdge_sid-7D98C541-9EF4-46A3-81B5-3AE06CE17F05" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="414.94999530610096" y="149.9999950739743"></omgdi:waypoint>
        <omgdi:waypoint x="539.9999896640127" y="149.99999598496498"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
        <omgdi:waypoint x="849.491962572681" y="150.45319801530812"></omgdi:waypoint>
        <omgdi:waypoint x="1020.0000230239541" y="150.03427117012842"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
        <omgdi:waypoint x="879.9499646841429" y="354.9999953061343"></omgdi:waypoint>
        <omgdi:waypoint x="1033.9999848008158" y="354.9999953061343"></omgdi:waypoint>
        <omgdi:waypoint x="1033.9999848008158" y="163.94991974938068"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="830.4534061890572" y="169.4917780809857"></omgdi:waypoint>
        <omgdi:waypoint x="830.0976441294755" y="314.9999953061343"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="teacherNotPassFlow" id="BPMNEdge_teacherNotPassFlow" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="589.9999919533731" y="109.99999634921559"></omgdi:waypoint>
        <omgdi:waypoint x="589.9999919533731" y="45.000001534819575"></omgdi:waypoint>
        <omgdi:waypoint x="364.9999953061343" y="45.000001534819575"></omgdi:waypoint>
        <omgdi:waypoint x="364.9999953061343" y="109.99999471008795"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
        <omgdi:waypoint x="779.9999646842491" y="354.9999953061343"></omgdi:waypoint>
        <omgdi:waypoint x="364.9999953061343" y="354.9999953061343"></omgdi:waypoint>
        <omgdi:waypoint x="364.9999953061343" y="189.94999471008794"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="teacherPassFlow" id="BPMNEdge_teacherPassFlow" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
        <omgdi:waypoint x="639.9499919533686" y="150.10384244811357"></omgdi:waypoint>
        <omgdi:waypoint x="810.4583092155458" y="150.45832940661876"></omgdi:waypoint>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
4.4启动流程代码详解

流程实例

  • 现在已经在流程引擎中部署了流程定义,因此可以使用这个流程定义作为“蓝图”启动流程实例。

  • 我们使用RuntimeService启动一个流程实例。

  • 收集的数据作为一个java.util.Map实例传递,其中的键就是之后用于获取变量的标识符。

  • 这个流程实例使用key启动。这个key就是BPMN 2.0 XML文件中设置的id属性。

  • 在这个例子里是go。

  • 表达式分配

    Flowable使用UEL进行表达式解析。UEL代表Unified Expression Language,是EE6规范的一部分.Flowable支持两种UEL表达式: UEL-value 和UEL-method

这两个就是在XML文件中定义的变量
flowable:assignee=“${studentName}”
${outcome==‘通过’}
${day <= 2}

对应的BPMN 2.0 XML属性,我们通过id启动流程
<process id="go" name="学生请假流程" isExecutable="true">
学生对应的BPMN 2.0 XML属性 通过assignee学生可以发起请假流程
<userTask id="apply" name="请假申请" flowable:assignee="${studentName}" flowable:formFieldValidation="true">
      <extensionElements>
        <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
      </extensionElements>
    </userTask>
辅导员对应的BPMN 2.0 XML属性 通过candidateGroups可以查询辅导员下可以审批的任务
<userTask id="teacherPass" name="辅导员审批 " flowable:candidateGroups="a" flowable:formFieldValidation="true"></userTask>
院长对应的BPMN 2.0 XML属性 通过candidateGroups可以查询院长下可以审批的任务
 <userTask id="principalPass" name="院长审批" flowable:candidateGroups="b" flowable:formFieldValidation="true"></userTask>
学生发起请假流程代码:
 	@ApiOperation("学生提交请假申请")
    @PostMapping( "add/{day}/{studentUser}")
    public Result sub(@ApiParam("请假天数") @PathVariable("day") Integer day , @ApiParam("学生用户名")@PathVariable("studentUser") String studentUser) {
        // 学生提交请假申请
        Map<String, Object> map = new HashMap<>();
        map.put("day", day);
        map.put("studentName", studentUser);
        // leave为学生请假流程xml文件中的id
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("go", map);
        log.info("流程实例ID:" + processInstance.getId());
        //完成申请任务
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
        taskService.complete(task.getId());
        return new Result(true,"提交成功.流程Id为:" + processInstance.getId()) ;
    }
查询辅导员审批任务代码:
	@ApiOperation("辅导员获取审批管理列表")
    @GetMapping("teacherList")
    public Result teacherList() {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("a").list();
        List<TaskVO> taskVOList = tasks.stream().map(task -> {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            return new TaskVO(task.getId(), variables.get("day").toString(), variables.get("studentName").toString());
        }).collect(Collectors.toList());
        log.info("任务列表:" + tasks);
        if (tasks == null || tasks.size() == 0) {
            return new Result(false,"没有任务");
        }
        return new Result(true,"获取成功",taskVOList);
    }
查询院长审批任务代码:
	@ApiOperation("院长获取审批管理列表")
    @GetMapping("deanList")
    public Result deanList() {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("b").list();
        List<TaskVO> taskVOList = tasks.stream().map(task -> {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            return new TaskVO(task.getId(), variables.get("day").toString(), variables.get("studentName").toString());
        }).collect(Collectors.toList());
        if (tasks == null || tasks.size() == 0) {
            return new Result(false,"没有任务");
        }
        return new Result(true,"获取成功",taskVOList);
    }

辅导员进行审批任务代码(通过或驳回):
    @ApiOperation("辅导员批准")
    @GetMapping("teacherApply/{taskId}")
    public Result teacherApply(@ApiParam("任务id")@PathVariable("taskId") String taskId) {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("a").list();
        if (tasks == null) {
            return new Result(false, "没有任务");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "通过");
        for (Task task : tasks) {
            taskService.complete(task.getId(), map);
        }
        return new Result(true,"审批成功");
    }
院长进行审批任务代码(通过或驳回):
    @ApiOperation("院长批准")
    @GetMapping("deanApply/{taskId}")
    public Result apply(@ApiParam("任务id")@PathVariable("taskId") String taskId) {
        List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("b").list();
        if (tasks == null) {
            return new Result(false, "没有任务");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "通过");
        for (Task task : tasks) {
            taskService.complete(task.getId(), map);
        }
        return new Result(true,"审批成功");
    }

查询流程进度图
 	@ApiOperation("生成流程图")
    @GetMapping( "processDiagram/{taskId}")
    public void genProcessDiagram(HttpServletResponse httpServletResponse,@ApiParam("任务id")@PathVariable("taskId") String taskId) throws Exception {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(taskId).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 ,false);
        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();
            }
        }
    }

五.表结构讲解

  工作流程的相关操作都是操作存储在对应的表结构中,为了能更好的弄清楚Flowable的实现原理和细节,我们有必要先弄清楚Flowable的相关表结构及其作用。在Flowable中的表结构在初始化的时候会创建五类表结构,具体如下:

  • ACT_RE :'RE’表示 repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。
  • ACT_RU:'RU’表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Flowable只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。
  • ACT_HI:'HI’表示 history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。
  • ACT_GE: GE 表示 general。 通用数据, 用于不同场景下
  • ACT_ID: ’ID’表示identity(组织机构)。这些表包含标识的信息,如用户,用户组,等等。

具体的表结构的含义:

表分类表名解释
一般数据
[ACT_GE_BYTEARRAY]通用的流程定义和流程资源
[ACT_GE_PROPERTY]系统相关属性
流程历史记录
[ACT_HI_ACTINST]历史的流程实例
[ACT_HI_ATTACHMENT]历史的流程附件
[ACT_HI_COMMENT]历史的说明性信息
[ACT_HI_DETAIL]历史的流程运行中的细节信息
[ACT_HI_IDENTITYLINK]历史的流程运行过程中用户关系
[ACT_HI_PROCINST]历史的流程实例
[ACT_HI_TASKINST]历史的任务实例
[ACT_HI_VARINST]历史的流程运行中的变量信息
流程定义表
[ACT_RE_DEPLOYMENT]部署单元信息
[ACT_RE_MODEL]模型信息
[ACT_RE_PROCDEF]已部署的流程定义
运行实例表
[ACT_RU_EVENT_SUBSCR]运行时事件
[ACT_RU_EXECUTION]运行时流程执行实例
[ACT_RU_IDENTITYLINK]运行时用户关系信息,存储任务节点与参与者的相关信息
[ACT_RU_JOB]运行时作业
[ACT_RU_TASK]运行时任务
[ACT_RU_VARIABLE]运行时变量表
用户组表
[ACT_ID_BYTEARRAY]二进制数据表
[ACT_ID_GROUP]用户组信息表
[ACT_ID_INFO]用户信息详情表
[ACT_ID_MEMBERSHIP]人与组关系表
[ACT_ID_PRIV]权限表
[ACT_ID_PRIV_MAPPING]用户或组权限关系表
[ACT_ID_PROPERTY]属性表
[ACT_ID_TOKEN]记录用户的token信息
[ACT_ID_USER]用户表

;