Spring Boot3.x集成Flowable7.x(一)Spring Boot集成与设计、部署、发起、完成简单流程

news2025/2/24 17:55:56

一、Flowable简介

Flowable 是一个轻量级、开源的业务流程管理(BPM)和工作流引擎,旨在帮助开发者和企业实现业务流程的自动化。它支持 BPMN 2.0 标准,适用于各种规模的企业和项目。Flowable 的核心功能包括流程定义、流程执行、任务管理、历史记录查询等,广泛应用于企业级应用中。

官网:https://www.flowable.com/open-source/docs/

二、Spring Boot3整合

环境:JDK21、Spring Boot3.4.1、Flowable7.1.0

1.引入依赖

        <!-- Flowable启动引擎 -->
        <dependency>
            <groupId>org.flowable</groupId>
            <artifactId>flowable-spring-boot-starter</artifactId>
            <version>7.1.0</version>
        </dependency>

2.yml配置

注意本文数据源、数据库是MySQL和Druid,各位码友请根据实际情况调整

spring:
  #配置数据源
  datasource:
    #MySQL
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/foleable?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource

flowable:
  # 开启定时任务JOB
  async-executor-activate: true
  # 在引擎启动时,会自动更新数据库架构
  database-schema-update: true

3.线程池配置

1.提供一个全局的默认线程池

2.提供一个folwable依赖的线程池,注意Bean的名称一定要是applicationTaskExecutor

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 统一线程池管理
 *
 * @author xie
 **/
@Configuration
public class ThreadPoolTaskConfig {

    @Bean("applicationTaskExecutor")
    public ThreadPoolTaskExecutor applicationTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //此方法返回可用处理器的虚拟机的最大数量; 不小于1
        int core = Runtime.getRuntime().availableProcessors();
        executor.setCorePoolSize(core);//设置核心线程数
        executor.setMaxPoolSize(core * 2 + 1);//设置最大线程数
        executor.setKeepAliveSeconds(120);//除核心线程外的线程存活时间
        executor.setQueueCapacity(120);//如果传入值大于0,底层队列使用的是LinkedBlockingQueue,否则默认使用SynchronousQueue
        executor.setThreadNamePrefix("thread-default-execute");//线程名称前缀
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//设置拒绝策略,抛出 RejectedExecutionException来拒绝新任务的处理。
//        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//设置拒绝策略,使用主线程
//        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());//设置拒绝策略,直接丢弃掉
//        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());//设置拒绝策略,丢弃最早的未处理的任务请求。
        return executor;
    }
}

不配置线程池可能出现以下错误

三、流程定义

官方流程设计器

docker run -p 9096:8080 -d --name flow flowable/flowable-ui:6.8.0

访问IP加启动容器的端口

默认账户/密码:admin/test

进入设计器

创建流程并设计

创建测试流程
这里先简单画个流程 后期写个详细流程图的绘画

确定后进入设计界面

添加用活动后设置名称

点击用户任务设置用户任务的执行人

这里先简单设置一个固定的人,分配用户ID为:user1

再重复添加一个用户任务,添加一下结束事件

最后就依次连线就行了,选中开始节点然后连接到用户一

最后的效果图


点击保存

选择查看刚刚创建的流程

导出XML

四、部署流程

1.把导出的xml放入我们工程的resources目录

2.部署流程(这里先把我们后面用到的查询服务都注入)

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;
    
    @Resource
    private HistoryService historyService;

    @Resource
    IdentityService identityService;
   
     /**
     * 初始化流程
     *
     * @method: initFlow
     * @return:
     * @author: xie
     * @date: 2024/12/24 上午9:26
     **/
    @GetMapping("initFlow")
    @Transactional(rollbackFor = Exception.class)
    public void initFlow() {
        // 获取bpmn文件夹的所有.bpmn20.xml文件
        ClassPathResource bpmnFolder = new ClassPathResource("bpmn/");
        var files = bpmnFolder.getFile().listFiles((dir, name) -> name.endsWith(".bpmn20.xml"));

        if (files != null && files.length > 0) {
            // 创建部署对象
            var deploymentBuilder = repositoryService.createDeployment();

            for (var file : files) {
                // 添加BPMN文件到部署
                deploymentBuilder.addInputStream(file.getName(), file.toURI().toURL().openStream());
            }

            // 执行部署
            Deployment deployment = deploymentBuilder.deploy();
        }
    }

五、查询部署的全部流程

