在企业级应用开发中,审批流是典型的复杂业务场景。以请假流程为例,传统硬编码的实现方式通常是这样:
java复制// 伪代码示例:传统审批流程实现
public class LeaveService {
private static final int STATUS_START = 0;
private static final int STATUS_APPLY = 1;
private static final int STATUS_LEADER_APPROVE = 2;
// 更多状态...
public void submitLeave(LeaveRequest request) {
if(request.getCurrentStatus() == STATUS_START) {
request.setCurrentStatus(STATUS_APPLY);
// 保存到数据库...
}
}
public void leaderApprove(Long leaveId, boolean approved) {
LeaveRequest request = getFromDB(leaveId);
if(request.getCurrentStatus() == STATUS_APPLY) {
if(approved) {
request.setCurrentStatus(STATUS_LEADER_APPROVE);
} else {
request.setCurrentStatus(STATUS_REJECTED);
request.setRejected(true); // 需要额外字段记录驳回
}
// 更多嵌套判断...
}
}
}
这种实现方式存在三个致命缺陷:
而工作流引擎通过将流程定义与业务逻辑解耦,使用可视化建模工具定义流程,运行时引擎驱动流程流转,完美解决了这些问题。下表对比了两种实现方式的差异:
| 对比维度 | 硬编码实现 | Flowable工作流 |
|---|---|---|
| 流程定义 | 代码中写死 | 外部BPMN文件可动态加载 |
| 流程变更 | 需改代码并重新部署 | 只需更新BPMN文件 |
| 驳回/撤回处理 | 需额外字段和复杂逻辑 | 引擎原生支持 |
| 审批历史 | 需自行实现记录逻辑 | 自动完整记录 |
| 多人会签 | 实现复杂度高 | 并行网关原生支持 |
| 可视化监控 | 难以实现 | 自带监控界面 |
BPMN 2.0标准定义了丰富的建模元素,在实际项目中常用的包括:
网关(Gateway)的实战选择:
xml复制<exclusiveGateway id="decision1" />
<sequenceFlow sourceRef="decision1" targetRef="task1">
<conditionExpression xsi:type="tFormalExpression">
${amount <= 5000}
</conditionExpression>
</sequenceFlow>
xml复制<parallelGateway id="fork1" />
<sequenceFlow sourceRef="fork1" targetRef="financeReview" />
<sequenceFlow sourceRef="fork1" targetRef="legalReview" />
任务(Task)的实战应用:
xml复制<userTask id="leaderApprove" name="部门审批"
flowable:assignee="${departmentLeader}"
flowable:dueDate="${createDate.plusDays(2)}">
<extensionElements>
<flowable:formProperty id="comment"
name="审批意见"
type="string"/>
</extensionElements>
</userTask>
xml复制<serviceTask id="notifyHR"
flowable:class="com.example.LeaveNotifyDelegate" />
<!-- 或使用表达式 -->
<serviceTask id="syncData"
flowable:expression="${leaveService.syncToHRSystem(execution)}" />
合理的变量设计是流程灵活性的关键:
启动变量:流程实例级别的全局变量
java复制Map<String, Object> variables = new HashMap<>();
variables.put("applicant", "张三");
variables.put("days", 3);
runtimeService.startProcessInstanceByKey("leave", variables);
任务局部变量:只在当前任务链中有效
java复制taskService.complete(taskId,
Collections.singletonMap("approvalComment", "同意"));
变量作用域:
最佳实践:在流程定义中明确变量命名规范,如"apply_"前缀表示申请信息,"approval_"前缀表示审批信息,避免命名冲突。
现代Spring Boot项目推荐以下配置方式:
yaml复制# application.yml
flowable:
async-executor-activate: true # 启用异步执行器
database-schema-update: true # 自动更新数据库
history-level: audit # 完整的历史记录
mail:
server-host: smtp.example.com
server-port: 587
use-ssl: true
关键Bean的自定义配置示例:
java复制@Configuration
public class FlowableConfig {
@Bean
public SpringProcessEngineConfiguration processEngineConfiguration(
DataSource dataSource, PlatformTransactionManager transactionManager) {
SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
config.setDataSource(dataSource);
config.setTransactionManager(transactionManager);
config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
config.setAsyncExecutorActivate(true);
config.setMailServerHost("smtp.example.com");
return config;
}
}
运行时流程修改:
java复制// 动态添加审批节点
repositoryService.createChangeActivityStateBuilder()
.processInstanceId(processInstanceId)
.moveActivityIdTo("currentTask", "newApprovalTask")
.changeState();
流程版本控制策略:
changeStateAPI热更新startProcessInstanceByKeyAndVersion指定版本定时边界事件应用:
xml复制<boundaryEvent id="timeoutEvent" attachedToRef="approvalTask">
<timerEventDefinition>
<timeDuration>PT48H</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<sequenceFlow sourceRef="timeoutEvent" targetRef="escalationTask" />
数据库层面:
引擎配置:
java复制config.setAsyncExecutorNumberOfRetries(3);
config.setAsyncExecutorMaxAsyncJobsDuePerAcquisition(100);
config.setJpaHandleTransaction(false);
监控指标采集:
java复制// 使用Micrometer暴露指标
@Bean
public FlowableMetricsExtension flowableMetrics(
ProcessEngine processEngine, MeterRegistry meterRegistry) {
return new FlowableMetricsExtension(processEngine, meterRegistry);
}

