camunda流程引擎——Java集成Camunda(上)(笔记)

目录

  • 一、以一个处理流程开始
    • 1.1 后端
    • 1.2 前端
    • 1.3 执行
  • 二、Camunda的补充
    • 2.1 使用方式
    • 2.2 可视化平台的Cockpit
    • 2.3 流程相关数据
    • 2.4 表介绍
    • 2.5 前端集成Modeler
  • 三、用Java集成Camunda
    • 3.1 集成配置
    • 3.2 自动部署
      • 3.2.1 修改process.xml位置
      • 3.2.2 多进程引擎配置与多租户
    • 3.3 历史事件配置
      • 3.3.1 查询
      • 3.3.2 任务报告
    • 3.4 Service
    • 3.5 用户业务
    • 3.6 流程启动Controller
    • 3.7 业务任务-内部任务
      • 3.7.1 Java Class实现
      • 3.7.2 Delegate Expression实现
      • 3.7.3 Express实现
      • 3.7.4 流程的回退与重启与暂停
        • 回退
        • 重启
        • 暂停
    • 3.8 业务任务-外部任务
      • 3.8.1 异步响应:长轮询(Long Polling)
      • 3.8.2 注解方式
        • 配置与依赖
        • 写一个外部任务
        • 重试方式
        • 优先级
    • 3.9 任务监听器(引擎端)
    • 3.10 鉴权
    • 3.11 多实例任务
      • 3.11.1 顺序执行
      • 3.11.2 多实例内置变量
      • 3.11.3 并行执行
    • 3.12 脚本任务
  • 附录

集成与简单的使用请参看前文。camunda流程引擎基本使用(笔记)

流程活动可参考-流程引擎实施参考

例子学习于blibli-camunda工作流实战课程,该视频简介者有一些地方是错的,谨慎观看。

一、以一个处理流程开始

我们建立如下流程,安排:

  • 后端:加入购物车与物流发货。使用SpringBoot配合Camunda的包
  • 前端:付款(只有逻辑)。使用nodejs代码配合Camuanda的包(也可以用VUE等方式)
    在这里插入图片描述

1.1 后端

后端配置如下内容:

server:
  port: 8081

camunda:
  connect:
    url: "http://localhost:8080/engine-rest"
    timeout: 10000

注意,笔者现在使用的是7.2版本,和上篇文章版本不同(可不替换)。

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>CamudaDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm</groupId>
            <artifactId>camunda-external-task-client</artifactId>
            <version>7.20.0</version>
        </dependency>
    </dependencies>
</project>

创建一个config,配置Camunda连接

package com.camunda.demo.config;

import lombok.Data;
import org.camunda.bpm.client.ExternalTaskClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
@Data
public class CamundaConfig {
    /**
     * platform地址
     * */
    @Value("${camunda.connect.url}")
    private String CAMUNDA_URL;


    /**
     * 超时时间
     * */
    @Value("${camunda.connect.timeout}")
    private Integer TIME_OUT;

    @Bean
    public ExternalTaskClient getExternalTaskClient(){
        return  ExternalTaskClient.create()
                .baseUrl(CAMUNDA_URL)
                .asyncResponseTimeout(TIME_OUT)
                .build();
    }
}


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

package com.camunda.demo.shopping;

import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.client.ExternalTaskClient;
import org.camunda.bpm.engine.variable.Variables;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Map;

@Slf4j
@Component(value = "shoppingTask")
public class ShoppingTask {

    @Resource
    private ExternalTaskClient externalTaskClient;


    /**
     * 订阅一个Service(external类型)
     * */
    public void handleShoppingCart(){
        /**
         * 订阅服务
         * */
        externalTaskClient.subscribe("add_card")
                /**
                 * 流程定义,即所属流程不
                 * */
                .processDefinitionKey("Process_11089zy")
                /**
                 * 最长加锁时间
                 * */
                .lockDuration(2000)

                /**
                 * 订阅配置
                 * ===========
                 * 处理区域
                 *
                 * externalTask 外部传参相关
                 *
                 * externalTaskService 流程执行相关
                 * */

                .handler((externalTask,externalTaskService)->{
                    /**
                     * 处理逻辑
                     * */
                    log.info("开始加入到购物车");
                    Map<String,Object> goodVariable = Variables.createVariables()
                            .putValue("size","xl")
                            .putValue("count",2);
                    log.info("已加入到购物车:\n{}",goodVariable);
                    /**
                     * 执行完成
                     * */
                    externalTaskService.complete(externalTask,goodVariable);
                }).open();
    }

    public void handleDelivery(){
        externalTaskClient.subscribe("delivery")
                .processDefinitionKey("Process_11089zy")
                .lockDuration(2000)
                .handler(((externalTask, externalTaskService) -> {
                    Object toWhere = externalTask.getVariable("toWhere");
                    log.info("即将发往目的地:{}",toWhere);
                    externalTaskService.complete(externalTask);
                })).open();

    }


}


可以临时写一个启动类

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(MainApplication.class,args);
        ShoppingTask shoppingTask = (ShoppingTask)configurableApplicationContext.getBean("shoppingTask");
        shoppingTask.handleShoppingCart();
        shoppingTask.handleDelivery();
    }
}

注解方式也类似,见后文 外部任务。

1.2 前端

使用如下命令,初始化前端

 npm init -y

