springboot +flowable,简单实现工作流基础功能的demo例子

news2024/9/24 11:29:33

一.简介

对于flowable是什么以及关于此框架的具体信息可以参看此项目的官方文档:https://www.flowable.org/docs/userguide/index.html

Flowable is a light-weight business process engine written in Java.这是官网文档对此框架的完美解释:Flowable是一个用java语言写的轻量级工作流引擎。

流程引擎,也算是一个比较常见的工具了,我们在日常的很多开发中都会用到,当然用的最多的就是 OA 系统了,但是在一些非 OA 系统中,我们也会涉及到,比如一个 CRM 中,可能会有合同管理的需求,合同的审批,也是需要流程引擎的。

在简单了解flowable后与activiti框架相比的第一感觉就是开发方便快速,易与springBoot等各种框架快速整合。今天便写一个简单的例子,来感受下流程引擎到底是个啥。

二.springboot整合flowable

1.使用版本

springBoot版本:2.3.2.RELEASE
flowable版本:6.5.0

2.创建项目

创建一个 SpringBoot 项目,引入 Web、和 MySQL 驱动两个依赖,截图如下:
在这里插入图片描述

3.项目依赖pom.xml

springboot 版本

        <parent>
           <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <version>2.3.0.RELEASE</version>
         <relativePath/> <!-- lookup parent from repository -->
      </parent>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-engine</artifactId>
            <scope>compile</scope>
            <version>6.5.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter-basic</artifactId>
            <version>6.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-bpmn-layout</artifactId>
            <version>6.5.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

4.项目配置application.yml

数据库和flowable的配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/flowable?useSSL=false&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456

flowable:
  database-schema-update: true
   # flase:       默认值。activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常。(生产环境常用)
  # true:        activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建。(开发时常用)
  # create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)。(单元测试常用)
  # drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)。
  process:
    definition-cache-limit: -1
  activity-font-name: 宋体
  label-font-name: 宋体
  annotation-font-name: 宋体

flowable的配置,再默认情况下,位于 resources/processes 的流程都会被自动部署。
配置完成后,就可以启动项目了。项目启动成功之后,flowable 数据库中就会自动创建如下这些表,会自动初始化数据库,将来流程引擎运行时候的数据会被自动持久化到数据库中。。数据库表的截图如下:
在这里插入图片描述

三.画流程图

下载IDEA 插件 Flowable BPMN visualizer,如下图:
在这里插入图片描述
装好插件之后,我们在 resources 目录下新建 processes 目录,这个目录下的流程文件将来会被自动部署。
接下来我们在 processes 目录下,新建一个 BPMN 文件(插件装好了就有这个选项了),如下:
在这里插入图片描述
我们来画个请假的流程,就叫做 ask_for_leave.bpmn20.xml,注意最后面的 .bpmn20.xml 是固定后缀。
文件创建出来之后,右键单击,选择 View BPMN(Flowable) Diagram,就打开了可视化页面了,我们就可以来绘制自己的流程图了。
请假流程画出来是这样:
在这里插入图片描述
业务流程是这样的:
员工发起一个请假流程,首先是组长审核,组长审核通过了,就进入到经理审核,经理审核通过了,这个流程就结束了,如果组长审核未通过或者经理审核未通过,则流程给员工发送一个请假失败的通知,流程结束。

这个流程对应的 XML 文件,一些流程细节会在 XML 文件中体现出来,如下:

<process id="ask_for_leave" name="ask_for_leave" isExecutable="true">
  <userTask id="leaveTask" name="请假" flowable:assignee="#{leaveTask}"/>
  <userTask id="zuzhangTask" name="组长审核" flowable:assignee="#{zuzhangTask}"/>
  <userTask id="managerTask" name="经理审核" flowable:assignee="#{managerTask}"/>
  <exclusiveGateway id="managerJudgeTask"/>
  <exclusiveGateway id="zuzhangJudeTask"/>
  <endEvent id="endLeave" name="结束"/>
  <startEvent id="startLeave" name="开始"/>
  <sequenceFlow id="flowStart" sourceRef="startLeave" targetRef="leaveTask"/>
  <sequenceFlow id="modeFlow" sourceRef="leaveTask" targetRef="zuzhangTask"/>
  <sequenceFlow id="zuzhang_go" sourceRef="zuzhangJudeTask" targetRef="managerTask" name="通过">
    <conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='通过'}]]></conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="zuzhang_reject" sourceRef="zuzhangJudeTask" targetRef="sendMail" name="拒绝">
    <conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='拒绝'}]]></conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="jugdeFlow" sourceRef="managerTask" targetRef="managerJudgeTask"/>
  <sequenceFlow id="flowEnd" name="通过" sourceRef="managerJudgeTask" targetRef="endLeave">
    <conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='通过'}]]></conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="rejectFlow" name="拒绝" sourceRef="managerJudgeTask" targetRef="sendMail">
    <conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkResult=='拒绝'}]]></conditionExpression>
  </sequenceFlow>
  <serviceTask id="sendMail" flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>
  <sequenceFlow id="endFlow" sourceRef="sendMail" targetRef="askForLeaveFail"/>
  <endEvent id="askForLeaveFail" name="请假失败"/>
  <sequenceFlow id="zuzhangTask_zuzhangJudeTask" sourceRef="zuzhangTask" targetRef="zuzhangJudeTask"/>
</process>

XML 文件里面标签的释义,如下:
process:表示一个完整的工作流程。
startEvent:工作流中起点位置,也就是图中的绿色按钮。
endEvent:工作流中结束位置,也就是图中的红色按钮。
userTask:代表一个任务审核节点(组长、经理等角色),这个节点上有一个 flowable:assignee 属性,这表示这个节点该由谁来处理,将来在 Java 代码中调用的时候,我们需要指定对应的处理人的 ID 或者其他唯一标记。
serviceTask:这是服务任务,在具体的实现中,这个任务可以做任何事情。
exclusiveGateway:逻辑判断节点,相当于流程图中的菱形框。
sequenceFlow:链接各个节点的线条,sourceRef 属性表示线的起始节点,targetRef 属性表示线指向的节点,我们图中的线条都属于这种。

四.demo例子涉及的Java 类梳理

ProcessDefinition
这个最好理解,就是流程的定义,也就相当于规范,每个 ProcessDefinition 都会有一个 id。

ProcessInstance
这个就是流程的一个实例。简单来说,ProcessDefinition 相当于是类,而 ProcessInstance 则相当于是根据类 new 出来的对象。

Activity
Activity 是流程标准规范 BPMN2.0 里面的规范,流程中的每一个步骤都是一个 Activity。

Execution
Execution 的含义是流程的执行线路,通过 Execution 可以获得当前 ProcessInstance 当前执行到哪个 Activity了。

Task
Task 就是当前要做的工作。

五.代码

1.查看流程图接口

写一个接口,用来查看流程图的实时执行情况,这样方便我们查看流程到底执行到哪一步了。
代码如下:

@RestController
public class TestController {

    @Autowired
    RuntimeService runtimeService;

    @Autowired
    TaskService taskService;

    @Autowired
    RepositoryService repositoryService;

    @Autowired
    ProcessEngine processEngine;

