SpringBoot3 + Flowable7 工作流引擎使用笔记

news2024/11/25 14:48:25

目录

  • Flowable 简介
  • 流程设计器
    • 安装
    • 使用
  • SpringBoot 3 整合
  • 表结构
  • 流程部署
  • 启动流程
  • 流程审批
  • 流程挂起和激活
  • 任务分配
    • 固定分配
    • 表达式分配
      • 值表达式
      • 方法表达式
    • 监听器分配
  • 流程变量
    • 运行时变量
    • 历史变量
  • 身份服务
    • 候选人
    • 拾取任务
    • 归还任务
    • 指派给别人
    • 候选人组
      • 创建用户
      • 创建用户组
      • 用户关联用户组
      • 流程图设置用户组
      • 查询组待办任务
  • 网关
    • 排他网关
    • 并行网关
    • 包含网关

Flowable 简介

Flowable 由 Activiti 项目分支演变而来,用于构建和管理各种业务流程。其核心是一个通用的流程引擎,支持 BPMN 2.0(Business Process Model and Notation)标准,这是一种业务流程建模和执行的国际标准。Flowable 完全支持 BPMN 2.0,使得非技术人员也能参与流程的设计。

BPMN 是一种用于业务流程建模的标准化图形表示法。它定义了一套符号和规则,用于描述业务流程的各个方面,如任务、事件、网关等。BPMN 的目标是提供一种统一的、易于理解的图形化语言来表示业务流程。

BPMN 2.0是 BPMN 规范的2.0版本,是当前比较稳定且广泛使用的版本。

Flowable 7.x 是目前 Flowable 的最新版本,该版本基于 JDK 17,如果使用 Spring Boot 集成的话,需要 Spring Boot 的版本最少为 SpringBoot 3.x。

官方文档地址

流程设计器

安装

BPMN 定义了如何用符号来描述业务流程,这种符号组合在一起就是个模型,但是程序不能直接识别这些符号,因此 BPMN 规定使用 XML 格式来编码业务流程模型,这种编码形式称为 BPMN XML。

我们一般使用流程设计器画图业务流程模型,再导出为 BPMN 规范的 XML 文件,然后再使用。

Flowable 官方有提供一个名为 Flowable-UI 的东西,这是一个 Web 应用,可以直接在这上面设计业务流程,但是 Flowable-UI 从 Flowable 7 以后就没提供了,但是经过我的测试,用旧版本的UI 也可以实现对应功能,以下提供一个我目前使用版本的 Docker 命令:

docker run -p 8080:8080 flowable/flowable-ui:6.8.0

使用以下命令启动后,访问地址端口,可以看到以下界面:
flowable-ui
使用默认的用户名密码登录:

user: admin
password: test

flowable-ui登陆后的页面

使用

打开建模应用程序,可以看到以下界面:
在这里插入图片描述
点击创建流程,可以创建一个业务流程,其中模型 key 用于唯一标识一个业务流程模型,这个后面会有用的,模型名称可以重复:
在这里插入图片描述
创建完成后,进入流程的编辑页面:
在这里插入图片描述
编辑完成后,保存,查看模型,会进到这个界面,这里可以将模型导出成为XML:
在这里插入图片描述

SpringBoot 3 整合

使用 SpringBoot 整合,首先需要引入以下依赖:

<!-- https://mvnrepository.com/artifact/org.flowable/flowable-spring-boot-starter -->
<dependency>
	<groupId>org.flowable</groupId>
	<artifactId>flowable-spring-boot-starter</artifactId>
	<version>7.0.1</version>
</dependency>

由于 Flowable 运行需要数据库的支持,所以需要配置一个数据源:

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/flowable_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    type: com.zaxxer.hikari.HikariDataSource
flowable:
  # 关闭定时任务JOB
  async-executor-activate: false
  # 在引擎启动时,如果数据库架构与 Flowable 引擎期望的架构不一致,Flowable 会自动更新数据库架构。这包括创建缺失的表和列,以及修改现有的表和列以匹配最新版本
  database-schema-update: true

配置数据源后,启动项目,Flowable会自动识别数据库,并帮你新建对应的表,大约有70个。

表结构