使用如下命令,安装依赖(如果有版本问题,可以加–force,要注意找一个低版本的,高版本不支持直接启动(笔者使用2.2.0)

可以参考-camunda-external-task-client-js

npm install camunda-external-task-client-js
npm install -D open

直接写一个subscribe.js
在这里插入图片描述

在这里插入图片描述

const { Client, logger ,Variables} = require("camunda-external-task-client-js");


/**
 * 流程包
 * ==========
 * 配置项
 * */
const config = {baseUrl: "http://localhost:8080/engine-rest",use:logger,asyncResponseTimeout: 10000}

const client = new Client(config);

/**
 * 配置项
 * ========
 * 流程处理
 *
 * 订阅服务-同后端代码
 * */
client.subscribe('pay',{processDefinitionKey:"Process_11089zy"},
    async function({task,taskService}){

        /**
         * 读取我们加入购物车流程中的变量
         * */
        const size = task.variables.get('size');
        const count = task.variables.get('count');

        console.log(`顾客下单尺寸:${size},数量${count}`);

        /**
         * 添加新的变量
         * 订单送往哪里
         * */
        const processVariables = new Variables().set("toWhere","shanghai China")

        try{
            /**
             * 处理完成
             * 查看源码可知:compelete 返回的是Promise,因此需要await来等待
             * */
            await taskService.complete(task,processVariables);
            console.log("完成");
        }catch (e){
            console.error(`异常${e}`);
        }
    })

通过如下代码直接启动

node .\subscribe.js

1.3 执行

完成后,我们部署这个流程
在这里插入图片描述
登录可以看到我们的流程已经部署上去了,启动它。(基本使用见上一篇)
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

二、Camunda的补充

2.1 使用方式

  1. 嵌入式:及整合进项目,如上篇中的Java整合Camunda。代码与流程解耦。
  2. 组件式:被所有程序共有,类似一个消息队列。它将任务指派给不同程序。(一个调度组件)。代码与流程耦合。
  3. 中间件:做成一个平台,单独部署,第一部分就是这种方式(SAAS平台)。代码与流程解耦。
  4. 集群:camunda8,中间件集群。
  5. 云原生:camunda8,部署于k8s中,elasticsearch存储方式,以支持大型微服务。

2.2 可视化平台的Cockpit

可参考官方文档-Web Applications

我们启动platform平台时,可以进入到该页面。

在这里插入图片描述

  • Cockpit:监控中心(驾驶舱)
  • Tasklist:有权限可看到的任务
  • Admin:权限信息

点击进入Cockpit
在这里插入图片描述

  • Running Process Instance:流程实例
  • Open Incidents:异常
  • Open Human Task:人工任务
  • Deployed:部署的流程(流程定义)
    • Process Definitions:BPMN相关(流程)。 Cockpit 允许监控 BPMN 流程。仪表板是 BPMN 监视功能的入口点。选择已部署的流程定义或搜索流程实例。这将带您进入流程定义视图或流程实例视图。
    • Decision Definitions:DMN相关(决策)。Cockpit 允许监控 DMN 决策。仪表板是 DMN 监控功能的入口点。选择已部署的决策定义以访问决策定义视图。显示的有关决策定义和决策实例的所有数据都基于历史数据。与流程定义和流程实例不同,没有运行时数据,因为决策是即时执行的,没有中间等待状态或保存点。
    • Case Definitions: CMMN相关(案例,此功能仅包含在 Camunda 平台的企业版中,在社区版中不可用)。Cockpit允许监控CMMN案例。仪表板是 CMMN 监视功能的入口点。选择已部署的案例定义,或搜索案例实例。这将分别将您带到案例定义视图或案例实例视图。
    • Deployments:总部署(多版本,前面的多版本统一模型算一个)。显示所有部署、其资源以及这些资源的内容的概述。它允许删除现有部署,以及重新部署旧资源和创建新部署。可以显示部署中的资源内容。也可以从此视图下载单个资源

2.3 流程相关数据

在这里插入图片描述

进入某一个流程定义,我们是可以看到下列数据:

  • Deinition Version: 版本,默认每次修改再部署时,都会提高一个版本
  • Version Tag:版本标签,用户自定义版本
  • Definition Key:流程图的唯一ID。
  • Definition ID = Definition Key:Deinition Version:随机乱码。流程图下各个版本的唯一ID。
Definition ID 与 Definition Key 为 多 对 1
乱码的作用:同一流程图的同一版本可以交给不同租户(理解为公司),乱码就是为了区分不同租户,避免区分不开。
  • History Time to LIve:历史数据保存时间
  • Tenant ID:租户ID
  • Deployment ID:该Definition ID对应在数据库中的主键ID。
  • Definition Name:流程名称

流程实例
在这里插入图片描述

ID为流程实例的instance ID ,我们点进去

在这里插入图片描述

  • Instance ID:实例ID
  • Business Key:业务Key(流水号之类的)

其他部分

在这里插入图片描述

  • Variable:流程变量,以Map的形式,我们之前也操作过的
  • Incidents:异常
  • Called Process Instance:跨流程调用,调用其他实例
  • User Tasks:用户任务
  • Jobs:定时任务,可重做
  • External Task:外部任务

2.4 表介绍

关于Camunda的表可以参考camunda数据库表结构介绍
以及官方文档Database Schema

2.5 前端集成Modeler

参考官网-基于 Web 的 BPMN、DMN 和表单工具

三、用Java集成Camunda

集成可以参考

Spring Boot 集成

流程引擎配置

3.1 集成配置

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>CamundaEngine</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.1.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter</artifactId>
            <version>7.20.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.camunda.bpm.model</groupId>
                    <artifactId>camunda-cmmn-model</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm.model</groupId>
            <artifactId>camunda-cmmn-model</artifactId>
            <version>7.18.0</version>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
            <version>7.20.0</version>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
            <version>7.20.0</version>
        </dependency>
    </dependencies>
</project>

注意:

  1. 7.20.0版本中camunda-cmmn-model这个包笔者导不进来,因此另外导入了一个版本
  2. 导入了webapp前端方便操作,页面默认位置如下:
http://localhost:8080/camunda/app/welcome/default/#!/login
  1. 引入了RestAPI。相关文档可参考REST API Reference

yml文件配置可以参考官方给出的

server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/camunda?useUnicode=true&NamePatternMatchesAll=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  application:
    name:   work-engine
camunda:
  bpm:
    admin-user:
      id: demo
      password: demo
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.2 自动部署

启动时自动扫描文件

参考-官方文档-The processes.xml Deployment Descriptor

自定义部署(运行中添加)可以参考:【Camunda 三】Camunda模型文件部署


resources下,创建如下文件 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c189109a043f4314b797049c9f671bc8.png#pic_center)

process.xml默认配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<process-application
        xmlns="http://www.camunda.org/schema/1.0/ProcessApplication"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
    <process-archive name="loan-approval">
        <process-engine>default</process-engine>
        <properties>
            <property name="isDeleteUponUndeploy">false</property>
            <property name="isScanForProcessDefinitions">true</property>
        </properties>
    </process-archive>

</process-application>
  • process-archive name:这个是归档名称,可以为空
  • process-engine:部署到的流程引擎名称 ,可不填写默认(多引擎时需要)
  • isDeleteUponUndeploy:此属性控制取消部署流程应用程序是否应导致从数据库中删除流程引擎部署。默认设置为 false。如果此属性设置为 true,那么取消部署流程应用程序将导致从数据库中删除部署(包括流程实例)。
  • isScanForProcessDefinitions:如果此属性设置为 true,那么将自动扫描流程应用程序的类路径以查找可部署资源。

可以参考Camunda动态生成工作流流程定义并部署更新流程(新手上路版)

3.2.1 修改process.xml位置

如果需要修改process.xml位置,可以使用官方给出代码,如下:

@ProcessApplication(
    name="my-app",
    deploymentDescriptors={"path/to/my/processes.xml"}
)
public class MyProcessApp extends ServletProcessApplication {

}

3.2.2 多进程引擎配置与多租户

如何创建多进程引擎可以参考The Process Application class,有多种配置方式

<process-application
xmlns="http://www.camunda.org/schema/1.0/ProcessApplication"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <process-engine name="my-engine">
    <configuration>org.camunda.bpm.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration</configuration>
  </process-engine>

  <process-archive name="loan-approval" tenantId="tenant1>
    <process-engine>my-engine</process-engine>
    <properties>
      <property name="isDeleteUponUndeploy">false</property>
      <property name="isScanForProcessDefinitions">true</property>
    </properties>
  </process-archive>

</process-application>

3.3 历史事件配置

可参考-历史记录和审核事件日志

在这里插入图片描述

通过配置该项,决定记录的日志颗粒度。

  • NONE:不触发任何历史记录事件
  • ACTIVITY:触发以下事件:
    • 流程实例 START、UPDATE、END、MIGRATE:在流程实例启动、更新、结束和迁移时触发
    • 案例实例 CREATE、UPDATE、CLOSE:在创建、更新和关闭案例实例时触发
    • 活动实例 START、UPDATE、END、MIGRATE:在活动实例启动、更新、结束和迁移时触发
    • 案例活动实例 CREATE、UPDATE、END:在创建、更新和结束案例活动实例时触发
    • 任务实例 CREATE、UPDATE、COMPLETE、DELETE、MIGRATE:在创建、更新(即重新分配、委派等)、完成、删除和迁移任务实例时触发。
  • AUDIT:除了 history level 提供的事件外,还会触发以下事件:ACTIVITY
    • 变量实例 CREATE、UPDATE、DELETE、MIGRATE:在创建、更新、删除和迁移流程变量时触发。默认历史记录后端 (DbHistoryEventHandler) 将变量实例事件写入历史变量实例数据库表。此表中的行会随着变量实例的更新而更新,这意味着只有流程变量的最后一个值可用。
    • FULL:除了 history level 提供的事件外,还会触发以下事件:AUDIT
      • 表单属性 UPDATE:在创建和/或更新表单属性时触发。
        默认历史记录后端 (DbHistoryEventHandler) 将历史变量更新写入数据库。这样就可以使用历史记录服务检查过程变量的中间值。
        用户操作日志更新:当用户执行声明用户任务、委派用户任务等操作时触发。
      • 事件创建、删除、解决、迁移:在创建、删除、解决和迁移事件时触发
      • 历史作业日志 CREATE、FAILED、SUCCESSFUL、DELETED:在创建作业、作业执行失败或成功或作业已删除时触发
      • 决策实例 EVALUATE:当 DMN 引擎评估决策时触发。
      • Batch START、END:在批处理开始和结束时触发
      • 标识链接 ADD、DELETE:在添加、删除标识链接时,或者在设置或更改用户任务的受托人时,以及设置或更改用户任务的所有者时触发。
      • 历史外部任务日志 CREATED, DELETED, FAILED, SUCCESSFUL:在已创建、删除外部任务或外部任务执行已报告失败或成功时触发。
  • AUTO:如果您计划在同一数据库上运行多个引擎,则该级别非常有用。在这种情况下,所有引擎都必须使用相同的历史记录级别。与其手动保持配置同步,不如使用级别,引擎会自动确定数据库中已配置的级别。如果未找到,则使用默认值。
    • 请记住:如果您计划使用自定义历史记录级别,则必须为每个配置注册自定义级别,否则会引发异常。

请注意,使用默认历史记录后端时,历史记录级别存储在数据库中,以后无法更改。

设置方式:

ProcessEngine processEngine = ProcessEngineConfiguration
  .createProcessEngineConfigurationFromResourceDefault()
  .setHistory(ProcessEngineConfiguration.HISTORY_FULL)
  .buildProcessEngine();
  • 使用Spring XML或部署描述符(bpm-platform.xml,processes.xml)进行设置。

  • 使用 Camunda Wildfly 子系统时,可以通过 Wildfly 配置(独立 .xml、domain.xml)设置属性。

总之,只需要在xm中添加如下成员变量即可

<property name="history">audit</property>

3.3.1 查询

使用HistoryService可创建如下查询:

  • HistoricProcessInstanceQuery:历史进程实例查询
  • HistoricCaseInstanceQuery:历史案例实例查询
  • HistoricActivityInstanceQuery:历史活动实例查询
  • HistoricCaseActivityInstanceQuery:历史案例活动查询
  • HistoricVariableInstanceQuery:历史变量查询
  • HistoricDetailQuery:历史细节查询,记录变量更新等信息
  • HistoricTaskInstanceQuery:任务实例查询
  • HistoricIncidentQuery:历史事件(异常)查询
  • UserOperationLogQuery:用户操作日志查询
  • HistoricJobLogQuery:历史作业日志查询
  • HistoricDecisionInstanceQuery:历史决策实例查询
  • HistoricBatchQuery:历史批量处理查询
  • HistoricIdentityLinkLogQuery:与用户关联的日志查询
  • HistoricExternalTaskLogQuery:外部日志查询

3.3.2 任务报告

检索已完成任务的报告。
对于任务报告,有两种可能的报告类型:

  • 计数
  • 持续时间。

3.4 Service

Camunda提供各类Service以便使用。

  • ProcessEngine:第一次调用流程引擎时初始化并构建流程引擎,之后总是返回相同的流程引擎

    • 可以使用ProcessEngines.init() 和 ProcessEngines.destroy()来正确地创建和关闭所有的流程引擎
    • 通过ProcessEngine.XXService 来获取Service
    • SpringBoot中不需要如此
  • RepositoryService:用于管理和操作部署和流程定义的操作

  • RuntimeService:流程实例相关操作

  • TaskService:Activity,也就是对各个节点的相关操作(含挂起,激活等)

  • IdentityService:用户管理等相关操作

  • FormService:表单相关操作

    • 启动表单是在流程实例启动之前向用户显示的表单
    • 任务表单是在用户希望完成表单时显示的表单
  • HistoryService:历史记录相关

  • ManagementService:它允许检索关于数据库表和表元数据的信息。此外,它还公开了作业的查询功能和管理操作。作业在引擎中用于各种用途,如计时器、异步延续、延迟挂起/激活等

  • FilterService:允许创建和管理过滤器。过滤器是像任务查询一样存储的查询。Tasklist(自带的页面)使用过滤器来过滤用户任务。

    • Filter相关说明
  • ExternalTaskService:提供对外部任务实例的访问。(见第一部分)

  • CaseService:同RunService,不过用于案例

  • DecisionService:决策相关服务

3.5 用户业务

Modeler可参考

前置工作:使用平台自行创建一个员工

我们接下来创建一个请假流程:员工请假,上级审批。


创建如下审批流程:
在这里插入图片描述
初始化变量 starter,流程引擎将在流程发起时,自动将发起人信息填入。
在这里插入图片描述
指派给starter,即发起者,可以指派给人、组、用户组、以及设置有效期
在这里插入图片描述
在员工请假处添加一个基础表单
在这里插入图片描述

在领导审批处添加另一个用户(此处直接指派给我们的管理员也可以)

在这里插入图片描述

完成后部署即可。

package com.engine.controller;

import jakarta.annotation.Resource;
import org.camunda.bpm.engine.IdentityService;
import org.camunda.bpm.engine.RuntimeService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/leave")
public class LeaveController {
    /**
     * 用户、组、租户等相关
     * */
    @Resource
    private IdentityService identityService;

    /**
     * 流程实例操作
     * */
    @Resource
    private RuntimeService runtimeService;

    @GetMapping("/start/{user}")
    public void startProcess(@PathVariable("user")String user){
        /**
         * 表:act_id_user
         *
         * 在此处我们设置发起人信息,对象存储在了如下类型中
         * ThreadLocal<Authentication> currentAuthentication = new ThreadLocal();
         * 因此线程之间是隔离的,不会造成混用情况
         * */
        identityService.setAuthenticatedUserId(user);
        /**
         * 表:
         * act_re_procdef BPMN定义
         * act_re_deployment流程部署信息
         * act_ge_bytearray 部署的实际内容
         * 开启一个流程实例
         *
         * 开启后的实例存放于
         * 表:act_run_execution 以及  act_run_task
         * act_run_execution BPMN运行时记录,包含到那个节点了
         * act_run_task 流程总的记录,记录了执行到哪个节点了,指派给了谁等信息
         * 多个版本,默认使用最新版本
         * 启动加入流程变量时,需要加入startProcessInstanceByKey中
         * */
        runtimeService.startProcessInstanceByKey("Process_0pzlxi8");
    }
}

启动后调用接口,可以看到确实有一个流程,并且参数已经有了
在这里插入图片描述

我们使用页面去处理后,完成该流程。

3.6 流程启动Controller

可以做一个简单的启动类,帮助我们启动

package com.engine.controller;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.IdentityService;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@RequestMapping("/process")
public class ProcessController {
    @Resource
    private IdentityService identityService;

    @Resource
    private RuntimeService runtimeService;

    @GetMapping(value = {"" +
            "/start/{processKey}/user/{user}",
            "/start/{processKey}",
            "/start/{processKey}/businessKey/{businessKey}",
            "/start/{processKey}/{businessKey}/{user}"
    })
    public void startProcess(@PathVariable("processKey") String processKey,
                             @PathVariable(value = "user",required = false)String user,
                             @PathVariable(value = "businessKey",required = false)String businessKey

    ){
        if(user != null)
            identityService.setAuthenticatedUserId(user);
        ProcessInstance processInstance = null;
        if(businessKey != null)
            processInstance = runtimeService.startProcessInstanceByKey(processKey,businessKey);
        else processInstance =
                runtimeService.startProcessInstanceByKey(processKey);
        log.info("启动成功,\nInstanceId:{}\nDefinitionId:{}\nInstanceId:{}\nstarter:{}",
                processInstance.getRootProcessInstanceId(),
                processInstance.getProcessDefinitionId(),
                processInstance.getProcessInstanceId(),
                identityService.getCurrentAuthentication() == null?null:identityService.getCurrentAuthentication().getUserId()
        );
    }
}

3.7 业务任务-内部任务

创建如下结构

在这里插入图片描述

3.7.1 Java Class实现

在预约修理家电实现处选择java class,表示以流程引擎自己的Java服务实现。随后我们在集成的服务里面新建一个ReserveRepairService 服务,并把路径添加进去。

在这里插入图片描述

package com.engine.serviceTask;

import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;

@Slf4j
public class ReserveRepairService implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) throws Exception {
         log.info("\n当前流程实例-{}\n执行-{}\n事件名称-{}",
                execution.getProcessInstanceId(),
                execution.getCurrentActivityName(),
                execution.getEventName()
        );
    }
}

