flowable工作流看这一篇就够了(进阶篇 下)

目录

三、多人会签

3.1、多实例介绍

3.2、基本应用

案例一(静态指定数量)

案例二(动态数量和指派审批人)

案例三(表达式方式)

案例四(Java方法控制完成条件)

3.3、服务任务

3.4、子流程应用

四、动态表单

4.1、FlowableUI持久化

4.2、表单管理

创建表单

表单部署

流程启动绑定

表单数据查询

4.3、流程单个节点绑定

4.4、outcome

五、任务的回退

5.1、串行的回退

5.2、并行的回退(回退到单一节点)

5.3、并行的回退(单一节点回退到并行网关)

5.4、子流程回退

5.5、流程的撤销


三、多人会签

3.1、多实例介绍

多实例活动是为业务流程中的某个步骤定义重复的一种方式。在编程概念中,多实例与 for each 结构相匹配:它允许对给定集合中的每个项目按顺序或并行地执行某个步骤或甚至一个完整的子流程。

多实例是一个有额外属性(所谓的 “多实例特性”)的常规活动,它将导致该活动在运行时被多次执行。以下活动可以成为多实例活动。

  • Service Task 服务任务

  • Send Task 发送任务

  • User Task 用户任务

  • Business Rule Task 业务规则任务

  • Script Task 脚本任务

  • Receive Task 接收任务

  • Manual Task 手动任务

  • (Embedded) Sub-Process (嵌入)子流程

  • Call Activity 发起活动

  • Transaction Subprocess 事务子流程

网关或事件不能成为多实例。

如果一个活动是多实例的,这将由活动底部的三条短线表示。三条垂直线表示实例将以并行方式执行,而三条水平线表示顺序执行。

按照规范的要求,每个实例所创建的执行的每个父执行将有以下变量:

  • nrOfInstances: 实例的总数量

  • nrOfActiveInstances: 当前活动的,即尚未完成的实例的数量。对于一个连续的多实例,这将永远是1。

  • nrOfCompletedInstances: 已经完成的实例的数量。

这些值可以通过调用 “execution.getVariable(x) “方法检索。

此外,每个创建的执行将有一个执行本地变量(即对其他执行不可见,也不存储在流程实例级别)。

  • loopCounter: 表示该特定实例的for each循环中的索引

为了使一个活动成为多实例,活动xml元素必须有一个multiInstanceLoopCharacteristics子元素。

<multiInstanceLoopCharacteristics isSequential="false|true">
 ...
</multiInstanceLoopCharacteristics>

isSequential属性表示该活动的实例是按顺序执行还是并行执行。

3.2、基本应用

多实例应用中我们需要指的具体生成几个实例任务。指派的方式可以通过loopCardinality属性来指的。通过loopCardinality来指定既可以是固定值也可以指定表达式(只要结果是整数即可)

<multiInstanceLoopCharacteristics isSequential="false">
  <loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
或者
<multiInstanceLoopCharacteristics isSequential="false">
  <loopCardinality>${num}</loopCardinality>
</multiInstanceLoopCharacteristics>

案例一(静态指定数量)

部署启动:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("multiInstance-demo1.bpmn20.xml")
			.name("multiInstance-demo1")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * start process
 */
@Test
void startFlow() {
	runtimeService.startProcessInstanceById("multiInstance-demo1:1:85c57d01-a718-11ee-b365-1a473d673661");
}

我们先审批一个任务:

/**
 * 审批
 */
@Test
public void completeTask() {
	taskService.complete("c92542cb-a718-11ee-bb4d-1a473d673661");
}

审批结束task表还剩两个任务。

我们依次都审批一下:

现在task表进行到了“多实例-串行”任务

我们审批一下:

审批结束后,发现走到了“多实例-串行”的第二个任务。

我们依次将第二和第三任务都审批完成,最后流程结束。

案例二(动态数量和指派审批人)

部署并启动:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("multiInstance-demo2.bpmn20.xml")
			.name("multiInstance-demo2")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * start process
 */
