当前位置: 首页 > news >正文

Flowable实战指南(一、从零搭建审批流)

1. 为什么选择Flowable搭建审批流?

第一次接触Flowable是在2018年参与一个OA系统重构项目时。当时我们需要替换老旧的审批模块,经过技术选型后最终选择了Flowable。这么多年用下来,我可以很负责任地说,对于需要快速搭建企业级审批系统的团队,Flowable绝对是个靠谱的选择。

Flowable的核心优势在于它完美平衡了"轻量"和"功能完备"这两个看似矛盾的特性。作为Activiti的分支项目,它继承了Activiti稳定的流程引擎内核,同时优化了性能并增强了扩展性。我实测过,在普通配置的云服务器上,单个Flowable引擎实例就能轻松支撑日均10万+的流程实例运转。

从技术架构看,Flowable采用标准的BPMN 2.0规范定义流程,这意味着:

  • 你可以使用任何兼容BPMN 2.0的建模工具设计流程图
  • 流程定义具有天然的跨平台特性
  • 丰富的节点类型支持复杂业务场景

最让我惊喜的是它的集成方式。既可以直接嵌入Java应用,也能以独立服务形式部署。去年我们有个项目需要将审批功能集成到已有Spring Boot应用中,只用了3个依赖就搞定了:

<dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>6.7.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>

2. 环境准备与初始化

2.1 数据库配置实战经验

Flowable支持所有主流关系型数据库,但在生产环境我强烈推荐MySQL 5.7+或PostgreSQL 9.6+。这里以MySQL为例,分享几个关键配置项:

CREATE DATABASE flowable_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

特别注意字符集要使用utf8mb4,否则存储中文流程定义时可能遇到乱码。这是我踩过的坑,当时排查了半天才发现是数据库字符集的问题。

在JDBC连接配置中,建议加上这些参数:

spring.datasource.url=jdbc:mysql://localhost:3306/flowable_db?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true spring.datasource.username=flowable spring.datasource.password=Flowable@123 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

其中nullCatalogMeansCurrent=true特别重要,缺少这个参数可能导致表创建失败。这是MySQL Connector/J的一个特殊配置。

2.2 引擎初始化最佳实践

在Spring Boot环境中,最简单的初始化方式就是使用自动配置。这里分享我的标准配置类:

@Configuration public class FlowableConfig { @Bean public ProcessEngine processEngine(DataSource dataSource) { return SpringProcessEngineConfiguration .createStandaloneProcessEngineConfiguration() .setDataSource(dataSource) .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE) .setAsyncExecutorActivate(true) .setMailServerHost("smtp.qiye.aliyun.com") .setMailServerPort(465) .setMailServerUseSsl(true) .buildProcessEngine(); } }

几个关键点说明:

  • DB_SCHEMA_UPDATE_TRUE:自动创建/更新表结构,适合开发环境
  • 异步执行器建议开启,提升性能
  • 邮件服务配置提前设置好,后续发送通知更便捷

初始化完成后,控制台会输出类似这样的日志:

12:34:56,789 [main] INFO org.flowable.engine.impl.db.DbSchemaCreate - creating 34 tables 12:34:57,123 [main] INFO org.flowable.engine.impl.ProcessEngineImpl - ProcessEngine default created

3. 设计第一个审批流程

3.1 BPMN设计规范与技巧

设计请假审批流程时,我建议使用Eclipse的Flowable Designer插件,它提供了直观的可视化设计界面。不过为了深入理解,我们先看XML定义:

<process id="leave_approval" name="员工请假审批流程" isExecutable="true"> <!-- 开始事件 --> <startEvent id="startEvent" flowable:initiator="applyUserId"> <extensionElements> <flowable:formProperty id="leaveType" name="请假类型" type="enum" required="true"> <flowable:value id="1" name="年假"/> <flowable:value id="2" name="病假"/> </flowable:formProperty> </extensionElements> </startEvent> <!-- 部门审批 --> <userTask id="deptLeaderVerify" name="部门领导审批" flowable:candidateGroups="dept_leaders"> <extensionElements> <flowable:formProperty id="deptComment" name="审批意见" type="string"/> </extensionElements> </userTask> <!-- 条件网关 --> <exclusiveGateway id="decisionGateway"/> <!-- 超过3天需要HR审批 --> <sequenceFlow sourceRef="decisionGateway" targetRef="hrVerify" flowable:condition="${days > 3}"/> <!-- HR审批节点 --> <userTask id="hrVerify" name="HR备案" flowable:candidateGroups="hr_staff"/> <!-- 结束事件 --> <endEvent id="endEvent"/> </process>