3.7.2 Delegate Expression实现

在这里插入图片描述
使用Delegate表达式,可以让流程引擎直接调用自己的Bean,我们需要让这个bean实现Camunda提供的包,默认调用execute。

package com.engine.serviceTask;

import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.springframework.stereotype.Service;

@Service("doRepair")
@Slf4j
public class DoRepairService implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) throws Exception {
          log.info("\n当前流程实例-{}\n执行-{}\n事件名称-{}",
                execution.getProcessInstanceId(),
                execution.getCurrentActivityName(),
                execution.getEventName()
        );

        execution.setVariable("repairManName","打工人A");
    }
}

3.7.3 Express实现

在这里插入图片描述
该方式让流程引擎调用指定Bean的方法。需要指定接收返回的变量

package com.engine.serviceTask;

import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Service;

@Service("telCall")
@Slf4j
public class TelCallService {
    public Integer doCall(DelegateExecution execution){
         log.info("\n当前流程实例-{}\n执行-{}\n事件名称-{}",
                execution.getProcessInstanceId(),
                execution.getCurrentActivityName(),
                execution.getEventName()
        );
        /**
         * VariablesLocal 是该活动的变量,生命周期与活动一致。
         * Variables 是流程变量,生命周期与流程一致。当设置同名变量时,会进行覆盖。
         * 所有变量会:
         * 保存在表 act_ru_variable中,当有Task_ID时,代表是Local
         * act_hi_varinst 为历史变量表
         * */
        String repairManName = String.valueOf(execution.getVariable("repairManName"));
        log.info("请对-{}的服务打分",repairManName);
        return 10;
    }
}


