SpringBoot集成Flowable工作流

SpringBoot集成Flowable工作流

  • Flowable是什么?
  • 一、添加依赖
  • 二、flowable配置
  • 三、定义流程文件
    • 1.使用流程文件定义工作流
    • 2.idea使用插件来定义流程图
      • 1.安装插件
      • 2.创建bpmn文件并画流程图
      • 3.右击流程用模型设计器打开文件
  • 四、测试controller


Flowable是什么?

官方文档:https://tkjohn.github.io/flowable-userguide/#_introduction

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

一、添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter</artifactId>
        <version>6.4.1</version>
    </dependency>
</dependencies>

二、flowable配置

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/flowableDemo?useUnicode=true&characterEncoding=utf-8&&serverTimezone=UTC&nullCatalogMeansCurrent=true
    username: root
    password: root
flowable:
  #关闭定时任务JOB
  async-executor-activate: false

初次运行时flowable会将自动执行flowable中的初始化脚本完成工作流所需要的数据表的建立

如果出现不自动创建表的情况:
1.数据库是mysql8需要在连接字符串中添加&nullCatalogMeansCurrent=true
2.去除mysql引入依赖中runtime

三、定义流程文件

flowable建议采用业界标准BPMN2.0的XML来描述需要定义的工作流。

1.使用流程文件定义工作流

为了方便测试,这里采用一个开源项目中的流程文件,其描述如下:
ExpenseProcess.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: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="Expense" name="ExpenseProcess" isExecutable="true">
        <documentation>报销流程</documentation>
        <startEvent id="start" name="开始"></startEvent>
        <userTask id="fillTask" name="出差报销" flowable:assignee="${taskUser}">
            <extensionElements>
                <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler">
                    <![CDATA[false]]></modeler:initiator-can-complete>
            </extensionElements>
        </userTask>
        <exclusiveGateway id="judgeTask"></exclusiveGateway>
        <userTask id="directorTak" name="经理审批">
            <extensionElements>
                <flowable:taskListener event="create"
                                       class="com.erfou.flowabledemo.listener.ManagerTaskHandler"></flowable:taskListener>
            </extensionElements>
        </userTask>
        <userTask id="bossTask" name="老板审批">
            <extensionElements>
                <flowable:taskListener event="create"
                                       class="com.erfou.flowabledemo.listener.BossTaskHandler"></flowable:taskListener>
            </extensionElements>
        </userTask>
        <endEvent id="end" name="结束"></endEvent>
        <sequenceFlow id="directorNotPassFlow" name="驳回" sourceRef="directorTak" targetRef="fillTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="bossNotPassFlow" name="驳回" sourceRef="bossTask" targetRef="fillTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="flow1" sourceRef="start" targetRef="fillTask"></sequenceFlow>
        <sequenceFlow id="flow2" sourceRef="fillTask" targetRef="judgeTask"></sequenceFlow>
        <sequenceFlow id="judgeMore" name="大于500元" sourceRef="judgeTask" targetRef="bossTask">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${money > 500}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="bossPassFlow" name="通过" sourceRef="bossTask" targetRef="end">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="directorPassFlow" name="通过" sourceRef="directorTak" targetRef="end">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="judgeLess" name="小于500元" sourceRef="judgeTask" targetRef="directorTak">
            <conditionExpression xsi:type="tFormalExpression"><![CDATA[${money <= 500}]]></conditionExpression>
        </sequenceFlow>
    </process>
    <bpmndi:BPMNDiagram id="BPMNDiagram_Expense">
        <bpmndi:BPMNPlane bpmnElement="Expense" id="BPMNPlane_Expense">
            <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
                <omgdc:Bounds height="30.0" width="30.0" x="285.0" y="135.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="fillTask" id="BPMNShape_fillTask">
                <omgdc:Bounds height="80.0" width="100.0" x="405.0" y="110.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask">
                <omgdc:Bounds height="40.0" width="40.0" x="585.0" y="130.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="directorTak" id="BPMNShape_directorTak">
                <omgdc:Bounds height="80.0" width="100.0" x="735.0" y="110.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="bossTask" id="BPMNShape_bossTask">
                <omgdc:Bounds height="80.0" width="100.0" x="555.0" y="255.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
                <omgdc:Bounds height="28.0" width="28.0" x="771.0" y="281.0"></omgdc:Bounds>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
                <omgdi:waypoint x="315.0" y="150.0"></omgdi:waypoint>
                <omgdi:waypoint x="405.0" y="150.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
                <omgdi:waypoint x="505.0" y="150.16611295681062"></omgdi:waypoint>
                <omgdi:waypoint x="585.4333333333333" y="150.43333333333334"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess">
                <omgdi:waypoint x="624.5530726256983" y="150.44692737430168"></omgdi:waypoint>
                <omgdi:waypoint x="735.0" y="150.1392757660167"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="directorNotPassFlow" id="BPMNEdge_directorNotPassFlow">
                <omgdi:waypoint x="785.0" y="110.0"></omgdi:waypoint>
                <omgdi:waypoint x="785.0" y="37.0"></omgdi:waypoint>
                <omgdi:waypoint x="455.0" y="37.0"></omgdi:waypoint>
                <omgdi:waypoint x="455.0" y="110.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow">
                <omgdi:waypoint x="655.0" y="295.0"></omgdi:waypoint>
                <omgdi:waypoint x="771.0" y="295.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore">
                <omgdi:waypoint x="605.4340277777778" y="169.56597222222223"></omgdi:waypoint>
                <omgdi:waypoint x="605.1384083044983" y="255.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="directorPassFlow" id="BPMNEdge_directorPassFlow">
                <omgdi:waypoint x="785.0" y="190.0"></omgdi:waypoint>
                <omgdi:waypoint x="785.0" y="281.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow">
                <omgdi:waypoint x="555.0" y="295.0"></omgdi:waypoint>
                <omgdi:waypoint x="455.0" y="295.0"></omgdi:waypoint>
                <omgdi:waypoint x="455.0" y="190.0"></omgdi:waypoint>
            </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</definitions>