当然这里也可以进行分页查询已经写在下面注释了 这里为了测试 我就简单写了

    /***
     * 查询所有的流程实例
     * @method: queryAllDeployedProcesses
     * @return:
     * @author: xie
     * @date: 2024/12/20 下午1:12
     **/
    @GetMapping("/queryAllDeployedProcesses")
    public List<JSONObject> queryAllDeployedProcesses() {
        List<JSONObject> jsonObjects = new ArrayList<>();

        // 查询所有流程定义
        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()
                .orderByProcessDefinitionKey().asc() // 按流程定义的 Key 排序
                .latestVersion() // 只查询每个流程定义的最新版本
                .list();

        // 打印所有已部署的流程的 key 和 name
        for (ProcessDefinition processDefinition : processDefinitions) {
            System.out.println("Process ID: " + processDefinition.getId());
            System.out.println("Process Key: " + processDefinition.getKey());
            System.out.println("Process Name: " + processDefinition.getName());
            System.out.println("Process Version: " + processDefinition.getVersion());
            JSONObject object = new JSONObject();
            object.put("id", processDefinition.getId());
            object.put("key", processDefinition.getKey());
            object.put("name", processDefinition.getName());
            object.put("version", processDefinition.getVersion());

            jsonObjects.add(object);
        }

        //分页查询
        // 创建查询对象
//        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery()
//                .latestVersion() // 只查询最新版本的流程定义
//                .orderByProcessDefinitionKey().asc(); // 按流程定义的 Key 升序排序
//
//        // 获取总条数
//        long totalCount = query.count();
//
//        // 分页查询流程定义
//        List<ProcessDefinition> processDefinitions = query.listPage((pageNum - 1) * pageSize, pageSize);

        return jsonObjects;
    }

看看界面的效果吧,这个简单的前端页面展示方便查询

六、发起审批

    /**
     * 是否被拒绝标记
     */
    public static final String REFUSEFLAG = "refuseFlag";

    /**
     * 发起流程
     *
     * @param key : 流程信息
     * @method: startFlow
     * @return:
     * @author: xie
     * @date: 2024/12/20 下午1:43
     **/
    @GetMapping("/startFlow")
    @Transactional(rollbackFor = Exception.class)
    public String startFlow(@RequestParam("key") String key) {
        Map<String, Object> map = Map.of(
                "businessType", "业务类型(业务审批、请假、出差等)",
                "day", 1,
                REFUSEFLAG, false
        );

        //发起人用户ID
        String userId = SysConstan.USER_ID;

        //订单号
        String businessKey = "PO00001";

        //参数一 流程key
        //参数二 页面单据类型(比如请假流水号、订单号等)
        //参数三 运行时变量信息比如请假天数
        //设置发起人
        identityService.setAuthenticatedUserId(userId);

        //启动流程变量
        ProcessInstance processInstanceByKey = runtimeService.startProcessInstanceByKey(key, businessKey, map);

        log.info("流程实例id-{}", processInstanceByKey.getId());

        // 清除当前用户
        identityService.setAuthenticatedUserId(null);
        return processInstanceByKey.getId();
    }

注意:

参数key:指的是我们上一步部署流程后得到的key也就是页面查询的key

map对象是设置的变量集,比如我们请假的时候可能要根据不同的天数走不同的分支,这里的变量集后期可以自己封装一个对象指定一些必须传入的变量信息

拒绝标记:由于Flowable并不会记录流程是否是被拒绝而结束的流程实例

userId:Flowable默认是无状态的可以通过identityService临时设置发起人

businessKey:业务订单号,可以把它看做是审批的单据号后面审批通过了需要通知业务系统的时候需要用到

七、查询所有发起的流程及展示状态