我们也可以用#号表达式
在这里插入图片描述

 	public void getScore(DelegateExecution execution){
        log.info("\n当前流程实例-{}\n执行-{}\n事件名称-{}",
                execution.getProcessInstanceId(),
                execution.getCurrentActivityName(),
                execution.getEventName()
        );
        log.info("员工-{}的服务得分是:{}",
                execution.getVariable("repairManName"),
                execution.getVariable("score"));

    }

执行结果:
在这里插入图片描述

可以发现,这个我们启动的log在最后才打出来,是同步执行的。

3.7.4 流程的回退与重启与暂停

回退

可以参考Process Instance Modification

继续上述逻辑,我们想要在回访后,重新设置评分。

	@Resource
    RuntimeService runtimeService;

    @Resource
    RepositoryService repositoryService;

    public Integer doCall(DelegateExecution execution){
        log.info("\n当前流程实例-{}\n执行-{}\n事件名称-{}",
                execution.getProcessInstanceId(),
                execution.getCurrentActivityName(),
                execution.getEventName()
        );
        if(execution.getVariable("redo") != null && (Boolean) execution.getVariable("redo") ){
            log.info("重新打分:6分");
            execution.setVariable("rescore",6);
            return (Integer) execution.getVariable("score");
        }
        String repairManName = String.valueOf(execution.getVariable("repairManName"));
        log.info("请对-{}的服务打分",repairManName);
        return 10;
    }

    public void getScore(DelegateExecution execution){
        log.info("\n当前流程实例-{}\n执行-{}\n事件名称-{}",
                execution.getProcessInstanceId(),
                execution.getCurrentActivityName(),
                execution.getEventName()
        );
        if(execution.getVariable("rescore") == null){
            ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(execution.getProcessDefinitionId());
            List<ActivityImpl> activityList = processDefinitionEntity.getActivities();
            log.info("该流程的所有活动\n{}\n",activityList);
            int target = -1;
            int i = 0;
            for(;i < activityList.size(); i++,target++){
                if(activityList.get(i).getActivityId() == execution.getCurrentActivityId()){
                    break;
                }
            }
            if(target < 0){
                target = 0;
            }
            log.info("\n当前节点位置:{},id:{},name:{}\n回退节点位置:{},id:{},name:{}"
                    ,i,activityList.get(i).getActivityId(),activityList.get(i).getName()
                    ,target,activityList.get(target).getActivityId(),activityList.get(target).getName());
            log.info("想要重新打分");
            // 修改当前流程至上一流程前
            runtimeService
                    .createProcessInstanceModification(execution.getProcessInstanceId())
                    .startBeforeActivity(activityList.get(target).getActivityId(),execution.getActivityInstanceId())
                    .setVariable("redo",true)
                    .cancelActivityInstance(execution.getActivityInstanceId())
                    .execute();
            return;
        }
        log.info("员工-{}的服务得分是:{},修改得分是:{}",
                execution.getVariable("repairManName"),
                execution.getVariable("score"),
                execution.getVariable("rescore")
        );
    }