主要表前缀及其用途

  • ACT_RE_*:RE 代表 repository(存储)。这些表包含静态信息,如流程定义和流程的资源(图片、规则等)。RepositoryService 接口操作的表。
  • ACT_RU_*:RU 代表 runtime。这些表存储运行时信息,如流程实例、用户任务、变量、作业等。Flowable 只在流程实例运行中保存运行时数据,并在流程实例结束时删除记录,以保证运行时表小且快。RuntimeService 接口操作的表。
  • ACT_HI_*:HI 代表 history。这些表存储历史数据,如已完成的流程实例、变量、任务等。HistoryService 接口操作的表。
  • ACT_ID_*:ID 表示 identity(组织机构)。这些表包含标识的信息,如用户、用户组等。IdentityService 接口操作的表。
  • ACT_GE_*:通用数据表,用于存储各种情况下都可能需要的数据。

核心表

  • act_ge_bytearray:二进制数据表,用于存储流程定义、流程模板、流程图的图片等
  • act_re_deployment:记录部署操作的表,一次部署操作对应一条记录
  • act_re_procde:流程定义表,一次部署可以部署多个流程,一个流程对应一条记录
  • act_ru_task:存储运行中流程的任务节点信息,常用于查询人员或部门的待办任务。
  • act_ru_execution:运行时流程执行实例表,记录运行中流程运行的各个分支信息。
  • act_hi_procinst:历史流程实例表,存储流程实例历史数据(包含正在运行的流程实例)。

流程部署

  1. 导出流程模型 XML
  2. 通过 ProcessEngine 获取 RepositoryService,或直接注入 RepositoryService
    private ProcessEngine processEngine;
    private RepositoryService repositoryService;
    
    @Autowired
    public void setRepositoryService(RepositoryService repositoryService) {
        this.repositoryService = repositoryService;
    }
    
    @Autowired
    public void setProcessEngine(ProcessEngine processEngine) {
        this.processEngine = processEngine;
    }
    
    @Test
    public void test() {
        System.out.println(repositoryService != null && repositoryService == processEngine.getRepositoryService());
    }
    
  3. 部署流程图,一次部署可以部署多个
    @Test
    public void testDeploy() {
        Deployment deploy = repositoryService
                //创建一个部署
                .createDeployment()
                //部署的流程图
                .addClasspathResource("flowable/Example01.bpmn20.xml")
                //这次部署的名字
                .name("test_deploye")
                //执行部署
                .deploy();
    }
    

启动流程

  1. 部署一个流程,获取到部署的流程ID,或者获取到流程定义 Key,就是一开始画图时候填写的那个 Key,ID 和 Key 可以从 ACT_RE_PROCDEF 表忠获取。
  2. 通过 ProcessEngine 获取 RepositoryService,或直接注入 RepositoryService
    private RuntimeService runtimeService;
    
    @Autowired
    public void setRuntimeService(RuntimeService runtimeService) {
    	this.runtimeService = runtimeService;
    }
    
  3. 通过流程 ID 或 Key 启动一个流程实例
    @Test
    public void testStart() {
    	ProcessInstance processInstanceById = runtimeService.startProcessInstanceById("Example01:1:085efe64-57f2-11ef-aa6e-4c034f4db418");
    	ProcessInstance processInstanceByKey = runtimeService.startProcessInstanceByKey("Example01");
    }
    

流程审批

  1. 任务类的操作都是使用 TaskService 完成的,可以使用 ProcessEngine 获取,或直接注入
    private TaskService taskService;
    
    @Autowired
    public void setTaskService(TaskService taskService) {
    	this.taskService = taskService;
    }
    
  2. 查询某个用户的代办任务,获取到 task id
    @Test
    public void testQuery() {
    	List<Task> taskList = taskService
    		.createTaskQuery()
    		//查询所有zhangsan用户代办的任务
    		.taskAssignee("zhangsan")
    		.list();
    }
    
  3. 使用 complete 完成审批
    @Test
    public void testComplete() {
        taskService.complete("e4c98b66-5856-11ef-85d0-4c034f4db418");
    }
    

流程挂起和激活

Flowable 可以针对流程定义和流程实例进行流程挂起和激活。

如果将流程定义挂起,再尝试启动一个新的流程实例,会报 xxx is suspended 异常,但是不会影响已经创建的实例继续:

@Test
public void testSuspend() {
    //挂起流程定义
    repositoryService.suspendProcessDefinitionById("Example01:1:085efe64-57f2-11ef-aa6e-4c034f4db418");
}

@Test
public void testActivate() {
    //激活流程定义
    repositoryService.activateProcessDefinitionById("Example01:1:085efe64-57f2-11ef-aa6e-4c034f4db418");
}

如果对某一个流程实例进行挂起,则该实例无法再进行后续操作:

@Test
public void testSuspendTask() {
    //挂起流程实例
    runtimeService.suspendProcessInstanceById("997106c1-586f-11ef-8c2e-4c034f4db418");
}

@Test
public void testActivateTask() {
    //激活流程实例
    runtimeService.activateProcessInstanceById("997106c1-586f-11ef-8c2e-4c034f4db418");
}

任务分配

Flowable 的任务分配是流程管理中的一个重要环节,它决定了流程中的任务由谁来执行。

Flowable支持多种任务分配方式,主要包括固定分配、表达式分配和监听器分配。

固定分配

固定分配是最直接的任务分配方式,即在绘制流程图时,直接在流程文件中通过 Assignee 属性来指定任务的执行者。这种方式简单明了,但缺乏灵活性,一旦流程设计完成,任务的执行者就固定下来了。
在这里插入图片描述

表达式分配

表达式分配是 Flowable 中更为灵活的任务分配方式,它允许在流程执行时动态地确定任务的执行者。Flowable 支持两种 UEL(Unified Expression Language)表达式:值表达式(Value Expression)和方法表达式(Method Expression)。

值表达式

解析为一个值,通常用于直接指定任务的执行者。例如,${assignee} 这样的表达式会在流程执行时被解析为流程变量 assignee 的值,该值即为任务的执行者。
在这里插入图片描述

设置好表达式后,可以在启动流程实例时或调用 complete 时使用 map 传递变量,传递过的变量可以在后续的步骤中通用:

@Test
public void testStart() {
    Map<String, Object> map = new HashMap<>();
    map.put("assignee", "zhangsan");
    ProcessInstance processInstanceById = runtimeService.startProcessInstanceById(
            "Example_02:1:736ec65b-588c-11ef-8923-4c034f4db418", map
    );
}

@Test
public void testComplete() {
    Map<String, Object> map = new HashMap<>();
    map.put("assignee1", "lisi");
    taskService.complete("c228521f-588d-11ef-b80c-4c034f4db418", map);
}

方法表达式

调用一个方法,可以带或不带参数。这种方法允许在流程执行时通过调用某个方法来动态确定任务的执行者。例如,${userService.findAssigneeByTaskId(taskId)} 这样的表达式会调用userService 的 findAssigneeByTaskId 方法来获取任务的执行者。

这里的 userService 需要是容器中的一个 bean。

监听器分配

监听器分配是一种更为高级的任务分配方式,它通过在流程中设置监听器来动态地改变任务的执行者。监听器可以在任务创建、分配、完成等关键节点触发,并执行相应的逻辑来改变任务的执行者。例如,可以设置一个任务创建监听器,在任务创建时根据某些条件(如发起人的部门、任务的优先级等)来动态指定任务的执行者。

  1. 编写一个监听器类实现 TaskListener 接口
    public class MyTaskListener implements TaskListener {
        @Override
        public void notify(DelegateTask delegateTask) {
            
        }
    }
    
  2. 将类的全限定名设置到流程图
    在这里插入图片描述
    在这里插入图片描述
  3. 重写 notify 方法实现指派
    public class MyTaskListener implements TaskListener {
        @Override
        public void notify(DelegateTask delegateTask) {
            switch (delegateTask.getEventName()) {
                case EVENTNAME_CREATE -> {
                    //节点创建,指派处理人
                    delegateTask.setAssignee("zhangsan");
                }
            }
        }
    }
    

流程变量

流程变量是 Flowable 管理工作流时根据管理需要而设置的变量。它们用于在流程的不同阶段之间传递信息,如任务分配、条件判断等。流程变量可以是任何类型的数据,如字符串、整数、浮点数、布尔值等。

流程变量的作用域可以是一个流程实例(ProcessInstance)、一个任务(Task)或者是一个执行实例(execution)。流程变量的默认作用域是流程实例,此时可以称为global变量。Global变量在整个流程实例中都是可见的,但变量名不允许重复,后设置的值会覆盖之前设置的值。任务和执行实例的变量作用域相对较小,仅针对一个任务或一个执行实例范围,称为local变量。Local变量在不同的任务或执行实例中互不影响,即使变量名相同也不会互相冲突。

Flowable 将流程变量分为两种类型:运行时变量和历史变量。

  • 运行时变量:流程实例运行时的变量,存入 act_ru_variable 表中。在流程实例运行结束时,这些变量在表中会被删除。因此,查询一个已经完结的流程实例的变量时,需要在历史变量表中查找。
  • 历史变量:存入 act_hi_varinst 表中。在流程启动时,流程变量会同时存入历史变量表中;在流程结束时,历史表中的变量仍然存在。这可以视为“永久代”的流程变量。

