Flowable学习文档

TaskService

一般TaskService的方法及使用场景

  1. createTaskQuery():创建一个任务查询器,用于查询任务的状态和详细信息。
  2. complete(taskId):完成任务,将任务从“正在进行”状态转换为“已完成”状态。
  3. claim(taskId, userId):声明任务,设置任务执行人。
  4. setAssignee(taskId, userId):设置任务执行人,同claim。
  5. setDueDate(taskId, dueDate):设置任务到期日期。
  6. setPriority(taskId, priority):设置任务优先级。
  7. setParentTaskId(taskId, parentTaskId):设置任务的父任务。
  8. setVariable(taskId, variableName, value):设置任务变量。

互斥网关实现

  1. 创建一个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>
    
  2. 在Java代码中定义流程变量,并启动流程

    Map<String, Object> variables = new HashMap<>();
    variables.put("isTask1", true);
    ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("oneTaskProcess", variables);
    
  3. 在互斥网关的表达式中使用流程变量:

    <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 文件包括以下几个主要部分:

  1. definitions:包含整个流程定义。
  2. process:表示一个具体的流程。
  3. startEvent:流程的开始
  4. userTask:用户任务节点,表示需要人工处理的任务。
  5. sequenceFlow:用于连接流程中的元素,表示流程执行的顺序。
  6. endEvent:流程的结束事件。

节点类型及作用

在 Flowable 的 BPMN 2.0 文件中,有多种节点类型。以下是一些常见的节点类型及其作用:

  1. startEvent:开始事件,表示流程的起点。
  2. endEvent:结束事件,表示流程的终点。
  3. userTask:用户任务,需要人工处理的任务。
  4. serviceTask:服务任务,系统自动执行的任务,通常用于调用外部服务或执行脚本。
  5. scriptTask:脚本任务,用于执行脚本代码。

网关类型及作用

在 Flowable 的 BPMN 2.0 文件中,有多种网关类型。以下是一些常见的网关类型及其作用:

  1. exclusiveGateway:排他网关,根据条件选择一个分支进行执行。当满足多个条件时,只会选择第一个满足条件的分支。
  2. parallelGateway:并行网关,用于同时执行多个分支。它有两种用途:一是用于同步并行执行的分支,二是用于分支的并行执行。
  3. inclusiveGateway:包容网关,根据条件选择一个或多个分支进行执行。当满足多个条件时,会执行所有满足条件的分支。
  4. eventBasedGateway:事件网关,等待并捕获多个事件。捕获到的第一个事件将触发对应的分支,其他事件将被忽略。

TaskService 和 RuntimeService 常用方法

TaskService

TaskService 主要用于管理任务。以下是一些常用方法:

  1. createTaskQuery():创建一个任务查询对象,用于查询符合条件的任务。
  2. claim(String taskId, String userId):认领任务,将任务分配给指定的用户。
  3. complete(String taskId):完成任务,任务会进入下一个流程节点。
  4. setVariable(String taskId, String variableName, Object value):设置任务的变量。
  5. getVariable(String taskId, String variableName):获取任务的变量。

RuntimeService

RuntimeService 主要用于管理流程实例。以下是一些常用方法:

  1. startProcessInstanceByKey(String processDefinitionKey):通过流程定义的 key 来启动一个流程实例。
  2. createProcessInstanceQuery():创建一个流程实例查询对象,用于查询符合条件的流程实例。
  3. signalEventReceived(String signalName):触发信号事件,用于控制流程的执行。
  4. setVariable(String executionId, String variableName, Object value):设置流程实例的变量。
  5. 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 时,流程变量的值需要被序列化以便存储。当对象发生变更时,可能导致存储的序列化数据与当前对象不兼容,从而引发序列化问题。要解决这个问题,可以使用以下方法:

  1. 确保对象变更时向后兼容。
  2. 使用可控的序列化方式,如 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 被锁,进而使程序无法启动。为了解决这个问题,可以:

  1. 检查程序是否正常关闭,确保程序在关闭时正确释放资源。
  2. 清除 Flowable 的锁表中的锁记录,但请注意备份数据以防止数据丢失。

工作中突然领导需求要做该BPM引擎,花费了一段时间去学习了该技术,当然还有一点要提的是,引入flowable会同时创建50~60张表,在应用启动的时候也会变慢,还是需要根据具体需要去引入flowable。当然画图工具也有:比如IDEA的BPMN绘图插件,还有flowable的UI绘图,虽然这两个画流程图还是很恶心。

Q.E.D.


一个平凡人的追梦之旅