在这里插入图片描述

重启

依据官方例子流程重启动
在这里插入图片描述

直接删除实例

ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().singleResult();
runtimeService.deleteProcessInstance(processInstance.getId(), "any reason");

然后重新启动即可

runtimeService.restartProcessInstance(processInstance.getProcessDefinitionId())
	.startBeforeActivity("receivePayment")
	.startBeforeActivity("shipOrder")
	.processInstanceIds(processInstance.getId())
	.execute();

重启后,全局变量将会带入重启流程。需手动设置局部变量,例如通过调用 。RuntimeService.setVariableLocal(…)

从技术上讲,已创建一个新的流程实例。历史进程实例和重新启动的进程实例的 ID 不同。

也可以使用-RESTAPI

暂停

使用如下方法即可

runtimeService.suspendProcessInstanceById();

runtimeService.startProcessInstanceById();

3.8 业务任务-外部任务

可参考-外部任务

外部任务的执行流程:

4.complete()
2.fetchAndLock()
3.handle
1.subscribe(topic) & register(handler)
Custom(Task)Handler
External Task Client
Camunda Rest API
  1. 外部任务注册至External Task Client
  2. 有实例任务时,由External Task Client抓取任务并锁定(避免重复消费)
  3. 分发至对应外部任务
  4. 外部任务完成后告知External Task Client
  5. 失败时上报异常,流程会卡在该节点:可重试。