其中的两个代理类为:

import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;
 
public class ManagerTaskHandler implements TaskListener {
 
    @Override
    public void notify(DelegateTask delegateTask) {
        delegateTask.setAssignee("经理");
    }
 
}
import org.flowable.engine.delegate.TaskListener;
import org.flowable.task.service.delegate.DelegateTask;

public class BossTaskHandler implements TaskListener {

    @Override
    public void notify(DelegateTask delegateTask) {
        delegateTask.setAssignee("老板");
    }

}

2.idea使用插件来定义流程图

1.安装插件

在这里插入图片描述

2.创建bpmn文件并画流程图

在resource目录下新建文件夹process,在process文件夹上右键新建,创建出来的文件是xml,项目在启动的时候会自动部署流程,无需手动部署
在这里插入图片描述

3.右击流程用模型设计器打开文件

在这里插入图片描述

四、测试controller

package com.erfou.flowabledemo.controller;


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.stereotype.Controller;
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;


@Controller
@RequestMapping(value = "expense")
public class ExpenseController {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ProcessEngine processEngine;

/***************此处为业务代码******************/

    /**
     * 添加报销
     *
     * @param userId    用户Id
     * @param money     报销金额
     * @param description 描述
     */
    @RequestMapping(value = "add")
    @ResponseBody
    public String addExpense(String userId, Integer money, String description) {
        //启动流程
        HashMap<String, Object> map = new HashMap<>();
        map.put("taskUser", userId);
        map.put("money", money);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Expense", map);
        return "提交成功.流程Id为:" + processInstance.getId();
    }
    /**
     * 获取审批管理列表
     */
    @RequestMapping(value = "/list")
    @ResponseBody
    public Object list(String userId) {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
        for (Task task : tasks) {
            System.out.println(task.toString());
        }
        return tasks.toString();
    }
    /**
     * 批准
     *
     * @param taskId 任务ID
     */
    @RequestMapping(value = "apply")
    @ResponseBody
    public String apply(String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new RuntimeException("流程不存在");
        }
        //通过审核
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "通过");
        taskService.complete(taskId, map);
        return "processed ok!";
    }
    /**
     * 拒绝
     */
    @ResponseBody
    @RequestMapping(value = "reject")
    public String reject(String taskId) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("outcome", "驳回");
        taskService.complete(taskId, map);
        return "reject";
    }
    /**
     * 生成流程图
     *
     * @param processId 任务ID
     */
    @RequestMapping(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();
            }
        }
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/38111.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEA运行TOMCAT出现404

