学习笔记 | Activiti7

什么是工作流?

业务流程。

举个例子:

假设有一个在线博客平台,我们要让一篇新的文章从作者的头脑里发表出来。整个过程可以分为以下几个步骤:

创建文章草稿

:作者登录博客平台,点击“写新文章”的按钮,开始写文章。这篇文章的状态是“草稿”,还没有发布。

编辑和审阅

:作者写完后,可以选择把文章给编辑团队看。编辑们会读文章,提供建议和修改意见。文章还是“草稿”状态。

修改文章

:作者按照编辑的意见进行修改,并再次提交给编辑。这个过程可能需要多轮修改,直到编辑满意为止。

审稿

:编辑确认文章没有问题后,会将文章交给审稿人员。审稿人员会仔细检查文章内容,确保没有错误或问题。

发布文章

:经过审稿人员的确认,文章会被标记为“已发布”,并在博客平台上公开展示给读者们。

这样,一篇文章就从作者的构思,经过编辑和审稿,最终发布到博客平台上了。这个过程就是一个简单的工作流。

工作流的实现演变

在没有专门的工作流引擎之前,我们之前为了实现流程控制,通常的做法就是采用状态字段的值来跟踪流程的变化情况。这样不用角色的用户,通过状态字段的取值来决定记录是否显示。

这是一种最为原始的方式。通过状态字段虽然做到了流程控制,但是当我们的流程发生变更的时候,这种方式所编写的代码也要进行调整。比如:提交一个审批,一个小组长跟一个小职员的流程是不一样的。那逻辑就复杂了。

Activiti7概述

Activiti是一个工作流引擎, activiti可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统开发维护成本。

官方网站:

Open Source Business Automation | Activiti

BPM

BPM(Business Process Management),即业务流程管理,是一种规范化的构造端到端的业务流程,以持续的提高组织业务效率。

BPMN

BPMN是一种用来描述业务流程的图形化表示方法。它可以帮助企业和组织更好地理解、分析和改进自己的业务流程。

BPMN 是目前被各 BPM 厂商广泛接受的 BPM 标准。Activiti 就是使用 BPMN 2.0 进行流程建模、流程执行管理,它包括很多的建模符号,比如:

Event

用一个圆圈表示、它是流程在运行中发生的事件

活动用圆角矩形表示,一个流程由一个活动或多个活动组成

Actviti的使用步骤

1、集成Activiti

Activiti是一个工作流引擎(其实就是一堆jar包API),业务系统访问(操作)activiti的接口,就可以方便的操作流程相关数据,这样就可以把工作流环境与业务系统的环境集成在一起。

2、流程定义

使用activiti流程建模工具(activity-designer)定义业务流程(.bpmn文件) 。

.bpmn文件就是业务流程定义文件,通过xml定义业务流程。

3、流程定义部署

activiti部署业务流程定义(.bpmn文件)。

使用activiti提供的api把流程定义内容存储起来,在Activiti执行过程中可以查询定义的内容

Activiti执行把流程定义内容存储在数据库中

4、启动一个流程实例

流程实例也叫:ProcessInstance

启动一个流程实例表示开始一次业务流程的运行。

在员工请假流程定义部署完成后,如果张三要请假就可以启动一个流程实例,如果李四要请假也启动一个流程实例,两个流程的执行互相不影响。

5、用户查询待办任务(Task)

因为现在系统的业务流程已经交给activiti管理,通过activiti就可以查询当前流程执行到哪了,当前用户需要办理什么任务了,这些activiti帮我们管理了,而不需要开发人员自己编写在sql语句查询。

6、用户办理任务

用户查询待办任务后,就可以办理某个任务,如果这个任务办理完成还需要其它用户办理,比如采购单创建后由部门经理审核,这个过程也是由activiti帮我们完成了。

7、流程结束

当任务办理完成没有下一个任务结点了,这个流程实例就完成了。

创建Activiti需要的表

1 创建一个springboot的工程。

2 导入依赖

 <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <slf4j.version>1.6.6</slf4j.version>
    <log4j.version>1.2.12</log4j.version>
    <activiti.version>7.0.0.Beta1</activiti.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-engine</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn 模型处理 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-model</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn 转换 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-converter</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn json数据转换 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-json-converter</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- bpmn 布局 -->
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-bpmn-layout</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- activiti 云支持 -->
    <dependency>
        <groupId>org.activiti.cloud</groupId>
        <artifactId>activiti-cloud-services-api</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.40</version>
    </dependency>
    <!-- mybatis-plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    <!-- 链接池 -->
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>
</dependencies>

3 新建application.yml

spring:
  datasource:
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

4 新建activiti.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 这里可以使用 链接池 dbcp-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql:///activiti-demo" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <property name="maxActive" value="3" />
        <property name="maxIdle" value="1" />
    </bean>

    <bean id="processEngineConfiguration"
          class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!-- 引用数据源 上面已经设置好了-->
        <property name="dataSource" ref="dataSource" />
        <!-- activiti数据库表处理策略 -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>