    @GetMapping("/createPic")
    public void showPic(HttpServletResponse resp, String processId) throws Exception {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
        if (pi == null) {
            return;
        }
        List<Execution> executions = runtimeService
                .createExecutionQuery()
                .processInstanceId(processId)
                .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 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 = resp.getOutputStream();
            while ((legth = in.read(buf)) != -1) {
                out.write(buf, 0, legth);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

1.开启一个新流程接口

开启一个新流程,代码如下:

String staffId = "1000";
/**
 * 开启一个流程
 */
@Test
void askForLeave() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("leaveTask", staffId);
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("ask_for_leave", map);
    runtimeService.setVariable(processInstance.getId(), "name", "javaboy");
    runtimeService.setVariable(processInstance.getId(), "reason", "休息一下");
    runtimeService.setVariable(processInstance.getId(), "days", 10);
    logger.info("创建请假流程 processId:{}", processInstance.getId());
}

首先由员工发起一个请假流程,map 中存放的 leaveTask 是我们在 XML 流程文件中提前定义好的,提前定义好当前这个任务创建之后,该由谁来处理,这里我们是假设由工号为 1000 的员工来发起这样一个请假流程。同时,我们还设置了一些额外信息。ask_for_leave 是我们在 XML 文件中定义的一个 process 的名称。

执行这个单元测试方法,执行完成后,控制台会打印出当前这个流程的 id,我们拿着这个 id 去访问 createPic的接口,结果如下:
在这里插入图片描述
可以看到,请假用红色的框框起来了,说明当前流程走到了这一步。

2.将请求提交给组长

接下来将这个请假流程向后推进一步,将请假事务提交给组长,代码如下:

String zuzhangId = "90";
/**
 * 提交给组长审批
 */
@Test
void submitToZuzhang() {
    //员工查找到自己的任务,然后提交给组长审批
    List<Task> list = taskService.createTaskQuery().taskAssignee(staffId).orderByTaskId().desc().list();
    for (Task task : list) {
        logger.info("任务 ID:{};任务处理人:{};任务是否挂起:{}", task.getId(), task.getAssignee(), task.isSuspended());
        Map<String, Object> map = new HashMap<>();
        //提交给组长的时候,需要指定组长的 id
        map.put("zuzhangTask", zuzhangId);
        taskService.complete(task.getId(), map);
    }
}

首先我们利用 staffId 查找到当前员工的 id,进而找到当前员工需要执行的任务,遍历这个任务,调用 taskService.complete 方法将任务提交给组长,注意在 map 中指定组长的 id。

提交完成后,我们再去看流程图片,如下:
在这里插入图片描述
可以看到,流程图走到组长审批了。

3.组长审批

组长现在有两种选择,同意或者拒绝,同意的代码如下:

/**
 * 组长审批-批准
 */
@Test
void zuZhangApprove() {
    List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
    for (Task task : list) {
        logger.info("组长 {} 在审批 {} 任务", task.getAssignee(), task.getId());
        Map<String, Object> map = new HashMap<>();
        //组长审批的时候,如果是同意,需要指定经理的 id
        map.put("managerTask", managerId);
        map.put("checkResult", "通过");
        taskService.complete(task.getId(), map);
    }
}

通过组长的 id 查询组长的任务,同意的话,需要指定经理,也就是这个流程下一步该由谁来处理。

拒绝的代码如下:

/**
 * 组长审批-拒绝
 */
@Test
void zuZhangReject() {
    List<Task> list = taskService.createTaskQuery().taskAssignee(zuzhangId).orderByTaskId().desc().list();
    for (Task task : list) {
        logger.info("组长 {} 在审批 {} 任务", task.getAssignee(), task.getId());
        Map<String, Object> map = new HashMap<>();
        //组长审批的时候,如果是拒绝,就不需要指定经理的 id
        map.put("checkResult", "拒绝");
        taskService.complete(task.getId(), map);
    }
}

拒绝的话,就没那么多事了,直接设置 checkResult 为拒绝即可。

假设这里执行了同意,那么流程图如下:
在这里插入图片描述

4.经理审批

经理审批和组长审批差不多,只不过经理这里是最后一步了,不需要再指定下一位处理人了,同意的代码如下:

/**
 * 经理审批自己的任务-批准
 */
@Test
void managerApprove() {
    List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
    for (Task task : list) {
        logger.info("经理 {} 在审批 {} 任务", task.getAssignee(), task.getId());
        Map<String, Object> map = new HashMap<>();
        map.put("checkResult", "通过");
        taskService.complete(task.getId(), map);
    }
}

拒绝代码如下:

/**
 * 经理审批自己的任务-拒绝
 */
@Test
void managerReject() {
    List<Task> list = taskService.createTaskQuery().taskAssignee(managerId).orderByTaskId().desc().list();
    for (Task task : list) {
        logger.info("经理 {} 在审批 {} 任务", task.getAssignee(), task.getId());
        Map<String, Object> map = new HashMap<>();
        map.put("checkResult", "拒绝");
        taskService.complete(task.getId(), map);
    }
}

5.拒绝流程

如果组长拒绝了或者经理拒绝了,我们也有相应的处理方案,首先在 XML 流程文件定义时,如下:

<serviceTask id="sendMail" flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="org.javaboy.flowable.AskForLeaveFail"/>

如果请假被拒绝,会进入到这个 serviceTask,serviceTask 对应的处理类是 org.javaboy.flowable.AskForLeaveFail,该类的代码如下:

public class AskForLeaveFail implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        System.out.println("请假失败。。。");
    }
}