关键组件:
流程定义校验:防止部署恶意BPMN文件
java复制BpmnModel model = repositoryService.getBpmnModel(processDefinitionId);
FlowableBpmnModelValidator.validateModel(model);
SQL注入防护:使用引擎API而非原生SQL查询
java复制// 安全做法
taskService.createTaskQuery()
.taskCandidateUser(userId)
.list();
// 危险做法
jdbcTemplate.query("SELECT * FROM ACT_RU_TASK WHERE ASSIGNEE_ = '" + userId + "'");
权限控制矩阵:
| 操作 | 角色 | 权限验证方式 |
|---|---|---|
| 启动流程 | 申请人 | Spring Security @PreAuthorize |
| 认领任务 | 审批人 | taskService.addCandidateUser |
| 查看历史 | 审计员 | 自定义PermissionProvider |
| 流程定义部署 | 管理员 | Flowable IDM集成 |
并行会签(所有审批人同意):
xml复制<userTask id="multiApprove" name="多部门会签">
<multiInstanceLoopCharacteristics
isSequential="false"
flowable:collection="${approvers}"
flowable:elementVariable="approver">
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 1}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
串行会签(依次审批):
xml复制<multiInstanceLoopCharacteristics
isSequential="true"
flowable:elementVariable="approver">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
嵌入式子流程:共享父流程变量
xml复制<subProcess id="subprocess1">
<startEvent id="subStart"/>
<!-- 子流程内容 -->
<endEvent id="subEnd"/>
</subProcess>
调用活动:复用独立流程定义
xml复制<callActivity id="callHRAudit" calledElement="hrAuditProcess">
<extensionElements>
<flowable:in source="applicant" target="employeeName"/>
</extensionElements>
</callActivity>
业务异常处理:
java复制try {
runtimeService.suspendProcessInstanceById(processInstanceId);
// 补偿逻辑...
runtimeService.activateProcessInstanceById(processInstanceId);
} catch (FlowableException e) {
// 告警通知
}
系统容错设计:
java复制@Transactional(propagation = Propagation.REQUIRES_NEW)
public void safeCompleteTask(String taskId) {
try {
taskService.complete(taskId);
} catch (FlowableOptimisticLockingException e) {
// 重试逻辑
}
}
yaml复制logging:
level:
org.flowable: DEBUG
org.flowable.common.engine.impl.interceptor: INFO
定制化日志追加器:
java复制public class FlowableMDCLogger implements CommandContextCloseListener {
@Override
public void closing(CommandContext commandContext) {
// 将流程实例ID等信息注入MDC
}
}
内置API端点:
code复制GET /flowable-rest/service/management/engine
GET /flowable-rest/service/history/historic-process-instances
Grafana监控看板:
使用ExecutionListener注入性能探针:
java复制public class PerformanceListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
long startTime = System.currentTimeMillis();
execution.setVariable(execution.getCurrentActivityId() + "_start", startTime);
}
}
在流程定义中配置:
xml复制<extensionElements>
<flowable:executionListener
event="start"
class="com.example.PerformanceListener"/>
</extensionElements>
| 环境 | 数据库策略 | 流程版本控制 | 监控级别 |
|---|---|---|---|
| 开发环境 | H2内存数据库 | 频繁部署新版本 | DEBUG日志 |
| 测试环境 | 独立MySQL实例 | 版本冻结 | 基础指标采集 |
| 预发环境 | 生产数据库只读镜像 | 与生产版本一致 | 完整监控 |
| 生产环境 | 高可用MySQL集群 | 灰度发布 | 告警+追踪 |
bash复制flowable-bpmn-validator src/main/resources/processes/*.bpmn20.xml
groovy复制stage('Deploy Process') {
steps {
sshPublisher(
transfers: [
[
sourceFiles: 'target/classes/processes/*.bpmn20.xml',
remoteDirectory: '/app/resources/processes'
]
]
)
}
}
启动前检查:
运行时监控:
定期维护:
典型错误:
java复制@Transactional
public void approveTask(String taskId) {
// 业务逻辑...
taskService.complete(taskId); // 可能抛出异常
// 后续操作...
}
正确做法:
java复制public void safeApprove(String taskId) {
try {
transactionTemplate.execute(status -> {
taskService.complete(taskId);
return null;
});
} catch (FlowableException e) {
// 处理异常
}
}
问题场景:
java复制Map<String, Object> vars = new HashMap<>();
vars.put("complexObj", new MyBusinessObject()); // 未实现Serializable
runtimeService.startProcessInstanceByKey("process", vars);
解决方案:
java复制ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(obj);
vars.put("complexObjJson", json);
java复制config.setCustomPreVariableTypes(
Collections.singletonList(new JacksonJsonTypeHandler()));
N+1查询问题:
java复制List<ProcessInstance> instances = runtimeService.createProcessInstanceQuery().list();
for (ProcessInstance pi : instances) {
// 每次循环都会触发新的查询
Map<String, Object> vars = runtimeService.getVariables(pi.getId());
}
优化方案:
java复制List<ProcessInstance> instances = runtimeService
.createProcessInstanceQuery()
.includeProcessVariables() // 一次性获取变量
.list();
RabbitMQ事件监听:
java复制@RabbitListener(queues = "flowable.tasks")
public void handleTaskEvent(TaskEvent event) {
if (event.getEventType().equals("create")) {
// 发送通知等操作
}
}
Kafka流程事件生产者:
java复制config.getEventDispatcher().addEventListener(
new AbstractFlowableEventListener() {
@Override
public void onEvent(FlowableEvent event) {
kafkaTemplate.send("flowable-events", serialize(event));
}
}
);
Drools决策表集成:
xml复制<businessRuleTask id="decisionTask"
flowable:ruleVariablesInput="${order}"
flowable:resultVariable="decisionResult"
flowable:rules="com.example.OrderApprovalRules"/>
跨服务调用模式:
xml复制<serviceTask id="callHR"
flowable:type="http"
flowable:requestMethod="POST"
flowable:url="http://hr-service/api/leave"
flowable:headers="${serviceHeaders}"/>
java复制runtimeService.addEventListener(
new SignalEventListener() {
@Override
public void onEvent(FlowableEvent event) {
// 调用其他服务
}
},
SignalEventListener.class
);
在实际项目落地过程中,我们发现最关键的不仅是技术实现,更是要建立流程治理规范。建议从这几个方面入手:
通过将Flowable与Spring Boot生态深度整合,我们成功将某大型企业的采购审批流程从平均3天缩短到4小时,审批节点异常率从15%降至2%以下。这充分证明了工作流引擎在现代企业应用中的价值。