一、前情回顾
在04篇中,我们用代码的形式控制了一个简单的绩效流程的运转,并且基本熟悉了流程运行过程中涉及到的相关表。让我们继续回到这个简单的绩效流程,它依然是有所欠缺的,比如在实际业务场景中,我们在绩效的每个环节都应该有个分数,但目前我们只是指派个人去完成各个节点,并没有输入分数和保存分数的步骤。因此,在今天的文章中,我们将使用Flowable包含的流程变量来管理流程中的自定义数据。
二、认识流程变量
1.定义
首先,我们来看下流程变量的官方定义:在Flowable中,流程变量是在流程实例按步骤执行时需要保存并使用的数据,这些数据被称为变量(variable)。流程实例可以持有变量,这些变量被称为流程变量。
我们目前只需要知道变量可以在Java服务任务中用于调用外部服务(例如为服务调用提供输入或结果存储),参考上面绩效分数的例子。我们在实际业务中执行任何流程都可能涉及到有数据流转,流程变量负责的就是在流程流转的过程中传递业务参数。
2.分类
为了提高使用效率,Flowable将变量分为两种:运行时变量和历史变量:
运行时变量:这是流程实例运行时的变量,存入
act_ru_variable
表中。在流程实例运行结束时,此实例的变量在表中删除。在流程实例创建及启动时,可以设置流程变量,也可以在流程执行中加入变量。由于流程实例结束时,对应在运行时表的数据跟着被删除,所以,查询一个已经完结流程实例的变量,只能在历史变量表中查找。历史变量:历史变量存入
act_hi_varinst
表中。在流程启动时,流程变量会同时存入历史变量表中。这样即使在流程结束时,历史表中的变量仍然存在。这里可以想想前文中的历史流程也是这样,在Flowable的设计中,我们一个流程启动到结束过程中涉及到的所有东西都可以在相应的历史表上查到。3.类型
一般来说,一个变量由一个名称和值组成,就是个经典的KV结构。K是字符串类型的,V可以是基本数据类型,也可以是个Java对象(Ps:实现了Serializable接口)。如果流程变量名称相同的时候,后一次的值替换前一次的值。
三、使用流程变量
说了这么多,接下来让我们在我们前面定义的绩效流程中加入流程变量的使用,在自评阶段就需要打分,然后上级评和隔级评都需要打分,并且最后隔级评给出的分数权重最大,也就是本次绩效的最终的分数。
1.接口优化
部署流程模型就不赘述了,详情请翻阅04篇,这里主要为了更贴近实际,部署流程后返回流程定义id,这样前端拿到流程定义id也就可以发起请求来启动流程了,这里大家具体看下代码,和原本相比,多了第2步:
@PostMapping("/deploy")
public ResponseEntity<Object> createProcessDef() {
//1,创建部署对象
Deployment deployment = repositoryService.createDeployment()
//2.添加流程定义文件
.addClasspathResource("process/performance.bpmn20.xml")
//3.设置流程名称
.name("绩效流程")
//4.部署
.deploy();
//2.通过流程部署id查询流程定义id
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId()).singleResult();
//3.返回流程定义id
return new ResponseEntity<>(new BaseResponse<>(processDefinition.getId()), HttpStatus.OK);
}
2.插入全局流程变量
完成了流程的部署后就可以启动流程了,因为我们在部署成功之后返回了流程定义id,那么这里就可以传参调用了。我们也需要在这时创建这个分数变量,大家可以在这里结合具体场景想想,作为执行层是不是得在完成自评前写好自评分数然后提交。于是我们对原有的启动流程接口进行改造:
/**
* 启动流程.
*
* @return 流程key
*/
@PostMapping("/start/{processId}")
public ResponseEntity<Object> startProcessDef(@PathVariable String processId) {
//1.启动流程
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processId);
//2.维护该流程的分数变量
runtimeService.setVariable(processInstance.getId(), "score", 0);
//3.返回流程实例对象id
return new ResponseEntity<>(new BaseResponse<>(processInstance.getId()), HttpStatus.OK);
}
在原有代码的基础上我们通过runtimeService设置了一个流程变量,这个流程变量是全局的,也就是这个流程没结束的情况下都可以查到并且进行维护,相同key的value会被最新的覆盖,我们执行这个接口后看下act_ru_variable表的变化:
在这张流程变量表上我们可以看到多了一条数据,注意看我圈起来的3个字段:NAME和TEXT很明了,就对应着前面的key和value,这个EXECUTION_ID则是我们设置的流程实例ID,如果我们不设置,引擎也会自动生成,我这里只是方便演示这么做的(Ps:不建议自己改,因为当你的流程有分支时,这个执行ID它就是动态的),我们要通过这个执行ID进行对应变量的查询与维护(Ps:再次强调,这张表只负责运行时变量)。
3.维护流程变量
既然在启动流程的时候设置了变量,那么在接下来的每个节点都可以对变量进行更新,从而实现我们提到的需求,通过前面设置的执行ID查询关联的变量,并根据key对value进行维护,代码如下:
@PostMapping("/complete/agentUser/{agentUser}/score/{score}/processInstanceId/{processInstanceId}")
public ResponseEntity<Object> completeTask(@PathVariable String agentUser,
@PathVariable String score,
@PathVariable String processInstanceId) {
//1.查询指定用户的待办任务
String agentTaskId = findUserAgentTask(agentUser);
//2.更新绩效流程变量
runtimeService.setVariable(processInstanceId, "score", score);
//3.完成指定任务的审批
taskService.complete(agentTaskId);
//4.返回响应
return new ResponseEntity<>(new BaseResponse<>(null), HttpStatus.OK);
}
然后使用Apifox进行接口测试,对比04篇这个接口多了两个参数,分别是分数和流程实例ID。我说明下分数在实际场景中肯定是用户填写后由前端传来的;而流程实例ID在前面的启动流程中我们也有返回,所以前端同学也能拿到:
执行成功后,我们再去观察下act_ru_variable表中的那条流程变量,会发现它也同步更新了:
那么我们这里结合实际场景思考下,在我们现在设计的这套流程里,是不是每个节点都能通过流程ID查询并维护这个分数变量从而实现前面提到的业务需求。需要注意的是:我这里只是假设的业务场景,所以各位读者在自己实操的时候要根据自己业务需求来做,这个系列的文章主要目的是尽可能帮各位读者理清各种表的作用和各种API的用法。
4.查询历史变量
当我们这个流程执行结束的时候,act_ru_variable表中的这条数据就消失了:
这时候我们想再查到它就需要到act_hi_varinst表里,也就是流程变量历史表,通过流程ID就可以查到它,那么大家想想如果业务需要查询历史绩效包括分数,是不是就很简单了:
5.插入局部流程变量
和02小标题进行对比,我们发现区别在于一个是全局,另一个是局部。大家观察下前面的部分里的流程变量在这个流程没结束前,任何节点都可以查询和维护,可以理解成它的生命周期伴随着那个流程。但局部流程变量是我们在某个节点声明,一旦这个节点执行过之后,它就和节点的任务一样都只能去历史表里查询了,所以全局和局部最大的区别就是生命周期的不同。
知道了它们的区别,接下来我们看看怎么使用,首先runtimeService和taskService都可以设置流程变量,这里主要是API的变化,代码如下:
@PostMapping("/complete/agentUser/{agentUser}/score/{score}/processInstanceId/{processInstanceId}")
public ResponseEntity<Object> completeTask(@PathVariable String agentUser,
@PathVariable String score,
@PathVariable String processInstanceId) {
//1.查询指定用户的待办任务
String agentTaskId = findUserAgentTask(agentUser);
//2.更新绩效流程变量
runtimeService.setVariable(processInstanceId, "score", score);
//3.设置任务节点流程变量
taskService.setVariableLocal(agentTaskId, "score2", score);
//4.完成指定任务的审批
taskService.complete(agentTaskId);
//5.返回响应
return new ResponseEntity<>(new BaseResponse<>(null), HttpStatus.OK);
}
看03处就是在设置局部流程变量,我在完成任务接口上设置了一个任务节点变量,也就是当我执行这个接口后,只能在历史表里看到这条score2的数据,调用接口测试下:
执行成功后,我们再去act_hi_varinst表查看有没有key为score2的数据:
可以看到此时key为score2的流程变量直接在历史表里,大家需要明白的是,无论是全局流程变量和局部流程变量都需要结合实际业务需求与场景进行使用,我这里只是告诉大家有这么个东西,在项目中怎么用是需要思考的,假设我们当前的这个绩效流程需要查询绩效历史的时候同时展示各个历史节点的分数,大家想想这个局部流程变量是不是就可以派上用场了。但是这也只是一种实现思路,其实用全局流程变量也可以实现,在每个节点设置变量的时候多设置一下,这个新变量在原key的基础上加上任务id,后续它就不会被覆盖了。所以API是很丰富的,主要看大家想怎么用。
6.补充
在这里补充一下,runtimeService也有设置局部流程变量,但传的ID是会影响结果的。如果你传的是流程ID,它就是全局,即使你用这个API;如果你传的是任务ID,它则是关联对应任务的局部流程变量。
runtimeService.setVariableLocal(XXId, "score", score);
四、小结
首先对一直支持该系列的读者表示感谢,同时也对大家表示抱歉,由于近期工作较忙,导致文章更新速度较慢。其实一开始写这个系列只是想让自己回顾下,但是随着慢慢的更新,开始有读者在评论区“催更”,让我看到了这个系列是有价值的,确实可以帮到别人,所以我也会持续更新下去的,给大家打个包票该系列至少更新10篇,尽可能地为大家详细介绍Flowable的用法以及引导大家结合实际业务进行思考。另外,个人水平毕竟有限,如果文章中有错误的地方,也欢迎大家批评指正!