@Test
void startFlow() {
	Map<String,Object> map = new HashMap<>();
	map.put("users", Arrays.asList("张三","李四","王五"));
	runtimeService.startProcessInstanceById("multiInstance-demo2:1:9e822c93-a774-11ee-92a1-1a473d673661",map);
}

发现有三个并行的用户任务。

案例三(表达式方式)

上面的例子我们一旦设置了3个用户并行节点,那就必须3个都审批才能流程结束,那怎么才能灵活点呢?比如3个节点里有1个用户审批就算流程结束。

${nrOfCompletedInstances/nrOfInstances >= 0.5 }

这句代码的意思是:审批完成数量如果大于等于0.5,就流程结束。我们设置张三、李四、王五三个用户并行节点,其实只要张三和李四审批通过,流程就结束了,因为3分之2大于0.5。

部署并启动:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("multiInstance-demo3.bpmn20.xml")
			.name("multiInstance-demo3")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}


/**
 * start process
 */
@Test
void startFlow() {
	Map<String,Object> map = new HashMap<>();
	map.put("users", Arrays.asList("张三","李四","王五"));
	runtimeService.startProcessInstanceById("multiInstance-demo3:1:47da4ebf-a777-11ee-b7e9-1a473d673661",map);
}

我们将其中两个审批:

/**
 * 审批
 */
@Test
public void completeTask() {
	taskService.complete("a3b2a811-a779-11ee-92e7-1a473d673661");
}

三个用户任务两个审批通过流程就结束了。

案例四(Java方法控制完成条件)

创建Java类:

/**
 * 动态处理会签 完成条件
 */
@Component("multiInstanceDelegate")
public class MultiInstanceDelegate {

    public boolean completeInstanceTask(DelegateExecution execution) {
        // 获取当前多实例中的相关的参数
        // 总得流程实例数量
        Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");
        // 当前活跃的实例数量【没有审批的数量】
        Integer nrOfActiveInstances = (Integer) execution.getVariable("nrOfActiveInstances");
        // 当前已经审批的数量
        Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances");
        System.out.println("nrOfInstances = " + nrOfInstances);
        System.out.println("nrOfActiveInstances = " + nrOfActiveInstances);
        System.out.println("nrOfCompletedInstances = " + nrOfCompletedInstances);
        return nrOfCompletedInstances > nrOfActiveInstances;
    }

}

在xml中绑定类:

部署并启动:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("multiInstance-demo4.bpmn20.xml")
			.name("multiInstance-demo4")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}


/**
 * start process
 */
@Test
void startFlow() {
	Map<String,Object> map = new HashMap<>();
	map.put("users", Arrays.asList("张三","李四","王五"));
	runtimeService.startProcessInstanceById("multiInstance-demo3:2:1b63c2dd-a77c-11ee-b233-1a473d673661",map);
}

我们审批两个:

3.3、服务任务

上面的案例都是在用户任务中实现。我们也可以在Service Task来实现。具体如下:

部署并启动:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("multiInstance-demo5.bpmn20.xml")
			.name("multiInstance-demo5")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * start process
 */
@Test
void startFlow() {
	runtimeService.startProcessInstanceById("multiInstance-demo5:1:917cf491-a77d-11ee-be15-1a473d673661");
}

将此用户任务审批:

/**
 * 审批
 */
@Test
public void completeTask() {
	taskService.complete("a90a0247-a77d-11ee-b929-1a473d673661");
}

3.4、子流程应用

多实例的场景也可以在子流程中来使用,具体我们通过案例来讲解。本质上和我们前面介绍的是差不多的。

部署并启动:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("multiInstance-demo6.bpmn20.xml")
			.name("multiInstance-demo6")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * start process
 */
@Test
void startFlow() {
	runtimeService.startProcessInstanceById("multiInstance-demo6:1:ad738209-a77e-11ee-a6e4-1a473d673661");
}

审批一下用户任务1:

结果出现了并行的两个子流程。

四、动态表单

在实际的工作流审批中我们肯定 需要携带相关的数据的:

  1. 业务数据--流程实例绑定业务主键

  2. 表单数据--动态表单动态绑定

涉及到业务主键操作的方法:

 // 在启动流程实例的时候绑定业务主键 1001231是我们自己随便设置的
runtimeService.startProcessInstanceById(processId,"1001231");
// 也可以通过runtimeService来动态的更新
 runtimeService.updateBusinessKey();
// 通过 ProcessInstance 来获取对应的业务主键
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId("ffe242db-9720-11ee-80a7-c03c59ad2248")
                .singleResult();
String businessKey = processInstance.getBusinessKey();

4.1、FlowableUI持久化

为了更好的介绍表单的内容我们先把FlowableUi的数据持久化到MySQL数据库中。我们来看看应该要如何来修改。先找到flowable-default.properties这个属性文件。来修改数据库的配置信息,webapps\flowable-ui\WEB-INF\classes在这个目录下

然后我们还需要添加MySQL的驱动到lib目录中:

然后重启FlowableUI服务即可。

数据库有87张表。

4.2、表单管理

创建表单

我们拿一个文本、一个多行文本、一个日期:

分别对这三个组件编辑:

我们看数据库:

我们创建的表单在act_de_model表中。

其中model_editor_json字段是我们的表单组件json。

表单部署

通过FlowableUI我们创建的Form表单,然后我们来看看应该要如何来部署表单,以及和流程关联后做流程操作以及相关的流程数据的查询出来。我们先通过单元测试来看看

部署这块我们需要通过FormRepositoryService来处理。

我们现在resources下创建.form文件:

然后将我们刚才绘制的表单数据,act_de_model表中的model_editor_json字段值都复制到此文件中。

部署:

/**
 * 1.部署流程
 * 2.部署表单
 * 3.启动带有表单的流程-->创建了对应的流程实例
 */
@Test
public void deployFormFlow(){
	FormDeployment deploy = formRepositoryService.createDeployment()
			.addClasspathResource("first.form")
			.name("报销表单")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
}

表单部署会涉及到的几张表结构。

表名作用
act_fo_form_definitionForm表单定义表
act_fo_form_deploymentForm表单部署表
act_fo_form_resourceForm表单资源表

其实我们还有另一种方式来部署,采用addString方法:

@Autowired
FormRepositoryService formRepositoryService;

/**
 * 1.部署流程
 * 2.部署表单
 * 3.启动带有表单的流程-->创建了对应的流程实例
 */
@Test
public void deployFormFlow(){
	// 1.获取需要部署的form文件
	String json = "{\"name\":\"报销流程表单\",\"key\":\"expenseAccountForm\",\"version\":0,\"fields\":[{\"fieldType\":\"FormField\",\"id\":\"amount\",\"name\":\"报销金额\",\"type\":\"integer\",\"value\":null,\"required\":true,\"readOnly\":false,\"overrideId\":true,\"placeholder\":\"0\",\"layout\":null},{\"fieldType\":\"FormField\",\"id\":\"reason\",\"name\":\"报销原因\",\"type\":\"text\",\"value\":null,\"required\":false,\"readOnly\":false,\"overrideId\":true,\"placeholder\":null,\"layout\":null},{\"fieldType\":\"FormField\",\"id\":\"expenseDate\",\"name\":\"报销日期\",\"type\":\"date\",\"value\":null,\"required\":false,\"readOnly\":false,\"overrideId\":true,\"placeholder\":null,\"layout\":null}],\"outcomes\":[]}";
	FormDeployment deploy = formRepositoryService.createDeployment()
			.addString("报销流程表单.form", json)
			.name("报销表单")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
}

这里我们就直接从数据库中把表单的JSON数据拷贝出来存储在String类型中,然后通过formRepositoryService来实现部署操作,此次需要注意如果通过非xxx.form 文件的方式部署,我们添加ResourceName资源名称的时候,必须要加上.form后缀,不然部署会失败。