这里包含了:我的发起、我参与审批的流程、根据业务单号查询审批流程等查询功能

    /**
     * 查询所有流程实例
     *
     * @method: queryAllprocess
     * @return:
     * @author: xie
     * @date: 2024/12/20 下午3:35
     **/
    @GetMapping("queryAllprocess")
    public List<JSONObject> queryAllprocess() {
        // 获取所有活跃的流程实例(包括已结束的)
        List<HistoricProcessInstance> allProcessInstances = historyService.createHistoricProcessInstanceQuery()
                //查询用户参与过的流程实例
                //.involvedUser("1")
                //查询发起人用户
                //.startedBy(SysConstan.USER_ID)
                //根据变量查询(自己设的变量)
                //.variableValueEquals("businessType", "查询的业务类型")
                //根据订单号模糊查询
                //.processInstanceBusinessKeyLikeIgnoreCase("orderCode")
                .orderByProcessInstanceStartTime().asc()  // 可以按ID排序,便于调试
                .list(); // 查询所有的流程实例,包括历史的和活跃的

        List<JSONObject> jsonObjects = new ArrayList<>();

        for (HistoricProcessInstance processInstance : allProcessInstances) {
            String processInstanceId = processInstance.getId();
            JSONObject json = new JSONObject();
            if (processInstance.getEndTime() == null) {
                json.put("status", "审批中");
            } else {
                json.put("status", "审批完成");
            }
            json.put("id", processInstance.getProcessDefinitionId());
            json.put("processInstanceId", processInstanceId);
            json.put("startUser", processInstance.getStartUserId());
            json.put("key", processInstance.getProcessDefinitionKey());
            json.put("businessKey", processInstance.getBusinessKey());
            json.put("name", processInstance.getProcessDefinitionName());
            json.put("deleteReason", processInstance.getDeleteReason());
            json.put("startTime", DateUtil.format(processInstance.getStartTime(), "yyyy-MM-dd HH:mm:ss"));
            json.put("endTime", processInstance.getEndTime() != null ? DateUtil.format(processInstance.getEndTime(), "yyyy-MM-dd HH:mm:ss") : "");

            // 获取与任务相关的所有变量
            // 获取该流程实例的变量
            List<HistoricVariableInstance> variables = historyService.createHistoricVariableInstanceQuery()
                    .processInstanceId(processInstanceId)
                    .list();
            if(CollUtil.isNotEmpty(variables)){
                for (HistoricVariableInstance variable : variables) {
                    json.put(variable.getVariableName(), variable.getValue());

                    if (REFUSEFLAG.equals(variable.getVariableName()) && variable.getValue() != null && variable.getValue().toString().equalsIgnoreCase("true")) {
                        json.put("status", "审批驳回");
                    }
                }
            }

            jsonObjects.add(json);
        }

        return jsonObjects;
    }

这里就用到了发起流程时是否驳回这个变量,判断当前业务单据是否被驳回

查询流程时可以有多种条件查询比如查询我发起的审批流程上面代码已经列出查询条件

对应的页面简单展示,发起后进入到用户一审批

八、我的代办任务

这里注释了用户ID条件查询,实际业务系统应该是要设置当前用户的ID查询

    /**
     * 获取代办列表 (这里暂时查看所有的)
     *
     * @method: getTasks
     * @return:
     * @author: xie
     * @date: 2024/12/20 下午1:44
     **/
    @GetMapping("/allTasks")
    public List<JSONObject> getTasks() {
        List<Task> taskList = taskService
                .createTaskQuery()
                //查询业务类型为指定的任务
                //.processInstanceBusinessKey("LEAVE")
                //查询所有zhangsan用户代办的任务
                //.taskAssignee(SysConstan.USER_ID)
                .list();

        List<JSONObject> jsonObjects = new ArrayList<>();

        for (Task task : taskList) {
            JSONObject json = new JSONObject();
            json.put("id", task.getId());
            json.put("name", task.getName());
            json.put("user", task.getAssignee());
            json.put("processDefinitionId", task.getProcessDefinitionId());
            json.put("processInstanceId", task.getProcessInstanceId());

            // 获取与任务相关的所有变量
            Map<String, Object> taskVariables = taskService.getVariables(task.getId());

            // 打印出任务的变量
            json.putAll(taskVariables);

            jsonObjects.add(json);
        }

        return jsonObjects;
    }

九、完成任务

这里的任务ID为上面我的代办任务接口查询出来Task的ID

     /**
     * 完成任务
     *
     * @method: testComplete
     * @return:
     * @author: xie
     * @date: 2024/12/20 下午1:44
     **/
    @GetMapping("/testComplete")
    @Transactional(rollbackFor = Exception.class)
    public boolean testComplete(@RequestParam("id") String taskId) {     // 获取任务对应的流程实例ID
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();

        // 检查任务是否为空
        if (task == null) {
            System.out.println("任务不存在");
            return false;
        }

        String processInstanceId = task.getProcessInstanceId();

        String orderCode = (String) runtimeService.getVariable(processInstanceId, "orderCode");

        log.info("业务单据:{}", orderCode);
        // 1. 设置新执行者
        //taskService.setAssignee(taskId, newAssigneeId); // 任务的执行者被替换为新的用户

        taskService.addComment(taskId, processInstanceId, "备注信息test");
        // 完成任务
        taskService.complete(taskId);

        // 查询当前流程实例的所有活动任务
        boolean isFinish =  processInstanceFinished(processInstanceId);
        log.debug("流程是否完成L:{}", isFinish);

        return isFinish;
    }

    /**
     * 校验当前流程是否结束了
     * @method: isProcessInstanceFinished
     * @param processInstanceId :
     * @return:
     * @author: xie
     * @date: 2024/12/23 下午1:57
     **/
    public boolean processInstanceFinished(String processInstanceId) {
        // 获取当前流程实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();

        // 如果 processInstance 为空,表示该流程实例已结束
        return processInstance == null;
    }

