SpringBoot集成Flowable

news2024/12/27 8:00:03
一、工作流介绍


1、概念
通过计算机对业务流程的自动化管理。工作流是建立在业务流程的基础上,一个软件的系统核心根本上还是系统的业务流程,工作流只是协助进行业务流程管理。

解决的是:在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标

2、工作流系统
概念:具有工作流功能的系统
比如,OA、ERP系统,可能涉及工作流,都可以叫工作流系统

3、具体应用
关键业务流程:订单、报价处理、合同审核、客户电话处理、供应链管理等
行政管理类:出差申请、加班申请、请假申请、用车申请、各种办公用品申请、购买申请、日报周报等凡是原来手工流转处理的行政表单。
人事管理类:员工培训安排、绩效考评、职位变动处理、员工档案信息管理等。
财务相关类:付款请求、应收款处理、日常报销处理、出差报销、预算和计划申请等。
客户服务类:客户信息管理、客户投诉、请求处理、售后服务管理等。
特殊服务类:SO系列对应流程、质量管理对应流程、产品数据信息管理、贸易公司报关处理、物流公司货物跟踪处理等各种通过表单逐步手工流转完成的任务均可应用工作流软件自动规范地实施。
4、实现方式对比
原始方式:就是采用状态值来跟踪流程的变化,通过这个值去决定不同用户是否展示,耦合度高
新的工作流引擎,可以灵活调整,实现简单

二、Flowable概述


具体参考官网:https://tkjohn.github.io/flowable-userguide/#_introduction

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

2、BPM
业务流程管理,一种规范化的构造端到端的业务流程,提高组织业务效率。

3、BPMN
业务流程模型和符号,由BPMI 开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。

一般就是用来画我们需要的流程图。
常用符号

网关

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

并行网关(Parallel Gateway) 他的所有出口都会被执行,可以理解为开多线程同时执行多个任务。

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

任务

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

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

接受任务(Receive Task)

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

结构

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

一个流程必须包含一个事件(如:开始事件)和至少一个结束(事件)。其中网关的作用是流程流转逻辑的控制。任务则分很多类型,他们各司其职,所有节点均由连线联系起来。

4、为什么选择Flowable?
修复了activiti6很多的bug,可以实现零成本从activiti迁移到flowable。flowable目前已经支持加签、动态增加实例中的节点、支持cmmn、dmn规范。这些都是activiti6目前版本没有的。

flowable已经支持所有的历史数据使用mongdb存储,activiti没有。
flowable支持事务子流程,activiti没有。
flowable支持多实例加签、减签,activiti没有。
flowable支持httpTask等新的类型节点,activiti没有。
flowable支持在流程中动态添加任务节点,activiti没有。
flowable支持历史任务数据通过消息中间件发送,activiti没有。
flowable支持java11,activiti没有。
flowable支持动态脚本,,activiti没有。
flowable支持条件表达式中自定义juel函数,activiti没有。
flowable支持cmmn规范,activiti没有。
flowable修复了dmn规范设计器,activit用的dmn设计器还是旧的框架,bug太多。
flowable屏蔽了pvm,activiti6也屏蔽了pvm(因为6版本官方提供了加签功能,发现pvm设计的过于臃肿,索性直接移除,这样加签实现起来更简洁、事实确实如此,如果需要获取节点、连线等信息可以使用bpmnmodel替代)。
flowable与activiti提供了新的事务监听器。activiti5版本只有事件监听器、任务监听器、执行监听器。
flowable对activiti的代码大量的进行了重构。
activiti以及flowable支持的数据库有h2、hsql、mysql、oracle、postgres、mssql、db2。其他数据库不支持的。使用国产数据库的可能有点失望了,需要修改源码了。
flowable支持jms、rabbitmq、mongodb方式处理历史数据,activiti没有。

 5、 Flowable基础表结构

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

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

Flowable在项目启动的时候会自动创建表,以下是主要几张表介绍

ACT_RU_TASK:每次启动的流程都会在这张表中,表示代办项,流程结束会删除该流程数据
ACT_RU_EXECUTION:流程执行过程表,会存该流程正在执行的过程数据,流程结束会删除该流程数据
ACT_RU_VARIABLE:流程变量表,流程中传的参数都会在该表存储,流程结束会删除该流程数据
ACT_HI_PROCINST:历史运行流程,当流程处理完了, 在ACT_RU_* 表中就不会有数据, 可以在该表中查询历史
ACT_HI_TASKINST:历史运行的task信息,
ACT_RE_PROCDEF:流程模板记录,同一个key多次发布version_字段会递增
ACT_RE_DEPLOYMENT:部署的流程模板,可以启动流程使用的

三、创建项目

创建一个maven项目,引入springboot、mysql、flowable

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>
    <artifactId>flowable-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencies>
        <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>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
        <!--flowable-->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter</artifactId>
            <version>6.4.1</version>
        </dependency>


        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.22</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>
</project>

application.yml

注意mysql数据库驱动版本差异

  • com.mysql.jdbc.Driver:

这是 旧版 MySQL JDBC 驱动的类名。
该驱动属于 mysql-connector-java 5.x 或更早版本。
它是 MySQL 5.x 系列及之前的 JDBC 驱动程序的标准类名。
从 MySQL 5.1 到 MySQL 5.7 版本,使用的是这个驱动。

  • com.mysql.cj.jdbc.Driver:

这是 新版 MySQL JDBC 驱动的类名。
该驱动是 MySQL 8.x 及之后版本的标准 JDBC 驱动程序。
com.mysql.cj.jdbc.Driver 是从 mysql-connector-java 8.x 开始引入的。
com.mysql.cj 代表 "Connector/J" 的缩写,cj 指代的是 Connector/J 新的模块命名空间。

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/flowable-test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.jdbc.Driver
flowable:
  # 业务流程设计的表自动创建
  database-schema-update: true
  # 异步执行耗时操作,提升性能
  async-executor-activate: false

FlowableApplication.java 

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

运行并启动项目,会在数据库自动生成工作流所使用的表

四、画流程图

这里使用 IDEA 插件 Flowable BPMN visualizer,如下图:

装好插件之后,在 resources 目录下新建 processes 目录,这个目录下的流程文件将来会被自动部署

新建一个ask_for_leave.bpmn20.xml文件(.bpmn20.xml是固定文件后缀,不需要填写)

文件创建出来之后,右键单击,选择 View BPMN(Flowable) Diagram,就打开了此文件的可视化页面了,然后就可以来绘制自己的流程图

然后在可视化页面空白处,右键点击,就可以添加不同的符号,来绘制流程图

绘制完成

我们来看下这个流程对应的 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">
  <process id="ask_for_leave" name="ask_for_leave" isExecutable="true">
    <startEvent id="startLeave"/>
    <userTask id="leaveTask" name="请假" flowable:assignee="#{leaveUser}"/>
    <sequenceFlow id="flowStart" sourceRef="startLeave" targetRef="leaveTask"/>
    <userTask id="zuZhangTask" name="组长审核" flowable:assignee="#{zuZhangUser}"/>
    <sequenceFlow id="modeFlow" sourceRef="leaveTask" targetRef="zuZhangTask"/>
    <exclusiveGateway id="zuZhangJudgeGateway"/>
    <sequenceFlow id="zuZhang_go_judge" sourceRef="zuZhangTask" targetRef="zuZhangJudgeGateway"/>
    <userTask id="managerTask" name="经理审核" flowable:assignee="#{managerUser}"/>
    <sequenceFlow id="zuZhangPass" sourceRef="zuZhangJudgeGateway" targetRef="managerTask" name="组长审核通过">
      <conditionExpression xsi:type="tFormalExpression">${var:equals(zuZhangCheckResult,"通过")}</conditionExpression>
    </sequenceFlow>
    <endEvent id="endLeave"/>
    <exclusiveGateway id="managerJudgeGateway"/>
    <sequenceFlow id="manager_go_judge" sourceRef="managerTask" targetRef="managerJudgeGateway"/>
    <sequenceFlow id="managerPass" sourceRef="managerJudgeGateway" targetRef="endLeave" name="经理审核通过">
      <conditionExpression xsi:type="tFormalExpression">${var:equals(managerCheckResult,"通过")}</conditionExpression>
    </sequenceFlow>
    <serviceTask id="sendFailMessage" flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="com.test.service.LeaveFailService"/>
    <sequenceFlow id="zuZhangRefuse" sourceRef="zuZhangJudgeGateway" targetRef="sendFailMessage" name="组长审核拒绝">
      <conditionExpression xsi:type="tFormalExpression">${var:equals(zuZhangCheckResult,"拒绝")}</conditionExpression>
    </sequenceFlow>
    <endEvent id="failEndLeave"/>
    <sequenceFlow id="go_fail_end" sourceRef="sendFailMessage" targetRef="failEndLeave"/>
    <sequenceFlow id="managerRefuse" sourceRef="managerJudgeGateway" targetRef="sendFailMessage" name="经理审核拒绝">
      <conditionExpression xsi:type="tFormalExpression">${var:equals(managerCheckResult,"拒绝")}</conditionExpression>
    </sequenceFlow>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_ask_for_leave">
    <bpmndi:BPMNPlane bpmnElement="ask_for_leave" id="BPMNPlane_ask_for_leave">
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-92e89192-ae6e-4c06-84ea-e3deb6d92402" bpmnElement="startLeave">
        <omgdc:Bounds x="-454.35" y="-60.0" width="30.0" height="30.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-9ab478cf-067b-4984-a12e-0b14d28d6bb5" bpmnElement="leaveTask">
        <omgdc:Bounds x="-381.4296" y="-85.0" width="100.0" height="80.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-5e4ce6f7-76af-4972-91a1-ce776b2ca0c7" bpmnElement="flowStart">
        <omgdi:waypoint x="-424.35" y="-45.0"/>
        <omgdi:waypoint x="-381.4296" y="-45.0"/>
      </bpmdi:BPMNEdge>
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-46866177-80d1-4db8-abd7-f0426459804a" bpmnElement="zuZhangTask">
        <omgdc:Bounds x="-234.44722" y="-85.0" width="100.0" height="80.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-c65d7d95-b9f8-44e1-89a3-0bf2a9e95738" bpmnElement="modeFlow">
        <omgdi:waypoint x="-281.4296" y="-45.0"/>
        <omgdi:waypoint x="-234.44722" y="-45.0"/>
      </bpmdi:BPMNEdge>
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-53989386-6942-4e52-8f37-9076c52057a6" bpmnElement="zuZhangJudgeGateway">
        <omgdc:Bounds x="-78.09332" y="-65.0" width="40.0" height="40.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-4b34e417-f0c0-40ef-8bb6-7690e136d67f" bpmnElement="zuZhang_go_judge">
        <omgdi:waypoint x="-134.44722" y="-45.0"/>
        <omgdi:waypoint x="-78.09332" y="-45.0"/>
      </bpmdi:BPMNEdge>
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-86d89ed3-dcb1-451c-b96c-ba3ef43baeba" bpmnElement="managerTask">
        <omgdc:Bounds x="26.607117" y="-85.0" width="100.0" height="80.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-c2ec3e89-62ab-4e90-af28-0cf551530ca0" bpmnElement="zuZhangPass">
        <omgdi:waypoint x="-38.093323" y="-45.0"/>
        <omgdi:waypoint x="26.607117" y="-45.0"/>
      </bpmdi:BPMNEdge>
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-dd6c6e8f-e05d-4e50-9852-672656a76494" bpmnElement="endLeave">
        <omgdc:Bounds x="284.48773" y="-59.999996" width="30.0" height="30.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-fa4fc0dc-9773-4944-9fde-d2741dc9386b" bpmnElement="managerJudgeGateway">
        <omgdc:Bounds x="164.3713" y="-65.0" width="40.0" height="40.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-4d924eb1-02a1-43d3-b884-d7a3e5918359" bpmnElement="manager_go_judge">
        <omgdi:waypoint x="126.60712" y="-45.0"/>
        <omgdi:waypoint x="164.3713" y="-45.0"/>
      </bpmdi:BPMNEdge>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-9918da8c-b7e5-4ec1-866b-dbfae2c6ea05" bpmnElement="managerPass">
        <omgdi:waypoint x="204.3713" y="-45.0"/>
        <omgdi:waypoint x="284.48773" y="-44.999996"/>
      </bpmdi:BPMNEdge>
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-ccc89da3-1cf3-414b-8c39-3a90c8a42326" bpmnElement="sendFailMessage">
        <omgdc:Bounds x="-108.09332" y="41.54" width="100.0" height="80.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-98a4db5f-6ff9-48d0-a358-9470a03c8f1c" bpmnElement="zuZhangRefuse">
        <omgdi:waypoint x="-58.093323" y="-25.0"/>
        <omgdi:waypoint x="-58.093323" y="41.54"/>
      </bpmdi:BPMNEdge>
      <bpmdi:BPMNShape xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="shape-48b0f47e-8768-4275-b013-575039f2cd20" bpmnElement="failEndLeave">
        <omgdc:Bounds x="-199.44724" y="66.54" width="30.0" height="30.0"/>
      </bpmdi:BPMNShape>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-08d8631a-3c62-486f-9a65-6f1d1a63c317" bpmnElement="go_fail_end">
        <omgdi:waypoint x="-108.09332" y="81.54"/>
        <omgdi:waypoint x="-169.44724" y="81.54"/>
      </bpmdi:BPMNEdge>
      <bpmdi:BPMNEdge xmlns:bpmdi="http://www.omg.org/spec/BPMN/20100524/DI" id="edge-136a3062-a9f0-4c34-a275-0d6b51fc55d3" bpmnElement="managerRefuse">
        <omgdi:waypoint x="184.3713" y="-25.0"/>
        <omgdi:waypoint x="184.3713" y="81.54"/>
        <omgdi:waypoint x="-8.093322" y="81.53999"/>
      </bpmdi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>
五、流程接口代码

AskForLeaveController.java

package com.test.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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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;

@RestController
public class AskForLeaveController {
    @Autowired
    RuntimeService runtimeService;

    @Autowired
    RepositoryService repositoryService;

    @Autowired
    TaskService taskService;

    @Autowired
    ProcessEngine processEngine;

    /**
     * 查看流程进度图
     * @param resp
     * @param processId 流程id
     * @throws Exception
     */
    @GetMapping("/viewFlowChart")
    public void viewFlowChart(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();
            }
        }
    }

    /**
     * 开启一个请假流程
     */
    @GetMapping("/askForLeave")
    public String askForLeave() {
        Map<String, Object> map = new HashMap<>();
        map.put("leaveUser", "张三");
        map.put("name", "ego");
        map.put("reason", "出去玩");
        map.put("days", 10);
        ProcessInstance instance = runtimeService.startProcessInstanceByKey("ask_for_leave", map);
        return "开启请假流程 processId:" + instance.getId();
    }

    /**
     * 员工请假提交给组长审核
     */
    @GetMapping("/submitToZuZhang")
    public String submitToZuZhang() {
        // 员工查找到自己的任务,然后提交给组长审批
        List<Task> list = taskService.createTaskQuery().taskAssignee("张三").orderByTaskId().desc().list();
        String info = null;
        for (Task task: list) {
            info = "任务 ID:" + task.getId() + ",任务申请人:" + task.getAssignee() + ",任务是否挂起:" + task.isSuspended();
            //提交给组长的时候,需要指定组长的id
            Map<String, Object> map = new HashMap<>();
            map.put("zuZhangUser", "李四");
            taskService.complete(task.getId(), map);
        }
        return info;
    }

    /**
     * 组长审核请假
     */
    @GetMapping("/zuZhangApprove")
    public String zuZhangApprove() {
        List<Task> list = taskService.createTaskQuery().taskAssignee("李四").orderByTaskId().desc().list();
        String info = null;
        for (Task task: list) {
            if ("组长审核".equals(task.getName())) {
                info = "任务 ID:" + task.getId() + ",组长审核人:" + task.getAssignee() + ",任务是否挂起:" + task.isSuspended();
                // 提交给经理的时候,需要指定经理的id
                Map<String, Object> map = new HashMap<>();
                map.put("managerUser", "王五");
                map.put("zuZhangCheckResult", "通过");
                map.put("zuZhangUser", "李四");
                try {
                    taskService.complete(task.getId(), map);
                } catch (Exception e) {
                    e.printStackTrace();
                    info = "组长审核失败:" + task.getId() + " " + task.getAssignee();
                }
            }
        }
        return info;
    }

    /**
     * 经理审核通过
     */
    @GetMapping("/managerApprove")
    public String managerApprove() {
        List<Task> list = taskService.createTaskQuery().taskAssignee("王五").orderByTaskId().desc().list();
        String info = null;
        for (Task task: list) {
            info = "经理:" + task.getAssignee() + ",审核通过:" + task.getId() + " 任务";
            Map<String, Object> map = new HashMap<>();
            map.put("managerCheckResult", "通过");
            taskService.complete(task.getId(), map);
        }
        return info;
    }

    /**
     * 经理审核拒绝
     */
    @GetMapping("/managerRefuse")
    public String managerRefuse() {
        List<Task> list = taskService.createTaskQuery().taskAssignee("王五").orderByTaskId().desc().list();
        String info = null;
        for (Task task: list) {
            info = "经理:" + task.getAssignee() + ",审核拒绝:" + task.getId() + " 任务";
            Map<String, Object> map = new HashMap<>();
            map.put("managerCheckResult", "拒绝");
            taskService.complete(task.getId(), map);
        }
        return info;
    }
}

LeaveFailService.java

对应流程图中的<serviceTask id="sendFailMessage" flowable:exclusive="true" name="发送失败提示" isForCompensation="true" flowable:class="com.test.service.LeaveFailService"/>

package com.test.service;

import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Component;

@Component
public class LeaveFailService implements JavaDelegate {
    @Override
    public void execute(DelegateExecution delegateExecution) {
        System.out.println("审批不通过 " + delegateExecution.getCurrentActivityId());
    }
}

FlowableConfig.java,防止流程图出现中文乱码的配置类

package com.test.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 springProcessEngineConfiguration) {
        springProcessEngineConfiguration.setActivityFontName("宋体");
        springProcessEngineConfiguration.setLabelFontName("宋体");
        springProcessEngineConfiguration.setAnnotationFontName("宋体");
    }
}
五、测试一下流程接口

1、开启一个请假流程,http://localhost:8080/askForLeave

查看流程进度图,参数是开启的流程的processId

http://localhost:8080/viewFlowChart?processId=ef67cd51-af01-11ef-8fdb-005056c00001,

可以看到,请假这个userTask用红色的框框起来了,说明当前流程走到了这一步

2、员工请假提交给组长审核,http://localhost:8080/submitToZuZhang

查看流程进度图

 3、组长审核请假,http://localhost:8080/zuZhangApprove

查看流程进度

​​​​​​​​​​​​​​​​​​​​​

4、经理审核拒绝,http://localhost:8080/managerRefuse

调用LeaveFailService,控制台打印

流程结束

5、重新开启一个流程,重复1-4,试一试“经理审核通过”

​​​​​​​

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

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

相关文章

1-1 Gerrit实用指南

注&#xff1a;学习gerrit需要拥有git相关知识&#xff0c;如果没有学习过git请先回顾git相关知识点 黑马程序员git教程 一小时学会git git参考博客 git 实操博客 1.0 定义 Gerrit 是一个基于 Web 的代码审查系统&#xff0c;它使用 Git 作为底层版本控制系统。Gerrit 的主要功…

鸿蒙开发:自定义一个任意位置弹出的Dialog

前言 鸿蒙开发中&#xff0c;一直有个问题困扰着自己&#xff0c;想必也困扰着大多数开发者&#xff0c;那就是&#xff0c;系统提供的dialog自定义弹窗&#xff0c;无法实现在任意位置进行弹出&#xff0c;仅限于CustomDialog和Component struct的成员变量&#xff0c;这就导致…

Matlab模块From Workspace使用数据类型说明

Matlab原文连接&#xff1a;Load Data Using the From Workspace Block 模型&#xff1a; 从信号来源的数据&#xff1a; timeseries 数据&#xff1a; sampleTime 0.01; numSteps 1001;time sampleTime*[0:(numSteps-1)]; time time;data sin(2*pi/3*time);simin time…

架构01-演进中的架构

零、文章目录 架构01-演进中的架构 1、原始分布式时代&#xff1a;Unix设计哲学下的服务探索 &#xff08;1&#xff09;背景 时间&#xff1a;20世纪70年代末到80年代初计算机硬件&#xff1a;16位寻址能力、不足5MHz时钟频率的处理器、128KB左右的内存转型&#xff1a;从…

社群赋能电商:小程序 AI 智能名片与 S2B2C 商城系统的整合与突破

摘要&#xff1a;本文聚焦于社群在电商领域日益凸显的关键地位&#xff0c;深入探讨在社群粉丝经济迅猛发展背景下&#xff0c;小程序 AI 智能名片与 S2B2C 商城系统如何与社群深度融合&#xff0c;助力电商突破传统运营局限&#xff0c;挖掘新增长点。通过分析社群对电商的价值…

【electron-vite】搭建electron+vue3框架基础

一、拉取项目 electron-vite 中文文档地址&#xff1a; https://cn-evite.netlify.app/guide/ 官网网址&#xff1a;https://evite.netlify.app/ 版本 vue版本&#xff1a;vue3 构建工具&#xff1a;vite 框架类型&#xff1a;Electron JS语法&#xff1a;TypeScript &…

EasyDarwin搭建直播推流服务

学习链接 easydarwin官网 - 这里看介绍 easydarwin软件下载地址 - 百度网盘 easydarwin视频 B站 文章目录 学习链接使用下载EasyDarwin压缩包&#xff0c;并解压到目录启动EasyDarwin点播直播easyplayer.jsapidocffmpeg推流rtsp & ffplay拉流 使用 下载EasyDarwin压缩包…

【RISC-V CPU debug 专栏 2 -- Debug Module (DM), non-ISA】

文章目录 调试模块(DM)功能必须支持的功能可选支持的功能兼容性要求规模限制Debug Module Interface (DMI)总线类型地址与操作地址空间控制机制Debug Module Interface Signals请求信号响应信号信号流程Reset Control复位控制方法全局复位 (`ndmreset`)Hart 复位 (`hartreset…

【WRF后处理】WRF模拟效果评价及可视化:MB、RMSE、IOA、R

【WRF后处理】模拟效果评价及可视化 准备工作模型评价指标Python实现代码Python处理代码:导入站点及WRF模拟结果可视化图形及评价指标参考在气象和环境建模中(如使用 WRF 模型进行模拟),模型性能评价指标是用于定量评估模拟值与观测值之间偏差和拟合程度的重要工具。 本博客…

基于JSP+MySQL的网上招聘系统的设计与实现

摘要 在这样一个经济飞速发展的时代&#xff0c;人们的生存与生活问题已成为当代社会需要关注的一个焦点。对于一个刚刚 踏入社会的年轻人来说&#xff0c;他对就业市场和形势了解的不够详细&#xff0c;同时对自己的职业规划也很模糊&#xff0c;这就导致大量的 时间被花费在…

机器学习——生成对抗网络(GANs):原理、进展与应用前景分析

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一. 生成对抗网络的基本原理二. 使用步骤2.1 对抗性训练2.2 损失函数 三. GAN的变种和进展四. 生成对抗网络的应用五. 持续挑战与未来发展方向六. 小结 前言 生…

vue.js学习(day 14)

目录 综合案例&#xff1a;商品列表 自定义指令 main.js(全局注册) import Vue from vue import App from ./App.vueVue.config.productionTip false// //1.全局注册指令 // Vue.directive(focus,{ // //inserted 会在 指令所在的元素&#xff0c;被插入到页面中时触发 /…

C++类中多线程的编码方式

问题 在C++代码中,一般的代码是需要封装在类里面,比如对象,方法等。否则就不能很好的利用C++面向对象的能力了。 但是这个方式在处理线程时会碰到一个问题。 考虑下面一个简单的场景: class demoC { public:std::thread t;int x;void threadFunc(){std::cout<<x&…

使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件

使用 Tkinter 创建一个简单的 GUI 应用程序来合并视频和音频文件 Python 是一门强大的编程语言&#xff0c;它不仅可以用于数据处理、自动化脚本&#xff0c;还可以用于创建图形用户界面 (GUI) 应用程序。在本教程中&#xff0c;我们将使用 Python 的标准库模块 tkinter 创建一…

LearnOpenGL学习(入门--变换,坐标系统,摄像机)

完整代码见&#xff1a;zaizai77/Cherno-OpenGL: OpenGL 小白学习之路 glm::mat4 trans; trans glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0)); trans glm::scale(trans, glm::vec3(0.5, 0.5, 0.5)); 我们把箱子在每个轴都缩放到0.5倍&#xff0c;然…

LLamafactory API部署与使用异步方式 API 调用优化大模型推理效率

文章目录 背景介绍第三方大模型API 介绍LLamafactory 部署API大模型 API 调用工具类项目开源 背景介绍 第三方大模型API 目前&#xff0c;市面上有许多第三方大模型 API 服务提供商&#xff0c;通过 API 接口向用户提供多样化的服务。这些平台不仅能提供更多类别和类型的模型…

【Leecode】Leecode刷题之路第66天之加一

题目出处 66-加一-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 66-加一-官方解法 方法1&#xff1a;找出最长的后缀9 思路&#xff1a; 代码示例&#xff1a;&#xff08;Java&#…

uniapp-vue2引用了vue-inset-loader插件编译小程序报错

报错信息 Error: Vue packages version mismatch: - vue3.2.45 (D:\qjy-myApp\admin-app\node_modules\vue\index.js) - vue-template-compiler2.7.16 (D:\qjy-myApp\admin-app\node_modules\vue-template-compiler\package.json) This may cause things to work incorrectly.…

【人工智能-科普】图神经网络(GNN):与传统神经网络的区别与优势

文章目录 图神经网络(GNN):与传统神经网络的区别与优势什么是图神经网络?图的基本概念GNN的工作原理GNN与传统神经网络的不同1. 数据结构的不同2. 信息传递方式的不同3. 模型的可扩展性4. 局部与全局信息的结合GNN的应用领域总结图神经网络(GNN):与传统神经网络的区别与…

【C#设计模式(15)——命令模式(Command Pattern)】

前言 命令模式的关键通过将请求封装成一个对象&#xff0c;使命令的发送者和接收者解耦。这种方式能更方便地添加新的命令&#xff0c;如执行命令的排队、延迟、撤销和重做等操作。 代码 #region 基础的命令模式 //命令&#xff08;抽象类&#xff09; public abstract class …