就这种问题&#xff0c;每个人的原因都不一定一样&#xff0c;我出现这种问题的解决方法在这里记录一下。顺便把我的配置记录一下。 除了本文的问题&#xff0c;还有可能是默认打开的文件名错了&#xff0c;或者端口被占用。 软件版本IDEA2023 TOMCAT9 亲测&#xff1a;IDE…

linux文件锁(保证只能同时启动一个实例,不同时启动多个实例)

文章目录 C如果程序异常退出&#xff0c;会自动释放锁码&#xff1f; shell脚本python脚本 C 可以使用Linux中的进程锁来实现只能启动一个实例的功能。一种常见的方法是使用文件锁&#xff08;File Locking&#xff09;。 可以在程序启动时创建一个特定的文件&#xff0c;并尝…

54、Mysql索引的数据结构,各自优劣

Mysql索引的数据结构&#xff0c;各自优劣 索引的数据结构和具体存储引擎的实现有关在MySQL中使用较多的索引有Hash索引&#xff0c;B树索引等InnoDB存储引擎的默认索引实现为: B树索引。对于哈希索引来说&#xff0c;底层的数据结构就是哈希表&#xff0c;因此在绝大多数需求…

Jmeter四种关联方法讲解

目录 方法一&#xff0c;从前一个请求中取&#xff0c;用正则表达式提取器。 二、json path extractor 三、json extractor 四、XPath Extractor 方法一&#xff0c;从前一个请求中取&#xff0c;用正则表达式提取器。 具体方法&#xff0c;在需要获得数据的请求上右击添加…

python+selenium进行cnblog的自动化登录测试

Web登录测试是很常见的测试&#xff0c;手动测试大家再熟悉不过了&#xff0c;那如何进行自动化登录测试呢&#xff01;本文就基于pythonselenium结合unittest单元测试框架来进行一次简单但比较完整的cnblog自动化登录测试&#xff0c;可提供点参考&#xff01;下面就包括测试代…

出海企业系列风险分析--网站需要验证码吗?

最近接待了几位从discuz来的用户&#xff0c;说是想要给自己海外的网站安装验证码&#xff0c;但是discuz境外服务器还要解析安装中心的DNS到境外服务器上&#xff0c;所以基于discuz建站的不好之处就在这里。 而且我们还讨论到一个问题&#xff0c;海外的网站&#xff0c;需要…

你知道mp3转换器怎么用吗?分享在线音频转换mp3怎么弄

飒飒&#xff1a;嘿&#xff0c;你有没有想过如何将在线音频转换为mp3格式&#xff1f; 潇潇&#xff1a;是的&#xff0c;我确实有过这个需求。在网上找到了一些工具和方法&#xff0c;可以帮助我们完成这个任务。 飒飒&#xff1a;那太好了&#xff01;你能告诉我一些详细的…

JS代码加密技术:僵尸代码植入

JS混淆加密&#xff1a;僵尸代码 僵尸代码植入&#xff0c;是JS混淆加密中一项很有用的技术。 比如JShaman&#xff0c;作为国内知名的JS混淆加密产品&#xff0c;便具备这一功能。 它可以给原始代码中增加一些额代的功能代码&#xff0c;比如&#xff1a;变量定义、函数调用…

Linux dpkg和dpkg-deb常用参数使用说明

名词解释 “dpkg ”是“ Debian Packager ”的简写。为“Debian” 专门开发的套件管理系统&#xff0c;方便软件的安装、更新及移除。所有源自“Debian”的“Linux ”发行版都会使用 “dpkg”&#xff0c;例如 “ Ubuntu ”、“Knoppix ”等。 dpkg-deb和dpkg的区别 dpkg-de…

一次元数据空间内存溢出的排查记录 | 京东云技术团队

在应用中&#xff0c;我们使用的 SpringData ES的 ElasticsearchRestTemplate来做查询&#xff0c;使用方式不对&#xff0c;导致每次ES查询时都新实例化了一个查询对象&#xff0c;会加载相关类到元数据中。最终长时间运行后元数据出现内存溢出&#xff1b; 问题原因&#xf…

Node.js下载安装和环境变量配置(详细教程)

目录 一、官网地址下载安装包 二、安装程序 三、环境配置 四、测试 五、安装淘宝镜像 5.1、附加&#xff1a;如果有出现问题的小伙伴们可以检查一下自己的配置有没有出错 一、官网地址下载安装包 https://nodejs.org/zh-cn/download/ 选择你的项目或系统对应的node.js版本…

Openlayers实战:加载GeoJSON

在OPenlayers的交互中,经常性的我们要加载一些数据,在这个实战中,演示的是加载GeoJSON文件。 GeoJSON 是一种使用JavaScript 对象表示法(JSON) 对地理数据结构进行编码的格式。简而言之,GeoJSON 为你提供了一种简单的格式来表示简单的地理特征以及它们的非空间属性。 效果…

【CentOS安装make】

问题&#xff1a; bash: make: command not found 1、去官网查找最新版本 http://ftp.gnu.org/pub/gnu/make/ 查找最新安装包 2、下载解压缩 wget http://ftp.gnu.org/pub/gnu/make/make-4.2.tar.gz tar -zxvf make-4.2.tar.gz cd make-4.2mkdir buildcd build../configure…

在vite创建的vue3项目中使用Cesium加载czml路径信息和无人机模型

在vite创建的vue3项目中使用Cesium加载czml路径信息和无人机模型 用到的区域文件、地图标记文件、路径信息文件、模型文件 提取码&#xff1a;99jq 使用vite创建vue3项目 npm create vitelatestcd到创建的项目文件夹中 npm install安装Cesium npm i cesium vite-plugin-cesium…

opencv实战--角度测量和二维码条形码识别

文章目录 前言一、鼠标点击的角度测量二、二维码条形码识别 前言 一、鼠标点击的角度测量 首先导入一个带有角度的照片 然后下面的代码注册了一个鼠标按下的回调函数&#xff0c; 还有一个点的数列&#xff0c;鼠标事件为按下的时候就记录点&#xff0c;并画出点&#xff0c;…

uniapp左右滑动切换月份

左右滑动触发事件 给组件绑定事件,主要利用组件的触摸开始和触摸结束事件来实现: <view @touchstart="touchStart" @touchend="touchEnd"> 2,声明初始化点击位置变量startX data() {return {list:[],pageNum:1,pageSize:10,//初始化点击位置…

css之:is()、:where()和:has()伪元素的运用、使用、important

文章目录 简介1、:is()2、:where()3、:has() 简介 :is()、:where()和:has()伪元素是CSS中用于样式化元素的非常强大的工具。它们是在CSS选择器Level4规范中引入的。它们允许我们将样式应用于符合特定条件的任何元素&#xff0c;例如元素的类型、元素的位置和元素的后代。 1、:i…

【C#】并行编程实战:实现数据并行(4)

本章继续学习实现数据并行&#xff0c;本文主要介绍并行循环中的线程存储。这也是本章节的最后一篇。 本教程对应学习工程&#xff1a;魔术师Dix / HandsOnParallelProgramming GitCode 5、了解并行循环中的线程存储 默认情况下&#xff0c;所有并行循环都可以访问全局…

香橙派4和树莓派4B构建K8S集群实践之八: TiDB

目录 1. 说明 2. 准备工作 3. 安装 3.1 参考Tidb官方 v1.5安装说明 3.2 准备存储类 3.3 创建crd 3.4 执行operator 3.5 创建cluster/dashboard/monitor容器组 3.6 设置访问入口(Ingress & Port) 4. 装好后的容器状况 5. 遇到的问题 6. 参考 1. 说明 建立TiDB…

全志F1C200S嵌入式驱动开发(串口驱动)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 对于uboot、kernel和rootfs来说,他们的串口输出一般都是uart0。一般这么做,是没有问题的。只不过我们自己买的这块f1c200s电路板,设计者把uart转ttl的接口,改接到了uart1上面。…