3.8.1 异步响应:长轮询(Long Polling)

流程引擎对于客户端设计为长轮询模式拉取任务。可参考长轮询的实现方式

  1. 当没有外部任务可以用的时候,请求会被服务器挂起并加锁,防止重复消费
  2. 一旦有新的外部任务可以执行时,就会重新激活请求并执行响应
  3. 设置超时时间,可以在超时后释放该锁并不在挂起该任务

3.8.2 注解方式

非注解方式见第一部分。
官方文档 spring-boot-starter外部任务配置项 7.20

配置与依赖

此处可以另起一个项目。
引入如下同版本包

		<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>CamudaDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.1.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-external-task-client</artifactId>
            <version>7.20.0</version>
        </dependency>
    </dependencies>
</project>

配置

server:
  port: 8081
camunda:
  bpm:
    client:
      # 流程引擎地址
      base-url: http://localhost:8080/engine-rest
      # 长轮询持续时间(异步响应超时时间),默认为null
      # 设置后开启长轮询
      async-response-timeout: 20000
      # 一次最多拉取任务数量,默认10
      max-tasks: 1
      # 订阅topic的上锁时间,超时后,其他外部任务才能获取
      # 优先级小余直接在接口上配置,默认20,000
      lock-duration: 10000
      # 当前工作节点的ID
      worker-id: camunda-demo
spring:
  application:
    name: camunda demo

写一个外部任务

沿用上述流程,添加如下配置:
在这里插入图片描述

package com.camunda.demo.camundaSubscribe;

import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.client.spring.annotation.ExternalTaskSubscription;
import org.camunda.bpm.client.task.ExternalTaskHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class RepairSubscribe {
//全局配置优先级小余私有配置,因此超时会以此处为准
    @Bean
    @ExternalTaskSubscription(topicName = "try_self_repair",processDefinitionKeyIn = {"Process_10qrmih"},lockDuration = 2000)
    public ExternalTaskHandler TrySelfRepair(){
        return ((externalTask, externalTaskService) -> {
            /**
             * 是否免费修
             * */
            Boolean isFree = true;
            if(isFree){
                log.info("免费维修");
                externalTaskService.complete(externalTask);
            }
            else{
                log.info("自己修");
                /**
                 *  message stacktrace 重试次数 重试超时时间
                 * 重试次数为0时,会创建一个异常事件
                 * */
                externalTaskService.handleFailure(externalTask,"message-自己修不好","stacktrace",0,5000);
            }
        });
    }
}

为了方便,我们手动调整true,false来看一下效果。
true时:
在这里插入图片描述
可以看到转为外部服务时,就不再是同步等待了。
在这里插入图片描述
false时:

可以看到创建了一个异常
在这里插入图片描述

重试方式

需要设置重试时可以用如下格式:

Integer retries = 3if (externaltask.getRetries() != null) {
   retries = externaltask.getRetries() - 1;
}
externaltaskService.failure("Reason", retries);

在这里插入图片描述

优先级

在一个流程中,各个节点可以设置优先级,通过优先级,来让哪个流程实先执行。

优先级越高越先执行,默认都为0

在这里插入图片描述

3.9 任务监听器(引擎端)

在引擎端使用监听器,

添加如下配置,在结束时判断用户是否添写上门修理的地址,没有则使用默认地址

在这里插入图片描述

同理,于上门修理处添加一个监听器,在开始时监测

在这里插入图片描述

package com.engine.listener;

import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.ExecutionListener;
import org.springframework.stereotype.Component;

@Component("checkHomeAddress")
@Slf4j
public class CheckHomeAddressListener implements ExecutionListener {
    @Override
    public void notify(DelegateExecution execution) throws Exception {
        log.info("校验用户");
        String homeAddress = (String) execution.getVariable("homeAddress");
        if(StringUtils.isBlank(homeAddress)){
            log.info("未获取到地址");
            execution.setVariable("homeAddress","默认地址");
        }
    }
}
package com.engine.listener;

