Flowable学习文档
TaskService
一般TaskService的方法及使用场景
- createTaskQuery():创建一个任务查询器,用于查询任务的状态和详细信息。
- complete(taskId):完成任务,将任务从“正在进行”状态转换为“已完成”状态。
- claim(taskId, userId):声明任务,设置任务执行人。
- setAssignee(taskId, userId):设置任务执行人,同claim。
- setDueDate(taskId, dueDate):设置任务到期日期。
- setPriority(taskId, priority):设置任务优先级。
- setParentTaskId(taskId, parentTaskId):设置任务的父任务。
- setVariable(taskId, variableName, value):设置任务变量。
互斥网关实现
-
创建一个bpmn文件,并在其中添加一个互斥网关:
<?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"> <process id="oneTaskProcess"> <startEvent id="start" /> <sequenceFlow id="flow1" sourceRef="start" targetRef="gateway" /> <exclusiveGateway id="gateway" name="互斥网关" /> <sequenceFlow id="flow2" sourceRef="gateway" targetRef="task1" /> <sequenceFlow id="flow3" sourceRef="gateway" targetRef="task2" /> <userTask id="task1" name="任务1" /> <userTask id="task2" name="任务2" /> </process> </definitions>
-
在Java代码中定义流程变量,并启动流程
Map<String, Object> variables = new HashMap<>(); variables.put("isTask1", true); ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess", variables);
-
在互斥网关的表达式中使用流程变量:
<exclusiveGateway id="gateway" name="互斥网关"> <conditionExpression xsi:type="tFormalExpression"> ${isTask1 == true} </conditionExpression> </exclusiveGateway>
当流程变量isTask1
的值为true
时,流程将执行任务1;当值为false
时,流程将执行任务2。
这就是一个简单的Flowable互斥网关的例子。你可以根据自己的需求调整代码实现。
实现服务模块任务
一般执行服务任务,我们需要实现TaskListener接口,并添加对应的方法
eg:
@Service
public class MyTaskListener implements TaskListener {
@Autowired
private TaskRepository taskRepository;
@Override
public void notify(DelegateTask delegateTask) {
Task task = new Task();
task.setTaskId(delegateTask.getId());
task.setTaskName(delegateTask.getName());
task.setTaskDescription(delegateTask.getDescription());
taskRepository.save(task);
}
}
<serviceTask id="saveTask" name="Save Task" activiti:class="com.example.MyTaskListener">
<extensionElements>
<activiti:taskListener event="complete" class="com.example.MyTaskListener" />
</extensionElements>
</serviceTask>
<serviceTask id="sid-991a024b-d502-4e13-be54-d1749a69e9e8" flowable:exclusive="true" name="准备材料">
<extensionElements>
<flowable:taskListener event="create" class="com.example.MyTaskListener" />
</extensionElements>
</serviceTask>
客户模块实现
bpmn20文件
<userTask id="directorTak" name="经理审批">
<extensionElements>
<flowable:taskListener event="create"
class="com.sagamiyun.flowable.listener.ManagerTaskHandler"/>
</extensionElements>
</userTask>
<userTask id="bossTask" name="老板审批">
<extensionElements>
<flowable:taskListener event="create"
class="com.sagamiyun.flowable.listener.BossTaskHandler"/>
</extensionElements>
</userTask>
ManagerTaskHandler
public class ManagerTaskHandler implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
// 获取相关的数据
String taskId = delegateTask.getId();
String taskName = delegateTask.getName();
String assignee = delegateTask.getAssignee();
...
// 执行与流程相关的业务逻辑
...
}
}
BossTaskHandler
public class BossTaskHandler implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
// 获取相关的数据
String taskId = delegateTask.getId();
String taskName = delegateTask.getName();
String assignee = delegateTask.getAssignee();
...
// 执行与流程相关的业务逻辑
...
}
}
分支判断
在对应的<sequenceFlow/>
标签中
<sequenceFlow id="directorNotPassFlow" name="驳回" sourceRef="directorTak" targetRef="fillTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="bossNotPassFlow" name="驳回" sourceRef="bossTask" targetRef="fillTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="bossPassFlow" name="通过" sourceRef="bossTask" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="directorPassFlow" name="通过" sourceRef="directorTak" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
</sequenceFlow>
输入对应的判断条件
在对应的用户服务中
/**
* 批准
*
* @param taskId 任务ID
*/
@RequestMapping(value = "apply")
@ResponseBody
public String apply(String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new RuntimeException("流程不存在");
}
//通过审核
HashMap<String, Object> map = new HashMap<>();
// 重点
map.put("outcome", "通过");
taskService.complete(taskId, map);
return "processed ok!";
}
/**
* 拒绝
*/
@ResponseBody
@RequestMapping(value = "reject")
public String reject(String taskId) {
HashMap<String, Object> map = new HashMap<>();
// 重点
map.put("outcome", "驳回");
taskService.complete(taskId, map);
return "reject";
}
乱码解决
配置FlowableConfig配置类
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Override
public void configure(SpringProcessEngineConfiguration engineConfiguration) {
engineConfiguration.setActivityFontName("宋体");
engineConfiguration.setLabelFontName("宋体");
engineConfiguration.setAnnotationFontName("宋体");
}
}
入门Demo
为什么使用flowable,就是因为在多节点流程及审批中,涉及多个节点的审批,这样会牵扯太多的连表查询,如果流程过多,无非加重了程序员的心智负担,这时候就需要引入flowable或其他BPM引擎,如camunda。使用该类BPM引擎的有点就是,业务在提交审批驳回这些流程中,可以将对应数据put进该线程ID及工作ID中,然后通过执行一些RuntimeService的方法来将该线程推送到其他节点,如驳回节点,通过节点,同时他还设置了自动流程执行节点,将某个类实现JavaDelegate来实现自动执行流程,当然这个节点还有其他类型,如httpASK,script脚本执行,并行流程执行等,这里由于我技术有限,就只讲用户节点和服务节点
Flowable 的引入
要在项目中使用 Flowable,首先需要添加相应的依赖。以下是一个 Maven 项目的依赖示例:
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.6.0</version>
</dependency>
创建一个简单的 BPMN 2.0 文件
例如 process.bpmn20.xml
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn20="http://www.flowable.org/bpmn20"
id="sampleProcess"
targetNamespace="http://www.flowable.org/processdef">
<bpmn2:process id="myProcess" name="My Sample Process">
<bpmn2:startEvent id="start" />
<bpmn2:sequenceFlow id="flow1" sourceRef="start" targetRef="approveTask" />
<bpmn2:userTask id="approveTask" name="Approve Request">
<bpmn2:incoming>flow1</bpmn2:incoming>
<bpmn2:outgoing>flow2</bpmn2:outgoing>
</bpmn2:userTask>
<bpmn2:sequenceFlow id="flow2" sourceRef="approveTask" targetRef="end" />
<bpmn2:endEvent id="end" />
</bpmn2:process>
</bpmn2:definitions>
使用 Flowable 的 API 来部署并启动这个流程
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
public void deployProcess() {
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("process.bpmn20.xml")
.deploy();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");
}
BPMN 2.0 文件结构
BPMN 2.0 文件是用 XML 编写的,它包含了流程定义的所有信息,如流程节点、连线、事件等。一个典型的 BPMN 2.0 文件包括以下几个主要部分:
definitions
:包含整个流程定义。process
:表示一个具体的流程。startEvent
:流程的开始userTask
:用户任务节点,表示需要人工处理的任务。sequenceFlow
:用于连接流程中的元素,表示流程执行的顺序。endEvent
:流程的结束事件。
节点类型及作用
在 Flowable 的 BPMN 2.0 文件中,有多种节点类型。以下是一些常见的节点类型及其作用:
startEvent
:开始事件,表示流程的起点。endEvent
:结束事件,表示流程的终点。userTask
:用户任务,需要人工处理的任务。serviceTask
:服务任务,系统自动执行的任务,通常用于调用外部服务或执行脚本。scriptTask
:脚本任务,用于执行脚本代码。
网关类型及作用
在 Flowable 的 BPMN 2.0 文件中,有多种网关类型。以下是一些常见的网关类型及其作用:
exclusiveGateway
:排他网关,根据条件选择一个分支进行执行。当满足多个条件时,只会选择第一个满足条件的分支。parallelGateway
:并行网关,用于同时执行多个分支。它有两种用途:一是用于同步并行执行的分支,二是用于分支的并行执行。inclusiveGateway
:包容网关,根据条件选择一个或多个分支进行执行。当满足多个条件时,会执行所有满足条件的分支。eventBasedGateway
:事件网关,等待并捕获多个事件。捕获到的第一个事件将触发对应的分支,其他事件将被忽略。
TaskService 和 RuntimeService 常用方法
TaskService
TaskService 主要用于管理任务。以下是一些常用方法:
createTaskQuery()
:创建一个任务查询对象,用于查询符合条件的任务。claim(String taskId, String userId)
:认领任务,将任务分配给指定的用户。complete(String taskId)
:完成任务,任务会进入下一个流程节点。setVariable(String taskId, String variableName, Object value)
:设置任务的变量。getVariable(String taskId, String variableName)
:获取任务的变量。
RuntimeService
RuntimeService 主要用于管理流程实例。以下是一些常用方法:
startProcessInstanceByKey(String processDefinitionKey)
:通过流程定义的 key 来启动一个流程实例。createProcessInstanceQuery()
:创建一个流程实例查询对象,用于查询符合条件的流程实例。signalEventReceived(String signalName)
:触发信号事件,用于控制流程的执行。setVariable(String executionId, String variableName, Object value)
:设置流程实例的变量。getVariable(String executionId, String variableName)
:获取流程实例的变量。
使用示例
开启流程
Map<String, Object> variables = new HashMap<>();
variables.put("taskUser", SecurityUtils.getUser().getId());
ProcessInstance partsApproval = runtimeService.startProcessInstanceByKey("parts_approval", variables);
bpmn20
<userTask id="sid-d394db3a-8346-490e-94c8-d8e18c206b4c" name="订单用户" flowable:assignee="${taskUser}"/>
有互斥网关的情况下,通过节点
Map<String, Object> variables = new HashMap<>();
variables.put("outcome", "审核通过");
taskService.complete(task.getId(), variables);
bpmn20
<sequenceFlow id="sid-b9f268ee-0b8c-4b8c-a521-ee46f1166918" sourceRef="sid-c5a51a30-9b12-499d-8b1b-436bfb721b6d" targetRef="sid-6b6f5142-0d90-4684-b16c-e2c9fdc72c1d" name="审核通过">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='审核通过'}]]> </conditionExpression>
</sequenceFlow>
有互斥网关的情况下,驳回task
Map<String, Object> variables = new HashMap<>();
variables.put("outcome", "审核驳回");
taskService.complete(task.getId(), variables);
bpmn20
<sequenceFlow id="sid-1de28ad7-a2e3-496f-ba0e-6f704c87eb5e" sourceRef="sid-c5a51a30-9b12-499d-8b1b-436bfb721b6d"
targetRef="sid-d394db3a-8346-490e-94c8-d8e18c206b4c" name="审核驳回">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='审核驳回'}]]></conditionExpression>
</sequenceFlow>
将对应线程实例推送到指定节点
var currentActivityIds = new ArrayList<String>();
currentActivityIds.add(PART_AUDIT_NODE_ID);
runtimeService.createChangeActivityStateBuilder().processInstanceId(productionInstance.getProcessInstanceId()).moveActivityIdsToSingleActivityId(currentActivityIds, REVIEW_NODE_ID).changeState();
常见问题
序列化问题
在使用 Flowable 时,流程变量的值需要被序列化以便存储。当对象发生变更时,可能导致存储的序列化数据与当前对象不兼容,从而引发序列化问题。要解决这个问题,可以使用以下方法:
- 确保对象变更时向后兼容。
- 使用可控的序列化方式,如 JSON 或 XML,这样即使对象发生变更,也能确保反序列化的正确性。
序列化方式示例
以下是一个使用 JSON 序列化方式存储流程变量的例子:
import com.fasterxml.jackson.databind.ObjectMapper;
@Autowired
private TaskService taskService;
public void setJsonVariable(String taskId) {
Map<String, Object> data = new HashMap<>();
data.put("key1", "value1");
data.put("key2", "value2");
ObjectMapper objectMapper = new ObjectMapper();
try {
String jsonData = objectMapper.writeValueAsString(data);
taskService.setVariable(taskId, "jsonData", jsonData);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
public Map<String, Object> getJsonVariable(String taskId) {
String jsonData = (String) taskService.getVariable(taskId, "jsonData");
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(jsonData, new TypeReference<Map<String, Object>>() {});
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
在这个例子中,我们使用 Jackson 库将流程变量序列化为 JSON 字符串。当需要设置流程变量时,我们将 Map 对象转换为 JSON 字符串,然后使用 taskService.setVariable()
方法将其存储。当需要获取流程变量时,我们使用 taskService.getVariable()
方法获取 JSON 字符串,然后将其转换回 Map 对象。 使用这种方式,即使对象发生变更,我们仍然可以确保序列化和反序列化的正确性。
Flowable 被锁导致程序无法启动
当 Flowable 程序没有正常关闭时,可能导致 Flowable 被锁,进而使程序无法启动。为了解决这个问题,可以:
- 检查程序是否正常关闭,确保程序在关闭时正确释放资源。
- 清除 Flowable 的锁表中的锁记录,但请注意备份数据以防止数据丢失。
工作中突然领导需求要做该BPM引擎,花费了一段时间去学习了该技术,当然还有一点要提的是,引入flowable会同时创建50~60张表,在应用启动的时候也会变慢,还是需要根据具体需要去引入flowable。当然画图工具也有:比如IDEA的BPMN绘图插件,还有flowable的UI绘图,虽然这两个画流程图还是很恶心。
Q.E.D.