19. Flowable 与 BPMN 工作流引擎实战

深入 BPMN 2.0 规范、Flowable 工作流引擎设计、审批流程建模、事件驱动与 Spring Boot 整合

在企业应用中,业务流程管理(BPM) 是核心基础设施之一:请假审批、订单审核、采购流程、财务核销等都需要灵活可变的流程支持。硬编码流程的方式在需求变更时成本极高,而 BPMN 2.0 工作流引擎通过可视化建模 + 运行时解释执行,实现了业务逻辑与流程编排的分离。

1. BPMN 2.0 核心概念

1.1 基本元素

元素类型图标用途XML 标签
开始事件流程起点<startEvent>
结束事件流程终点<endEvent>
用户任务需要人工处理<userTask>
服务任务▭+齿轮自动执行的服务<serviceTask>
网关分支/汇聚<exclusiveGateway>
顺序流流转方向<sequenceFlow>
子流程▭+框嵌套流程<subProcess>

1.2 网关类型

网关行为使用场景
排他网关 (XOR)只走一条满足条件的分支条件审批:金额>1万走高级审批
并行网关 (AND)所有分支同时执行会签:需要多部门同时审批
包容网关 (OR)走所有满足条件的分支可选审批:满足的条件都走
事件网关基于事件选择分支等待多个外部事件之一

2. Flowable 架构

2.1 核心引擎

ProcessEngine                        TaskService
  ├── RepositoryService ← 部署流程定义     ├── 查询任务
  ├── RuntimeService  ← 启动/控制流程实例   ├── 完成任务
  ├── TaskService     ← 用户任务管理        └── 委派/转办
  ├── HistoryService  ← 历史数据查询
  ├── ManagementService ← 引擎管理
  └── IdentityService ← 用户/组管理

2.2 数据模型

表前缀用途
ACT_RE_*Repository: 流程定义、部署
ACT_RU_*Runtime: 运行时实例、任务、变量
ACT_HI_*History: 历史流程、活动、变量
ACT_ID_*Identity: 用户、组、成员关系
ACT_EVT_LOG事件日志

2.3 Spring Boot Starter 整合

<dependencies>
    <dependency>
        <groupId>org.flowable</groupId>
        <artifactId>flowable-spring-boot-starter</artifactId>
        <version>6.8.0</version>
    </dependency>
</dependencies>
spring:
  flowable:
    database-schema-update: true    # 自动建表
    async-executor-activate: true   # 启用异步执行器
    history-level: full             # 完整历史记录
    db-history-used: true

3. 流程定义与部署

3.1 请假流程 BPMN XML

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:flowable="http://flowable.org/bpmn"
             targetNamespace="http://mycompany.com/leave">

  <process id="leaveApproval" name="请假审批流程">

    <!-- 开始 -->
    <startEvent id="start" />
    <sequenceFlow sourceRef="start" targetRef="applyTask" />

    <!-- 申请人填写 -->
    <userTask id="applyTask" name="填写请假申请"
              flowable:assignee="${applicant}"
              flowable:formKey="leaveForm">
      <extensionElements>
        <flowable:taskListener event="complete"
            class="com.myapp.listener.CalcDurationListener" />
      </extensionElements>
    </userTask>
    <sequenceFlow sourceRef="applyTask" targetRef="decisionGateway" />

    <!-- 排他网关:根据天数判断 -->
    <exclusiveGateway id="decisionGateway" />

    <sequenceFlow sourceRef="decisionGateway" targetRef="managerTask">
      <conditionExpression xsi:type="tFormalExpression">
        ${days &lt;= 3}
      </conditionExpression>
    </sequenceFlow>

    <sequenceFlow sourceRef="decisionGateway" targetRef="directorTask">
      <conditionExpression xsi:type="tFormalExpression">
        ${days &gt; 3}
      </conditionExpression>
    </sequenceFlow>

    <!-- 经理审批(<=3天) -->
    <userTask id="managerTask" name="部门经理审批"
              flowable:candidateGroups="managers">
      <extensionElements>
        <flowable:taskListener event="create"
            class="com.myapp.listener.NotifyManagerListener" />
      </extensionElements>
    </userTask>
    <sequenceFlow sourceRef="managerTask" targetRef="exclusiveGateway2" />

    <!-- 总监审批(>3天) -->
    <userTask id="directorTask" name="总监审批"
              flowable:candidateGroups="directors" />
    <sequenceFlow sourceRef="directorTask" targetRef="exclusiveGateway2" />

    <!-- 汇聚网关 -->
    <exclusiveGateway id="exclusiveGateway2" />
    <sequenceFlow sourceRef="exclusiveGateway2" targetRef="hrTask" />

    <!-- HR 备案 -->
    <userTask id="hrTask" name="HR 备案"
              flowable:candidateGroups="hr" />
    <sequenceFlow sourceRef="hrTask" targetRef="end" />

    <endEvent id="end" />

  </process>
</definitions>

3.2 部署流程

@Service
public class ProcessDeploymentService {
    @Autowired private RepositoryService repositoryService;

    public void deploy() {
        Deployment deployment = repositoryService.createDeployment()
            .addClasspathResource("processes/leave-approval.bpmn20.xml")
            .addClasspathResource("processes/leave-form.form")  // 可选:表单定义
            .name("请假审批流程v1.0")
            .category("hr")
            .deploy();

        System.out.println("部署ID: " + deployment.getId());
        System.out.println("部署时间: " + deployment.getDeploymentTime());
    }
}

4. 流程运行时API

4.1 启动流程实例

@Service
public class LeaveService {
    @Autowired private RuntimeService runtimeService;
    @Autowired private TaskService taskService;

    public String startLeaveProcess(String applicant, int days, String reason) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("applicant", applicant);
        variables.put("days", days);
        variables.put("reason", reason);
        variables.put("startTime", new Date());

        ProcessInstance instance = runtimeService
            .startProcessInstanceByKey("leaveApproval", variables);

        return instance.getId(); // 流程实例ID,后续查询用
    }
}

4.2 查询与完成任务

@Service
public class TaskQueryService {
    @Autowired private TaskService taskService;

    // 查询待办任务
    public List<Task> getTodoList(String assignee) {
        return taskService.createTaskQuery()
            .taskAssignee(assignee)      // 指定执行人
            .orderByTaskCreateTime().desc()
            .list();
    }

    // 查询组任务
    public List<Task> getGroupTasks(String groupId) {
        return taskService.createTaskQuery()
            .taskCandidateGroup(groupId)
            .orderByTaskCreateTime().desc()
            .list();
    }

    // 认领任务
    public void claimTask(String taskId, String userId) {
        taskService.claim(taskId, userId);
    }

    // 完成任务(带审批意见)
    public void completeTask(String taskId, String userId, boolean approved, String comment) {
        // 添加审批意见
        taskService.addComment(taskId, null, approved ? "同意" : "驳回", comment);

        Map<String, Object> variables = new HashMap<>();
        variables.put("approved", approved);
        variables.put("approver", userId);
        variables.put("approveTime", new Date());

        taskService.complete(taskId, variables);
    }

    // 转办任务
    public void delegateTask(String taskId, String delegateTo) {
        taskService.delegateTask(taskId, delegateTo);
    }

    // 驳回/退回上一步
    public void rejectTask(String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        String processInstanceId = task.getProcessInstanceId();

        // 获取历史节点,回退到申请节点
        List<HistoricActivityInstance> list = historyService
            .createHistoricActivityInstanceQuery()
            .processInstanceId(processInstanceId)
            .activityType("userTask")
            .orderByHistoricActivityInstanceStartTime().desc()
            .list();

        String targetNodeId = list.size() > 1 ? list.get(1).getActivityId() : null;
        runtimeService.createChangeActivityStateBuilder()
            .processInstanceId(processInstanceId)
            .moveActivityIdTo(task.getTaskDefinitionKey(), targetNodeId)
            .changeState();
    }
}

4.3 流程变量与监听

// 全局变量监听
@Component
public class ProcessVariableListener implements ExecutionListener {
    @Override
    public void notify(DelegateExecution execution) {
        String eventName = execution.getEventName(); // start, end, take
        String processInstanceId = execution.getProcessInstanceId();
        Map<String, Object> variables = execution.getVariables();

        log.info("流程[{}]事件[{}],变量: {}", processInstanceId, eventName, variables);
    }
}

// 任务监听
@Component
public class NotifyManagerListener implements TaskListener {
    @Autowired private NotificationService notificationService;

    @Override
    public void notify(DelegateTask delegateTask) {
        String event = delegateTask.getEventName(); // create, assignment, complete
        if ("create".equals(event)) {
            String assignee = delegateTask.getAssignee();
            String taskName = delegateTask.getName();
            notificationService.send(assignee, "您有新的审批任务: " + taskName);
        }
    }
}

5. 事件驱动与异步处理

5.1 消息边界事件

<!-- 超时自动处理 -->
<userTask id="managerTask" name="经理审批">
  <boundaryEvent id="timer" attachedToRef="managerTask" cancelActivity="true">
    <timerEventDefinition>
      <timeDuration>PT24H</timeDuration>  <!-- 24小时超时 -->
    </timerEventDefinition>
  </boundaryEvent>
  <sequenceFlow sourceRef="timer" targetRef="autoApproveTask" />
</userTask>

<serviceTask id="autoApproveTask" name="自动通过"
             flowable:class="com.myapp.delegate.AutoApproveDelegate" />

5.2 信号与消息事件

// 发送信号(全局广播)
runtimeService.signalEventReceived("policyUpdated");

// 发送消息(定向)
runtimeService.messageEventReceived("paymentReceived",
    runtimeService.createExecutionQuery()
        .processInstanceBusinessKey(orderId)
        .singleResult().getId());
<!-- 接收消息继续流程 -->
<intermediateCatchEvent id="messageCatch">
  <messageEventDefinition messageRef="paymentReceived" />
</intermediateCatchEvent>

6. 动态表单与流程设计器

6.1 Flowable Modeler

Flowable 提供开源的 Web 流程设计器(基于 BPNM.js),支持:

  • 拖拽式流程建模
  • 表单设计器
  • 决策表(DMN)设计
# 启用 Flowable UI
spring:
  flowable:
    modeler:
      enabled: true
    rest:
      app:
        admin:
          user-id: admin
          password: admin
          first-name: Admin

6.2 表单引擎

{
  "key": "leaveForm",
  "name": "请假申请表单",
  "fields": [
    {
      "id": "startDate",
      "name": "开始日期",
      "type": "date",
      "required": true
    },
    {
      "id": "endDate",
      "name": "结束日期",
      "type": "date",
      "required": true
    },
    {
      "id": "reason",
      "name": "请假原因",
      "type": "multi-line-text",
      "required": true
    },
    {
      "id": "type",
      "name": "请假类型",
      "type": "dropdown",
      "options": [
        { "id": "annual", "name": "年假" },
        { "id": "sick", "name": "病假" },
        { "id": "personal", "name": "事假" }
      ]
    }
  ]
}

7. 企业级扩展实践

7.1 多租户流程隔离

// 启动时设置租户ID
identityService.setAuthenticatedUserId(userId);

ProcessInstance instance = runtimeService
    .createProcessInstanceBuilder()
    .processDefinitionKey("leaveApproval")
    .tenantId("tenant_" + companyId)  // 租户隔离
    .variables(variables)
    .start();

7.2 流程版本控制

// 查询最新版本
ProcessDefinition latest = repositoryService
    .createProcessDefinitionQuery()
    .processDefinitionKey("leaveApproval")
    .latestVersion()
    .singleResult();

// 启动时指定版本
ProcessInstance instance = runtimeService
    .startProcessInstanceById(latest.getId(), variables);

7.3 流程性能优化

优化点建议
历史级别非必要时 history-level: audit 而非 full
异步执行服务任务使用 flowable:async="true"
批量部署一次部署多个流程定义
定时清理定期删除 6 个月前的历史数据
索引优化ACT_RU_TASKASSIGNEE_PROC_INST_ID_

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java-enterprise」更多文章

  1. 限流算法深度解析:令牌桶、漏桶与滑动窗口计数
  2. Java 代码质量:SonarQube、Checkstyle 与 SpotBugs 工程化实践
  3. Spring IoC 容器与依赖注入原理深度剖析