十、驳回任务

processInstanceId指的是发起流程的实例ID

    /**
     * 驳回流程
     * @method: stopFlow 
     * @param processInstanceId : 
     * @return: 
     * @author: xie
     * @date: 2024/12/24 下午4:38 
     **/
    @GetMapping("stopFlow")
    @Transactional(rollbackFor = Exception.class)
    public void stopFlow(@RequestParam("id") String processInstanceId) {
        // 在删除前设置拒绝变量
        runtimeService.setVariable(processInstanceId, "refuseFlag", true);

        //拒绝 后一个参数是拒绝的原因
        runtimeService.deleteProcessInstance(processInstanceId, "驳回任务备注原因");
    }

十一、 完成效果展示

1.BPMN-JS插件渲染的连接线不起效果

2.Flowable引擎自带构建的图片没有经过的节点


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

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

相关文章

网络安全-openssl工具

OpenSSl是一个开源项目&#xff0c;包括密码库和SSL/TLS工具集。它已是在安全领域的事实标准&#xff0c;并且拥有比较长的历史&#xff0c;现在几乎所有的服务器软件和很多客户端都在使用openssl&#xff0c;其中基于命令行的工具是进行加密、证书管理以及测试最常用到的软件。…

【Web开发】PythonAnyWhere免费部署Django项目

PythonAnyWhere免费部署Django项目 文章目录 PythonAnyWhere免费部署Django项目将项目上传到GitHub从GitHub下载Django项目创建Web应用配置静态文件将项目上传到GitHub 打开项目,输入以下命令,生成Django项目依赖包。pip list --format=freeze > requirements.txt打开Git …

视频的分片上传

分片上传需求分析&#xff1a; 项目中很多地方需要上传视频&#xff0c;如果视频很大&#xff0c;上传到服务器需要很多时间 &#xff0c;这个时候体验就会很差。所以需要前端实现分片上传的功能。 要实现分片上传&#xff0c;需要对视频进行分割&#xff0c;分割成不同的大小…

Moonshot AI 新突破:MoBA 为大语言模型长文本处理提效论文速读

前言 在自然语言处理领域&#xff0c;随着大语言模型&#xff08;LLMs&#xff09;不断拓展其阅读、理解和生成文本的能力&#xff0c;如何高效处理长文本成为一项关键挑战。近日&#xff0c;Moonshot AI Research 联合清华大学、浙江大学的研究人员提出了一种创新方法 —— 混…

Deepseek首页实现 HTML

人工智能与未来&#xff1a;机遇与挑战 引言 在过去的几十年里&#xff0c;人工智能&#xff08;AI&#xff09;技术取得了突飞猛进的发展。从语音助手到自动驾驶汽车&#xff0c;AI 正在深刻地改变我们的生活方式、工作方式以及社会结构。然而&#xff0c;随着 AI 技术的普及…

VS2022配置FFMPEG库基础教程

1 简介 1.1 起源与发展历程 FFmpeg诞生于2000年&#xff0c;由法国工程师Fabrice Bellard主导开发&#xff0c;其名称源自"Fast Forward MPEG"&#xff0c;初期定位为多媒体编解码工具。2004年后由Michael Niedermayer接任维护&#xff0c;逐步发展成为包含音视频采…

kafka基本知识

什么是 Kafka&#xff1f; Apache Kafka 是一个开源的分布式流处理平台&#xff0c;最初由 LinkedIn 开发&#xff0c;后来成为 Apache 软件基金会的一部分。Kafka 主要用于构建实时数据管道和流处理应用程序。它能够高效地处理大量的数据流&#xff0c;广泛应用于日志收集、数…

类型系统下的语言分类与类型系统基础

类型系统是一种根据计算值的种类对程序语法进行分类的方式&#xff0c;目的是自动检查是否有可能导致错误的行为。 —Benjamin.C.Pierce&#xff0c;《类型与编程语言》&#xff08;2002&#xff09; 每当谈到编程语言时&#xff0c;人们常常会提到“静态类型”和“动态类型”。…

有没有使用wxpython开发的类似于visio或drawio的开源项目(AI生成)