</beans>

5 创建测试类执行方法

package cn.swj.activiti;

import cn.swj.activity.ActivitiDemoApplication;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @Author swj
 * @Date 2023/7/20 11:03
 * @Description: TODO
 * @Version 1.0
 */
@SpringBootTest(classes = ActivitiDemoApplication.class)
@RunWith(SpringRunner.class)
public class ActivitiDemoTest {

    /**
     * 生成 activiti的数据库表
     */
    @Test
    public void testCreateDbTable() {
        //使用classpath下的activiti.cfg.xml中的配置创建processEngine
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        System.out.println(processEngine);
    }
}

表结构的介绍

ACT_RE : 'RE'表示 repository。 这个前缀的表包含了流程定义和流程静态资源 (图片,规则,等等)。

ACT_RU:'RU'表示 runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti 只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。 这样运行时表可以一直很小速度很快。

ACT_HI:'HI'表示 history。 这些表包含历史数据,比如历史流程实例, 变量,任务等等。

ACT_GE : GE 表示 general。 通用数据, 用于不同场景下

表分类

表名

解释

一般数据

[ACT_GE_BYTEARRAY]

通用的流程定义和流程资源

[ACT_GE_PROPERTY]

系统相关属性

流程历史记录

[ACT_HI_ACTINST]

历史的流程实例

[ACT_HI_ATTACHMENT]

历史的流程附件

[ACT_HI_COMMENT]

历史的说明性信息

[ACT_HI_DETAIL]

历史的流程运行中的细节信息

[ACT_HI_IDENTITYLINK]

历史的流程运行过程中用户关系

[ACT_HI_PROCINST]

历史的流程实例

[ACT_HI_TASKINST]

历史的任务实例

[ACT_HI_VARINST]

历史的流程运行中的变量信息

流程定义表

[ACT_RE_DEPLOYMENT]

部署单元信息

[ACT_RE_MODEL]

模型信息

[ACT_RE_PROCDEF]

已部署的流程定义

运行实例表

[ACT_RU_EVENT_SUBSCR]

运行时事件

[ACT_RU_EXECUTION]

运行时流程执行实例

[ACT_RU_IDENTITYLINK]

运行时用户关系信息,存储任务节点与参与者的相关信息

[ACT_RU_JOB]

运行时作业

[ACT_RU_TASK]

运行时任务

[ACT_RU_VARIABLE]

运行时变量表

Activiti的六大Service服务

RepositoryService 仓储服务

@Autowired
private RepositoryService repositoryService;

仓储服务可以用来部署我们的流程图,还可以创建我们的流程部署查询对象,用于查询刚刚部署的流程列表,便于我们的管理流程,方法如下。

主要操作:流程部署、流程定义

@Autowired
private RepositoryService repositoryService;

/**
 * 部署流程部署*
 */
@Test
public void createDeployment() {

    //1、使用RepositoryService进行部署
    Deployment deployment = repositoryService.createDeployment()
            .addClasspathResource("bpmn/test.bpmn20.xml") // 添加bpmn资源
            .name("出差申请流程")
            .deploy();

    log.info("DeploymentId   ---------- {}",deployment.getId());
    log.info("DeploymentName ---------- {}",deployment.getName());

}

/**
 * 查询所有流程部署*
 */
@Test
public void list() {
    DeploymentQuery deploymentQuery = repositoryService.createDeploymentQuery();
    List<Deployment> list = deploymentQuery.list();
    list.forEach(System.out::println);
}

/**
 * 根据名字查询流程部署*
 */
@Test
public void listByName() {
    DeploymentQuery deploymentQuery = repositoryService.createDeploymentQuery();
    deploymentQuery.deploymentNameLike("%出差%");
    List<Deployment> list = deploymentQuery.list();
    list.forEach(System.out::println);
}

/**
 * 删除流程部署*
 */
@Test
public void delete() {
    //repositoryService.deleteDeployment("15001");
    repositoryService.deleteDeployment("15001",true);
}

/**
 * 查询所有流程定义*
 */
@Test
public void queryProcessDefinition() {
    List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
    list.forEach(System.out::println);
}

/**
 * 根据流程部署id查询所有流程定义*
 */
@Test
public void queryProcessDefinitionByDeploymentId() {
    List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId("15001").list();
    list.forEach(System.out::println);
}

RunTimeServce 运行时服务

运⾏时服务主要⽤来开启流程实例,⼀个流程实例对应多个任务,也就是多个流程节点,⽐如请假审批是⼀个流程实例,部⻔主管,部⻔经理,总经理都是节点,我们开启服务是通过流程定义key或者流程定义id来开启的,⽅法如下:

主要操作:开启流程实例

@Autowired
private RuntimeService runtimeService;

@Autowired
private RepositoryService repositoryService;

/**
 * 启动流程实例,只能根据流程定义id或者流程定义key去启动实例*
 */