流程启动绑定

然后我们来看看在具体流程中是如何和表单关联起来的。先来看看在表单起始的时候就绑定。

然后保存流程即可,注意保存成功后在act_de_relation中会生成一条流程表单的对应管理,对应的在act_de_model中会生成一条刚刚创建的流程信息如下:

对应的对应关系:

部署并启动:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("报销流程.bpmn20.xml")
			.name("报销流程")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * 启动流程
 */
@Test
public void startFlowWithForm() {
	Map<String,Object> map = new HashMap<>();
	map.put("amount",10000);
	map.put("reason","维护客户关系");
	map.put("bxDate","2023-05-06");
	runtimeService.startProcessInstanceWithForm("bxtask:1:eaca369b-a7af-11ee-9cdc-1a473d673661",null,map,"报销流程");
}

表单数据查询

/**
 * 获取流程绑定的表单数据
 */
@Test
public void getTaskFormInfo() {
	// 流程定义ID 对应task表PROC_DEF_ID_字段
	String proDefId = "bxtask:1:eaca369b-a7af-11ee-9cdc-1a473d673661";
	// 流程实例ID 对应task表PROC_INST_ID_字段
	String proInsId = "0967c741-a7b1-11ee-a085-1a473d673661";
	FormInfo startFormModel = runtimeService.getStartFormModel(proDefId, proInsId);
	System.out.println("startFormModel.getKey() = " + startFormModel.getKey());
	System.out.println("startFormModel.getName() = " + startFormModel.getName());
	System.out.println("startFormModel.getDescription() = " + startFormModel.getDescription());

	// 获取表单对应的数据
	SimpleFormModel formModel = (SimpleFormModel) startFormModel.getFormModel();
	List<FormField> fields = formModel.getFields();
	for (FormField field: fields) {
		System.out.println("field.getId() = " + field.getId());
		System.out.println("field.getName() = " + field.getName());
		System.out.println("field.getValue() = " + field.getValue());
	}

}

4.3、流程单个节点绑定

单个节点绑定其实就是在用户任务上关联Form表单,具体如下:

部署并启动:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("form-demo2.bpmn20.xml")
			.name("form-demo2")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * start process
 */
@Test
void startFlow() {
	Map<String,Object> map = new HashMap<>();
	map.put("amount",999);
	map.put("reason","我想报销");
	map.put("bxDate","2023-05-06");
	runtimeService.startProcessInstanceById("form-demo2:1:7fa62223-a7b4-11ee-8ab4-1a473d673661",map);
}

当我们再调用如下方法查询表单对应的数据时:

/**
 * 获取流程绑定的表单数据
 */
@Test
public void getTaskFormInfo() {
	// 流程定义ID 对应task表PROC_DEF_ID_字段
	String proDefId = "form-demo2:1:7fa62223-a7b4-11ee-8ab4-1a473d673661";
	// 流程实例ID 对应task表PROC_INST_ID_字段
	String proInsId = "c611efbb-a7b4-11ee-a580-1a473d673661";
	FormInfo startFormModel = runtimeService.getStartFormModel(proDefId, proInsId);
	System.out.println("startFormModel.getKey() = " + startFormModel.getKey());
	System.out.println("startFormModel.getName() = " + startFormModel.getName());
	System.out.println("startFormModel.getDescription() = " + startFormModel.getDescription());

	// 获取表单对应的数据
	SimpleFormModel formModel = (SimpleFormModel) startFormModel.getFormModel();
	List<FormField> fields = formModel.getFields();
	for (FormField field: fields) {
		System.out.println("field.getId() = " + field.getId());
		System.out.println("field.getName() = " + field.getName());
		System.out.println("field.getValue() = " + field.getValue());
	}

}

我们将代码改为:

/**
 * 获取绑定在单独节点上的表单数据
 */