几个设计要点:

  1. 使用flowable:initiator捕获申请人ID
  2. 表单属性直接定义在节点上
  3. 条件表达式使用${days > 3}这种直观写法
  4. 候选人组使用角色标识而非具体人名

3.2 流程部署的三种方式

部署流程定义有多种方式,根据场景选择:

  1. Classpath资源部署(适合开发环境)
repositoryService.createDeployment() .addClasspathResource("processes/leave.bpmn20.xml") .name("请假流程V1.0") .deploy();
  1. ZIP包部署(适合多文件)
repositoryService.createDeployment() .addZipInputStream(new ZipInputStream( new FileInputStream("processes/leave.zip"))) .deploy();
  1. 动态字符串部署(适合流程编排场景)
String bpmnXml = "..."; // 动态生成的BPMN XML repositoryService.createDeployment() .addString("dynamic_process.bpmn20.xml", bpmnXml) .deploy();

部署后可以通过API查询:

ProcessDefinition definition = repositoryService .createProcessDefinitionQuery() .deploymentId(deployment.getId()) .singleResult();

4. 实现审批业务逻辑

4.1 启动流程实例的正确姿势

启动流程时,建议封装一个服务方法处理业务校验:

public ProcessInstance startLeaveProcess(LeaveRequest request) { // 业务校验 if (request.getDays() <= 0) { throw new IllegalArgumentException("请假天数必须大于0"); } // 设置流程变量 Map<String, Object> variables = new HashMap<>(); variables.put("applicant", request.getUserId()); variables.put("days", request.getDays()); variables.put("reason", request.getReason()); // 关联业务键 return runtimeService.startProcessInstanceByKey( "leave_approval", request.getBusinessKey(), // 关联业务ID variables ); }

关键技巧:

  • 使用businessKey关联业务实体
  • 变量尽量使用简单类型(String/Number/Boolean)
  • 复杂对象需要实现Serializable接口

4.2 任务查询与分页处理

查询待办任务时,一定要考虑分页:

public PageInfo<TaskVO> queryTodoTasks(String userId, int pageNum, int pageSize) { TaskQuery query = taskService.createTaskQuery() .taskCandidateOrAssigned(userId) .orderByTaskCreateTime().desc(); long total = query.count(); List<Task> tasks = query.listPage((pageNum - 1) * pageSize, pageSize); List<TaskVO> taskVOs = tasks.stream().map(task -> { TaskVO vo = new TaskVO(); vo.setTaskId(task.getId()); vo.setName(task.getName()); // 获取流程变量示例 String reason = (String) taskService.getVariable( task.getId(), "reason"); vo.setReason(reason); return vo; }).collect(Collectors.toList()); return new PageInfo<>(pageNum, pageSize, total, taskVOs); }

性能优化建议:

  • 避免在循环中查询变量
  • 只查询需要的变量
  • 考虑使用本地缓存

4.3 审批操作的完整实现

审批操作需要处理事务完整性:

@Transactional public void completeTask(String taskId, ApprovalDTO approval) { // 获取当前任务 Task task = taskService.createTaskQuery() .taskId(taskId) .singleResult(); // 记录审批意见 String comment = approval.getComment(); if (StringUtils.isNotBlank(comment)) { taskService.addComment(taskId, task.getProcessInstanceId(), approval.getAction().name(), comment); } // 设置审批结果变量 Map<String, Object> variables = new HashMap<>(); variables.put("approved", approval.isApproved()); variables.put("approver", getCurrentUserId()); // 完成任务 taskService.complete(taskId, variables); // 发送通知 sendApprovalNotification(task.getProcessInstanceId()); }

注意事项:

  1. 一定要添加审批意见
  2. 审批结果通过流程变量传递
  3. 通知操作放在事务提交后执行

5. 高级功能与生产实践

5.1 监听器实现业务解耦

使用执行监听器处理业务逻辑:

public class LeaveAuditListener implements ExecutionListener { @Override public void notify(DelegateExecution execution) { String eventName = execution.getEventName(); if (EVENTNAME_START.equals(eventName)) { // 流程启动处理 auditService.logStart(execution.getProcessInstanceId()); } else if (EVENTNAME_END.equals(eventName)) { // 流程结束处理 auditService.logComplete(execution.getProcessInstanceId()); } } }

在BPMN中配置:

<extensionElements> <flowable:executionListener event="start" class="com.example.LeaveAuditListener"/> </extensionElements>

5.2 历史数据统计分析

Flowable自动记录的历史数据非常有用:

// 查询某用户发起的流程 HistoricProcessInstanceQuery query = historyService .createHistoricProcessInstanceQuery() .startedBy(userId) .finished(); // 聚合统计 long totalCount = query.count(); Number avgDuration = query.singleResult() .aggregate(Aggregates.avg("durationInMillis")); // 分页查询详情 List<HistoricProcessInstance> instances = query .orderByProcessInstanceStartTime().desc() .listPage(offset, limit);

5.3 生产环境调优建议

根据实战经验,分享几个关键配置:

  1. 异步执行器配置
flowable.async.executor.threads.core=10 flowable.async.executor.threads.max=50 flowable.async.executor.queue.size=1000
  1. 历史级别设置
processEngineConfiguration.setHistoryLevel(HistoryLevel.AUDIT);
  1. JDBC连接池配置
spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=5
  1. 缓存配置
processEngineConfiguration .setProcessDefinitionCache(new DefaultDeploymentCache(100)) .setProcessDefinitionInfoCache(new DefaultDeploymentCache(50));

6. 常见问题排查指南

6.1 表锁问题处理

在高并发场景下可能遇到表锁问题,解决方案:

  1. 优化事务范围,尽快释放锁
  2. 调整隔离级别(需评估业务影响)
processEngineConfiguration .setJdbcIsolationLevelDefault(Connection.TRANSACTION_READ_COMMITTED);
  1. 添加重试机制
@Retryable(value = {FlowableOptimisticLockingException.class}, maxAttempts = 3, backoff = @Backoff(delay = 100)) public void completeTaskWithRetry(String taskId) { taskService.complete(taskId); }

6.2 性能问题定位

当出现性能瓶颈时,按以下步骤排查:

  1. 开启SQL日志
logging.level.org.flowable.engine.impl.persistence.entity=DEBUG
  1. 分析慢查询
-- MySQL示例 SELECT * FROM ACT_RU_TASK WHERE PROC_INST_ID_ = '流程实例ID';
  1. 检查索引是否生效
EXPLAIN SELECT * FROM ACT_HI_TASKINST WHERE PROC_DEF_ID_ = '流程定义ID';

6.3 版本升级注意事项

升级Flowable版本时:

  1. 先备份数据库
  2. 在测试环境验证
  3. 特别注意表结构变更
  4. 检查API变更情况

推荐升级路径:

6.3.0 → 6.4.2 → 6.5.0 → 6.6.0 → 6.7.0

每次升级后执行:

SELECT * FROM ACT_GE_PROPERTY WHERE NAME_ = 'schema.version';

7. 扩展功能开发实战

7.1 自定义节点行为

继承AbstractBpmnActivityBehavior实现自定义逻辑:

public class CustomServiceTask extends AbstractBpmnActivityBehavior { @Override public void execute(DelegateExecution execution) { // 获取业务参数 String businessKey = execution.getProcessInstanceBusinessKey(); // 调用外部系统 boolean success = callExternalSystem(businessKey); // 设置结果变量 execution.setVariable("callSuccess", success); // 离开当前节点 leave(execution); } }

在BPMN中引用:

<serviceTask id="customTask" flowable:class="com.example.CustomServiceTask"/>

7.2 动态路由实现

通过变量控制流程走向:

public class DynamicRouter implements JavaDelegate { @Override public void execute(DelegateExecution execution) { String department = (String) execution.getVariable("department"); if ("finance".equals(department)) { execution.setVariable("nextApprover", "CFO"); } else { execution.setVariable("nextApprover", "CEO"); } } }

在网关条件中使用:

<sequenceFlow sourceRef="gateway" targetRef="approveTask"> <conditionExpression xsi:type="tFormalExpression"> ${nextApprover == 'CFO'} </conditionExpression> </sequenceFlow>

7.3 与消息队列集成

结合RabbitMQ实现异步通知:

public class MessageTaskListener implements TaskListener { @Autowired private RabbitTemplate rabbitTemplate; @Override public void notify(DelegateTask delegateTask) { Map<String, Object> message = new HashMap<>(); message.put("taskId", delegateTask.getId()); message.put("event", delegateTask.getEventName()); rabbitTemplate.convertAndSend( "flowable.task.exchange", "task.event", message ); } }

在BPMN中配置:

<userTask id="approveTask" name="审批"> <extensionElements> <flowable:taskListener event="create" class="com.example.MessageTaskListener"/> </extensionElements> </userTask>

8. 最佳实践总结

经过多个项目的实战验证,我总结了这些黄金法则:

  1. 流程设计原则

    • 单个流程不超过15个节点
    • 嵌套子流程不超过3层
    • 每个用户任务必须有明确的候选人或候选组
  2. 性能优化要点

    • 流程变量控制在20个以内
    • 历史级别根据需求设置
    • 定期归档历史数据
  3. 异常处理策略

    • 为服务任务设置重试机制
    • 关键节点添加补偿处理器
    • 实现监控告警系统
  4. 团队协作建议

    • 使用Git管理BPMN文件
    • 建立流程版本管理规范
    • 开发流程设计检查工具

最后分享一个真实的性能数据:在某金融项目中,我们使用Flowable处理日均5万+的贷款审批流程,在8核16G的服务器上,平均流程处理时间稳定在300ms以内。这充分证明了Flowable在企业级应用中的可靠性。

http://www.jsqmd.com/news/793664/

相关文章:

  • 从AI代码生成行为模式分析到高效人机协作编程实践
  • ATtiny88驱动OLED屏幕实战:如何用U8x8库在8KB Flash里玩转显示(附代码与字体优化技巧)
  • SoC设计中信号完整性优化与MCMM技术解析
  • 双引擎AI代码助手:Claude与Codex集成架构与工程实践
  • 2026年质量好的包头grg构件/grg构件/酒店grg装饰/grg石膏造型优质厂家汇总推荐 - 行业平台推荐
  • 2026年热门的喷枪公司对比推荐 - 行业平台推荐
  • 从零到一:基于MercuryTours的QTP自动化测试脚本实战演练
  • 异步FIFO设计解析:跨时钟域数据安全交换与工程实践
  • Helium MCP:让AI助手掌握区块链查询能力的MCP协议实践
  • DHT11传感器数据读取老出错?Arduino避坑指南与常见故障排查
  • Go语言网络监控工具wiremonitor:轻量级数据包捕获与事件化分析实战
  • 2026年评价高的KTV职业装定制高评分公司推荐 - 品牌宣传支持者
  • 告别抓包!用Python脚本一键下载钉钉直播回放(附源码及详细配置)
  • 给网络工程师的O-RAN入门指南:从传统RAN到开放架构,到底改变了什么?
  • 2026年比较好的盐城网站优化/盐城官网建设/盐城做网站/盐城网站设计服务型公司推荐 - 行业平台推荐
  • 硬件混淆技术:UC方案在芯片安全中的原理与实践
  • 5分钟掌握B站视频下载:免费获取4K大会员高清视频完整指南
  • ESP8266刷机翻车实录:从esptool报错到乐鑫官方工具救场,我的避坑指南全记录
  • CAN总线EMI/ESD保护电路设计与器件选型指南
  • HomeAssistant Docker版安装后必做的5件事:从时区校准到开机自启(附systemd完整配置)
  • 2026年口碑好的包头grg构件/grg定制/酒店grg装饰/grg吊顶定制加工厂家推荐 - 品牌宣传支持者
  • 综合评估国际半导体展哪家好?汇聚主流国际半导体展资源 - 品牌2026
  • SwiftLLM:在Swift应用中原生集成大语言模型的实践指南
  • 基于HC32L136的工业物联网LCD数码屏驱动与低功耗实战解析
  • GM游戏官网哪个好 不花冤枉钱玩BT游戏
  • 别再只用真彩色了!Landsat 8/9 波段组合保姆级指南:从城市扩张到农业监测的实战应用
  • 你的ADC测量结果“跳”得厉害?可能是没用对过采样与均值滤波
  • 企业微信机器人如何接收并解析用户发送的图片消息内容
  • 用Xilinx FPGA的进位链(Carry Chain)实现高精度TDC:从原理到后仿真的保姆级避坑指南
  • Grafana变量查询实战:从零构建动态Dashboard标签筛选