@Test
public void startProcessInstance() {
    ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId("15001").singleResult();
    ProcessInstance processInstance = runtimeService.startProcessInstanceById(def.getId());

    log.info("processInstanceId ------- {}",processInstance.getId());
    log.info("deploymentId      ------- {}",processInstance.getProcessDefinitionId());
    log.info("activityId        ------- {}",processInstance.getActivityId());
}

/**
 * 查询所有流程实例*
 */
@Test
public void listProcessInstance() {
    List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery().processDefinitionId("test:1:15003").list();
    processInstanceList.forEach(System.out::println);
}

/**
 * 开启流程实例的时候添加我们自己的业务id*
 */
@Test
public void startWithSetBusinessKey() {
    //获取流程定义id
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId("22501").singleResult();
    //启动实例
    ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), "businessId");
    System.out.println(processInstance.getBusinessKey());
}

当我们⽤仓储服务部署了流程图之后,就会产⽣⼀个流程部署id,⼀个流程部署id对应⼀个流程定义,⼀个流程定义对应多个流程实例,流程定义和流程实例之间的关系就好⽐是类和对象的关系。⼀个流程实例对应多个任务节点。

TaskService 任务服务

主要操作: 任务

任务服务是⽤来可以⽤来领取,完成,查询任务列表功能的,使⽤⽅法分别如下:

/**
 * //根据任务id和⽤户领取任务
 * taskService.claim(String taskId, String userId)
 * //根据任务id完成⾃⼰节点的任务
 * taskService.complete(String taskId)
 * //创建任务查询对象之后根据候选⼈也就是任务处理⼈查询⾃⼰的任务列表
 * taskService.createTaskQuery().taskAssignee(String assignee)
 */

@Autowired
private TaskService taskService;

/**
 * 查询流程实例的所有*
 */
@Test
public void listTask() {
    List<Task> taskList = taskService.createTaskQuery().processDefinitionId("test:1:15003").taskAssignee("zhansan").list();
    taskList.forEach(System.out::println);
}

/**
 * 完成自己的任务*
 */
@Test
public void complete() {
    Task task = taskService.createTaskQuery().taskAssignee("zhansan").processDefinitionId("test:1:15003").singleResult();
    taskService.complete(task.getId());
}

HistoryService 历史服务

主要操作:历史信息

历史服务可以查看审批⼈曾经审批完成了哪些项⽬,审批项⽬总共花了多少时间,以及在哪个环节⽐较耗费时间等等,便于审批⼈查看历史信息,⽅法如下。

@Autowired
private HistoryService historyService;

@Test
public void listTaskByAssignee() {
    List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().taskAssignee("zhansan").finished().list();
    for (HistoricTaskInstance taskInstance : list) {
        System.out.println(taskInstance.getId());
        System.out.println(taskInstance.getName());
        System.out.println(taskInstance.getProcessDefinitionId());
        System.out.println(taskInstance.getProcessInstanceId());
        System.out.println(taskInstance.getWorkTimeInMillis());
        System.out.println("<==========================>");
    }
}

DynamicBpmnService 修改流程定义的服务

在Activiti中,DynamicBpmnService 是一个用于动态修改和更新流程定义(BPMN模型)的服务。它允许您在流程部署后,通过编程方式对已部署的流程定义进行修改,添加或删除流程元素,而无需重新部署整个流程。

使用 DynamicBpmnService 可以实现以下功能:

修改流程元素:

您可以在运行时通过 DynamicBpmnService 修改已部署的流程的各种元素,如任务、连接线、条件等。这对于需要根据运行时条件调整流程逻辑的场景非常有用。

动态添加任务:

您可以通过 DynamicBpmnService 动态地向流程中添加任务、网关、事件等元素,从而在不停机的情况下扩展或调整流程的功能。

删除任务:

如果需要,您也可以使用 DynamicBpmnService 从已部署的流程定义中删除任务或其他元素,以适应业务变化。

流程优化:

可以使用该服务来进行流程优化,以实现更高效的业务流程。

总之,DynamicBpmnService 提供了一种在流程运行时进行动态流程定义修改的机制,使您能够根据实际需求对已部署的流程进行灵活调整,而无需重新部署整个流程。这对于灵活性和快速响应业务变化是非常有帮助的。需要注意的是,虽然这种动态修改流程的能力非常强大,但也需要小心使用,以确保不会引入意外的问题。

ManagementService 管理和监控引擎运行时状态的服务

在Activiti中,ManagementService 是一个用于管理和监控引擎运行时状态的服务。它提供了一些用于管理和监控流程引擎的操作,使您能够以编程方式执行一些管理任务,而无需直接访问底层的数据库或进行复杂的配置。

ManagementService 提供了一系列方法,用于执行以下操作:

作业管理:

ManagementService 允许您管理作业(jobs),这些作业用于异步执行一些任务,如定时器触发的事件。您可以查询、挂起、恢复、删除和重新执行作业。

流程实例删除:

您可以使用 ManagementService 删除特定的流程实例,不管其当前的状态。

数据库操作:

ManagementService 提供了一些方法来执行数据库操作,如执行原生的SQL查询。

清理历史数据:

您可以使用 ManagementService 执行一些历史数据的清理操作,以便维护数据库性能。

引擎配置:

您可以获取和修改引擎的配置信息,如数据库表前缀等。

执行异步操作:

ManagementService 提供了一些方法来执行一些耗时的操作,如数据的导入和导出。

需要注意的是,ManagementService 提供的操作通常是对引擎运行时状态进行管理和维护的,这些操作可能具有潜在的风险,因此需要小心谨慎地使用。

总之,ManagementService 是用于管理和监控Activiti引擎运行时状态的服务,提供了一些管理操作和功能,使您能够在运行时对引擎进行维护和管理。

Activiti入门

创建Activiti工作流主要包含以下几步:

1、定义流程,按照BPMN的规范,使用流程定义工具,用流程符号把整个流程描述出来

2、部署流程,把画好的流程定义文件,加载到数据库中,生成表的数据

3、启动流程,使用java代码来操作数据库表中的内容

流程符号

事件event

活动 Activity

活动是工作或任务的一个通用术语。一个活动可以是一个任务,还可以是一个当前流程的子处理流程; 其次,你还可以为活动指定不同的类型。常见活动如下:

网关 GateWay

网关用来处理决策,有几种常用网关需要了解:

排他网关 (x)

——只有一条路径会被选择。流程执行到该网关时,按照输出流的顺序逐个计算,当条件的计算结果为true时,继续执行当前网关的输出流;

如果多条线路计算结果都是 true,则会执行第一个值为 true 的线路。如果所有网关计算结果没有true,则引擎会抛出异常。

排他网关需要和条件顺序流结合使用,default 属性指定默认顺序流,当所有的条件不满足时会执行默认顺序流。

并行网关 (+)

——所有路径会被同时选择

拆分 —— 并行执行所有输出顺序流,为每一条顺序流创建一个并行执行线路。

合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。

包容网关 (+)

—— 可以同时执行多条线路,也可以在网关上设置条件

拆分 —— 计算每条线路上的表达式,当表达式计算结果为true时,创建一个并行线路并继续执行

合并 —— 所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。

事件网关 (+)

—— 专门为中间捕获事件设置的,允许设置多个输出流指向多个不同的中间捕获事件。当流程执行到事件网关后,流程处于等待状态,需要等待抛出事件才能将等待状态转换为活动状态。

流向 Flow

流是连接两个流程节点的连线。常见的流向包含以下几种:

入门程序

安装activiti BPM visualizer

先画一个流程文件

画出来这个

//部署流程定义
//1、创建ProcessEngine
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2、得到RepositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
//3、使用RepositoryService进行部署
Deployment deployment = repositoryService.createDeployment()
        .addClasspathResource("bpmn/test.bpmn20.xml") // 添加bpmn资源
        .name("出差申请流程")
        .deploy();
//4、输出部署信息
System.out.println("流程部署id:" + deployment.getId());
System.out.println("流程部署名称:" + deployment.getName());

流程定义部署在activiti后就可以通过工作流管理业务流程了,也就是说上边部署的出差申请流程可以使用了。

针对该流程,启动一个流程表示发起一个新的出差申请单,这就相当于java类与java对象的关系,类定义好后需要new创建一个对象使用,当然可以new多个对象。对于请出差申请流程,张三发起一个出差申请单需要启动一个流程实例,出差申请单发起一个出差单也需要启动一个流程实例。

代码如下:

/**
 * 启动流程实例
 */
@Test
public void testStartProcess(){
    //1、创建ProcessEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    //2、获取RunTimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    //3、根据流程定义Id启动流程
    ProcessInstance processInstance = runtimeService
            .startProcessInstanceByKey("test");
    //输出内容
    System.out.println("流程定义id:" + processInstance.getProcessDefinitionId());
    System.out.println("流程实例id:" + processInstance.getId());
    System.out.println("当前活动Id:" + processInstance.getActivityId());
}

流程启动后,任务的负责人就可以查询自己当前需要处理的任务,查询出来的任务都是该用户的待办任务。

/**
     * 查询当前个人待执行的任务
     */
    @Test
    public void testFindPersonalTaskList() {
//        任务负责人
        String assignee = "zhangsan";
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        创建TaskService
        TaskService taskService = processEngine.getTaskService();
//        根据流程key 和 任务负责人 查询任务
        List<Task> list = taskService.createTaskQuery()
                .processDefinitionKey("test") //流程Key
                .taskAssignee(assignee)//只查询该任务负责人的任务
                .list();

        for (Task task : list) {

            System.out.println("流程实例id:" + task.getProcessInstanceId());
            System.out.println("任务id:" + task.getId());
            System.out.println("任务负责人:" + task.getAssignee());
            System.out.println("任务名称:" + task.getName());

        }
    }

任务负责人查询待办任务,选择任务进行处理,完成任务。

// 完成任务
    @Test
    public void completTask(){
//        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取taskService
        TaskService taskService = processEngine.getTaskService();

//        根据流程key 和 任务的负责人 查询任务
//        返回一个任务对象
        Task task = taskService.createTaskQuery()
                .processDefinitionKey("test") //流程Key
                .taskAssignee("lishi")  //要查询的负责人
                .singleResult();

//        完成任务,参数:任务id
        taskService.complete(task.getId());
    }

查询流程相关信息,包含流程定义,流程部署,流程定义版本

/**
     * 查询流程定义
     */
    @Test
    public void queryProcessDefinition(){
        //        获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        得到ProcessDefinitionQuery 对象
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
//          查询出当前所有的流程定义
//          条件:processDefinitionKey =evection
//          orderByProcessDefinitionVersion 按照版本排序
//        desc倒叙
//        list 返回集合
        List<ProcessDefinition> definitionList = processDefinitionQuery.processDefinitionKey("test")
                .orderByProcessDefinitionVersion()
                .desc()
                .list();
//      输出流程定义信息
        for (ProcessDefinition processDefinition : definitionList) {
            System.out.println("流程定义 id="+processDefinition.getId());
            System.out.println("流程定义 name="+processDefinition.getName());
            System.out.println("流程定义 key="+processDefinition.getKey());
            System.out.println("流程定义 Version="+processDefinition.getVersion());
            System.out.println("流程部署ID ="+processDefinition.getDeploymentId());
        }

    }

/**
 * 删除流程*
 */
@Test
public void deleteDeployment() {
    // 流程部署id
    String deploymentId = "1";

    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 通过流程引擎获取repositoryService
    RepositoryService repositoryService = processEngine
            .getRepositoryService();
    //删除流程定义,如果该流程定义已有流程实例启动则删除时出错
    repositoryService.deleteDeployment(deploymentId,true);
    //设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
    //repositoryService.deleteDeployment(deploymentId, true);
}

说明:

1) 使用repositoryService删除流程定义,历史表信息不会被删除

2) 如果该流程定义下没有正在运行的流程,则可以用普通删除。

如果该流程定义下存在已经运行的流程,使用普通删除报错,可用级联删除方法将流程及相关记录全部删除。

先删除没有完成流程节点,最后就可以完全删除流程定义信息

项目开发中级联删除操作一般只开放给超级管理员使用.

 //下载文件
 @Test
    public void  queryBpmnFile() throws IOException {
//        1、得到引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        2、获取repositoryService
        RepositoryService repositoryService = processEngine.getRepositoryService();
//        3、得到查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey("myEvection")
                .singleResult();
//        4、通过流程定义信息,得到部署ID
        String deploymentId = processDefinition.getDeploymentId();
//        5、通过repositoryService的方法,实现读取图片信息和bpmn信息
//        png图片的流
        InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
//        bpmn文件的流
        InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
//        6、构造OutputStream流
        File file_png = new File("d:/evectionflow01.png");
        File file_bpmn = new File("d:/evectionflow01.bpmn");
        FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
        FileOutputStream pngOut = new FileOutputStream(file_png);
//        7、输入流,输出流的转换
        IOUtils.copy(pngInput,pngOut);
        IOUtils.copy(bpmnInput,bpmnOut);
//        8、关闭流
        pngOut.close();
        bpmnOut.close();
        pngInput.close();
        bpmnInput.close();
    }

即使流程定义已经删除了,流程执行的历史信息通过前面的分析,依然保存在activiti的act_hi_*相关的表中。所以我们还是可以查询流程执行的历史信息,可以通过HistoryService来查看相关的历史记录。

/**
     * 查看历史信息
     */
    @Test
    public void findHistoryInfo(){
//      获取引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//        获取HistoryService
        HistoryService historyService = processEngine.getHistoryService();
//        获取 actinst表的查询对象
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
//        查询 actinst表,条件:根据 InstanceId 查询
//        instanceQuery.processInstanceId("2501");
//        查询 actinst表,条件:根据      查询
        instanceQuery.processDefinitionId("test:1:3");
//        增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序
        instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
//        查询所有内容
        List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
//        输出
        for (HistoricActivityInstance hi : activityInstanceList) {
            System.out.println(hi.getActivityId());
            System.out.println(hi.getActivityName());
            System.out.println(hi.getProcessDefinitionId());
            System.out.println(hi.getProcessInstanceId());
            System.out.println("<==========================>");
        }
    }

流程实例绑定业务id

Activiti提供的数据是针对于该框架所需要的流程控制维护的数据,也就是数据库25张表存放的数据,但是在业务系统中,业务数据如何与Activiti框架进⾏关联?通过流程实例数据的BusinessKey字段来实现关联。在创建流程实例时,指明BusinessKey即可。

示例代码:

/**
 * 开启流程实例的时候添加我们自己的业务id*
 */
@Test
public void startWithSetBusinessKey() {
    //获取流程定义id
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId("22501").singleResult();
    //启动实例
    ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), "businessId");
    System.out.println(processInstance.getBusinessKey());
}

流程实例的挂起跟激活

当因为业务需要,将执⾏的流程挂起,或者将已被挂起的流程激活,可以通过Activiti框架,对流程进⾏挂起和激活。

@Test
public void processInstanceIsSuspended() {
    //获取流程定义id
    ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId("22501").singleResult();
    //1、获取流程实例
    ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processDefinitionId(def.getId()).singleResult();
    boolean suspended = processInstance.isSuspended();
    //如果激活
    if(!suspended) {
        //挂起实例
        runtimeService.suspendProcessInstanceById(processInstance.getId());
    }

    //如果挂起
    if(suspended) {
        //激活实例
        runtimeService.activateProcessInstanceById(processInstance.getId());
    }

}

使⽤变量设置Assignee

1、在画流程图的时候需要在assignee那边写一个插值表达式

${assginee0}

2、在创建实例的时候new一个Map在值存到里面并将map作为值传入创建实例的方法中

示例代码:

@Test
public void createProcessInstanceWithCustomAssignee() {
    //1、获取流程定义id
    ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId("30001").singleResult();

    //2、新建一个流程实例

    Map<String, Object> map = new HashMap<>();
    map.put("assignee0","zhangsan");
    map.put("assignee1","lishi");
    map.put("assignee2","wangwu");

    ProcessInstance processInstance = runtimeService.startProcessInstanceById(def.getId(), map);


    System.out.println(processInstance.getId());

}

使⽤变量驱动流程⾛向

一样使用插值表达式

@Test
public void createProcessInstanceWithCustomAssignee() {
    //1、获取流程定义id
    ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId("30001").singleResult();

    //2、新建一个流程实例

    Map<String, Object> map = new HashMap<>();
    map.put("assignee0","zhangsan");
    map.put("assignee1","lishi");
    map.put("assignee2","wangwu");
    map.put("days","3");

    ProcessInstance processInstance = runtimeService.startProcessInstanceById(def.getId(), map);


    System.out.println(processInstance.getId());

}

使⽤Local局部变量

有的变量只是在某个节点会使用到这样子就可以直接使用局部变量

@Test
public void localVariable() {
    taskService.setVariableLocal("123","day",3);
    taskService.complete("123");
}

组任务

如果任务负责⼈是单⼀存在的,且任务负责⼈因为默写原因没有办法完成任务,那么流程就没办法执⾏下去了。此时可以为任务设置候选⼈,通过候选⼈拾取、执⾏、归还等操作,完成组任务相关的操作

任务候选⼈

创建任务候选⼈,允许⼀个任务可以被多个负责⼈领取。通过流程图或者程序来指明任务的候选⼈。查询候选⼈查询任务的代码如下:

可以通过去候选人去查询任务

List<Task> list = taskService.createTaskQuery()
        .processDefinitionId("defId")
        .taskCandidateOrAssigned("候选人或者责任人")
        .list();

候选⼈拾取任务

任务候选⼈先拾取任务,才能执⾏任务

Task task = taskService.createTaskQuery()
        .processDefinitionId("defId")
        .taskCandidateUser("候选人")
        .singleResult();

taskService.claim(task.getId(),"候选人");

候选⼈归还任务

候选⼈拾取任务后,在没执⾏任务之前,可以归还任务

Task task = taskService.createTaskQuery()
        .processDefinitionId("defId")
        .taskCandidateUser("候选人")
        .singleResult();

taskService.setAssignee(task.getId(),null);

任务转交

任务候选⼈拾取完任务后,可以选择归还任务,也可以选择把任务转交给其他候选⼈。

Task task = taskService.createTaskQuery()
        .processDefinitionId("defId")
        .taskCandidateUser("候选人")
        .singleResult();

taskService.setAssignee(task.getId(),"otherUser");

SpringBoot整合Activiti7入门案例

1、创建一个SpringBoot工程

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


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.14</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

2、导入依赖

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <activiti.version>7.0.0.Beta1</activiti.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.activiti</groupId>
        <artifactId>activiti-spring-boot-starter</artifactId>
        <version>${activiti.version}</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.40</version>
    </dependency>
    <!-- mybatis-plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    <!-- 链接池 -->
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>
</dependencies>

3、新建俩个配置文件

数据库连接信息根据自己的信息进行修改

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                    http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 这里可以使用 链接池 dbcp-->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql:///activiti-demo" />
        <property name="username" value="root" />
        <property name="password" value="root" />
        <property name="maxActive" value="3" />
        <property name="maxIdle" value="1" />
    </bean>

    <bean id="processEngineConfiguration"
          class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
        <!-- 引用数据源 上面已经设置好了-->
        <property name="dataSource" ref="dataSource" />
        <!-- activiti数据库表处理策略 -->
        <property name="databaseSchemaUpdate" value="true"/>
    </bean>

</beans>
spring:
  datasource:

    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  activiti:
    database-schema-update: true
    db-history-used: true
    history-level: full

4、在resource下新建一个processes目录

5、初始化bean

package cn.swj.activity.config;

import org.activiti.engine.*;
import org.activiti.engine.test.ActivitiRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author swj
 * @Date 2023/8/5 16:22
 * @Description: TODO
 * @Version 1.0
 */
@Configuration
public class ActivitiConfig {

    //创建ProcessEngine
    private ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

    @Bean
    public RepositoryService repositoryService() {

        //1、得到RepositoryService实例
        RepositoryService repositoryService = processEngine.getRepositoryService();

        return repositoryService;
    }

    @Bean
    public RuntimeService runtimeService() {

        //1、得到 RuntimeService 实例
        RuntimeService runtimeService = processEngine.getRuntimeService();

        return runtimeService;
    }

    @Bean
    public TaskService taskService() {

        //1、得到 TaskService 实例
        TaskService taskService = processEngine.getTaskService();

        return taskService;
    }

    @Bean
    public HistoryService historyService() {

        //1、得到 TaskService 实例
        HistoryService historyService = processEngine.getHistoryService();

        return historyService;
    }

    @Bean
    public DynamicBpmnService dynamicBpmnService() {

        //1、得到 DynamicBpmnService 实例
        DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();

        return dynamicBpmnService;
    }

    @Bean
    public ManagementService managementService() {

        //1、得到 ManagementService 实例
        ManagementService managementService = processEngine.getManagementService();

        return managementService;
    }


}

6、安装插件

7、画一个流程图

在resource\processes下新建一个流程图

<?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:activiti="http://activiti.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.activiti.org/processdef">
  <process id="leave" name="leave" isExecutable="true">
    <startEvent id="sid-b2417e62-b70b-4f06-90b4-4a9c453e7ebc"/>
    <endEvent id="sid-a25bc48b-1191-413d-a0ab-263c2d27793b"/>
    <userTask id="sid-37861cb6-80d2-45dc-9b50-375ad320a01f" name="员工提交请假申请" activiti:assignee="${assignee0}"/>
    <userTask id="sid-b6df96ad-44df-4295-bbe6-c74ab4cf7f7e" name="上级领导审批" activiti:assignee="${assignee1}"/>
    <userTask id="sid-204ae414-37c9-47e2-a84b-bd34dcb7463a" name="人事记录" activiti:assignee="${assignee2}"/>
    <sequenceFlow id="sid-effa22e4-daaa-4508-9628-7906e9bfd274" sourceRef="sid-b2417e62-b70b-4f06-90b4-4a9c453e7ebc" targetRef="sid-37861cb6-80d2-45dc-9b50-375ad320a01f"/>
    <sequenceFlow id="sid-2a2c5aa6-2f52-4963-9ca4-f63412cd8b67" sourceRef="sid-37861cb6-80d2-45dc-9b50-375ad320a01f" targetRef="sid-b6df96ad-44df-4295-bbe6-c74ab4cf7f7e"/>
    <sequenceFlow id="sid-24db9d32-2541-41a3-84e4-1228f1b1e806" sourceRef="sid-b6df96ad-44df-4295-bbe6-c74ab4cf7f7e" targetRef="sid-204ae414-37c9-47e2-a84b-bd34dcb7463a"/>
    <sequenceFlow id="sid-fda019e5-a786-441a-b226-22993b36c82a" sourceRef="sid-204ae414-37c9-47e2-a84b-bd34dcb7463a" targetRef="sid-a25bc48b-1191-413d-a0ab-263c2d27793b"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_leave">
    <bpmndi:BPMNPlane bpmnElement="leave" id="BPMNPlane_leave">
      <bpmndi:BPMNShape id="shape-27b24655-a3d8-4cb6-b1e2-d94f9382dfc9" bpmnElement="sid-b2417e62-b70b-4f06-90b4-4a9c453e7ebc">
        <omgdc:Bounds x="-70.0" y="-145.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-f86c2c60-9f01-4430-943e-ea5232d724df" bpmnElement="sid-a25bc48b-1191-413d-a0ab-263c2d27793b">
        <omgdc:Bounds x="-75.0" y="200.0" width="30.0" height="30.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="shape-ac9635c3-16cb-42e3-a5dc-ed54f96a2d47" bpmnElement="sid-37861cb6-80d2-45dc-9b50-375ad320a01f">
        <omgdc:Bounds x="-110.0" y="-95.0" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="sid-b8b9d748-c04f-4624-b13b-5ad6367d5f84" bpmnElement="sid-b6df96ad-44df-4295-bbe6-c74ab4cf7f7e">
        <omgdc:Bounds x="-110.0" y="0.95951843" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="sid-9a68376e-da4c-464a-be4e-c2d48feaf397" bpmnElement="sid-204ae414-37c9-47e2-a84b-bd34dcb7463a">
        <omgdc:Bounds x="-110.0" y="94.34161" width="100.0" height="80.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="edge-3dc5ee67-24c6-475f-9fb9-dbffa94892f7" bpmnElement="sid-effa22e4-daaa-4508-9628-7906e9bfd274">
        <omgdi:waypoint x="-62.5" y="-115.0"/>
        <omgdi:waypoint x="-60.0" y="-95.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-ddc9236c-49bd-410b-b40b-a99899d08eef" bpmnElement="sid-2a2c5aa6-2f52-4963-9ca4-f63412cd8b67">
        <omgdi:waypoint x="-60.0" y="-15.0"/>
        <omgdi:waypoint x="-60.0" y="0.95951843"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-002614a7-62e7-4307-b60e-855b2863b828" bpmnElement="sid-24db9d32-2541-41a3-84e4-1228f1b1e806">
        <omgdi:waypoint x="-60.0" y="80.95952"/>
        <omgdi:waypoint x="-60.0" y="94.34161"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="edge-afc8140f-3ffc-403e-9bbb-b62819df498b" bpmnElement="sid-fda019e5-a786-441a-b226-22993b36c82a">
        <omgdi:waypoint x="-60.0" y="174.34161"/>
        <omgdi:waypoint x="-60.0" y="200.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

8、启动项目

9、编写测试用例

package cn.swj.activiti;

import cn.hutool.core.util.ObjectUtil;
import cn.swj.activity.ActivitiDemoApplication;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;

/**
 * @Author swj
 * @Date 2023/8/6 16:12
 * @Description: TODO
 * @Version 1.0
 */
@Slf4j
@SpringBootTest(classes = ActivitiDemoApplication.class)
@RunWith(SpringRunner.class)
public class SubmitLeaveTest {

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;


    /**
     * 1、员工提交请假申请
     * 2、上级领导审批
     * 3、人事记录
     */


    /**
     * 员工提交请假申请*
     */
    @Test
    public void submitLeave() {
        //1、创建流程实例
        //初始化流程里面的负责人信息
        HashMap<String, Object> map = new HashMap<>();
        map.put("assignee0", "zhangsan");
        map.put("assignee1", "lishi");
        map.put("assignee2", "wangwu");
        //创建实例
        ProcessDefinition def = repositoryService.createProcessDefinitionQuery()
                .deploymentId("d0d0f7c2-3439-11ee-83a8-cec84ba31bc7")
                .singleResult();
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(def.getId(), map);


        //2、完成张三任务
        Task task = taskService.createTaskQuery().deploymentId("d0d0f7c2-3439-11ee-83a8-cec84ba31bc7").taskAssignee("zhangsan").singleResult();

        if (ObjectUtil.isEmpty(task)) {
            return;
        }

        taskService.complete(task.getId());

        log.info("张三请假申请已经提交~");


        //3、完成李四的任务
        //查询李四的任务
        Task lishiTask = taskService.createTaskQuery().deploymentId("d0d0f7c2-3439-11ee-83a8-cec84ba31bc7").taskAssignee("lishi").singleResult();

        if(ObjectUtil.isEmpty(lishiTask)) {
            return;
        }

        log.info("查询李四的任务: {}", lishiTask.getName());

        //完成

        taskService.complete(lishiTask.getId());

        //4、完成王五的任务
        //查询王五的任务
        Task wangwuTask = taskService.createTaskQuery().deploymentId("d0d0f7c2-3439-11ee-83a8-cec84ba31bc7").taskAssignee("wangwu").singleResult();

        //完成
        if(ObjectUtil.isEmpty(wangwuTask)) {
            return;
        }

        log.info("查询王五的任务: {}", wangwuTask.getName());

        taskService.complete(wangwuTask.getId());

        log.info("流程结束~");


    }
}

相关推荐

  1. Activity7框架使用学习记录

    2024-01-12 14:54:02       9 阅读
  2. Android学习笔记activity生命周期

    2024-01-12 14:54:02       23 阅读
  3. Activiti7 整合SpringBoot

    2024-01-12 14:54:02       26 阅读
  4. Vue学习笔记-activated和deactivated生命周期

    2024-01-12 14:54:02       37 阅读
  5. YOLOv7 学习笔记

    2024-01-12 14:54:02       39 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-12 14:54:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-12 14:54:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-12 14:54:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-12 14:54:02       18 阅读

热门阅读

  1. 算法习题练习

    2024-01-12 14:54:02       28 阅读
  2. .net core 6 集成和使用 mongodb

    2024-01-12 14:54:02       36 阅读
  3. 【大数据面试】常见数仓建模面试题附答案

    2024-01-12 14:54:02       33 阅读
  4. vue3知识盲点总结

    2024-01-12 14:54:02       28 阅读
  5. js中console.log()的使用方法

    2024-01-12 14:54:02       35 阅读
  6. 箭头函数与普通函数的差异

    2024-01-12 14:54:02       34 阅读
  7. Django身份验证初试

    2024-01-12 14:54:02       43 阅读
  8. 两两交换链表中的节点【链表】

    2024-01-12 14:54:02       38 阅读
  9. 使用OTB数据集需要注意的一个问题

    2024-01-12 14:54:02       37 阅读