import lombok.extern.slf4j.Slf4j;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.ExecutionListener;
import org.springframework.stereotype.Component;

@Component("noticeCustomer")
@Slf4j
public class NoticeCustomerListener implements ExecutionListener {
    @Override
    public void notify(DelegateExecution execution) throws Exception {
        log.info("开始通知客户");
        String homeAddress = (String) execution.getVariable("homeAddress");

        log.info("尊敬的客户您好,维修师傅正在前往:{},请耐心等候",homeAddress);
    }
}

在这里插入图片描述

3.10 鉴权

官方可以用web.xml配置,建议使用另外实现。
比如整合SpringSecurity等。

3.11 多实例任务

当需要执行一个循环的流程时,可以让其作为多实例任务。
多实例任务分为:
- 串行多实例任务
- 并行多实例任务

修改前面的请假流程,让它变为多实例任务。

下图中,可以看到:

  • 三根竖线:异步执行
  • 三根横线:顺序执行

在这里插入图片描述

在这里插入图片描述
可以看到上图可以添加多实例

  • Loop cardinality:执行循环次数
  • Completion condition:循环跳出条件,当满足时,可以提前结束循环
  • collection:不指定循环次数,直接循环对象(list)
  • Element:List 中的E
  • Asynchronous before/after:异步前/异步后操作

3.11.1 顺序执行

我们先做顺序执行:
在这里插入图片描述
另外写一个启动类,并提前添加用户:test,zhangsan,lisi,wangwu

@GetMapping("/start/multi")
    public void startProcess(){
        identityService.setAuthenticatedUserId("test");
        List<String> leaders = new ArrayList<>();
        leaders.add("zhangsan");
        leaders.add("lisi");
        leaders.add("wangwu");
        VariableMap variableMap = Variables.createVariables();
        variableMap.put("leaders",leaders);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Process_multi_task",variableMap);
        log.info("启动成功,\nInstanceId:{}\nDefinitionId:{}\nInstanceId:{}\nstarter:{}",
                processInstance.getRootProcessInstanceId(),
                processInstance.getProcessDefinitionId(),
                processInstance.getProcessInstanceId(),
                identityService.getCurrentAuthentication() == null ? null : identityService.getCurrentAuthentication().getUserId()
        );

    }

如果我们在使用张三前先用李四的账号,会发现虽然有但不能评价。

3.11.2 多实例内置变量

同init的时候后可以直接用内置变量starter,多任务中可以使用如下内置变量:

  • nrOfActiveInstances:当前活动的实例数量
  • loopCounter:循环计数器,办理人在列表中的索引
  • nrOfInstances:多实例任务中总共的实例数目
  • nrOfCompletedInstances:已经完成的实例数量

3.11.3 并行执行

如下图修改,只要有两个人同意就可以。

在这里插入图片描述

此时评价将不分先后,此外可以看到同时开始了循流程
在这里插入图片描述

3.12 脚本任务

Camunda支持大多是JSR-223的脚本引擎。比如JavaScript、Groovy等。

对前面请假流程增加扣年假这个操作。

innline 即在如下图中写入
在这里插入图片描述
External则是外部,通过路径引用,需要给出返回变量名。
需要用如下前缀:

classpath://  也就是需要放置于工程目录下,通过Springboot的逻辑加载
deployment:// 也就是需要放置于模型扫描目录下,一同部署于数据库

附录

camunda中文-官方文档
camunda内部构造
camunda英文-官方文档
blibli-camunda工作流实战课程
camunda数据库表结构介绍

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《Java 核心技术·卷I (第11版)》笔记

文章目录 第1章 Java程序设计概述1.1 Java程序设计平台1.2 Java “白皮书” 的关键术语1.2.1 简单性1.2.2 面向对象1.2.3 分布式1.2.4 健壮性1.2.5 安全性1.2.6 体系结构中立1.2.7 可移植性1.2.8 解释型1.2.9 高性能1.2.10 多线程1.2.11 动态性 1.3 Java applet 与 Internet1.4…

线性回归在数据库中的应用

简介 今天看到微信群有人问&#xff0c;如何知道数据库一年的磁盘增量&#xff1f;如果没有研究过统计学&#xff0c;IT人员对于这个问题就只能靠经验了去断定了。没经验的往往都是回复扩容越大越好。当然未来的事情我们是无法预料的。本博主就通过简单的线性回归做一个计算&am…

XS9922B-国产cvi协议,满足国内车载视频传输领域国产化降本需求

XS9922B 是一款 4 通道模拟复合视频解码芯片&#xff0c;支持 HDCCTV 高清协议和 CVBS 标 清协议&#xff0c;视频制式支持 720P/1080P 高清制式和 960H/D1 标清制式。芯片将接收到的高清 模拟复合视频信号经过模数转化&#xff0c;视频解码以及 2D 图像处理之后&#xff0c;转…

CVE-2023-49371|RuoYi 若依后台管理系统存在SQL注入漏洞

0x00 前言 RuoYi是一个后台管理系统&#xff0c;基于经典技术组合&#xff08;Spring Boot、Apache Shiro、MyBatis、Thymeleaf&#xff09;主要目的让开发者注重专注业务&#xff0c;降低技术难度&#xff0c;从而节省人力成本&#xff0c;缩短项目周期&#xff0c;提高软件安…

相信99%的朋友都没有注意到的数据库时间类型的问题

文章目录 创建表SQL实例小测试知识点小测试可以怎样处理只有查询有问题吗&#xff1f;MySQL时间 很多时候&#xff0c;程序运行起来没有问题&#xff0c;并不代表程序就精确&#xff0c;例如创建时间多一秒少一秒这种事情&#xff0c;很多时候是没有人注意到这个问题。 当然&am…

C++/语法@初始化列表

目录 初始化列表特征疑惑区别必在初始化列表中初始化的三种成员变量1、引用成员变量程序例子&#xff1a;运行结果&#xff1a; 2、const成员变量程序例子&#xff1a;运行结果&#xff1a; 3、自定义类型成员&#xff08;没有默认构造函数的类&#xff09;程序例子&#xff1a…

【LeetCode:2132. 用邮票贴满网格图 | 二维前缀和 + 二维差分和】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【docker 】Dockerfile指令学习

学习文档地址 上篇文章&#xff1a;【docker 】基于Dockerfile创建镜像 Dockerfile指令文档地址 .dockerignore 文件 Dockerfile指令 常见的指令 Dockerfile 指令说明FROM指定基础镜像&#xff0c;用于后续的指令构建。MAINTAINER指定Dockerfile的作者/维护者。&#xff…

伦敦金投资者的本质其实是风险管理者

长期在市场中可以稳定盈利的投资者&#xff0c;他们的秘密是什么&#xff1f;很多人以为&#xff0c;肯定是他有别人所没有的交易策略。其实并不是&#xff0c;交易技术固然很重要&#xff0c;但在持续盈利的问题上&#xff0c;技术所占的重要性是次要的&#xff0c;而主要的是…

Django 模型操作 - 多对多(九)

一、多对多关联管理器(对象调用) 前提&#xff1a;多对多&#xff08;双向均有关联管理器&#xff09;一对多&#xff08;只有多的那个类的对象有关联管理器&#xff0c;即反向才有&#xff09; 语法格式&#xff1a;正向&#xff1a;属性名反向&#xff1a;小写类名加 _set注意…

H3C ER G2系列路由器信息泄露漏洞

H3C ER G2系列路由器信息泄露漏洞 免责声明漏洞描述漏洞影响漏洞危害漏洞页面漏洞复现1. 构造poc2. 发生数据包&#xff0c;获取密码3. 登录系统 免责声明 仅用于技术交流,目的是向相关安全人员展示漏洞利用方式,以便更好地提高网络安全意识和技术水平。 任何人不得利用该文章…

详解—【C++】lambda表达式

目录 前言 一、lambda表达式 二、lambda表达式语法 2.1. lambda表达式各部分说明 2.2. 捕获列表说明 三、函数对象与lambda表达式 前言 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法。 #include <algorithm> #i…

Java EE 多线程之多线程案例

文章目录 1. 多线程案例1.1 单例模式1.1.1 饿汉模式1.1.2 懒汉模式1.1.3 多线程下的单例模式 1.2 阻塞队列1.2.1 阻塞队列定义1.2.2 生产者消费者模型的意义1.2.4 标准库中的阻塞队列1.2.5 实现阻塞队列1.2.6 用阻塞队列实现生产者消费者模型 1.3 实现定时器1.3.1 标准库中的定…

V2X在做什么?连接未来智能出行的车联网(上)

来源&#xff1a;德思特测试测量 德思特分享丨V2X在做什么&#xff1f;连接未来智能出行的车联网&#xff08;上&#xff09; 原文链接&#xff1a;德思特分享 | V2X在做什么&#xff1f;连接未来智能出行的车联网&#xff08;上&#xff09; 欢迎关注虹科&#xff0c;为您提…

美易官方:零售销售数据提振信心

美易全球投资中心副总裁Kenny Jolin表示全球股市在经历了动荡之后逐渐恢复了稳定。最近&#xff0c;美国股市表现强劲&#xff0c;连续六天上涨&#xff0c;道琼斯指数也创下了新高。这一趋势不仅反映了投资者信心的恢复&#xff0c;也表明了全球经济正在逐渐复苏。 他说&#…

如何在Centos 7环境下安装MySQL并登录

目录 先获取MySQL官方yum源 然后正常使用yum命令下载mysql即可完成MySQL的下载 使用mysql客户端登录mysqld服务端 能够登录mysql客户端后&#xff0c;我们最后还需要做一点配置 先获取MySQL官方yum源&#xff08;包括对yum源的介绍&#xff09; 介绍一下yum源 yum源就是一…

文献管理器Zotero使用WebDAV结合内网穿透实现公网环境跨平台同步文献笔记

文章目录 一、Zotero安装教程二、群晖NAS WebDAV设置三、Zotero设置四、使用公网地址同步Zotero文献库五、使用永久固定公网地址同步Zotero文献库 Zotero 是一款全能型 文献管理器,可以 存储、管理和引用文献&#xff0c;不但免费&#xff0c;功能还很强大实用。 ​ Zotero 支…

net实践记录

文章目录 前言是否使用继承快捷输入 实体&#xff1b;引用class&#xff0c;提示有保护性System.NullReferenceException:“未将对象引用设置到对象的实例。” 总结 前言 记录使用.net 项目开发过程基础问题记录&#xff0c;便于快速回顾与查询&#xff1b; 是否使用继承 快捷…

关于git clone速度极慢的解决方法

&#xff01;&#xff01;&#xff01;&#xff01;前提条件&#xff1a;得有一个可靠且稳定的梯子&#xff0c;如果没有接下来的就不用看了 前言&#xff1a;我在写这篇文章前&#xff0c;也搜索过很多相关git clone速度很慢的解决方法&#xff0c;但是很多很麻烦&#xff0c…

Maven环境搭建及配置

Maven环境搭建及配置 1.下载部署 官方网站下载正式版的Maven文件,打开bin目录&#xff0c;复制路径然后去环境变量中的path下配置环境变量&#xff0c; 如果只有一个用户只需要在上面path配置复制的路径,当然也可以直接在下面配置,下面配置默认给所有用户都配置 设置完成打开控…