文章目录
- 前言
- 定义说明
- 定义
- 流程变量的作用域(范围)
- 绘制流程图
- 文件部署数据库
- 设置流程变量并执行流程
- Global 作用域 流程变量设置
- 启动流程时设定变量
- 查询已经创建的流程信息
- 完成提交申请节点任务,推进工作流任务执行
- 完成部门经理审批 推进节点
- Local 流程变量
- 删除已部署的模板
前言
之前项目中,涉及到金融担保合同的审批,有家客户的需求大致如下:
提出担保合同申请,如果金额大于xxxx时,需要走集团审批;
若小于xxx时,可以直接由本单位审批,不需要走集团复核审。
如果此处依旧使用类似之前的简单流程,则将不再适用。像这种需求牵扯到因为某一个或某些条件,导致可能会走不同的流程问题,又该如何设计流程图和实现呢?
定义说明
上面说到了,当申请的合同金额达到某个阈值时,会走某一条审批路线。可以使用流程变量
这项功能实现。
定义
【疑问】什么是 流程变量 呢?
流程变量,就是在流程图中定义某些节点上的变量信息,通过代码传入对应的数据,流程图能够按照提前设计好的条件去判断执行哪一条处理路线。
流程变量的类型,在activiti中是Map<String,Object>
,所以流程变量比业务关键字要强大的多,变量值可以是 字符串,也可以刷 pojo 对象。
如果是 pojo 对象,注意 这个对象必须实现 序列化接口 serializable 。
流程变量的作用域(范围)
流程变量的作用域可以设置为 Local 与 Global 两种。
- Global变量
这个是流程变量的默认作用域,表示是一个完整的流程实例。Global变量中变量名不能重复。如果设置了相同的变量名,后面设置的值会直接覆盖
前面设置的变量值。 - Local 变量
Local变量的作用域只针对一个任务或一个执行实例
的范围,没有流程实例大。
Local变量由于作用在不同的任务或不同的执行实例中,所以不同变量的作用域是互不影响的,变量名可以相同。Local变量名也可以和Global变量名相同,不会有影响。
绘制流程图
接下来绘制一个大致符合要求的流程审批图。如下所示:
上述节点中,设定不同的Assignee
值,其中在连线上,设置${amt < 10000000}
和${amt >= 10000000}
条件。
这里的条件,符合 UEL表达式。
其中,提交申请操作,为了下面的案例区分,此处也采取变量的方式进行配置。
完整文件demo02.bpmn
,代码如下所示:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://www.activiti.org/testm1713963525382" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1713963525382" name="" targetNamespace="http://www.activiti.org/testm1713963525382" typeLanguage="http://www.w3.org/2001/XMLSchema"><process id="demo02" isClosed="false" isExecutable="true" name="内部与外部申请" processType="None"><startEvent id="_2" name="StartEvent"/><userTask activiti:assignee="${assignee0}" activiti:exclusive="true" id="_3" name="提交申请"/><userTask activiti:assignee="financer" activiti:exclusive="true" id="_6" name="财务审批"/><endEvent id="_7" name="EndEvent"/><sequenceFlow id="_13" sourceRef="_6" targetRef="_7"/><userTask activiti:assignee="manager" activiti:exclusive="true" id="_16" name="部门经理审批"/><sequenceFlow id="_17" sourceRef="_3" targetRef="_16"/><userTask activiti:assignee="group" activiti:exclusive="true" id="_18" name="集团审批"><documentation id="_18_D_1"/></userTask><sequenceFlow id="_19" sourceRef="_16" targetRef="_18"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${amt >= 10000000}]]></conditionExpression></sequenceFlow><sequenceFlow id="_20" sourceRef="_18" targetRef="_6"/><sequenceFlow id="_21" sourceRef="_16" targetRef="_6"><conditionExpression xsi:type="tFormalExpression"><![CDATA[${amt < 10000000}]]></conditionExpression></sequenceFlow><sequenceFlow id="_24" sourceRef="_2" targetRef="_3"/></process><bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram"><bpmndi:BPMNPlane bpmnElement="demo02"><bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2"><dc:Bounds height="32.0" width="32.0" x="400.0" y="65.0"/><bpmndi:BPMNLabel><dc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3"><dc:Bounds height="55.0" width="85.0" x="375.0" y="155.0"/><bpmndi:BPMNLabel><dc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="_6" id="Shape-_6"><dc:Bounds height="55.0" width="85.0" x="385.0" y="490.0"/><bpmndi:BPMNLabel><dc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="_7" id="Shape-_7"><dc:Bounds height="32.0" width="32.0" x="410.0" y="590.0"/><bpmndi:BPMNLabel><dc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="_16" id="Shape-_16"><dc:Bounds height="55.0" width="85.0" x="375.0" y="255.0"/><bpmndi:BPMNLabel><dc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNShape><bpmndi:BPMNShape bpmnElement="_18" id="Shape-_18"><dc:Bounds height="55.0" width="85.0" x="380.0" y="370.0"/><bpmndi:BPMNLabel><dc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNShape><bpmndi:BPMNEdge bpmnElement="_13" id="BPMNEdge__13" sourceElement="_6" targetElement="_7"><di:waypoint x="426.0" y="545.0"/><di:waypoint x="426.0" y="590.0"/><bpmndi:BPMNLabel><dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="_24" id="BPMNEdge__24" sourceElement="_2" targetElement="_3"><di:waypoint x="416.0" y="97.0"/><di:waypoint x="416.0" y="155.0"/><bpmndi:BPMNLabel><dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="_17" id="BPMNEdge__17" sourceElement="_3" targetElement="_16"><di:waypoint x="417.5" y="210.0"/><di:waypoint x="417.5" y="255.0"/><bpmndi:BPMNLabel><dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="_19" id="BPMNEdge__19" sourceElement="_16" targetElement="_18"><di:waypoint x="420.0" y="310.0"/><di:waypoint x="420.0" y="370.0"/><bpmndi:BPMNLabel><dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="_20" id="BPMNEdge__20" sourceElement="_18" targetElement="_6"><di:waypoint x="425.0" y="425.0"/><di:waypoint x="425.0" y="490.0"/><bpmndi:BPMNLabel><dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNEdge><bpmndi:BPMNEdge bpmnElement="_21" id="BPMNEdge__21" sourceElement="_16" targetElement="_6"><di:waypoint x="375.0" y="282.5"/><di:waypoint x="320.0" y="430.0"/><di:waypoint x="385.0" y="517.5"/><bpmndi:BPMNLabel><dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel></bpmndi:BPMNEdge></bpmndi:BPMNPlane></bpmndi:BPMNDiagram>
</definitions>
保存文件,同时可以采取之前的方式生成png图片,或者截图也行。
文件部署数据库
使用之前的代码,将该流程文件部署到activiti的数据表中。
/*** 单文件上传部署逻辑*/
@Test
public void addTable(){ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();RepositoryService repositoryService = defaultProcessEngine.getRepositoryService();Deployment demo1 = repositoryService.createDeployment().addClasspathResource("bpmn/demo02.bpmn") // 添加流程图.addClasspathResource("bpmn/demo02.png") // 对应的流程图片 支持 png|jpg|gif|svg.name("内部与外部申请审批流程").deploy();System.out.println("流程部署id===》"+demo1.getId());System.out.println("流程部署name===》"+demo1.getName());
}
设置流程变量并执行流程
设置流程变量的时机一般在流程最初启动时就设置好,或者在下一级就需要使用时的任务节点上进行设置。
根据activiti不同的流程变量作用域,进行不同时机点的流程变量设定。
Global 作用域 流程变量设置
启动流程时设定变量
在流程最初启动的时候,就将对应的流程变量值设置上,此时的流程变量在整个流程中都可用。
/*** 启动实例* 这里针对条件 分别启动两个流程实例*/
@Test
public void startProcess(){// 获取流程引擎ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();// 获取 runtime 服务RuntimeService runtimeService = defaultProcessEngine.getRuntimeService();// 指定哪个模板String tempKey = "demo02";// 流程变量 可以是对象 也可以是单个的类型变量 比如 字符串HashMap<String, Object> paramMap = new HashMap<>();//paramMap.put("amt",10000); // 流程1// 指定责任人//paramMap.put("assignee0","worker"); // 流程1paramMap.put("amt",10000001); // 流程2paramMap.put("assignee0","worker1"); // 流程2// 启动流程实例runtimeService.startProcessInstanceByKey(tempKey,paramMap);
}
因为有设置条件,这里就启动两个流程实例
对象,进行验证链路的执行顺序。分为 流程1
和流程2
。
当启动完成后,可以先查询当前数据库中的流程信息。
查询已经创建的流程信息
执行下面的代码,查看日志打印信息。
/*** 查询流程实例信息*/
@Test
public void queryTask(){// 执行已部署的流程模板名称String tempKey = "demo02";ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = defaultProcessEngine.getTaskService();List<Task> list = taskService.createTaskQuery().processDefinitionKey(tempKey) // 指定哪个流程图模板.list();if(!CollectionUtil.isEmpty(list)){list.forEach(x->{System.out.println("流程实例 id "+x.getProcessInstanceId());System.out.println("任务 id "+x.getId());System.out.println("任务负责人 "+x.getAssignee());System.out.println("任务名称 "+x.getName());System.out.println("===========================================");});}
}
完成提交申请节点任务,推进工作流任务执行
编写对应的逻辑,将当前提交申请的流程进行审批操作,将流程推进至下一节点中。
先进行流程1
的流程任务推进。
/*** 工作流的节点与状态的扭转* 这里有2个流程 分别对对应的任务 id 进行流程的推进*/
@Test
public void completTask(){String tempKey = "demo02";// 负责人 信息String assignee = "worker"; // 流程1// String assignee = "worker1"; // 流程2// 获取数据库的连接信息ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = defaultProcessEngine.getTaskService();Task task = taskService.createTaskQuery().processDefinitionKey(tempKey).taskAssignee(assignee).singleResult();if(Objects.nonNull(task)){System.out.println("========= 任务 "+ task.getId()+" 进行流程推进 ==========");taskService.complete(task.getId());}}
再查询当前模板下的所有推进信息。
/*** 查询流程实例信息*/
@Test
public void queryTask(){// 执行已部署的流程模板名称String tempKey = "demo02";ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = defaultProcessEngine.getTaskService();List<Task> list = taskService.createTaskQuery().processDefinitionKey(tempKey) // 指定哪个流程图模板.list();if(!CollectionUtil.isEmpty(list)){list.forEach(x->{System.out.println("流程实例 id "+x.getProcessInstanceId());System.out.println("任务 id "+x.getId());System.out.println("任务负责人 "+x.getAssignee());System.out.println("任务名称 "+x.getName());System.out.println("===========================================");});}
}
按照流程1
的方式,将流程2也进行推进操作。
完成部门经理审批 推进节点
由于两个不同的流程实例,采取的是同一个部门经理审批,继续执行上面的代码,将部门经理岗位的审批任务进行推进操作。
/*** 部门经理审批任务 实现任务流程节点的推进* 工作流的节点与状态的扭转* 这里有2个流程 分别对对应的任务 id 进行流程的推进*/
@Test
public void completTask2(){String tempKey = "demo02";// 负责人 信息String assignee = "manager";// 获取数据库的连接信息ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();TaskService taskService = defaultProcessEngine.getTaskService();List<Task> tasks = taskService.createTaskQuery().processDefinitionKey(tempKey).taskAssignee(assignee).list();if(!CollectionUtil.isEmpty(tasks)){tasks.forEach(task->{taskService.complete(task.getId());});}}
执行完成后,再次进行查询,观察开局设置金额后,最终审批操作在分支的节点情况。
Local 流程变量
上面说到的是Global 流程变量
的设置,是在启动流程实例的时候,进行对应全局变量值的设定。
那么 local 流程变量
则可以在任务执行推进过程中进行设置。
比如任务的完成操作,框架中提供了下面的方法。
删除已部署的模板
如果中途绘制与执行对应的工作流模板部署,需要进行删除操作,可以执行下面的代码实现。
/*** 查询流程定义 的一些内容*/
@Test
public void queryProcessDefinition(){// 获取数据库的连接信息ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();RepositoryService repositoryService = defaultProcessEngine.getRepositoryService();ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();// 指定需要查询哪个流程模板信息String flowId = "demo02";List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey(flowId) // 指定是哪个流程模板.orderByProcessDefinitionVersion() // 排序字段.desc().list();if(!CollectionUtil.isEmpty(list)){list.forEach(process->{System.out.println("流程定义 id "+process.getId());System.out.println("流程定义 name "+process.getName());System.out.println("流程定义 key "+process.getKey());System.out.println("流程定义 version "+process.getVersion());System.out.println("流程部署 id "+process.getDeploymentId());System.out.println("=============================");});}
}/*** 删除 已部署 的流程实例。* 注意:如果当前流程实例并未执行完成,进行删除时会出现报错。** 需要根据 RepositoryService 去查询 DeploymentId*/
@Test
public void deleteDeployment(){// 流程部署idString deployMentId = "25001"; // 流程部署 id// 获取数据库的连接信息ProcessEngine defaultProcessEngine = ProcessEngines.getDefaultProcessEngine();RepositoryService repositoryService = defaultProcessEngine.getRepositoryService();// 当该已部署的流程,存在流程中的实例时,执行当前流程会报错!//repositoryService.deleteDeployment(deployMentId);// 如果需要进行强制删除 则可以采取下列的方式进行repositoryService.deleteDeployment(deployMentId,true);
}