Flowable 工作流引擎在 OA 系统中的深度实践
·
Flowable 工作流引擎在 OA 系统中的深度实践
📖 相关链接
官方网站
演示站点
开源仓库

目录
1. 工作流引擎概述
1.1 工作流引擎的价值
在 OA 系统中,工作流引擎是核心组件,承担着流程定义、流程执行、任务管理等关键职责。一个优秀的工作流引擎需要具备以下能力:
| 能力维度 | 技术要求 | 业务价值 |
|---|---|---|
| 流程建模 | 支持 BPMN2.0 标准、可视化建模 | 业务人员可自主设计流程 |
| 流程执行 | 高性能、高可用、事务一致性 | 保障业务流程稳定运行 |
| 任务管理 | 多种任务分配策略、任务委派/转办 | 灵活应对组织架构变化 |
| 流程监控 | 实时监控、性能分析、异常预警 | 及时发现和处理问题 |
| 扩展能力 | 自定义任务、自定义服务 | 适配复杂业务场景 |

1.2 Flowable vs 其他工作流引擎
在选择工作流引擎时,我们对比了主流方案:
| 特性 | Flowable | Activiti | Camunda | jBPM |
|---|---|---|---|---|
| 社区活跃度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| BPMN2.0支持 | 完整 | 完整 | 完整 | 完整 |
| 性能表现 | 优秀 | 良好 | 优秀 | 良好 |
| 文档完善度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Spring集成 | 原生支持 | 原生支持 | 原生支持 | 良好 |
| DMN支持 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 云原生 | 支持 | 有限 | 优秀 | 良好 |
| 学习曲线 | 适中 | 适中 | 陡峭 | 陡峭 |
Flowable 的优势:
- ** Activiti 原班人马打造**:延续了 Activiti 的设计理念,同时增加了更多企业级特性
- 功能完整性:支持 BPMN、DMN、Form、Content 等全套规范
- Spring Boot 深度集成:开箱即用的 Spring Boot Starter
- 性能优异:在流程实例数、吞吐量等指标上表现优秀
- 社区活跃:持续迭代更新,问题响应及时
1.3 点狮 OA 的流程需求
点狮 OA 系统的流程场景包括但不限于:
人事类:请假申请、加班申请、调休申请、离职申请
行政类:用车申请、会议室申请、印章申请、物品领用
财务类:差旅申请、报销申请、付款申请、借款申请
业务类:合同审批、订单审批、报价审批、项目审批
这些流程的共同特点:
- ✅ 流程节点复杂(会签、或签、条件分支)
- ✅ 审批层级多(3-10级审批)
- ✅ 需要与业务数据深度集成
- ✅ 要求高性能(日均10万+流程实例)
- ✅ 需要灵活的流程配置能力
2. Flowable 架构深度解析
2.1 Flowable 核心架构
Flowable 采用分层架构设计,核心层次如下:
┌─────────────────────────────────────────────────────────────┐
│ API 层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Flowable API│ │ BPMN API │ │ Form API │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 引擎层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Process Engine│ │ Task Engine │ │ Form Engine │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 持久层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ DB Schema │ │ MyBatis Mapper│ │ JPA Entity │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.2 核心组件详解
2.2.1 ProcessEngine(流程引擎)
流程引擎是 Flowable 的核心,负责流程的定义、执行、管理:
// ProcessEngine 配置
@Configuration
public class FlowableConfiguration {
@Bean
public ProcessEngine processEngine() {
return ProcessEngineConfiguration
.createProcessEngineConfigurationFromResourceDefault()
.buildProcessEngine();
}
@Bean
public RepositoryService repositoryService(ProcessEngine processEngine) {
return processEngine.getRepositoryService();
}
@Bean
public RuntimeService runtimeService(ProcessEngine processEngine) {
return processEngine.getRuntimeService();
}
@Bean
public TaskService taskService(ProcessEngine processEngine) {
return processEngine.getTaskService();
}
@Bean
public HistoryService historyService(ProcessEngine processEngine) {
return processEngine.getHistoryService();
}
@Bean
public ManagementService managementService(ProcessEngine processEngine) {
return processEngine.getManagementService();
}
}
核心服务职责:
| 服务 | 职责 | 主要方法 |
|---|---|---|
| RepositoryService | 流程定义管理 | deploy()、getDeployment() |
| RuntimeService | 流程实例管理 | startProcessInstanceByKey() |
| TaskService | 任务管理 | complete()、claim() |
| HistoryService | 历史数据查询 | createHistoric…Query |
| ManagementService | 系统管理 | getMetaData() |
2.2.2 数据库表结构
Flowable 使用 60+ 张表来存储流程相关数据,核心表分类:
-- 流程定义表(ACT_RE_)
ACT_RE_PROCDEF -- 流程定义表
ACT_RE_DEPLOYMENT -- 部署表
ACT_RE_MODEL -- 模型表
-- 流程实例表(ACT_RU_)
ACT_RU_EXECUTION -- 流程实例执行表
ACT_RU_TASK -- 任务实例表
ACT_RU_VARIABLE -- 流程变量表
ACT_RU_IDENTITYLINK -- 身份关联表
-- 历史数据表(ACT_HI_)
ACT_HI_PROCINST -- 历史流程实例
ACT_HI_TASKINST -- 历史任务实例
ACT_HI_VARINST -- 历史变量实例
ACT_HI_DETAIL -- 历史详情
-- 其他表(ACT_...)
ACT_EVT_LOG -- 事件日志
ACT_PROCDEF_INFO -- 流程定义信息
ACT_GE_BYTEARRAY -- 通用字节数组表
2.3 Flowable 与 Spring Boot 集成
点狮 OA 使用 Flowable 的 Spring Boot Starter:
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>${flowable.version}</version>
</dependency>
# application.yml
flowable:
# 数据库配置
database-schema-update: true
# 异步执行器配置
async-executor-activate: true
async-history-enabled: true
async-history-duration: 1h
# 流程定义缓存
process-definition-cache-limit: 200
# 任务执行器配置
executor-activate-thread-count: 10
executor-async-thread-count: 10
# 休眠任务配置
async-executor-message-queue-mode: true
3. 流程引擎集成与配置
3.1 自定义流程引擎配置
点狮 OA 对 Flowable 进行了深度定制:
@Configuration
public class PointlionFlowableConfiguration {
/**
* 自定义流程引擎配置
*/
@Bean
public ProcessEngineConfiguration processEngineConfiguration(
DataSource dataSource,
PlatformTransactionManager transactionManager) {
SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration();
// 基础配置
configuration.setDataSource(dataSource);
configuration.setTransactionManager(transactionManager);
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
// 性能优化配置
configuration.setAsyncExecutorEnabled(true); // 启用异步执行
configuration.setAsyncHistoryEnabled(true); // 启用异步历史
configuration.setAsyncHistoryDuration("PT1H"); // 历史数据异步保存1小时
configuration.setEnableExecutorPerProcessDefinition(true); // 每个流程定义独立执行器
// 缓存配置
configuration.setProcessDefinitionCacheLimit(200); // 流程定义缓存
configuration.setProcessDefinitionCacheEnabled(true);
// 事件监听器
configuration.setEventListeners(Arrays.asList(
new ProcessInstanceEventListener(),
new TaskCompletedEventListener()
));
// 自定义配置
configuration.setCustomPreVariableExpressions(Arrays.asList(
new CurrentTimeExpression(),
new CurrentUserExpression()
));
return configuration;
}
/**
* 流程引擎 Bean
*/
@Bean
public ProcessEngine processEngine(ProcessEngineConfiguration configuration) {
return configuration.buildProcessEngine();
}
/**
* 扩展任务服务
*/
@Bean
public PointlionTaskService pointlionTaskService(ProcessEngine processEngine) {
return new PointlionTaskService(processEngine);
}
/**
* 流程监听器配置
*/
@Bean
public GlobalEventListener globalEventListener() {
GlobalEventListener listener = new GlobalEventListener();
listener.setOnCommitted() {
@Override
public void onCommitted(ActivitiEvent event) {
handleProcessEvent(event);
}
};
return listener;
}
}
3.2 自定义任务服务
扩展 TaskService 增加业务方法:
@Service
public class PointlionTaskService {
@Resource
private TaskService taskService;
@Resource
private RuntimeService runtimeService;
/**
* 完成任务并设置变量
*/
public void completeWithVariables(String taskId, Map<String, Object> variables) {
taskService.setVariables(taskId, variables);
taskService.complete(taskId);
}
/**
* 批量完成任务
*/
public void batchComplete(List<String> taskIds) {
taskIds.forEach(taskId -> {
try {
taskService.complete(taskId);
} catch (Exception e) {
log.error("批量完成任务失败: taskId={}", taskId, e);
}
});
}
/**
* 委派任务
*/
public void delegateTask(String taskId, String userId) {
TaskEntity task = (TaskEntity) taskService.createTaskQuery()
.taskId(taskId)
.singleResult();
if (task != null) {
task.setOwner(task.getAssignee());
task.setAssignee(userId);
taskService.saveTask(task);
}
}
/**
* 加签任务
*/
public void addCounterSign(String taskId, String userId, boolean before) {
TaskEntity task = (TaskEntity) taskService.createTaskQuery()
.taskId(taskId)
.singleResult();
if (task != null) {
// 创建新的子任务
Task newTask = taskService.newTask();
newTask.setName(task.getName() + "-加签");
newTask.setAssignee(userId);
newTask.setParentTaskId(task.getId());
newTask.setProcessInstanceId(task.getProcessInstanceId());
taskService.saveTask(newTask);
}
}
/**
* 减签任务
*/
public void removeCounterSign(String taskId) {
TaskEntity task = (TaskEntity) taskService.createTaskQuery()
.taskId(taskId)
.singleResult();
if (task != null && StringUtils.isNotBlank(task.getParentTaskId())) {
// 删除子任务
taskService.deleteTask(taskId, true);
}
}
}
3.3 流程事件监听器
@Component
public class ProcessInstanceEventListener implements FlowableEventListener {
@Override
public void onEvent(FlowableEvent event) {
switch (event.getType()) {
case PROCESS_CREATED:
handleProcessCreated((FlowableEntityEvent) event);
break;
case PROCESS_COMPLETED:
handleProcessCompleted((FlowableEntityEvent) event);
break;
case TASK_CREATED:
handleTaskCreated((FlowableEntityEvent) event);
break;
case TASK_COMPLETED:
handleTaskCompleted((FlowableEntityEvent) event);
break;
default:
break;
}
}
/**
* 流程实例创建事件
*/
private void handleProcessCreated(FlowableEntityEvent event) {
String processInstanceId = event.getProcessInstanceId();
String processDefinitionId = event.getProcessDefinitionId();
log.info("[流程创建] processInstanceId={}, processDefinitionId={}",
processInstanceId, processDefinitionId);
// 记录流程创建日志
// 发送通知消息
// 更新业务状态
}
/**
* 流程实例完成事件
*/
private void handleProcessCompleted(FlowableEntityEvent event) {
String processInstanceId = event.getProcessInstanceId();
log.info("[流程完成] processInstanceId={}", processInstanceId);
// 记录流程完成日志
// 发送完成通知
// 更新业务数据
// 生成流程报告
}
/**
* 任务创建事件
*/
private void handleTaskCreated(FlowableEntityEvent event) {
String taskId = ((TaskEntity) event).getId();
String assignee = ((TaskEntity) event).getAssignee();
log.info("[任务创建] taskId={}, assignee={}", taskId, assignee);
// 发送任务通知
// 更新待办列表
}
/**
* 任务完成事件
*/
private void handleTaskCompleted(FlowableEntityEvent event) {
String taskId = ((TaskEntity) event).getId();
log.info("[任务完成] taskId={}", taskId);
// 记录任务完成日志
// 触发下一步流程
// 更新业务数据
}
@Override
public boolean isFailOnException() {
return false;
}
@Override
public boolean isAsync() {
return false;
}
}
4. 自定义流程设计器实现
4.1 流程设计器架构
点狮 OA 的流程设计器采用前后端分离架构:
┌─────────────────────────────────────────────────────────────┐
│ 前端(Vue3) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 流程设计器组件 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 节点面板 │ │ 属性面板 │ │ 工具栏 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ 流程图画布(SVG) │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓ REST API
┌─────────────────────────────────────────────────────────────┐
│ 后端(Spring Boot) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 流程保存接口 │ │ 流程部署接口 │ │ 流程导出接口 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Repository │ │ Runtime │ │ Task │ │
│ │ Service │ │ Service │ │ Service │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
4.2 流程模型数据结构
流程设计器的核心数据结构:
/**
* 流程模型 DTO
*/
@Data
public class ProcessModelDTO {
/**
* 模型ID
*/
private String id;
/**
* 模型名称
*/
private String name;
/**
* 模型KEY
*/
private String key;
/**
* 流程分类
*/
private String category;
/**
* 流程版本
*/
private Integer version;
/**
* 流程描述
*/
private String description;
/**
* BPMN XML
*/
private String bpmnXml;
/**
* 流程图JSON(前端使用)
*/
private String flowJson;
/**
* 表单配置
*/
private List<FormFieldDTO> formFields;
/**
* 流程权限配置
*/
private ProcessPermissionDTO permission;
}
/**
* 节点定义
*/
@Data
public class NodeDefinitionDTO {
/**
* 节点ID
*/
private String id;
/**
* 节点名称
*/
private String name;
/**
* 节点类型
*/
private NodeType type;
/**
* 节点位置
*/
private Position position;
/**
* 审批人配置
*/
private AssigneeConfigDTO assigneeConfig;
/**
* 表单权限
*/
private FormPermissionDTO formPermission;
/**
* 超时配置
*/
private TimeoutConfigDTO timeoutConfig;
/**
* 监听器配置
*/
private List<ListenerConfigDTO> listeners;
}
/**
* 节点类型
*/
public enum NodeType {
START_EVENT, // 开始节点
END_EVENT, // 结束节点
USER_TASK, // 用户任务
SERVICE_TASK, // 服务任务
EXCLUSIVE_GATEWAY, // 排他网关
PARALLEL_GATEWAY, // 并行网关
INCLUSIVE_GATEWAY, // 包容网关
SUB_PROCESS, // 子流程
CALL_ACTIVITY // 调用活动
}
4.3 流程保存与部署
@Service
public class ProcessModelService {
@Resource
private RepositoryService repositoryService;
@Resource
private ProcessDefinitionService processDefinitionService;
/**
* 保存流程模型
*/
@Transactional
public String saveProcessModel(ProcessModelDTO modelDTO) {
// 1. 验证流程模型
validateProcessModel(modelDTO);
// 2. 转换为 BPMN XML
String bpmnXml = convertToBpmnXml(modelDTO);
// 3. 保存到数据库
ProcessDefinitionDO processDefinition = new ProcessDefinitionDO();
processDefinition.setId(modelDTO.getId());
processDefinition.setName(modelDTO.getName());
processDefinition.setKey(modelDTO.getKey());
processDefinition.setCategory(modelDTO.getCategory());
processDefinition.setBpmnXml(bpmnXml);
processDefinition.setFlowJson(modelDTO.getFlowJson());
processDefinition.setVersion(getNextVersion(modelDTO.getKey()));
processDefinitionMapper.insert(processDefinition);
// 4. 部署到 Flowable
return deployProcess(modelDTO.getKey(), bpmnXml);
}
/**
* 部署流程
*/
private String deployProcess(String key, String bpmnXml) {
DeploymentBuilder deployment = repositoryService.createDeployment()
.name(key)
.addString(key + ".bpmn20.xml", bpmnXml);
Deployment deploy = deployment.deploy();
log.info("[流程部署] key={}, deploymentId={}", key, deploy.getId());
return deploy.getId();
}
/**
* 转换为 BPMN XML
*/
private String convertToBpmnXml(ProcessModelDTO modelDTO) {
BpmnModel bpmnModel = new BpmnModel();
Process process = new Process();
process.setId(modelDTO.getKey());
process.setName(modelDTO.getName());
bpmnModel.addProcess(process);
// 解析流程JSON,构建BPMN模型
List<NodeDefinitionDTO> nodes = parseFlowJson(modelDTO.getFlowJson());
for (NodeDefinitionDTO node : nodes) {
switch (node.getType()) {
case START_EVENT:
process.addFlowElement(createStartEvent(node));
break;
case END_EVENT:
process.addFlowElement(createEndEvent(node));
break;
case USER_TASK:
process.addFlowElement(createUserTask(node));
break;
case EXCLUSIVE_GATEWAY:
process.addFlowElement(createExclusiveGateway(node));
break;
// 其他节点类型...
}
}
// 添加连线
addSequenceFlows(process, nodes);
// 转换为 XML
return new BpmnXMLConverter().convertToXML(bpmnModel);
}
/**
* 创建用户任务
*/
private UserTask createUserTask(NodeDefinitionDTO node) {
UserTask userTask = new UserTask();
userTask.setId(node.getId());
userTask.setName(node.getName());
// 设置审批人
AssigneeConfigDTO assigneeConfig = node.getAssigneeConfig();
if (assigneeConfig != null) {
switch (assigneeConfig.getType()) {
case USER:
userTask.setAssignee(assigneeConfig.getExpression());
break;
case ROLE:
userTask.setCandidateGroups(assigneeConfig.getExpression());
break;
case INITIATOR:
userTask.setAssignee("${initiator}");
break;
case CUSTOM:
// 自定义任务监听器处理
addTaskListener(userTask, assigneeConfig);
break;
}
}
// 设置表单权限
if (node.getFormPermission() != null) {
setFormPermission(userTask, node.getFormPermission());
}
return userTask;
}
/**
* 添加任务监听器
*/
private void addTaskListener(UserTask userTask, AssigneeConfigDTO config) {
FlowableListener listener = new FlowableListener();
listener.setImplementation(config.getListenerClass());
listener.setEvent("create");
userTask.addTaskListener(listener);
}
}
4.4 流程设计器前端核心代码
// 流程设计器主组件
<template>
<div class="process-designer">
<!-- 工具栏 -->
<div class="toolbar">
<el-button @click="saveProcess">保存</el-button>
<el-button @click="deployProcess">部署</el-button>
<el-button @click="exportProcess">导出</el-button>
</div>
<!-- 节点面板 -->
<div class="node-panel">
<div class="node-item"
v-for="node in nodeTypes"
:key="node.type"
draggable="true"
@dragstart="onDragStart($event, node)">
<i :class="node.icon"></i>
<span>{{ node.name }}</span>
</div>
</div>
<!-- 流程图画布 -->
<div class="canvas-container"
@drop="onDrop"
@dragover="onDragOver">
<svg ref="svgCanvas" class="flow-canvas">
<!-- 流程连线 -->
<line v-for="line in lines"
:key="line.id"
:x1="line.startX"
:y1="line.startY"
:x2="line.endX"
:y2="line.endY"
stroke="#999" />
<!-- 流程节点 -->
<g v-for="node in nodes"
:key="node.id"
@click="selectNode(node)"
@mousedown="startDrag($event, node)">
<rect :x="node.x"
:y="node.y"
:width="node.width"
:height="node.height"
:fill="node.color"
stroke="#666" />
<text :x="node.x + node.width/2"
:y="node.y + node.height/2">
{{ node.name }}
</text>
</g>
</svg>
</div>
<!-- 属性面板 -->
<div class="property-panel">
<el-form v-if="selectedNode" :model="selectedNode">
<el-form-item label="节点名称">
<el-input v-model="selectedNode.name" />
</el-form-item>
<el-form-item label="审批人配置" v-if="selectedNode.type === 'userTask'">
<el-select v-model="selectedNode.assigneeType">
<el-option label="指定人员" value="user" />
<el-option label="角色" value="role" />
<el-option label="发起人" value="initiator" />
<el-option label="自定义" value="custom" />
</el-select>
</el-form-item>
<el-form-item label="表单权限">
<el-checkbox-group v-model="selectedNode.formPermissions">
<el-checkbox label="read">只读</el-checkbox>
<el-checkbox label="write">编辑</el-checkbox>
<el-checkbox label="hide">隐藏</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { saveProcessModel, deployProcess } from '@/api/process'
const nodes = ref([])
const lines = ref([])
const selectedNode = ref(null)
const nodeTypes = [
{ type: 'startEvent', name: '开始', icon: 'el-icon-video-play' },
{ type: 'userTask', name: '用户任务', icon: 'el-icon-user' },
{ type: 'exclusiveGateway', name: '排他网关', icon: 'el-icon-share' },
{ type: 'endEvent', name: '结束', icon: 'el-icon-video-pause' }
]
// 保存流程
const saveProcess = async () => {
const processModel = {
id: generateId(),
name: '请假流程',
key: 'leave_process',
category: 'oa',
nodes: nodes.value,
lines: lines.value
}
await saveProcessModel(processModel)
ElMessage.success('保存成功')
}
// 部署流程
const deployProcess = async () => {
await deployProcess(processId.value)
ElMessage.success('部署成功')
}
</script>
5. 复杂流程编排实战
5.1 会签(并行审批)实现
会签是指一个审批节点需要多人同时审批,所有人都同意后才可进入下一节点:
/**
* 会签配置
*/
@Data
public class ParallelSignConfig {
/**
* 会签人员列表
*/
private List<String> users;
/**
* 会签比例(如:0.5表示50%通过即可)
*/
private double passRate;
/**
* 是否需要所有人都同意
*/
private boolean needAllAgree;
/**
* 超时配置
*/
private Long timeout;
}
/**
* 会签任务监听器
*/
@Component
public class ParallelSignListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
String processInstanceId = delegateTask.getProcessInstanceId();
String taskId = delegateTask.getId();
// 获取会签配置
ParallelSignConfig config = getParallelSignConfig(delegateTask);
// 创建多个子任务
List<String> users = config.getUsers();
for (String user : users) {
TaskEntity subTask = (TaskEntity) taskService.newTask();
subTask.setName(delegateTask.getName() + "-会签");
subTask.setAssignee(user);
subTask.setParentTaskId(taskId);
subTask.setProcessInstanceId(processInstanceId);
subTask.setCategory("parallel-sign");
taskService.saveTask(subTask);
}
// 保存会签配置
runtimeService.setVariable(processInstanceId,
"parallel_sign_" + taskId, config);
}
/**
* 检查会签是否完成
*/
public boolean checkParallelSignComplete(String parentTaskId) {
List<Task> subTasks = taskService.createTaskQuery()
.parentTaskId(parentTaskId)
.list();
// 如果没有子任务,说明已全部完成
if (CollectionUtils.isEmpty(subTasks)) {
return true;
}
// 获取会签配置
String processInstanceId = subTasks.get(0).getProcessInstanceId();
ParallelSignConfig config = (ParallelSignConfig) runtimeService
.getVariable(processInstanceId, "parallel_sign_" + parentTaskId);
// 统计已完成的任务
long completedCount = subTasks.stream()
.filter(task -> task.getEndTime() != null)
.count();
// 判断是否达到通过比例
if (config.isNeedAllAgree()) {
return completedCount == config.getUsers().size();
} else {
return completedCount >= config.getUsers().size() * config.getPassRate();
}
}
}
5.2 或签(任意一人审批)实现
或签是指多人审批时,任意一人审批通过即可进入下一节点:
/**
* 或签配置
*/
@Data
public class OrSignConfig {
/**
* 审批人员列表
*/
private List<String> users;
/**
* 是否抢占式(一人审批后其他人自动失效)
*/
private boolean isPreemptive;
}
/**
* 或签任务监听器
*/
@Component
public class OrSignListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
String processInstanceId = delegateTask.getProcessInstanceId();
String taskId = delegateTask.getId();
// 获取或签配置
OrSignConfig config = getOrSignConfig(delegateTask);
// 设置候选用户
for (String user : config.getUsers()) {
taskService.addCandidateUser(taskId, user);
}
// 保存或签配置
runtimeService.setVariable(processInstanceId,
"or_sign_" + taskId, config);
}
/**
* 或签完成后处理其他任务
*/
public void handleOrSignComplete(String completedTaskId) {
TaskEntity completedTask = (TaskEntity) taskService.createTaskQuery()
.taskId(completedTaskId)
.singleResult();
if (completedTask == null) {
return;
}
String processInstanceId = completedTask.getProcessInstanceId();
OrSignConfig config = (OrSignConfig) runtimeService
.getVariable(processInstanceId, "or_sign_" + completedTaskId);
// 如果是抢占式,删除其他待审批任务
if (config.isPreemptive()) {
List<Task> otherTasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.taskDefinitionKey(completedTask.getTaskDefinitionKey())
.list();
for (Task task : otherTasks) {
if (!task.getId().equals(completedTaskId)) {
taskService.deleteTask(task.getId(), "或签抢占");
}
}
}
}
}
5.3 依次审批(顺序会签)实现
依次审批是指多人按顺序依次审批:
/**
* 依次审批配置
*/
@Data
public class SequentialSignConfig {
/**
* 审批人员顺序列表
*/
private List<String> users;
/**
* 当前审批人索引
*/
private int currentIndex;
}
/**
* 依次审批监听器
*/
@Component
public class SequentialSignListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
String processInstanceId = delegateTask.getProcessInstanceId();
String taskId = delegateTask.getId();
// 获取顺序审批配置
SequentialSignConfig config = getSequentialSignConfig(delegateTask);
// 分配第一个审批人
if (config.getUsers().isEmpty()) {
throw new BusinessException("审批人不能为空");
}
delegateTask.setAssignee(config.getUsers().get(0));
config.setCurrentIndex(0);
// 保存配置
runtimeService.setVariable(processInstanceId,
"sequential_sign_" + taskId, config);
}
/**
* 完成当前任务,创建下一个任务
*/
public void handleSequentialSignComplete(String completedTaskId) {
TaskEntity completedTask = (TaskEntity) taskService.createTaskQuery()
.taskId(completedTaskId)
.singleResult();
if (completedTask == null) {
return;
}
String processInstanceId = completedTask.getProcessInstanceId();
SequentialSignConfig config = (SequentialSignConfig) runtimeService
.getVariable(processInstanceId, "sequential_sign_" + completedTaskId);
// 检查是否还有下一个审批人
int nextIndex = config.getCurrentIndex() + 1;
if (nextIndex >= config.getUsers().size()) {
// 所有审批人都已完成,结束流程
return;
}
// 创建下一个任务
TaskEntity nextTask = (TaskEntity) taskService.newTask();
nextTask.setName(completedTask.getName());
nextTask.setAssignee(config.getUsers().get(nextIndex));
nextTask.setProcessInstanceId(processInstanceId);
nextTask.setTaskDefinitionKey(completedTask.getTaskDefinitionKey());
taskService.saveTask(nextTask);
// 更新索引
config.setCurrentIndex(nextIndex);
runtimeService.setVariable(processInstanceId,
"sequential_sign_" + completedTask.getId(), config);
}
}
5.4 条件分支(排他网关)实现
排他网关根据条件选择一个分支执行:
/**
* 条件分支配置
*/
@Data
public class ConditionBranchConfig {
/**
* 条件表达式
*/
private String expression;
/**
* 条件名称
*/
private String name;
/**
* 目标节点ID
*/
private String targetNodeId;
}
/**
* 条件表达式解析器
*/
@Component
public class ConditionExpressionParser {
/**
* 解析条件表达式
*/
public boolean parse(String expression, Map<String, Object> variables) {
// 使用 Spring Expression Language (SpEL)
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
// 添加自定义函数
context.registerFunction("getAmount", new MethodExpression(
clazz -> clazz.getDeclaredMethod("getAmount")
));
Expression exp = parser.parseExpression(expression);
Boolean result = exp.getValue(context, variables, Boolean.class);
return result != null && result;
}
}
/**
* 条件分支处理器
*/
@Service
public class ConditionBranchHandler {
@Resource
private ConditionExpressionParser parser;
/**
* 执行条件分支
*/
public String executeConditionBranch(String processInstanceId,
List<ConditionBranchConfig> branches) {
// 获取流程变量
Map<String, Object> variables = runtimeService.getVariables(processInstanceId);
// 遍历条件分支
for (ConditionBranchConfig branch : branches) {
if (parser.parse(branch.getExpression(), variables)) {
return branch.getTargetNodeId();
}
}
// 没有匹配的条件,使用默认分支
return getDefaultBranch(branches);
}
}
6. 流程性能优化
6.1 性能瓶颈分析
流程引擎的性能瓶颈通常出现在以下几个方面:
| 瓶颈点 | 影响 | 优化方案 |
|---|---|---|
| 数据库查询 | 流程实例查询慢 | 索引优化、缓存 |
| 流程变量存储 | 变量序列化开销大 | 变量精简、外部存储 |
| 历史数据 | 历史表膨胀 | 异步保存、定期归档 |
| 任务查询 | 待办任务查询慢 | 缓存、查询优化 |
| 流程实例 | 大量实例占用内存 | 实例池、归档 |
6.2 数据库优化
-- 为常用查询字段添加索引
CREATE INDEX idx_ru_task_assignee ON act_ru_task(assignee_);
CREATE INDEX idx_ru_task_proc_inst ON act_ru_task(proc_inst_id_);
CREATE INDEX idx_ru_execution_proc_inst ON act_ru_execution(proc_inst_id_);
CREATE INDEX idx_hi_proc_inst_end_time ON act_hi_procinst(end_time_);
CREATE INDEX idx_hi_taskinst_proc_inst ON act_hi_taskinst(proc_inst_id_);
-- 为流程变量查询优化
CREATE INDEX idx_ru_task_var_name ON act_ru_variable(name_);
CREATE INDEX idx_ru_task_var_task ON act_ru_variable(task_id_);
6.3 缓存策略
@Configuration
@EnableCaching
public class FlowableCacheConfiguration {
/**
* 流程定义缓存
*/
@Bean
public CacheManager flowableCacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
// 流程定义缓存
cacheManager.registerCustomCache("processDefinition",
new ConcurrentMapCache("processDefinition", 200, true));
// 流程实例缓存
cacheManager.registerCustomCache("processInstance",
new ConcurrentMapCache("processInstance", 500, true));
// 任务缓存
cacheManager.registerCustomCache("task",
new ConcurrentMapCache("task", 1000, true));
return cacheManager;
}
/**
* 流程定义缓存服务
*/
@Service
public class ProcessDefinitionCacheService {
@Cacheable(value = "processDefinition", key = "#processDefinitionId")
public ProcessDefinition getProcessDefinition(String processDefinitionId) {
return repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.singleResult();
}
@CacheEvict(value = "processDefinition", key = "#processDefinitionId")
public void evictProcessDefinition(String processDefinitionId) {
// 流程定义更新时清除缓存
}
}
}
6.4 异步处理优化
/**
* 异步流程处理器
*/
@Service
public class AsyncProcessHandler {
@Resource
private RuntimeService runtimeService;
@Resource
private ThreadPoolExecutor asyncExecutor;
/**
* 异步启动流程实例
*/
@Async("asyncExecutor")
public void startProcessAsync(String processDefinitionKey,
Map<String, Object> variables) {
try {
runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
} catch (Exception e) {
log.error("[异步启动流程] 失败: processDefinitionKey={}",
processDefinitionKey, e);
}
}
/**
* 异步完成任务
*/
@Async("asyncExecutor")
public void completeTaskAsync(String taskId,
Map<String, Object> variables) {
try {
taskService.complete(taskId, variables);
} catch (Exception e) {
log.error("[异步完成任务] 失败: taskId={}", taskId, e);
}
}
}
6.5 历史数据归档
/**
* 历史数据归档服务
*/
@Service
public class HistoryArchiveService {
@Resource
private HistoryService historyService;
/**
* 归档指定时间之前的历史数据
*/
@Transactional
public void archiveHistoryData(Date archiveDate) {
// 1. 查询需要归档的数据
List<HistoricProcessInstance> processInstances = historyService
.createHistoricProcessInstanceQuery()
.finishedBefore(archiveDate)
.list();
// 2. 导出到归档表
for (HistoricProcessInstance instance : processInstances) {
archiveProcessInstance(instance);
}
// 3. 删除已归档的数据
deleteArchivedData(archiveDate);
}
/**
* 归档流程实例
*/
private void archiveProcessInstance(HistoricProcessInstance instance) {
// 保存到归档表
ProcessInstanceArchive archive = new ProcessInstanceArchive();
BeanUtils.copyProperties(instance, archive);
archive.setArchiveTime(new Date());
processInstanceArchiveMapper.insert(archive);
// 归档任务数据
List<HistoricTaskInstance> tasks = historyService
.createHistoricTaskInstanceQuery()
.processInstanceId(instance.getId())
.list();
for (HistoricTaskInstance task : tasks) {
TaskArchive taskArchive = new TaskArchive();
BeanUtils.copyProperties(task, taskArchive);
taskArchive.setArchiveId(archive.getId());
taskArchiveMapper.insert(taskArchive);
}
}
}
7. 流程监控与管理
7.1 流程监控指标
/**
* 流程监控服务
*/
@Service
public class ProcessMonitorService {
@Resource
private RuntimeService runtimeService;
@Resource
private HistoryService historyService;
@Resource
private TaskService taskService;
/**
* 获取流程监控数据
*/
public ProcessMonitorDTO getProcessMonitorData(String processDefinitionKey) {
ProcessMonitorDTO monitor = new ProcessMonitorDTO();
// 统计运行中的流程实例
long runningCount = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.count();
// 统计已完成的流程实例
long completedCount = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.finished()
.count();
// 统计待办任务数
long pendingTaskCount = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.count();
// 统计平均流程耗时
double avgDuration = historyService.createHistoricProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.finished()
.list()
.stream()
.mapToLong(HistoricProcessInstance::getDurationInMillis)
.average()
.orElse(0);
// 统计流程状态分布
Map<String, Long> statusDistribution = getStatusDistribution(processDefinitionKey);
monitor.setRunningCount(runningCount);
monitor.setCompletedCount(completedCount);
monitor.setPendingTaskCount(pendingTaskCount);
monitor.setAvgDuration(avgDuration);
monitor.setStatusDistribution(statusDistribution);
return monitor;
}
/**
* 获取流程状态分布
*/
private Map<String, Long> getStatusDistribution(String processDefinitionKey) {
Map<String, Long> distribution = new HashMap<>();
// 按节点统计待办任务
List<Task> tasks = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.list();
tasks.forEach(task -> {
String nodeKey = task.getTaskDefinitionKey();
distribution.put(nodeKey, distribution.getOrDefault(nodeKey, 0L) + 1);
});
return distribution;
}
}
7.2 流程异常预警
/**
* 流程异常预警服务
*/
@Service
public class ProcessAlertService {
@Resource
private SmsSendService smsSendService;
@Resource
private EmailSendService emailSendService;
/**
* 检查并预警超时流程
*/
@Scheduled(cron = "0 */30 * * * ?") // 每30分钟执行一次
public void checkTimeoutProcesses() {
// 查询运行时间超过阈值的流程实例
long timeoutThreshold = 24 * 60 * 60 * 1000; // 24小时
Date timeoutDate = new Date(System.currentTimeMillis() - timeoutThreshold);
List<ProcessInstance> timeoutInstances = runtimeService
.createProcessInstanceQuery()
.startedBefore(timeoutDate)
.list();
for (ProcessInstance instance : timeoutInstances) {
// 发送预警通知
sendTimeoutAlert(instance);
// 记录预警日志
logTimeoutAlert(instance);
}
}
/**
* 检查并预警积压任务
*/
@Scheduled(cron = "0 */10 * * * ?") // 每10分钟执行一次
public void checkBacklogTasks() {
// 查询积压超过阈值的任务
long backlogThreshold = 100;
String taskDefinitionKey = "leave_approval";
long taskCount = taskService.createTaskQuery()
.taskDefinitionKey(taskDefinitionKey)
.count();
if (taskCount > backlogThreshold) {
// 发送预警通知
sendBacklogAlert(taskDefinitionKey, taskCount);
}
}
/**
* 发送超时预警
*/
private void sendTimeoutAlert(ProcessInstance instance) {
String initiator = instance.getStartUserId();
String processName = instance.getProcessDefinitionName();
// 发送短信通知
smsSendService.sendSingleSms(
initiator,
"流程超时预警",
"您的" + processName + "流程已超时24小时未完成,请及时处理"
);
// 发送邮件通知
emailSendService.sendEmail(
initiator,
"流程超时预警",
buildTimeoutEmailContent(instance)
);
}
}
8. 业务数据集成方案
8.1 流程变量与业务数据绑定
/**
* 流程变量管理服务
*/
@Service
public class ProcessVariableService {
@Resource
private RuntimeService runtimeService;
@Resource
private TaskService taskService;
/**
* 启动流程并绑定业务数据
*/
public String startProcessWithBusinessData(String processDefinitionKey,
String businessKey,
Map<String, Object> businessData) {
// 1. 精简流程变量(只存储必要字段)
Map<String, Object> processVariables = new HashMap<>();
processVariables.put("businessKey", businessKey);
processVariables.put("businessType", getBusinessType(processDefinitionKey));
processVariables.put("initiator", getCurrentUserId());
// 2. 启动流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
processDefinitionKey,
businessKey,
processVariables
);
// 3. 将业务数据ID与流程实例关联
saveBusinessProcessMapping(businessKey, instance.getId(), businessData);
return instance.getId();
}
/**
* 获取业务数据
*/
public Map<String, Object> getBusinessData(String processInstanceId) {
// 1. 获取 businessKey
String businessKey = (String) runtimeService.getVariable(
processInstanceId, "businessKey");
// 2. 查询业务数据映射
BusinessProcessMapping mapping = businessProcessMappingMapper
.selectByProcessInstanceId(processInstanceId);
if (mapping == null) {
return Collections.emptyMap();
}
// 3. 根据业务类型查询实际业务数据
return getBusinessDataByType(mapping.getBusinessType(), mapping.getBusinessKey());
}
}
8.2 流程回调机制
/**
* 流程回调接口
*/
public interface ProcessCallback {
/**
* 流程启动回调
*/
void onProcessStarted(ProcessInstance processInstance);
/**
* 流程完成回调
*/
void onProcessCompleted(HistoricProcessInstance processInstance);
/**
* 任务创建回调
*/
void onTaskCreated(DelegateTask task);
/**
* 任务完成回调
*/
void onTaskCompleted(DelegateTask task);
}
/**
* 流程回调管理器
*/
@Component
public class ProcessCallbackManager {
private Map<String, ProcessCallback> callbackMap = new ConcurrentHashMap<>();
/**
* 注册回调
*/
public void registerCallback(String processDefinitionKey, ProcessCallback callback) {
callbackMap.put(processDefinitionKey, callback);
}
/**
* 触发流程启动回调
*/
public void triggerProcessStarted(ProcessInstance processInstance) {
ProcessCallback callback = callbackMap.get(processInstance.getProcessDefinitionKey());
if (callback != null) {
callback.onProcessStarted(processInstance);
}
}
/**
* 触发流程完成回调
*/
public void triggerProcessCompleted(HistoricProcessInstance processInstance) {
ProcessCallback callback = callbackMap.get(processInstance.getProcessDefinitionKey());
if (callback != null) {
callback.onProcessCompleted(processInstance);
}
}
}
/**
* 请假流程回调实现
*/
@Component
public class LeaveProcessCallback implements ProcessCallback {
@Resource
private LeaveService leaveService;
@Override
public void onProcessStarted(ProcessInstance processInstance) {
String businessKey = processInstance.getBusinessKey();
// 更新请假单状态
leaveService.updateStatus(businessKey, LeaveStatus.APPROVING);
}
@Override
public void onProcessCompleted(HistoricProcessInstance processInstance) {
String businessKey = processInstance.getBusinessKey();
// 获取审批结果
Boolean approved = (Boolean) processInstance.getVariable("approved");
// 更新请假单状态
if (approved) {
leaveService.updateStatus(businessKey, LeaveStatus.APPROVED);
} else {
leaveService.updateStatus(businessKey, LeaveStatus.REJECTED);
}
}
@Override
public void onTaskCreated(DelegateTask task) {
// 发送待办通知
sendTaskNotification(task);
}
@Override
public void onTaskCompleted(DelegateTask task) {
// 记录审批日志
recordApprovalLog(task);
}
}
9. 生产环境最佳实践
9.1 部署架构
┌─────────────────────────────────────────────────────────────┐
│ 负载均衡 │
│ Nginx / SLB │
└─────────────────────────────────────────────────────────────┘
↓
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ OA App-1 │ │ OA App-2 │ │ OA App-3 │
└──────────────┘ └──────────────┘ └──────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Flowable 集群 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Flowable-1 │ │ Flowable-2 │ │ Flowable-3 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 数据库集群 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Master │ ←────────→ │ Slave │ │
│ └──────────────┘ 复制 └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
9.2 配置文件
# 生产环境配置
flowable:
# 数据库配置
database-schema-update: false
# 异步执行器配置
async-executor-activate: true
async-executor-message-queue-mode: true
async-history-enabled: true
async-history-duration: 12h
# 缓存配置
process-definition-cache-limit: 500
process-definition-cache-enabled: true
# 事件日志配置
enable-event-history: true
event-history-max-size: 10000
# 限制配置
mail-server-port: 25
# 部署配置
deployment-mode: single-node
9.3 监控指标
/**
* 流程监控指标收集
*/
@Component
public class FlowableMetricsCollector {
private MeterRegistry meterRegistry;
@Scheduled(fixedRate = 60000) // 每分钟收集一次
public void collectMetrics() {
// 流程实例数量
long instanceCount = runtimeService.createProcessInstanceQuery().count();
meterRegistry.gauge("flowable.process.instance.count", instanceCount);
// 任务数量
long taskCount = taskService.createTaskQuery().count();
meterRegistry.gauge("flowable.task.count", taskCount);
// 待办任务数量
long pendingCount = taskService.createTaskQuery()
.taskAssignee("${initiator}").count();
meterRegistry.gauge("flowable.task.pending.count", pendingCount);
// 历史流程实例数量
long historyCount = historyService.createHistoricProcessInstanceQuery()
.count();
meterRegistry.gauge("flowable.history.instance.count", historyCount);
}
}
10. 总结与展望
10.1 实践总结
通过在点狮 OA 系统中深度应用 Flowable 工作流引擎,我们获得了以下经验:
-
架构设计是关键
- 合理的服务分层设计
- 清晰的模块职责划分
- 灵活的扩展机制
-
性能优化需要体系化
- 数据库优化是基础
- 缓存策略是关键
- 异步处理是补充
-
业务集成要深度
- 流程变量精简化
- 业务数据外部化
- 回调机制标准化
-
监控运维很重要
- 实时监控指标
- 异常预警机制
- 定期归档清理
10.2 未来展望
随着技术的发展,工作流引擎也在不断演进,未来值得关注的方向:
-
云原生工作流
- Kubernetes 部署
- Service Mesh 集成
- 云原生监控
-
AI 驱动的流程优化
- 智能审批路径推荐
- 流程瓶颈自动识别
- 异常流程智能诊断
-
低代码/零代码
- 可视化流程设计
- 拖拽式表单配置
- 所见即所得
-
流程挖掘(Process Mining)
- 流程执行分析
- 流程优化建议
- 流程合规检查
参考资料
🔗 快速链接
演示站点
- 点狮多业务管理平台 - 在线体验
- Ruoyi-多业务单体版本 - 单体版本
开源项目
官方网站
- 点狮云官网 - 更多信息
相关文章
感谢阅读!如果觉得有帮助,欢迎 Star 支持我们的开源项目: ⭐
更多推荐




所有评论(0)