也就是请假失败会进入到这个方法中,现在我们就可以在这个方法中写失败的业务逻辑

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NetApp AFF C 系列全闪存存储解决方案

NetApp AFF C 系列: “C”代表“酷炫”(Cool) 采用最新的容量闪存技术&#xff0c;辅以若干一流的智能技术&#xff0c;您将获得一个经济实惠的闪存解决方案&#xff0c;它重新定义了安全性、可扩展性和可持续性。 为什么选择 AFF C 系列的容量闪存解决方案&#xff1f; 实现…

jmeter获取图片验证码-解密图片并识别

说明&#xff1a; 关于图片验证码的处理方式有三种方法&#xff1a;一是让开发屏蔽验证码&#xff0c;二是让开发后端指定一个万能验证码&#xff0c;三是使用OCR工具进行图片验证码的解密及识别&#xff0c;推荐使用前两种方法最省事&#xff1b; OCRServer工具识别图片验证码…

Netty核心源码分析(二),Netty的Server端接收请求过程源码分析

文章目录 系列文章目录一、连接请求接受过程源码分析1、事件的值2、processSelectedKeys获取事件&#xff08;1&#xff09;doReadMessages方法&#xff08;2&#xff09;pipeline的fireChannelRead方法&#xff08;3&#xff09;ServerBootstrapAcceptor的channelRead方法 3、…

关于数据挖掘和数据集成?

按照数据的生命周期&#xff0c;我们通常将大数据技术分为数据集成、数据存储、批流处理、数据查询与分析、数据调度与编排、数据开发、BI 7 个部分。 可以看到数据集成在数据生命周期最前面的位置&#xff0c;它负责将多个来自不同数据源的数据聚合存放在一个数据存储中&…

分布式任务调度框架Power-Job

分布式任务调度框架的由来及对比 在大型业务业务系统中&#xff0c;不可避免会出现一些需要定时执行需求的场景&#xff0c;例如定时同步数据&#xff0c;定时清洗数据&#xff0c;定时生成报表&#xff0c;大量机器一同执行某个任务&#xff0c;甚至有些需要分布式处理的任务…

中继器+js组件化GIS地图

虽然可以使用JavaScript注入的方式将GIS地图嵌入Axure&#xff0c;但每次使用地图都需要重复嵌入并修改代码&#xff0c;不太方便。那么&#xff0c;能不能实现组件化呢&#xff1f;我们可以使用中继器&#xff08;repeater&#xff09;将常用的地图参数提取出来&#xff0c;通…

力扣题库刷题笔记406-根据身高重建队列

1、题目如下&#xff1a; 2、个人Python代码实现 这里需要单独备注一下截图中第21行代码&#xff1a; 上图可以看到&#xff0c;已经对[5, 2]等元素进行了遍历循环&#xff0c;且[5, 2]左侧确实只存在[7, 0][6, 1]两个元素身高高于他&#xff0c;但是继续[5,0]循环完成后&#…

@Async异步线程:Spring 自带的异步解决方案

前言 在项目应用中&#xff0c;使用MQ异步调用来实现系统性能优化&#xff0c;完成服务间数据同步是常用的技术手段。如果是在同一台服务器内部&#xff0c;不涉及到分布式系统&#xff0c;单纯的想实现部分业务的异步执行&#xff0c;这里介绍一个更简单的异步方法调用。 对于…

FreeRTOS - 计数信号量

一.任务功能 1、修改按键功能&#xff0c;模拟停车位出入功能 2、当按键按下 获取车位 3、当按键抬起 释放车位 二.API接口 函数原型SemaphoreHandle_t xSemaphoreCreateCounting( ①UBaseType_t uxMaxCount,②UBaseType_t uxInitialCount );功能概述创建计数信号量&#xff0c…

详解空气质量API 使用

引言 空气污染是当今世界面临的一大环境问题&#xff0c;而空气质量监测数据是制定环境政策和公众健康计划的重要依据。通过提供空气质量查询 API&#xff0c;开发人员可以方便地获取中国境内多个城市的空气质量数据&#xff0c;从而更好地监测和管理空气质量。 本文将介绍的…