@Test
public void getTaskFormNodeInfo() {
	// 参数为task表的主键ID
	FormInfo taskFormModel = taskService.getTaskFormModel("c6180a43-a7b4-11ee-a580-1a473d673661");
	System.out.println("startFormModel.getKey() = " + taskFormModel.getKey());
	System.out.println("startFormModel.getName() = " + taskFormModel.getName());
	System.out.println("startFormModel.getDescription() = " + taskFormModel.getDescription());

	// 获取表单对应的数据
	SimpleFormModel formModel = (SimpleFormModel) taskFormModel.getFormModel();
	List<FormField> fields = formModel.getFields();
	for (FormField field: fields) {
		System.out.println("field.getId() = " + field.getId());
		System.out.println("field.getName() = " + field.getName());
		System.out.println("field.getValue() = " + field.getValue());
	}
}

我们将用户任务1审批,现在应该到了用户任务2:

我们用用户任务2的ID再次查询表单数据。

因为我们只在用户任务1节点上绑定了表单。

4.4、outcome

outcome用来指定表单审批对应的结果。表单审批完成后根据outcome的信息会在act_ru_variable中会生成一个form_表单ID_outcome这样一个流程变量,那么我们就可以根据这个流程变量来对应的路由到相关的流程中。比如:form_formbx2_outcome

注意:表单ID我们不用使用 -拼接。

我们先在表单上定义两个结果,分别是接受和拒绝:

全部开始节点绑定表单:

排他网关两侧设置条件:

先部署表单:

/**
 * 1.部署流程
 * 2.部署表单
 * 3.启动带有表单的流程-->创建了对应的流程实例
 */
@Test
public void deployFormFlow(){
	FormDeployment deploy = formRepositoryService.createDeployment()
			.addClasspathResource("first.form")
			.name("报销表单")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
}

部署并启动流程:

/**
 * Deploy
 */
@Test
void testDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("form-demo3.bpmn20.xml")
			.name("form-demo3")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * 启动流程
 */
@Test
public void startFlowWithForm() {
	Map<String,Object> map = new HashMap<>();
	map.put("amount",666);
	map.put("reason","我要报销");
	map.put("bxDate","2023-12-31");
	runtimeService.startProcessInstanceWithForm("form-demo3:1:3c1352cf-a7c1-11ee-a135-1a473d673661",null,map,"报销流程");
}

五、任务的回退

5.1、串行的回退

我们先从最简单的串行流程来分析,案例如下:

部署并启动:

/**
 * 部署流程
 */
@Test
void backDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("back-demo1.bpmn20.xml")
			.name("back-demo1")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * 启动流程
 */
@Test
public void startBackFlow() {
	runtimeService.startProcessInstanceById("back-demo1:1:1f66c13f-a7de-11ee-b6b2-1a473d673661");
}

审批一直到用户4:

/**
 * 审批
 */
@Test
public void completeBackTask() {
	taskService.complete("5ca9e3c9-a7de-11ee-a405-1a473d673661");
}

任务回退,从用户4退回到用户3:

/**
 * 任务回退
 */
@Test
public void backFlow() {
	runtimeService.createChangeActivityStateBuilder().
			processInstanceId("5ca5c514-a7de-11ee-a405-1a473d673661")
			//第一个参数:后面的ID,这里是用户4  第二个参数:前面的ID,这里是用户3
			.moveActivityIdTo("sid-E1666002-4CD3-49CD-9008-19A90600C1E0","sid-89C7AE8A-A433-47AA-A76C-A16EB6AB9B9A").changeState();
}

注意:moveActivityIdTo方法的参数是xml文件里<userTask>的id属性值。

当然,moveActivityIdTo方法也不光只能从后往前回退,也可以从前跳到后面某节点。

5.2、并行的回退(回退到单一节点)

接下来我们在并行的场景中来看看各种回退的场景。具体案例流程如下:

部署并启动:

/**
 * 部署流程
 */
@Test
void backDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("back-demo2.bpmn20.xml")
			.name("back-demo2")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * 启动流程
 */
@Test
public void startBackFlow() {
	runtimeService.startProcessInstanceById("back-demo2:1:f0ec9dae-a7e2-11ee-b807-1a473d673661");
}

现在走到“用户审批”:

审批:

将行政副总和业务负责人两个节点同时退回到用户审批节点:

/**
 * 任务回退
 */
@Test
public void backFlow() {
	runtimeService.createChangeActivityStateBuilder().
			processInstanceId("532fa4ac-a7e3-11ee-8e15-1a473d673661")
			// 因为是并行网关,所以要将usertask3和usertask2同时退回到usertask1节点
			.moveActivityIdsToSingleActivityId(Arrays.asList("usertask3","usertask2"),"usertask1").changeState();
}

我们重新多层审批,直到最后走到“总经理”节点:

5.3、并行的回退(单一节点回退到并行网关)

我们现在从“总经理”节点回退到“业务副总”和“行政副总”节点:

/**
 * 任务回退
 */
@Test
public void backFlow() {
	runtimeService.createChangeActivityStateBuilder().
			processInstanceId("532fa4ac-a7e3-11ee-8e15-1a473d673661")
			.moveSingleActivityIdToActivityIds("usertask5",Arrays.asList("usertask2","usertask4"))
			.changeState();
}

5.4、子流程回退

最后我们来看看带有子流程的场景下如果有回退的情况应该要如何来处理,案例如下:

部署并启动流程:

/**
 * 部署流程
 */
@Test
void backDeploy() {
	Deployment deploy = repositoryService.createDeployment()
			.addClasspathResource("back-demo3.bpmn20.xml")
			.name("back-demo3")
			.deploy();
	System.out.println("deploy.getId() = " + deploy.getId());
	System.out.println("deploy.getName() = " + deploy.getName());
}

/**
 * 启动流程
 */
@Test
public void startBackFlow() {
	runtimeService.startProcessInstanceById("back-demo3:1:76eba5cb-a7e8-11ee-abd5-1a473d673661");
}

审批:

/**
 * 审批
 */
@Test
public void completeBackTask() {
	taskService.complete("b86015f1-a7e8-11ee-88e0-1a473d673661");
}

从子流程“用户2”回退到用户1:

/**
 * 任务回退
 */
@Test
public void backFlow() {
	runtimeService.createChangeActivityStateBuilder().
			processInstanceId("b85826ac-a7e8-11ee-88e0-1a473d673661")
			.moveActivityIdTo("user2","user1")
			.changeState();
}

5.5、流程的撤销

​流程的撤销一般是流程的发起人感觉没有必要再做流程审批的推进了。想要结束流程的审批操作。这个时候我们可以直接通过runtimeService.deleteProcessInstance()方法来实现相关的流程撤销操作。

比如我们上面的并行案例中。进入到并行节点后想要结束:

部署并启动...

审批:

现在我们进行流程的撤销:

/**
 * 流程撤销
 */
@Test
public void deleteProcessInstance(){
	runtimeService.deleteProcessInstance("aedd050c-a7ea-11ee-af22-1a473d673661","我是删除原因");
}

刚才的流程已经没有了。

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

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

相关文章

加入近屿智能OJAC的AIGC星辰大海深度训练营:开启您的AI大模型之旅!

成为AI领域的专家吗&#xff1f;想要实现升职加薪吗&#xff1f;加入第六期近屿智能OJAC第六期AIGC星辰大海&#xff1a;大模型工程师与产品专家深度训练营&#xff0c;正是你学习AIGC技术&#xff0c;实现转型的绝佳机会。在这里&#xff0c;不仅是学习&#xff0c;更是您职业…

亲爱的程序猿们,元旦快乐!

新年祝福 在这个充满欢笑和祝福的日子里&#xff0c;我想对你们说&#xff1a; 新的一年&#xff0c;愿你们像代码一样充满逻辑&#xff0c;像算法一样追求高效&#xff0c;像编程语言一样多样化&#xff01; 2024年即将到来&#xff0c;预测几个行业趋势&#xff1a; 人工…

【数据结构】排序之交换排序(冒泡 | 快排)

交换目录 1. 前言2. 交换排序3. 冒泡排序3.1 分析3.2 代码实现 4. 快速排序4.1 hoare版本4.1.1 分析4.1.2 hoare版本代码 4.2 挖坑法4.2.1 分析4.2.2 挖坑法代码实现 4.3 前后指针版本4.3.1 分析4.3.2 前后指针版本代码实现 1. 前言 在之前的博客中介绍了插入排序&#xff0c;…

Windows搭建RTSP视频流服务(EasyDarWin服务器版)

文章目录 引言1、安装FFmpeg2、安装EasyDarWin3、实现本地\虚拟摄像头推流服务4、使用VLC或PotPlayer可视化播放器播放视频5、RTSP / RTMP系列文章 引言 RTSP和RTMP视频流的区别 RTSP &#xff08;Real-Time Streaming Protocol&#xff09;实时流媒体协议。 RTSP定义流格式&am…

Spring高手之路-Spring AOP

目录 什么是AOP Spring AOP有如下概念 补充&#xff1a; AOP是如何实现的 Spring AOP 是通过代理模式实现的。 Spring AOP默认使用标准的JDK动态代理进行AOP代理。 什么是AOP AOP(Aspect-Oriented Programming)&#xff0c;即面向切面编程&#xff0c;用人话说就是把公共的…

【小沐学Python】Python实现免费天气预报获取(OpenWeatherMap)

文章目录 1、简介1.1 工具简介1.2 费用1.3 注册1.4 申请key 2、接口说明2.1 One Call 3.02.2 Current Weather and Forecasts collection2.2.1 API 调用2.2.2 API 参数 2.3 Historical Weather collection2.4 Weather Maps collection2.5 Other weather APIs 3、接口测试3.1 例…

菜鸟网络Java实习一面面经

自我介绍&#xff0c;做过的项目 巴拉巴拉 你项目中用到redis&#xff0c;可以介绍一下为什么使用它吗&#xff1f; 基于内存操作&#xff0c;内存读写速度快。 支持多种数据类型&#xff0c;包括String、Hash、List、Set、ZSet等。 支持持久化。Redis支持RDB和AOF两种持久…

【Latex错误:】Package fontspec: The font “SIMLI“ cannot be found. LaTex [行 37,列1]

【Latex错误&#xff1a;】Package fontspec: The font "SIMLI" cannot be found. LaTex [行 37&#xff0c;列1] 解决方案 错误详情如下图所示&#xff1a; 最近使用latex写毕业论文&#xff0c;效率是快&#xff0c;但是出些一些错误就难得搞了&#xff0c;上面的…

python+django游戏分享论坛网站49c2c

本系统主要包括管理员和用户两个角色组成&#xff1b;主要包括首页、个人中心、用户管理、游戏类型管理、游戏文章管理、交流论坛、系统管理等功能的管理系统。 系统权限按管理员和用户两类涉及用户划分。 &#xff08;1&#xff09;管理员功能需求 管理员登陆后&#xff0c;主…

桶排序 BucketSort

桶排序 桶排序是将数组分散到有限的桶中&#xff0c;然后每个桶再分别排序&#xff0c;而每个桶的排序又可以使用其他排序方式进行排序&#xff0c;可以是桶排序也可以是其他排序。一句话就是: 划分多个范围相同的区间&#xff0c;每个子区间自排序最后合并。 桶的大小可以随…

计量经济学|学习笔记以及学习感悟

初级计量经济学着重于介绍基本的统计工具和经济模型&#xff0c;以帮助理解经济数据和经济现象之间的关系。它包括回归分析、假设检验和预测方法等内容。中级计量经济学则深入研究这些方法的理论基础和实际应用&#xff0c;包括更复杂的模型和技术&#xff0c;如面板数据分析、…

jwt 介绍

目录 1&#xff0c;jwt 的出现问题 2&#xff0c;jwt 介绍3&#xff0c;jwt 令牌的组成3.1&#xff0c;header3.2&#xff0c;payload3.3&#xff0c;signature 4&#xff0c;验证5&#xff0c;总结 身份验证相关内容&#xff1a; 浏览器 cookie 的原理&#xff08;详&#xff…

微服务实战系列之Dubbo(下)

前言 眼看着2023即将走远&#xff0c;心里想着似乎还有啥&#xff0c;需要再跟各位盆友叨叨。这不说曹操&#xff0c;曹操就来了。趁着上一篇Dubbo博文的余温尚在&#xff0c;博主兴匆匆地“赶制”了Dubbo的下集&#xff0c;以飨读者。 上一篇博主依然从Dubbo的内核出发&#…

Linux基础知识学习3

vim编辑器 其分为四种模式 1.普通(命令)模式 2.编辑模式 3.底栏模式 4.可视化模式 vim编辑器被称为编辑器之神&#xff0c;而Emacs更是神之编辑器 普通模式&#xff1a; 1.光标移动 ^ 移动到行首 w 跳到下一个单词的开头…

软件开发新手用哪个IDE比较好?软件开发最好的IDE都在这!

目录 IDES 的优点 最佳编程 IDE 列表 Java 开发的流行集成开发环境 JetBrains 的 IntelliJ IDEA NetBeans 适用于 C/ C、C# 编程语言的最佳 IDE Visual Studio 和 Visual Studio 代码 Eclipse PHP 开发的最佳 IDE PHPStorm Sublime Text Atom JavaScript 的顶级 I…

Windows10系统的音频不可用,使用疑难解答后提示【 一个或多个音频服务未运行】

一、问题描述 打开电脑&#xff0c;发现电脑右下角的音频图标显示为X&#xff08;即不可用&#xff0c;无法播放声音&#xff09;&#xff0c;使用音频自带的【声音问题疑难解答】&#xff08;选中音频图标&#xff0c;点击鼠标右键&#xff0c;然后选择“声音问题疑难解答(T)”…

procise纯PL流程点灯记录

procise纯PL流程点灯记录 一、概述 此篇记录使用procise工具构造JFMQL15T 纯PL工程&#xff0c;显示PL_LED闪烁&#xff1b; 硬件说明如下&#xff1a; 时钟引脚 Pl_CLK: U2 ,IO_L14P_T2_SRCC_34 PL_LED1 : E2, IO_L17P_T2_AD5P_35 PL_LED2: D6, IO_L2N_T0_AD8N_35 PL_LED3 :…

网易有道词典不能截屏翻译,不能联网解决办法

对应版本&#xff1a; win10系统&#xff0c;联想拯救者笔记本&#xff0c;网易有道词典8.10.2.0。 网易有道词典免费下载链接&#xff1a;https://download.csdn.net/download/qq_42755734/88684985 修改代理&#xff1a; youdao.com 0 取消勾选---不更新 效果&#xff1a…

CentOS 7 lvm 裸盘的扩容和缩容减盘 —— 筑梦之路

背景介绍 之前写过比较多的关于lvm的文章&#xff1a; CentOS 7 lvm 更换坏盘操作步骤小记 —— 筑梦之路_centos更换硬盘操作-CSDN博客 xfs ext4 结合lvm 扩容、缩容 —— 筑梦之路_ext4扩盘-CSDN博客 LVM逻辑卷元数据丢失恢复案例 —— 筑梦之路_pve lvm数据恢复-CSDN博客…

【MMdetection】MMdetection从入门到进阶

基础环境安装 步骤 0. 从官方网站下载并安装 Miniconda。 步骤 1. 创建并激活一个 conda 环境。 conda create --name openmmlab python3.8 -y conda activate openmmlab步骤 2. 基于 PyTorch 官方说明安装 PyTorch。 pip install torch2.0.1 torchvision0.15.2 torchaudio…