运行时变量

在流程实例启动时,可以通过 startProcessInstanceByKey 或 startProcessInstanceById 等方法的可选参数来设置流程变量。这些变量将作为全局变量(Global Variables),在整个流程实例中有效:

@Test
public void testStart() {
    Map<String, Object> map = new HashMap<>();
    map.put("v1", "123456");
    map.put("v2", "asfafs");
    map.put("v3", "fasaf");
    ProcessInstance processInstanceById = runtimeService.startProcessInstanceById(
            "Example_02:1:736ec65b-588c-11ef-8923-4c034f4db418", map
    );
}

在流程执行过程中,可以通过 setVariable、setVariableLocal、setVariables、setVariablesLocal 等方法为特定的执行实例(Execution)或任务(Task)设置变量。这些变量可以是局部变量(Local Variables),仅在当前执行实例或任务中有效:

runtimeService.setVariable(executionId, "variableName", variableValue);  
// 或者为当前执行实例设置局部变量  
runtimeService.setVariableLocal(executionId, "variableName", variableValue);

taskService.setVariable(taskId, "variableName", variableValue);
// 或者为当前task设置局部变量  
taskService.setVariableLocal(taskId, "variableName", variableValue);

可以在流程审批的时候设置变量:

@Test
public void testStart() {
    Map<String, Object> map = new HashMap<>();
    map.put("v1", "123456");
    map.put("v2", "asfafs");
    map.put("v3", "fasaf");
    taskService.complete(taskId, map);
}

获取流程变量:

Object variableValue = runtimeService.getVariable(executionId, variableName);
Object taskVariableValue = taskService.getVariable(taskId, variableName);

历史变量

Flowable 将历史变量存储在特定的数据库表中,通常这个表名为 ACT_HI_VARINST。这个表包含了流程实例运行过程中的所有变量实例的信息,包括变量的名称、类型、值以及它们所属的流程实例和任务实例等。

身份服务

候选人

在 Flowable 中,候选人是指可能被分配给某个任务的用户或用户组。当流程执行到某个任务节点时,该节点的候选人可以领取并执行任务。这种方式避免了在流程定义时固定设置任务负责人,使得在需要变更任务负责人时无需修改流程定义,提高了系统的可扩展性和灵活性。

可以在设计器直接分配候选人:
在这里插入图片描述

拾取任务

候选人不是审批人,需要进行拾取操作才能进行审批。多个候选人只有一个能变成审批人。

  1. 查看候选人任务
    List<Task> zhangsan = taskService.createTaskQuery().taskCandidateUser("zhangsan").list();
    
  2. 拾取任务
    taskService.claim(taskId,"zhangsan");
    

归还任务

@Test
public void testReturn() {
	//审批人 -> 候选人
	taskService.unclaim(taskId);
}

指派给别人

@Test
public void testSetAssignee() {
	//审批人指派给另一个审批人
	taskService.setAssignee(taskId, userId);
}

候选人组

创建用户

private IdentityService identityService;

@Autowired
public void setIdentityService(IdentityService identityService) {
    this.identityService = identityService;
}

@Test
public void testCreateUser() {
    User user = identityService.newUser("zhangsan");
    user.setEmail("xxx@163.com");
    identityService.saveUser(user);
}

创建用户组

@Test
public void testCreateGroup() {
    Group group = identityService.newGroup("group");
    identityService.saveGroup(group);
}

用户关联用户组

@Test
public void testMembership() {
    Group group = identityService.createGroupQuery().groupId("group").singleResult();
    List<User> userList = identityService.createUserQuery().list();
    for (User user : userList) {
        identityService.createMembership(user.getId(), group.getId());
    }
}

流程图设置用户组

在这里插入图片描述

查询组待办任务

@Test
public void testQueryGroupTask() {
    List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("group").list();
}

网关

网关是Flowable 工作流引擎中的一个重要组成部分,用于控制流程的执行路径和决策。Flowable支持多种类型的网关,其中最常见的包括排他网关(Exclusive Gateway)、并行网关(Parallel Gateway)等。

排他网关

排他网关,也被称为异或网关(XOR Gateway),是 Flowable 流程模型中的一个重要组件。它的主要功能是根据设定的条件对流程的执行路径进行选择和判断。

当流程执行到排他网关时,会按照所有出口顺序流定义的顺序对它们进行计算,并选择第一个条件计算为 true 的顺序流继续执行。如果没有可选的顺序流(即所有条件都不满足),则会抛出异常。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

并行网关

并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起。其功能是基于进入和外出顺序流的。

当流程执行到并行网关的分支点时,会为每个外出顺序流创建一个并发分支,所有分支并行执行。当所有分支都执行完毕后,流程会汇聚到并行网关的汇聚点,然后继续执行。

与其他网关不同的是,并行网关会忽略条件。
在这里插入图片描述

包含网关

包含网关可以看作是排他网关(Exclusive Gateway)和并行网关(Parallel Gateway)的结合体。它允许根据条件选择性地执行多个路径,而不仅仅是单一路径或并行执行所有路径。

包含网关在需要基于条件选择性执行多个并行路径的场景中非常有用。例如,在审批流程中,可能需要根据不同的审批条件同时触发多个审批人进行审批。
在这里插入图片描述

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

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

相关文章

startData

某音startData 记得加入学习群&#xff1a; python爬虫&js逆向3 714283180

贝壳财报解读:彰显韧性,下场拿地,迈向新周期

众所周知&#xff0c;当前我国房地产行业已经迈入“存量房时代”&#xff0c;相比“大扩张时代”&#xff0c;更偏细水长流型&#xff0c;也为贝壳这类“科技驱动的一站式新居住服务平台”&#xff0c;提供了发展舞台。 日前&#xff0c;贝壳披露2024年第二季度财报&#xff1…

第40课 Scratch入门篇:绘制围棋棋盘

绘制围棋棋盘 故事背景: 作为一个围棋手,要有一个好的棋盘才行,让我们来设计一个属于自己的棋盘吧! 程序原理: 这节课的原理很简单,就是通过x,y坐标的偏移来画线,难度就是坐标点的设置,其实坐标用的习惯了,这块也不复杂,让我们一起开始学习! 开始编程 1、删除预…

鸿蒙Text部分文字变色

工具类&#xff1a; export class TextUtil {public static readonly REGEX_B_S "<B>"public static readonly REGEX_B_E "</B>"/*** 获取高亮字符串列表* param str 原始字符串*/public static getHlList(str ?: string, regex ?: strin…

【docker】Dockerfile练习

1、overlay文件系统原理测试 cd /mnt mkdir A B C worker merged echo "From A">./A/a.txt echo "From A">./A/b.txt echo "From A">./A/c.txt echo "From B">./B/a.txt echo "From B">./B/d.txt echo &quo…

smallpdf: 免费高效的PDF水印添加工具

引言 在数字文档管理和分享的过程中&#xff0c;保护版权和确保文档的原创性变得尤为重要。PDF文件作为一种广泛使用的格式&#xff0c;经常需要添加水印来表明所有权或提醒查看者注意文档的敏感性。本文将介绍一款名为smallpdf的免费工具&#xff0c;它能够轻松地为PDF文件添…

第41课 Scratch入门篇:显示声波图形

显示声波图形 故事背景: 电脑的麦克风可以收到各种声音,我们来看看,通过图形把麦克风的声音显示出来,设计一个绘制声音的声波图形 程序原理: 这节课的原理很简单,就是通过x,y坐标的偏移来画线,难度就是坐标点的设置,其实坐标用的习惯了,这块也不复杂,让我们一起开始…

R是一种强大的编程语言和环,你为何还需要RStudio?

下面内容摘录自《R 语言与数据科学的终极指南》专栏文章的部分内容&#xff0c;每篇文章都在 5000 字以上&#xff0c;质量平均分高达 94 分&#xff0c;看全文请点击下面链接&#xff1a; 2章1节&#xff1a;R和RStudio的下载和安装&#xff08;Windows 和 Mac&#xff09;_r…

机器学习深度学习中的Warmup技术是什么?

机器学习&深度学习中的Warmup技术是什么&#xff1f; 在机器学习&深度学习模型的训练过程中&#xff0c;优化器的学习率调整策略对模型的性能和收敛性至关重要。Warmup是优化器学习率调整的一种技术&#xff0c;旨在改善训练的稳定性&#xff0c;特别是在训练的初期阶…

netCDF文件读写处理

1.什么是 NetCDF&#xff1f; NetCDF 是一组软件库和自描述、独立于机器的数据格式&#xff0c;支持创建、访问和共享面向数组的科学数据。NetCDF 由Unidata开发和维护。Unidata 提供用于地球科学教育和研究的数据和软件工具。Unidata 是大学大气研究公司 ( UCAR ) 社区计划 (…

遗传算法与深度学习实战(4)——遗传算法详解与实现

遗传算法与深度学习实战&#xff08;4&#xff09;——遗传算法详解与实现 0. 前言1. 遗传算法简介1.1 遗传学和减数分裂1.2 类比达尔文进化论 2. 遗传算法的基本流程2.1 创建初始种群2.2 计算适应度2.3 选择、交叉和变异2.4算法终止条件 3. 使用 Python 实现遗传算法3.1 构建种…

基于IMX8M_plus+FPGA+AI监护仪解决方案

监护仪是一种以测量和控制病人生理参数&#xff0c;并可与已知设定值进行比较&#xff0c;如果出现超标可发出警报的装置或系统。 &#xff08;1&#xff09;监护仪主要采集测量人体生理参数&#xff0c;心电、血压、血氧、体温等需要采集处理大量的数据&#xff0c;系统需要多…

vue-quill-editor富文本组件返回值居中样式不生效

最近项目有用到富文本编辑器&#xff0c;用的是vue-quill-editor富文本组件&#xff0c;但在使用过程中发现个问题&#xff1a; 明明在编辑时已经设置居中&#xff0c;并且详情弹窗的回显也正常居中&#xff0c;但放到其他地方后&#xff0c;返回值的居中就不生效了 问题截图如…

ES高级查询Query DSL查询详解、term术语级别查询、全文检索、highlight高亮

文章目录 ES高级查询Query DSLmatch_all返回源数据_source返回指定条数size分页查询from&size指定字段排序sort 术语级别查询term query术语查询terms query多术语查询range query范围查询exists queryids queryprefix query前缀查询wildcard query通配符查询fuzzy query模…

阿里财报透视:谁在投入?谁在收缩?

8月15日晚&#xff0c;阿里巴巴发布2025财年Q1业绩。由于阿里今年频繁对外表态&#xff0c;所以市场也很关注这份财报能不能反映一点东西。 此前5月的年报电话会&#xff0c;阿里 CFO 徐宏曾说&#xff0c;阿里密切关注ROI。而到了7月&#xff0c;又有媒体报道称阿里内部已达成…

李晨晨的嵌入式学习 DAY27

今天主要学习了线程的两种退出方式以及分离线程和互斥锁 一&#xff0c;进程结束 1.从线程执行函数中return 2.pthread_cancel发送取消请求 3.任何一个函数使用exit或主函数return 二&#xff0c;线程资源的回收 1.pthread_join 主线程关系子线程状态 昨天有提到 2.pthrea…

嵌入式人工智能ESP32(4-PWM呼吸灯)

1、PWM基本原理 PWM&#xff08;Pulse-width modulation&#xff09;是脉冲宽度调制的缩写。脉冲宽度调制是一种模拟信号电平数字编码方法。脉冲宽度调制PWM是通过将有效的电信号分散成离散形式从而来降低电信号所传递的平均功率的一种方式。所以根据面积等效法则&#xff0c;…

【Python机器学习】FP-growth算法——构建FP树

在第二次扫描数据集时会构建一棵FP树。为构建一棵树&#xff0c;需要一个容器来保存树。 创建FP树的数据结构 FP树要比书中其他树更加复杂&#xff0c;因此需要创建一个类来保存树的每一个节点&#xff1a; class treeNode:def __init__(self,nameValue,numOccur,parentNode…

【GLM-4微调实战】GLM-4-9B-Chat模型之Lora微调实战

系列篇章&#x1f4a5; No.文章1【GLM-4部署实战】GLM-4-9B-Chat模型本地部署实践指南2【GLM-4部署实战】GLM-4-9B-Chat模型之对话机器人部署测试3【GLM-4部署实战】GLM-4-9B-Chat模型之vLLM部署推理实践4【GLM-4微调实战】GLM-4-9B-Chat模型之Lora微调实战 目录 系列篇章&…

SpringBoot整合SMS短信服务

SpringBoot整合SMS短信服务 概览pom依赖yml配置配置类service 层interfaceimpl controller层 概览 了解阿里云用户权限操作开通阿里云短信服务添加短信模板添加签名编写测试代码编写可复用的微服务接口&#xff0c;实现验证码的发送 pom依赖 <!--aliyun 短信服务--> &l…