Redis入门学习笔记【一】

目录 一、redis是什么 二、Redis数据结构 2.1 Redis 的五种基本数据类型 2.1.1String&#xff08;字符串&#xff09; 2.1.2字符串列表&#xff08;lists&#xff09; 2.1.3字符串集合&#xff08;sets&#xff09; 2.1.5哈希&#xff08;hashes&#xff09; 2.2 Red…

设计模式详解-软件设计(五十六)

原创 真题详解(UML图)-软件设计&#xff08;五十五)https://blog.csdn.net/ke1ying/article/details/130311994 创建型、结构型、行为型 抽象工厂&#xff08;Abstruct Factory&#xff09; 提供一个创建系列相关或相互依赖的接口&#xff0c;无须指定他们具体的类。 适用于&…

07-Node.js—包管理工具

目录 1、概念介绍1.1 包是什么1.2 包管理工具1.3 常用的包管理工具 2、npm2.1 npm 的安装2.2 npm 基本使用2.2.1 初始化2.2.2 搜索包2.2.3 下载安装包2.2.4 require 导入 npm 包基本流程 2.3 生产环境与开发环境2.4 生产依赖与开发依赖2.5 全局安装2.5.1 修改 windows 执行策略…

CorelDRAW 2023版本更新内容及安装详细教程

这里是CorelDRAW 2023版本更新内容及安装详细教程: CorelDRAW 2023是最新更新版本,在界面和功能上做了较大提升与优化: 1. 简洁界面:采用全新设计界面,简约而不简单。菜单和工具栏进行了整合与重组,更加直观。拥有自动标记和提示,易于上手使用。 2. 全新工作空间:提供“轻量…

Qt — Graphics/View框架

文章目录 前言一、Qt图形系统介绍二、Graphics/View框架 前言 Qt的Graphics/View框架被用来存放、显示二维图形元素&#xff0c;处理那些对图形元素进行操作的交互命令。 一、Qt图形系统介绍 Qt 应用程序的图形界面包含各种控件&#xff0c;比如窗口、按钮、滚动条等。所有这…

三谈ChatGPT(ChatGPT可以解决问题的90%)

这是我第三次谈ChatGPT&#xff0c;前两篇主要谈了ChatGPT的概念&#xff0c;之所以火的原因和对人们的影响&#xff0c;以及ChatGPT可能存在的安全风险和将面临的监管问题。这一篇主要讲讲ChatGPT的场景和处理问题的逻辑。 这一次我特意使用了ChatGPT中文网页版体验了一番。并…

3个月,从功能测试进阶到自动化测试涨薪10k,我悟了....

因为我最近在分享自动化测试技术&#xff0c;经常被问到&#xff1a; 功能测试想转自动化&#xff0c;请问应该怎么入手&#xff1f;有没有好的资源推荐&#xff1f; 那么&#xff0c;接下来我就结合自己的经历聊一聊我是如何在工作中做自动化测试的。&#xff08;学习路线和…

EIGRP配置 路由过滤和汇总,以及默认路由

1.4.1 实验目的 通过对 EIGRP 路由过滤&#xff0c;汇总以及默认路由配置的实验的练习&#xff0c;从而掌握 EIGRP 路由过 滤的方法&#xff0c;EIGRP 路由汇总的方法和作用&#xff0c;以及如何为 EIGRP 配置默认路由。 1.4.2 实验拓扑 1.4.3 实验步骤 配置 R1&#xff0c…

【深度学习】计算分类模型的分类指标,计算accuracy_top-1、accuracy_top-5、precision、recall和f1_score

计算accuracy_top-1、accuracy_top-5、precision、recall和f1_score&#xff1a; &#xff08;1&#xff09;accuracy_top-1 np.sum(np.argmax(preds, axis1) np.argmax(actual, axis1)) / actual.shape[0] accuracy_top-1指标是假设预测数据中&#xff0c;最大值的index就是…

自动控制原理模拟卷8

自动控制原理模拟题八 Question1 求解以下电网络和机械系统的传递函数,并证明下图的电网络和机械系统有相同的数学模型。 解: 【图 ( a ) ({\rm a}) (a)系统传递函数】 根据复数阻抗的方法可得电网络的传递函数为: