工作流引擎之Flowable教程(整合SpringBoot)

news2024/9/24 23:28:58

简介

Flowable是什么,下面是官方文档介绍:

Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。这个章节将用一个可以在你自己的开发环境中使用的例子,逐步介绍各种概念与API。

说人话,无论是学生还是职场员工,都用过OA系统,其中的请假流程大家应该都用过,一般是:发起请假->辅导员审批->院长审批,类似这样的流程。如果自己去实现这种功能的话,我个人的想法是使用状态机模式,每个过程如何流转都需要自己去实现,比较麻烦。
而Flowable框架帮我解决了这个麻烦,只需要我们遵循BPMN 2.0(可以理解为一种流程规范)就可以帮我们自动完成。而且它提供了图形化页面,只需要使用拖拽的方式就可以完成流程的定义,我们只用关注业务逻辑即可。

快速开始

我们先用JavaSE简单体验一下,下面会带大家集成SpringBoot和使用图形化界面。
本节例子为:发起请求->经理审批,审批通过则结束,不通过则发送失败邮件。

目录结构

在这里插入图片描述

创建Maven工程

添加Flowable依赖和MySQL依赖

        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-engine</artifactId>
            <version>6.3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

编写BPMN文件

注:.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:xsd="http://www.w3.org/2001/XMLSchema"
             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"
             xmlns:flowable="http://flowable.org/bpmn"
             typeLanguage="http://www.w3.org/2001/XMLSchema"
             expressionLanguage="http://www.w3.org/1999/XPath"
             targetNamespace="http://www.flowable.org/processdef">

    <process id="holidayRequest" name="Holiday Request" isExecutable="true">

        <startEvent id="startEvent"/>
        <sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>

        <userTask id="approveTask" name="Approve or reject request" flowable:assignee="boss"/>
        <sequenceFlow sourceRef="approveTask" targetRef="decision"/>

        <exclusiveGateway id="decision"/>
        <sequenceFlow sourceRef="decision" targetRef="externalSystemCall">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[
          ${approved}
        ]]>
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow  sourceRef="decision" targetRef="sendRejectionMail">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[
          ${!approved}
        ]]>
            </conditionExpression>
        </sequenceFlow>

        <serviceTask id="externalSystemCall" name="Enter holidays in external system"
                     flowable:class="org.flowable.CallExternalSystemDelegate"/>
        <sequenceFlow sourceRef="externalSystemCall" targetRef="holidayApprovedTask"/>

        <userTask id="holidayApprovedTask" name="Holiday approved"/>
        <sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>

        <serviceTask id="sendRejectionMail" name="Send out rejection email"
                     flowable:class="com.ego.SendRejectionMail"/>
        <sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>

        <endEvent id="approveEnd"/>

        <endEvent id="rejectEnd"/>

    </process>

</definitions>

编写测试类

package com.ego;

import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.flowable.task.api.Task;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {

    static ProcessEngine processEngine = null;
    static String taskKey = "holidayRequest";
    static String boss = "boss";

    public static void main(String[] args) {

        init();
        deploy("holiday-request.bpmn20.xml");

        Map<String, Object> startVariables = new HashMap<>();
        startVariables.put("employee", "ego");
        startVariables.put("nrOfHolidays", "3");
        startVariables.put("description", "事假");

        startProcess(taskKey, startVariables);

        queryTask(taskKey, boss);


        Map<String, Object> completeVariables = new HashMap<>();
        completeVariables.put("approved", false);

        completeTask(taskKey, boss, completeVariables);

        history("holidayRequest:5:10003");

    }

    /**
     * 初始化
     */
    public static void init(){
        //获取配置类
        ProcessEngineConfiguration configuration = new StandaloneProcessEngineConfiguration();

        //配置数据库连接
        configuration
                .setJdbcDriver("com.mysql.cj.jdbc.Driver")
                .setJdbcUsername("root")
                .setJdbcPassword("root")
                .setJdbcUrl("jdbc:mysql://localhost:3306/flowable-demo?serverTimezone=UTC&nullCatalogMeansCurrent=true")
                //表结构不存在就自动创建
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);

        //获取引擎类ProcessEngine
        processEngine = configuration.buildProcessEngine();
    }

    /**
     * 部署
     * @param path 配置文件
     */
    public static void deploy(String path){
        RepositoryService repositoryService = processEngine.getRepositoryService();
        repositoryService.createDeployment()
                .addClasspathResource(path)
                .name("请求流程")
                .deploy();

    }

    /**
     * 启动流程实例
     * @param key 流程id
     * @param variables 参数集合
     */
    public static void startProcess(String key, Map<String, Object> variables){
        RuntimeService runtimeService = processEngine.getRuntimeService();
        runtimeService.startProcessInstanceByKey(key, variables);
    }

    /**
     * 查询任务
     * @param key 流程id
     * @param assignee 处理人
     */
    public static void queryTask(String key, String assignee){
        TaskService taskService = processEngine.getTaskService();

        //查询任务列表
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(assignee)
                .list();

        for (Task task : list) {
            System.out.println(task.getName());
        }
    }

    /**
     * 处理任务
     * @param key 流程id
     * @param assignee 处理人
     * @param variables 参数集合
     */
    public static void completeTask(String key, String assignee, Map<String, Object> variables){
        TaskService taskService = processEngine.getTaskService();

         List<Task> taskList = taskService.createTaskQuery()
                .processDefinitionKey(key)
                .taskAssignee(assignee)
                 .list();
         Task task = taskList.get(0);

        taskService.complete(task.getId(), variables);
    }

    /**
     * 查询历史记录
     * @param definitionId
     */
    public static void history(String definitionId){
        HistoryService historyService = processEngine.getHistoryService();
        List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery()
                .processDefinitionId(definitionId)
                .finished()
                .orderByHistoricActivityInstanceEndTime().asc()
                .list();
        for (HistoricActivityInstance history : list) {
            System.out.println(history.getActivityName() + ":" + history.getActivityId() + ":" + history.getDurationInMillis() + "毫秒");
        }

    }

}

package com.ego;

import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;

public class SendRejectionMail implements JavaDelegate {
    @Override
    public void execute(DelegateExecution delegateExecution) {
        System.out.println("不好意思,请假申请未通过...");
    }
}

使用Flowable-ui图形化界面

下载Flowable和Tomcat

Flowable网址:http://www.flowable.org/downloads.html
Tomcat网址:http://tomcat.apache.org/

部署

将Flowable的war包放到Tomcat的webapps目录下,然后启动Tomcat,
访问http://localhost:8080/flowable-ui,默认用户名和密码为 admin / test

绘制

在这里插入图片描述
我们此节和下一节用的例子比之前多一个环节,由组长和经理两个人审核。
在这里插入图片描述

使用建模器绘制,绘制后点击导出,生成xml文件。
在这里插入图片描述

整合SpringBoot

目录结构

在这里插入图片描述

创建Maven工程

添加依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ego</groupId>
    <artifactId>flowable</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>flowable</name>
    <description>flowable</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </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.7.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

编写配置文件

server:
  port: 8081
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/flowable-demo?serverTimezone=Asia/Shanghai&useSSL=false&nullCatalogMeansCurrent=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

flowable:
  database-schema-update: true


BPMN文件

我们直接将图形化导出的xml文件放到resources目录下即可。

<?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.2">
    <process id="askForLeave" name="请假流程" isExecutable="true">
        <startEvent id="startEvent1" flowable:formFieldValidation="true"/>
        <userTask id="sid-494B6EC2-AC32-4A6F-8A4F-8016190388FA" name="组长审批" flowable:assignee="#{mentor}" />
        <userTask id="sid-3D540C65-A644-4DF0-B8F5-332B4BD0C338" name="请假申请" flowable:assignee="#{employee}" />
        <sequenceFlow id="sid-6B958ED5-8CE1-451A-AF66-F4B22FBA621C" sourceRef="startEvent1" targetRef="sid-3D540C65-A644-4DF0-B8F5-332B4BD0C338"/>
        <sequenceFlow id="sid-AC31EA26-5738-499E-8634-3B0F88BAEDAC" sourceRef="sid-3D540C65-A644-4DF0-B8F5-332B4BD0C338" targetRef="sid-494B6EC2-AC32-4A6F-8A4F-8016190388FA"/>
        <exclusiveGateway id="sid-1C1F7CC6-1FA3-4F95-83BB-E75B3FDD330B"/>
        <sequenceFlow id="sid-AD16B066-863B-4ED5-8A89-6D4AFC57EAF1" sourceRef="sid-494B6EC2-AC32-4A6F-8A4F-8016190388FA" targetRef="sid-1C1F7CC6-1FA3-4F95-83BB-E75B3FDD330B"/>
        <userTask id="sid-89C5782A-6193-48B8-8891-5C36C46F73A4" name="经理审批" flowable:assignee="#{manager}" />
        <serviceTask id="sid-B3B3A401-C199-4F5E-8D96-2D0C21CE2BFB" name="发送失败邮箱" flowable:class="com.ego.flowable.task.FailTask"/>
        <exclusiveGateway id="sid-CB500461-17D2-4CEF-95E1-BF53CBC1116A"/>
        <endEvent id="sid-295CD660-15BC-45FE-8618-4CDCFE832FD7"/>
        <endEvent id="sid-22B062BD-ED1C-49DE-B97A-ADC092FB9F3B"/>
        <sequenceFlow id="sid-BC00842A-2187-47A3-8348-49629CA3C90C" sourceRef="sid-B3B3A401-C199-4F5E-8D96-2D0C21CE2BFB" targetRef="sid-22B062BD-ED1C-49DE-B97A-ADC092FB9F3B"/>
        <sequenceFlow id="sid-D29EA250-62A9-4976-B98F-6214DBD4D5A4" sourceRef="sid-89C5782A-6193-48B8-8891-5C36C46F73A4" targetRef="sid-CB500461-17D2-4CEF-95E1-BF53CBC1116A"/>
        <sequenceFlow id="sid-A5E01BEF-690A-4126-AD87-5DD05BBD57F7" sourceRef="sid-CB500461-17D2-4CEF-95E1-BF53CBC1116A" targetRef="sid-295CD660-15BC-45FE-8618-4CDCFE832FD7">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[ ${approved} ]]>
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="sid-A93D00FB-8777-47FB-8E4E-FB51E487956B" sourceRef="sid-1C1F7CC6-1FA3-4F95-83BB-E75B3FDD330B" targetRef="sid-89C5782A-6193-48B8-8891-5C36C46F73A4">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[ ${approved} ]]>
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="sid-41511EEA-AB96-474B-9A1D-A534F5A459DC" sourceRef="sid-CB500461-17D2-4CEF-95E1-BF53CBC1116A" targetRef="sid-B3B3A401-C199-4F5E-8D96-2D0C21CE2BFB">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[ ${!approved} ]]>
            </conditionExpression>
        </sequenceFlow>
        <sequenceFlow id="sid-2836484B-86A0-473D-84D2-6BF40FDD652A" sourceRef="sid-1C1F7CC6-1FA3-4F95-83BB-E75B3FDD330B" targetRef="sid-B3B3A401-C199-4F5E-8D96-2D0C21CE2BFB">
            <conditionExpression xsi:type="tFormalExpression">
                <![CDATA[ ${!approved} ]]>
            </conditionExpression>
        </sequenceFlow>
    </process>
    <bpmndi:BPMNDiagram id="BPMNDiagram_askForLeave">
        <bpmndi:BPMNPlane bpmnElement="askForLeave" id="BPMNPlane_askForLeave">
            <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
                <omgdc:Bounds height="30.0" width="30.000000000000007" x="59.99999821186071" y="149.99999061226887"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-494B6EC2-AC32-4A6F-8A4F-8016190388FA" id="BPMNShape_sid-494B6EC2-AC32-4A6F-8A4F-8016190388FA">
                <omgdc:Bounds height="79.99999999999999" width="100.00000000000006" x="419.999987483025" y="124.99998688697896"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-3D540C65-A644-4DF0-B8F5-332B4BD0C338" id="BPMNShape_sid-3D540C65-A644-4DF0-B8F5-332B4BD0C338">
                <omgdc:Bounds height="80.0" width="100.0" x="215.99999207258247" y="124.99999508261695"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-1C1F7CC6-1FA3-4F95-83BB-E75B3FDD330B" id="BPMNShape_sid-1C1F7CC6-1FA3-4F95-83BB-E75B3FDD330B">
                <omgdc:Bounds height="40.0" width="40.0" x="564.999987483025" y="144.99998688697895"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-89C5782A-6193-48B8-8891-5C36C46F73A4" id="BPMNShape_sid-89C5782A-6193-48B8-8891-5C36C46F73A4">
                <omgdc:Bounds height="80.0" width="100.0" x="649.999987483025" y="124.99998688697895"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-B3B3A401-C199-4F5E-8D96-2D0C21CE2BFB" id="BPMNShape_sid-B3B3A401-C199-4F5E-8D96-2D0C21CE2BFB">
                <omgdc:Bounds height="80.0" width="100.0" x="534.9999715387834" y="269.9999678134942"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-CB500461-17D2-4CEF-95E1-BF53CBC1116A" id="BPMNShape_sid-CB500461-17D2-4CEF-95E1-BF53CBC1116A">
                <omgdc:Bounds height="40.0" width="40.0" x="794.999987483025" y="144.99998688697895"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-295CD660-15BC-45FE-8618-4CDCFE832FD7" id="BPMNShape_sid-295CD660-15BC-45FE-8618-4CDCFE832FD7">
                <omgdc:Bounds height="28.0" width="28.0" x="879.999987483025" y="150.99998688697895"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNShape bpmnElement="sid-22B062BD-ED1C-49DE-B97A-ADC092FB9F3B" id="BPMNShape_sid-22B062BD-ED1C-49DE-B97A-ADC092FB9F3B">
                <omgdc:Bounds height="28.0" width="28.0" x="434.99998703599016" y="295.999958992008"/>
            </bpmndi:BPMNShape>
            <bpmndi:BPMNEdge bpmnElement="sid-AC31EA26-5738-499E-8634-3B0F88BAEDAC" id="BPMNEdge_sid-AC31EA26-5738-499E-8634-3B0F88BAEDAC" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.00000000000003" flowable:targetDockerY="39.99999999999999">
                <omgdi:waypoint x="315.9499920725825" y="164.9999930738821"/>
                <omgdi:waypoint x="419.9999870473585" y="164.99998889370505"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-6B958ED5-8CE1-451A-AF66-F4B22FBA621C" id="BPMNEdge_sid-6B958ED5-8CE1-451A-AF66-F4B22FBA621C" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
                <omgdi:waypoint x="89.94999771072398" y="164.9999909621731"/>
                <omgdi:waypoint x="215.99999203304566" y="164.99999391236872"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-2836484B-86A0-473D-84D2-6BF40FDD652A" id="BPMNEdge_sid-2836484B-86A0-473D-84D2-6BF40FDD652A" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
                <omgdi:waypoint x="585.4340131410052" y="184.50916665252876"/>
                <omgdi:waypoint x="585.138211259262" y="269.9999678134942"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-AD16B066-863B-4ED5-8A89-6D4AFC57EAF1" id="BPMNEdge_sid-AD16B066-863B-4ED5-8A89-6D4AFC57EAF1" flowable:sourceDockerX="50.000000000000036" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
                <omgdi:waypoint x="519.9499874830227" y="165.2162206532127"/>
                <omgdi:waypoint x="565.4130309612859" y="165.41303036523982"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-D29EA250-62A9-4976-B98F-6214DBD4D5A4" id="BPMNEdge_sid-D29EA250-62A9-4976-B98F-6214DBD4D5A4" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="20.5" flowable:targetDockerY="20.5">
                <omgdi:waypoint x="749.949987483023" y="165.21622065321273"/>
                <omgdi:waypoint x="795.4130309612859" y="165.41303036523982"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-BC00842A-2187-47A3-8348-49629CA3C90C" id="BPMNEdge_sid-BC00842A-2187-47A3-8348-49629CA3C90C" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
                <omgdi:waypoint x="534.9999715387834" y="309.9999645703004"/>
                <omgdi:waypoint x="462.9499152959249" y="309.9999598968591"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-A5E01BEF-690A-4126-AD87-5DD05BBD57F7" id="BPMNEdge_sid-A5E01BEF-690A-4126-AD87-5DD05BBD57F7" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
                <omgdi:waypoint x="834.5591744228417" y="165.37819201518406"/>
                <omgdi:waypoint x="880.0002630355089" y="165.08883877124302"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-41511EEA-AB96-474B-9A1D-A534F5A459DC" id="BPMNEdge_sid-41511EEA-AB96-474B-9A1D-A534F5A459DC" flowable:sourceDockerX="20.000012516974948" flowable:sourceDockerY="35.00001326203267" flowable:targetDockerX="99.0" flowable:targetDockerY="40.0">
                <omgdi:waypoint x="812.0900320941328" y="182.06913118148157"/>
                <omgdi:waypoint x="634.9499715387833" y="309.28173606088245"/>
            </bpmndi:BPMNEdge>
            <bpmndi:BPMNEdge bpmnElement="sid-A93D00FB-8777-47FB-8E4E-FB51E487956B" id="BPMNEdge_sid-A93D00FB-8777-47FB-8E4E-FB51E487956B" flowable:sourceDockerX="20.5" flowable:sourceDockerY="20.5" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
                <omgdi:waypoint x="604.5247245557606" y="165.4166535536456"/>
                <omgdi:waypoint x="649.999987483025" y="165.2181091577213"/>
            </bpmndi:BPMNEdge>
        </bpmndi:BPMNPlane>
    </bpmndi:BPMNDiagram>
</definitions>

编写Controller

为简化步骤,本案例将业务逻辑放在Controller里,实际开发请放到Service中。

package com.ego.flowable.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

@RestController
public class AskForLeaveController {

    private static final Logger LOGGER = LoggerFactory.getLogger(AskForLeaveController.class);

    @Autowired
    RuntimeService runtimeService;
    @Autowired
    TaskService taskService;
    @Autowired
    RepositoryService repositoryService;
    @Autowired
    ProcessEngine processEngine;

    private static final String EMPLOYEE = "1001";
    private static final String MENTOR = "101";
    private static final String MANAGER = "10";

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

    /**
     * 开启流程
     */
    @PostMapping("/start")
    public void start(){
        Map<String, Object> map = new HashMap<>();
        map.put("employee", EMPLOYEE);
        map.put("name", "ego");
        map.put("reason", "出去玩");
        map.put("days", 10);
        ProcessInstance instance = runtimeService.startProcessInstanceByKey("askForLeave", map);
        LOGGER.info("开启请假流程 processId:{}", instance.getId());
    }

    /**
     * 员工提交给组长
     */
    @PostMapping("/submitToMentor")
    public void submitToMentor(){
        List<Task> list = taskService.createTaskQuery()
                .taskAssignee(EMPLOYEE)
                .orderByTaskId()
                .desc()
                .list();
        for (Task task : list) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            LOGGER.info("员工任务:任务id:{}, 请假人:{}, 请假原因:{}, 请假天数:{}", task.getId(), variables.get("name"), variables.get("reason"), variables.get("days"));
            Map<String, Object> map = new HashMap<>();
            map.put("mentor", MENTOR);
            taskService.complete(task.getId(), map);
        }
    }

    /**
     * 组长提交给经理
     * @param approved 是否通过
     */
    @PostMapping("/submitToManager")
    public void submitToManager(Boolean approved){
        System.out.println(approved);
        List<Task> list = taskService.createTaskQuery()
                .taskAssignee(MENTOR)
                .orderByTaskId()
                .desc()
                .list();
        for (Task task : list) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            LOGGER.info("组长任务:任务id:{}, 请假人:{}, 请假原因:{}, 请假天数:{}", task.getId(), variables.get("name"), variables.get("reason"), variables.get("days"));
            Map<String, Object> map = new HashMap<>();
            map.put("approved", approved);
            if(approved){
                map.put("manager", MANAGER);
            }
            taskService.complete(task.getId(), map);
        }
    }

    /**
     * 经理审核
     * @param approved 是否通过
     */
    @PostMapping("/managerApprove")
    public void managerApprove(Boolean approved){
        List<Task> list = taskService.createTaskQuery()
                .taskAssignee(MANAGER)
                .orderByTaskId()
                .desc()
                .list();
        for (Task task : list) {
            Map<String, Object> variables = taskService.getVariables(task.getId());
            LOGGER.info("经理任务:任务id:{}, 请假人:{}, 请假原因:{}, 请假天数:{}", task.getId(), variables.get("name"), variables.get("reason"), variables.get("days"));
            Map<String, Object> map = new HashMap<>();
            map.put("approved", approved);
            taskService.complete(task.getId(), map);
        }
    }
}

package com.ego.flowable.task;

import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;

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

流程图中文显示方框问题

配置一下即可。

package com.ego.flowable.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("宋体");
    }
}

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

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

相关文章

C++新经典04--位运算

背景 许多网络游戏为了刺激玩家每天上线&#xff0c;都在游戏中设有“每日任务”——每天让玩家做一些任务&#xff0c;如杀怪、采集来赚取积分、金钱、经验等。每日任务根据游戏不同&#xff0c;数量也不同&#xff0c;每日任务比较少的网络游戏中&#xff0c;可能每日任务只…

多语言多模态(融合图像和文本)大模型-mPLUG-Owl论文解读

近期复现了mPLUG-Owl&#xff0c;效果提升了好几个点&#xff0c;特来精读一番&#xff1a;感谢大佬们的工作&#xff1a; 论文名称&#xff1a;mPLUG-Owl: Modularization Empowers Large Language Models with Multimodality 论文地址&#xff1a;https://arxiv.org/pdf/23…

使用GUI Guider工具开发嵌入式GUI应用(5)-使用timer对象显示动画

使用GUI Guider工具开发嵌入式GUI应用&#xff08;5&#xff09;-使用timer对象显示动画 文章目录 使用GUI Guider工具开发嵌入式GUI应用&#xff08;5&#xff09;-使用timer对象显示动画引言LVGL中的timer对象基于timer对象实现仪表走针小结 引言 设计GUI的显示元素动起来&a…

Websocket原理和实践

一、概述 1.websocket是什么&#xff1f; WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在WebSocket API中&#xff0c;浏览器和服务器只需要完成一次握手&…

如何快速优化 CnosDB 数据库性能与延迟:使用 Jaeger 分布式追踪系统

在正式的生产环境中&#xff0c;数据库的性能和延迟对于确保系统的稳定和高效运行至关重要。特别是在与 CnosDB 数据库进行交互时&#xff0c;更深入地了解其表现变得尤为重要。这时Jaeger 分布式追踪系统发挥了巨大的作用。在本篇博客中&#xff0c;我们将深入探讨如何通过使用…

ATA-4000系列高压功率放大器——应用场景介绍

ATA-4000系列是一款理想的可放大交、直流信号的高压功率放大器。最大输出310Vp-p(155Vp)电压&#xff0c;452Wp功率&#xff0c;可以驱动高压功率型负载。电压增益&#xff0c;直流偏置数控精细可调&#xff0c;为客户提供了丰富的测试选择。 图&#xff1a;ATA-4000系列高压功…

ndk开发-交叉编译

为什么要使用交叉编译&#xff1a; 在linux系统一般使用c c编译可执行程序或者so库文件。该程序只能在当前linux系统执行&#xff0c;为了将生成文件可以再android平台运行&#xff0c;必须使用交叉编译。ndk中提供了跟多android平台交叉编译链&#xff0c;所以首先下载ndk工具…

FPGA应用学习笔记-----布图布线

分割可以将运行时间惊人地减少到三个小时更小的布局布线操作&#xff0c;主要的结构不影响另一个&#xff01;和增量设计流程一样 关键路径布图&#xff1a; 对于不同的模块有不同的电路和不同的关键路径&#xff0c; 布图没有主要的分割&#xff0c;布图由两个小的区域组成&a…

KDD 2023 获奖论文公布,港中文、港科大等获最佳论文奖

ACM SIGKDD&#xff08;国际数据挖掘与知识发现大会&#xff0c;KDD&#xff09;是数据挖掘领域历史最悠久、规模最大的国际顶级学术会议&#xff0c;也是首个引入大数据、数据科学、预测分析、众包等概念的会议。 今年&#xff0c;第29届 KDD 大会于上周在美国加州长滩圆满结…

C语言入门教程,C语言学习教程(非常详细)第五章 循环结构与选择结构

C语言if else语句详解 前面我们看到的代码都是顺序执行的&#xff0c;也就是先执行第一条语句&#xff0c;然后是第二条、第三条……一直到最后一条语句&#xff0c;这称为顺序结构。 但是对于很多情况&#xff0c;顺序结构的代码是远远不够的&#xff0c;比如一个程序限制了只…

【Javaswing课设源码】学生信息管理 Mysql课程设计 管理员 教师 学生

文章目录 系统介绍 系统介绍 大学时代弄的一个课设&#xff0c;当时百度[学长敲代码]找的代做&#xff0c;代码思路很清晰&#xff0c;完全按照我的功能需求去做的&#xff0c;主要是价格便宜&#xff0c;真的爱了&#xff0c;现在回头学习也是不错的一个项目。大概内容如下 本…

springboot里 用zxing 生成二维码

引入pom <!--二维码依赖--><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version></dependency><dependency><groupId>com.google.zxing</groupId>…

【分类讨论】CF1674 E

Problem - E - Codeforces 题意&#xff1a; 思路&#xff1a; 样例&#xff1a; 这种分类讨论的题&#xff0c;主要是去看答案的最终来源是哪几种情况&#xff0c;这几种情况得不重不漏 Code&#xff1a; #include <bits/stdc.h>#define int long longusing i64 lon…

尚硅谷css3笔记

目录 一、新增长度单位 二、新增盒子属性 1.border-box 怪异盒模型 2.resize 调整盒子大小 3.box-shadow 盒子阴影 案例&#xff1a;鼠标悬浮盒子上时&#xff0c;盒子有一个过度的阴影效果 三、新增背景属性 1.background-origin 设置背景图的原点 2.background-clip 设置背…

基于IMX6ULLmini的linux裸机开发系列一:汇编点亮LED

思来想去还是决定记录一下点灯&#xff0c;毕竟万物皆点灯嘛 编程步骤 使能GPIO时钟 设置引脚复用为GPIO 设置引脚属性(上下拉、速率、驱动能力) 控制GPIO引脚输出高低电平 使能GPIO时钟 其实和32差不多 先找到控制LED灯的引脚&#xff0c;也就是原理图 文件名 C:/Us…

自动提示功能消失解决方案

如果绿叶子是不可点击状态&#xff0c;可以点一下列表中的配置文件

43、TCP报文(一)

本节内容开始&#xff0c;我们正式学习TCP协议中具体的一些原理。首先&#xff0c;最重要的内容仍然是这个协议的封装结构和首部格式&#xff0c;因为这里面牵扯到一些环环相扣的知识点&#xff0c;例如ACK、SYN等等&#xff0c;如果这些内容不能很好的理解&#xff0c;那么后续…

A. Copil Copac Draws Trees(Codeforces Round 875 (Div. 1))

Copil Copac is given a list of n − 1 n-1 n−1 edges describing a tree of n n n vertices. He decides to draw it using the following algorithm: Step 0 0 0: Draws the first vertex (vertex 1 1 1). Go to step 1 1 1.Step 1 1 1: For every edge in the inpu…

号外号外,最经典的16S数据库Greengenes2更新啦!!!

没错&#xff0c;这是真的&#xff0c;沉积十年之后&#xff0c;多样性研究中最经典的16S数据库——Greengenes数据库&#xff0c;竟&#xff01;然&#xff01;更&#xff01;新&#xff01;了&#xff01;惊不惊喜&#xff01;意不意外&#xff01; 遥想当年小编还是一个小白…

vue 数字递增(滚动从0到)

使用 html <Incremental :startVal"0" :endVal"1000" :duration"500" />js&#xff1a; import Incremental from /utils/num/numViewjs let lastTime 0 const prefixes webkit moz ms o.split( ) // 各浏览器前缀let requestAnimatio…