有没有使用wxpython开发的类似于visio或drawio的开源项目 是的&#xff0c;有一些使用wxPython开发的类似于Microsoft Visio或draw.io&#xff08;现为diagrams.net&#xff09;的开源项目。wxPython 是一个跨平台的GUI工具包&#xff0c;它允许Python开发者创建桌面应用程序&…

【MySQL 一 数据库基础】深入解析 MySQL 的索引(3)

索引 索引操作 自动创建 当我们为一张表加主键约束(Primary key)&#xff0c;外键约束(Foreign Key)&#xff0c;唯一约束(Unique)时&#xff0c;MySQL会为对应的的列自动创建一个索引&#xff1b;如果表不指定任何约束时&#xff0c;MySQL会自动为每一列生成一个索引并用ROW_I…

【C++】优先级队列宝藏岛

> &#x1f343; 本系列为初阶C的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:[小编的个人主页])小编的个人主页 > &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 > ✌️ &#x1f91e; &#x1…

List 接口中的 sort 和 forEach 方法

List 接口中的 sort 和 forEach 方法是 Java 8 引入的两个非常实用的函数&#xff0c;分别用于 排序 和 遍历 列表中的元素。以下是它们的详细介绍和用法&#xff1a; sort 函数 功能 对列表中的元素进行排序。 默认使用自然顺序&#xff08;如数字从小到大&#xff0c;字符…

MusicGPT的本地化部署与远程调用:让你的Windows电脑成为AI音乐工作站

文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 在如今快节奏的生活里&#xff0c;音乐不仅能够抚慰我们的心灵&#xff0c;还能激发无限创意。想象一下&#xff0c;在忙碌的工作间隙或闲暇时光中&#xff0c;只需输…

Unity通过Vosk实现离线语音识别方法

标注&#xff1a;deepseek直接生成&#xff0c;待验证 在Unity中实现离线语音识别可以通过集成第三方语音识别库来实现。以下是一个使用 Unity 和 Vosk&#xff08;一个开源的离线语音识别库&#xff09;的简单示例。 准备工作 Vosk&#xff1a;一个开源的离线语音识别库&am…

智能优化算法:莲花算法(Lotus flower algorithm,LFA)介绍,提供MATLAB代码

一、 莲花算法 1.1 算法原理 莲花算法&#xff08;Lotus flower algorithm&#xff0c;LFA&#xff09;是一种受自然启发的优化算法&#xff0c;其灵感来源于莲花的自清洁特性和授粉过程。莲花的自清洁特性&#xff0c;即所谓的“莲花效应”&#xff0c;是由其叶片表面的微纳…

企业数据集成:实现高效调拨出库自动化

调拨出库对接调出单-v&#xff1a;旺店通企业奇门数据集成到用友BIP 在企业信息化管理中&#xff0c;数据的高效流转和准确对接是实现业务流程自动化的关键。本文将分享一个实际案例&#xff0c;展示如何通过轻易云数据集成平台&#xff0c;将旺店通企业奇门的数据无缝集成到用…

数据库管理-第295期 IT架构与爆炸半径(20250221)

数据库管理295期 2025-02-21 数据库管理-第295期 架构与爆炸半径&#xff08;20250221&#xff09;1 术语新解2 硬件&#xff1a;存储VS本地盘3 数据库3.1 多模VS专用3.2 集中式VS分布式 4 公有云VS非公有云总结 数据库管理-第295期 架构与爆炸半径&#xff08;20250221&#x…

基于WOA鲸鱼优化的BiLSTM双向长短期记忆网络序列预测算法matlab仿真,对比BiLSTM和LSTM

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a/matlab2024b 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频…

DeepSeek私有化专家 | 云轴科技ZStack入选IDC中国生成式AI市场概览

DeepSeek 火爆全球AI生态圈&#xff0c;并引发企业用户大量私有化部署需求。 国际数据公司IDC近日发文《DeepSeek爆火的背后&#xff0c;大模型/生成式AI市场生态潜在影响引人关注》&#xff0c;认为中国市场DeepSeekAI模型的推出在大模型/生成式AI市场上引起了轰动&#xff0c…

npm在install时提示要安装python问题处理

使用npm\yarn\pnpm下载以来的时候&#xff0c;一直提示python异常&#xff0c;有的项目安装了python之后&#xff0c;下载依赖还是异常 而且旧版本项目使用python2,新的使用Python3…很烦 解决方案1&#xff1a;cnpm 安装教程&#xff1a; npm安装cnpm&#xff0c;解决node12\…