目录

  • 简介
  • 准备
    • JDK
    • MySQL
    • flowable-ui
  • 创建流程图
    • 要注意的地方
  • 编码
    • 依赖和配置
    • 控制器
    • 实体
    • Flowable任务处理类
  • 验证
    • 启动程序
    • 调用接口
  • 本文源码
  • 参考

简介

  • Flowable是一个轻量的Java业务流程引擎,用于实现业务流程的管理和自动化。相较于老牌的Activiti做了一些改进和扩展,实现更高的性能和更小的内存占用,支持更多的数据库类型。

准备

JDK

  • JDK 17

MySQL

  • flowable程序初始化会生产表,所以要数据库。
  • MySQL 数据库,我使用的是phpstudy(下载地址:https://www.xp.cn/phpstudy#phpstudy)集成环境的MySQL8.0.12

在这里插入图片描述

flowable-ui

  • 需要事先建一个流程给flowable,所以要个可视化界面创建流程。
  • flowable-ui:使用docker安装,使用命令:
 docker run -d --name fu -p 8080:8080 flowable/flowable-ui

在这里插入图片描述

  • 运行起来的网页效果,地址是 http://ip:8080/flowable-ui

默认帐号密码: admin test

在这里插入图片描述

  • 如果拉不下来镜像,请尝试以下方案:
    • 方案一、设置代理,参考拙作Docker设置代理
    • 方案二、更换镜像源,如下配置(编辑/etc/docker/daemon.json):
{"registry-mirrors": ["https://docker.m.daocloud.io"]
}

创建流程图

  • 实现一个创建采购订单,order.totalPrice金额大于1000要经理确认的功能。
    在这里插入图片描述
    在这里插入图片描述
  • 完整流程图
    在这里插入图片描述

要注意的地方

  • 设置分支条件,order.totalPrice大于1000要经理确认。
    在这里插入图片描述

  • 任务要绑定处理类
    在这里插入图片描述
    在这里插入图片描述

  • 经理确认节点要绑定参数 Assignee manager
    在这里插入图片描述
    在这里插入图片描述

编码

依赖和配置

  • 引入的包
		<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.11</version></dependency><!-- 阿里数据库连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.23</version></dependency><!-- Mysql驱动包 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>org.flowable</groupId><artifactId>flowable-spring-boot-starter</artifactId><version>7.1.0</version></dependency>
  • 程序配置文件
spring:application:name: flowable-sampleprofiles:active: dev
server:port: 8080# MyBatis配置
mybatis-plus:# 搜索指定包别名typeAliasesPackage: com.zzq.domain# 配置mapper的扫描,找到所有的mapper.xml映射文件mapperLocations: classpath*:mapper/**/*Mapper.xml# 加载全局的配置文件configLocation: classpath:mybatis/mybatis-config.xml# 日志配置
logging:level:com.zzq: debug
flowable:#  是否激活异步执行器async-executor-activate: false# 数据库模式更新策略,true表示自动更新数据库模式database-schema-update: true
  • application-dev.yml
# 数据源配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/flowable_sample?nullCatalogMeansCurrent=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: root
  • 下载文件放到项目中resources/processes
    在这里插入图片描述
    在这里插入图片描述
  • FlowableConfig
package com.zzq.config;import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;/*** FlowableConfig** @Description: 解决Diagram生成的流程图文字显示为”口口口“ 这是因为本地没有默认的字体,安装字体或者修改配置解决* @Author: zzq* @Date 2025/4/5 15:16* @since 1.0.0*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {@Overridepublic void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {springProcessEngineConfiguration.setActivityFontName("宋体");springProcessEngineConfiguration.setLabelFontName("宋体");springProcessEngineConfiguration.setAnnotationFontName("宋体");}
}

控制器

  • OrderFlowController
package com.zzq.controller;import com.zzq.domain.Order;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricActivityInstanceQuery;
import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity;
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.http.ResponseEntity;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.*;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;/*** Zhou Zhongqing* 2025-04-01* 订单流程控制器*/
@RestController
@RequestMapping("/orderFlow")
public class OrderFlowController {private static final Logger log = LoggerFactory.getLogger(OrderFlowController.class);@Resourceprivate HistoryService historyService;@Resourceprivate RepositoryService repositoryService;@Resourceprivate RuntimeService runtimeService;@Resourceprivate TaskService taskService;@Resourceprivate ProcessEngine processEngine;/*** 开始流程* @param content* @param totalPrice* @return*/@PostMapping("/create_order")public ResponseEntity<String> startFlow(String content, Integer totalPrice) {Map<String, Object> map = new HashMap<>();map.put("order", new Order(content, totalPrice));ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("flowable-sample", map);String processId = processInstance.getId();log.info("{} 流程实例ID:{} ", processInstance.getProcessDefinitionName(), processId);Task task = taskService.createTaskQuery().processInstanceId(processId).active().singleResult();taskService.complete(task.getId());return ResponseEntity.ok(processId);}/*** 订单列表,待确认的,返回任务id* @return*/@RequestMapping("/order_list")public String getOrderList() {List<Task> list = taskService.createTaskQuery().taskAssignee("manager").list();StringBuffer stringBuffer = new StringBuffer();list.stream().forEach(task -> stringBuffer.append(task.getId()+ " : " + runtimeService.getVariable(task.getExecutionId(), "order") + "\n"));return stringBuffer.toString();}/*** 经理确认* @param taskId* @return*/@PostMapping("/confirm/{taskId}")public ResponseEntity<String> confirm(@PathVariable String taskId) {Task task = taskService.createTaskQuery().taskId(taskId).singleResult();HashMap<String, Object> map = new HashMap<>();map.put("verified", true);taskService.complete(taskId, map);return ResponseEntity.ok("success");}/*** 生成图,某个流程处理进度显示* @param response* @param processId* @throws Exception*/@GetMapping(value = "/processDiagram/{processId}")public void genProcessDiagram(HttpServletResponse response, @PathVariable("processId") String processId) throws Exception{ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();if (null == pi) {return;}Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();//使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象String instanceId = task.getProcessInstanceId();List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(instanceId).list();//得到正在执行的Activity的IdList<String> activityIds = new ArrayList<>();List<String> flows = new ArrayList<>();List<HistoricActivityInstance> historyList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list();for (HistoricActivityInstance historicActivityInstance : historyList) {String activityId = historicActivityInstance.getActivityId();if("sequenceFlow".equals(historicActivityInstance.getActivityType())){flows.add(activityId);}}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();String format = "png";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 = response.getOutputStream();
//            while ((legth = in.read(buf)) != -1) {
//                out.write(buf, 0, legth);
//            }
//        } finally {
//            if (in != null) {
//                in.close();
//            }
//            if (out != null) {
//                out.close();
//            }
//        }IOUtils.copy(in, response.getOutputStream());}}

实体

  • Order
package com.zzq.domain;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;
import java.io.Serial;
@TableName(value = "t_order")
public class Order implements Serializable {@Serialprivate static final long serialVersionUID = 8347055723013141158L;public Order() {}public Order(String content, Integer totalPrice) {this.content = content;this.totalPrice = totalPrice;}public Order(Integer id, String content, Integer totalPrice) {this.id = id;this.content = content;this.totalPrice = totalPrice;}@TableId(value = "id",type = IdType.AUTO)private Integer id;private String content;private Integer totalPrice;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public Integer getTotalPrice() {return totalPrice;}public void setTotalPrice(Integer totalPrice) {this.totalPrice = totalPrice;}
}

Flowable任务处理类

  • CreateOderProcess
package com.zzq.process;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class CreateOderProcess implements JavaDelegate {private static final Logger log = LoggerFactory.getLogger(CreateOderProcess.class);@Overridepublic void execute(DelegateExecution delegateExecution) {log.info("订单创建成功 {}",delegateExecution.getVariable("order"));}
}
  • SendMailProcess
package com.zzq.process;import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class SendMailProcess implements JavaDelegate {private static final Logger log = LoggerFactory.getLogger(SendMailProcess.class);@Overridepublic void execute(DelegateExecution delegateExecution) {log.info("发送审核邮件 {} ",delegateExecution.getVariable("order"));}
}

验证

启动程序

  • 配置了database-schema-update: true第一次启动会自动创建表
    在这里插入图片描述

在这里插入图片描述

调用接口

  • 创建采购订单, /orderFlow/create_order ,返回流程实例ID

    • 不需要经理确认
      在这里插入图片描述
    • 需要经理确认
      在这里插入图片描述
  • 查看待确认的订单,返回任务id拼接Order
    在这里插入图片描述

  • 查看某个流程处理进度显示,传入流程实例id
    在这里插入图片描述

  • 经理调用确认采购订单,传入taskId
    在这里插入图片描述

  • 确认后再调用待确认订单接口也就没有刚才的任务id了
    在这里插入图片描述

  • 验证完成

本文源码

  • https://github.com/1030907690/flowable-sample

参考

  • https://www.bilibili.com/video/BV1gnkJYJEbg/
  • https://blog.csdn.net/qq_34162294/article/details/143806673
  • https://blog.51cto.com/u_16213663/10188533
  • https://blog.csdn.net/houyj1986/article/details/85546680

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

phpMyAdmin:一款经典的MySQL在线管理工具又回来了

phpMyAdmin 是一个免费开源、基于 Web 的 MySQL/MariaDB 数据库管理和开发工具。它提供了一个直观的图形用户界面&#xff0c;使得我们无需精通复杂的 SQL 命令也能执行大多数数据库管理任务。 phpMyAdmin 项目曾经暂停将近两年&#xff0c;不过 2025 年又开始发布新版本了。 …

存储服务一NFS文件存储概述

前言&#xff1a; 网络文件系统&#xff08;Network File System&#xff0c;NFS&#xff09;诞生于1984年&#xff0c;由Sun Microsystems首创&#xff0c;旨在解决异构系统间的文件共享需求。作为一种基于客户端-服务器架构的分布式文件协议&#xff0c;NFS允许远程主机通过T…

libimagequant 在 mac 平台编译双架构

在 macOS 上编译 libimagequant 的双架构&#xff08;aarch64 x86_64&#xff09;通用二进制库&#xff0c;以下是完整步骤&#xff1a;​​1. 准备 Rust 工具链​​ # 安装两个目标平台 rustup target add aarch64-apple-darwin x86_64-apple-darwin# 确认安装成功 rustup ta…

暑期自学嵌入式——Day01(C语言阶段)

点关注不迷路哟。你的点赞、收藏&#xff0c;一键三连&#xff0c;是我持续更新的动力哟&#xff01;&#xff01;&#xff01; 主页&#xff1a; 一位搞嵌入式的 genius-CSDN博客https://blog.csdn.net/m0_73589512?spm1011.2682.3001.5343感悟&#xff1a; 今天我认为最重…

Flutter基础(前端教程⑧-数据模型)

这个示例展示了如何创建数据模型、解析 JSON 数据&#xff0c;以及在 UI 中使用这些数据&#xff1a;import package:flutter/material.dart; import dart:convert;void main() {// 示例&#xff1a;手动创建User对象final user User(id: 1,name: 张三,age: 25,email: zhangsa…

SSRF10 各种限制绕过之30x跳转绕过协议限制

ssrf漏洞在厂商的处理下可能进行一些特殊处理导致我们无法直接利用漏洞 有以下四种&#xff1a; 1.ip地址限制绕过 2.域名限制绕过 3.30x跳转绕过域名限制 4.DNS rebinding绕过内网ip限制 本章我们讲30x跳转绕过域名限制 30x跳转绕过域名限制 之前我们使用ssrf漏洞时可以…

DNS解析过程和nmap端口扫描

目录 DNS解析流程&#xff1a; nmap端口扫描 指定扫描方式 TCP全连接扫描 -sT SYN半连接扫描 -sS -sT和 -sS的区别 Linux提权 利用好谷歌语法查找敏感信息 如果自己搭建了网站文件要放在phpstudy_pro\WWW下。 如果想要使用域名访问网站&#xff0c;需要在phpstudy_pro…

【基于开源大模型(如deepseek)开发应用及其发展趋势的一点思考】

1. 开源大模型技术发展现状1.1 DeepSeek等主流开源大模型的技术特性分析 DeepSeek作为当前最具代表性的开源大模型之一&#xff0c;其技术架构具有多项创新特性。该模型采用混合专家架构(MoE)&#xff0c;通过将视觉编码分离为"理解"和"生成"两条路径&…

java8 ConcurrentHashMap 桶级别锁实现机制

Java 8 ConcurrentHashMap 桶级别锁实现机制 Java 8 中的 ConcurrentHashMap 抛弃了分段锁设计&#xff0c;采用了更细粒度的桶级别锁&#xff08;bucket-level locking&#xff09;实现&#xff0c;这是其并发性能提升的关键。下面详细解析其实现原理&#xff1a; 1. 基本实现…

Python正则表达式实战指南

一 正则表达式库正则表达式是文本处理中不可或缺的强大工具&#xff0c;Python通过re模块提供了完整的正则表达式支持。本文将详细介绍re模块中最常用的match()、search()和findall()函数&#xff0c;以及贪婪模式与非贪婪模式的区别&#xff0c;帮助读者掌握Python中正则表达式…

使用球体模型模拟相机成像:地面与天空的可见性判断与纹理映射

在传统相机模拟中&#xff0c;地面通常被建模为一个平面&#xff08;Plane&#xff09;&#xff0c;这在低空场景下是合理的。但在更大视场范围或远距观察时&#xff0c;地球的曲率不可忽视。因此&#xff0c;我们需要将地面模型从平面升级为球体&#xff0c;并基于球面与光线的…

Agent自动化与代码智能

核心问题&#xff1a; 现在很多团队做AI系统有个大毛病&#xff1a;只顾追求“高大上”的新技术&#xff08;尤其是AI Agent&#xff09;&#xff0c;不管实际业务需不需要。 结果系统搞得又贵、又复杂、还容易出错。大家被“Agent”这个概念搞晕了&#xff1a;到底啥时候用简单…

SQL141 试卷完成数同比2020年的增长率及排名变化

SQL141 试卷完成数同比2020年的增长率及排名变化 withtemp as (selectexam_id,tag,date(submit_time) as submit_timefromexamination_infoleft join exam_record using (exam_id)wheresubmit_time is not null),2021_temp as (selecttag,count(*) as exam_cnt_21,rank() over…

C语言<数据结构-单链表>

链表是一种常见且重要的数据结构&#xff0c;在 C 语言中&#xff0c;它通过指针将一系列的节点连接起来&#xff0c;每个节点可以存储不同类型的数据。相比数组&#xff0c;链表在插入和删除元素时不需要移动大量数据&#xff0c;具有更好的灵活性&#xff0c;尤其适合处理动态…

archive/tar: unknown file mode ?rwxr-xr-x

这个是我在docker build报错的&#xff0c;这是一个node.js项目。我猜你也是一个node.js下的项目&#xff0c;或者前端项目。 解决方法&#xff1a; .dockerignore里面写一下node_modules就行了。 未能解决&#xff1a;archive/tar&#xff1a;未知文件模式&#xff1f;rwxr-…

【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库

文章目录背景界面当前支持的 Markdown 语法不支持的Markdown 语法代码节选背景 出于兴趣,我使用js实现了一个 markdown语法 -> ast语法树 -> html富文本的库, 其速度应当慢于正则实现的同类js库, 但是语法扩展性更好, 嵌套列表处理起来更方便. 界面 基于此js实现vue组…

【echarts踩坑记录】为什么第二个Y轴最大值不整洁

目录问题复现示意图&#xff1a;解决方法有以下几种&#xff1a;1. 在y轴配置中手动设置max属性&#xff1a;2. 使用ECharts提供的坐标轴标签格式化功能&#xff1a;&#x1f680;写在最后问题复现示意图&#xff1a; 今天在用echarts图表的时候&#xff0c;出现了一个小问题。…

Duplicate cleaner pro 的使用技巧

Duplicate cleaner pro 的使用技巧前言文件去重基本介绍经验之谈目录结构修改盘符起因方法手动分配方法‌数据修改方法安装sqlite-web修改数据库GPU加速安装驱动获取驱动和硬件信息安装CUDA配置环境变量&#xff08;如果是自定义安装&#xff09;创建程序<1>获取参数和命…

数字孪生技术引领UI前端设计新趋势:增强现实与虚拟现实的融合应用

hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩!一、引言&#xff1a;AR 与 VR 的 “割裂” 与数字孪生的 “融合” 契机增强现实&#xff08;AR&…

Qt使用dump文件记录并查找软件奔溃信息详细教程

Qt使用dump文件记录并查找软件奔溃信息一、dump文件概述1、dump文件的基本概念2、dump文件的常见类型3、dump文件的分析工具4、dump文件的应用场景二、具体实现步骤1、下载dbghelp库2、将库添加到自己的工程中3、main.cpp添加代码记录奔溃日志4、编写测试代码5、测试6、结果查看…