ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring Boot 整合 Flowable 的企业级落地指南

Spring Boot 整合 Flowable 的企业级落地指南 做中后台系统OA、ERP、CRM 这些迟早要和工作流引擎打交道。Activiti 停更了Camunda 学习曲线太陡Flowable 算是目前 Java 圈里的香饽饽。轻量、对 Spring Boot 友好社区也算活跃。但看官方文档和网上那些 demo 是一回事真要在生产环境跑起来全是坑。今天不扯虚的直接聊聊我在项目里用 Spring Boot 整合 Flowable 攒下来的实战经验。从建表、表单解耦到多实例会签再到最后怎么防着它把数据库搞崩咱们一点点捋。1. 核心概念别死记硬背当成状态机来理解Flowable 底层是死磕 BPMN 2.0 规范的。看官方文档那些高大上的名词容易晕其实你把它当成一个“带业务逻辑的状态机”就明白了Process Definition流程定义相当于 Java 里的Class就是那个 BPMN XML 解析后的元数据。Process Instance流程实例相当于Object是定义的一次具体执行。Execution执行令牌这个概念很关键。单线流程里它和流程实例是 1:1 的。但一旦遇到并行网关令牌就会分裂产生多个 Execution。Task任务分 UserTask给人干的和 ServiceTask给机器干的。Variable流程变量流程流转的“血液”。节点间传数据、网关判断走哪条线全靠它。BPMN 的核心元素其实就三类Events事件比如开始、结束、定时器、Tasks任务、Gateways网关排他、并行、包容。别去死记硬背 XML 标签用 Flowable 官方的建模工具或者 IDEA 插件拖拽几下就明白了。2. 集成与建表生产环境的第一道鬼门关引入依赖很简单现在 Flowable 都出 7.x 了别死磕老版本dependencygroupIdorg.flowable/groupIdartifactIdflowable-spring-boot-starter-process/artifactIdversion7.0.0/version/dependency重点说下数据库初始化策略。Flowable 通过database-schema-update控制 DDLflowable:database-schema-update:truetrue启动时检查表结构没有就建版本不对就升级。false不执行 DDL表不存在直接报错。create-drop/drop-create启动建/关时删或者先删后建。老鸟忠告本地开发你随便用true生产环境绝对、千万不要用true。让框架自动改线上表结构是找死。正确的姿势是把 Flowable 的 DDL 脚本抠出来交给 Flyway 或 Liquibase 做版本化管理然后把配置改成false。3. 流程部署与版本控制老实例走老流程Flowable 的版本控制核心就两个字KEY和VERSION。你可以把.bpmn20.xml扔在classpath:processes/下让它启动时自动部署也可以用RepositoryService通过 API 动态传 XML 部署。当部署一个流程时引擎会查数据库有没有同 KEY 的流程。有VERSION 就 1没有VERSION 就是 1。这里有个极易踩坑的地方部署新版本后新启动的实例会走新流程但已经跑在路上的老实例依然会死死咬住它启动时的老版本流程定义。如果业务方非逼着老实例也走新流程Flowable 提供了ProcessInstanceMigrationValidator做强制迁移。但我劝你三思线上跑着的数据给你迁移崩了背锅的可是你。通常的做法是老实例老办法走完新实例走新办法。4. 动态表单与变量坚决弃用自带表单引擎企业级避坑第一法则千万别用 Flowable 自带的 Form Engine那玩意儿简陋得令人发指根本应付不了企业级复杂的动态表单。标准做法是业务表单与流程引擎彻底解耦。表单数据老老实实存在你的业务表比如leave_request里。启动流程时只把“业务主键 ID”和“核心路由指标”塞进流程变量。MapString,ObjectvariablesnewHashMap();variables.put(businessKey,LEAVE-20231024-001);// 业务主键用来反查业务表variables.put(days,5);// 核心路由指标请假天数runtimeService.startProcessInstanceByKey(leave_process,variables);网关条件路由在排他网关Exclusive Gateway的连线上写 UEL 表达式连线 1经理批${days 3}连线 2总监批${days 3 days 7}连线 3CEO 批${days 7}注意排他网关必须配一条Default Flow默认连线。万一哪天业务数据脏了所有条件都没命中流程直接卡死半夜叫你起来修数据的时候你就知道错了。5. 多实例会签与驳回逻辑5.1 多实例Multi-Instance配置在 UserTask 上配多实例就能搞会签或串行。集合变量assigneeList比如[zhangsan, lisi]元素变量currentAssignee当前循环到的审批人isSequentialfalse 是并行会签true 是串行。5.2 完成条件Completion Condition通过completionCondition决定多实例啥时候结束会签全票通过${nrOfCompletedInstances nrOfInstances}或签一票通过${nrOfCompletedInstances 0}按比例${nrOfCompletedInstances / nrOfInstances 0.5}注nrOfCompletedInstances这些是引擎内置的局部变量直接用就行。5.3 驳回与撤回BPMN 规范里其实没有“驳回”这个概念本质就是节点跳转。从 6.4 版本开始官方给了ChangeActivityStateBuilder// 驳回把当前任务节点强行跳转到指定的历史节点processRuntime.changeActivityState(ChangeActivityStateBuilder.builder().processInstanceId(processInstanceId).moveSingleExecutionToActivityIds(currentExecutionId,targetActivityId).build());至于“撤回”其实就是审批人刚点同意下一节点的人还没处理。你查一下当前任务把执行令牌挪回原节点顺便把多余的流程变量清掉就行了。6. 监听器一定要用 Spring Bean 注入监听器是解耦的利器。Execution Listener绑在连线、事件、任务上。触发start、end、take。用来记流转日志。Task Listener只能绑在 UserTask 上。触发create、assignment、complete、delete。用来动态算审批人。血泪教训千万别在 BPMN XML 里写死 Java 类的全限定名classcom.xxx.MyListener。一旦你在监听器里想调个 Service还得自己去 Spring 容器里捞恶心死。直接用delegateExpression注入 Spring BeanComponent(dynamicAssigneeListener)publicclassDynamicAssigneeListenerimplementsTaskListener{AutowiredprivateUserServiceuserService;Overridepublicvoidnotify(DelegateTaskdelegateTask){StringdeptId(String)delegateTask.getVariable(deptId);StringleaderIduserService.getDeptLeader(deptId);delegateTask.setAssignee(leaderId);}}XML 里这么配flowable:taskListener eventcreate delegateExpression${dynamicAssigneeListener} /。清清爽爽。7. 历史数据归档Flowable 最大的痛点Flowable 的ACT_HI_*历史表是个无底洞。随着时间推移数据量无限膨胀查询性能断崖式下跌。生产环境必须做冷热分离。挂起Suspend冻结实例任务无法推进。适用于单据作废但要保留现场。终止Delete物理删除运行时数据ACT_RU_*在历史表里记个删除原因。归档策略热数据运行时 近 3 个月历史留在 MySQL。冷数据3 个月前结束的写个定时任务比如 XXL-JOB每天凌晨跑。查出ACT_HI_PROCINST里END_TIME_早于 3 个月前的 ID。把这些 ID 对应的所有历史表数据批量INSERT到归档库ClickHouse 或者单独的 MySQL 归档库。从主库DELETE。切记归档操作必须加分布式锁且分批处理每次 1000 条。你要是敢一次性 delete 几十万条数据库直接锁死给你看。8. 性能调优与集群部署8.1 性能调优异步化ServiceTask 里如果有复杂逻辑或调第三方接口必须在 BPMN 里勾选Asynchronous Continuations。引擎会把它转成 Job 丢给后台线程池不然大事务会把数据库连接池耗干。历史级别Flowable 有none,activity,audit,full。生产环境用audit默认就够了记录实例、任务和活动。千万别手贱用full连变量每次变更都记性能直接拉胯。配置flowable.history-level: audit。连接池用 HikariCP连接数别配太大流程引擎是 IOCPU 混合密集50-100 通常足够了。8.2 集群部署Flowable 原生支持集群靠的是数据库级别的分布式锁。所有节点连同一个库把flowable.async-executor-activate设为true。Async Executor 会去抢ACT_RU_JOB等表里的记录通过SELECT ... FOR UPDATE保证同一个 Job 只被一个节点执行。如果并发高到数据库锁成了瓶颈那就只能二次开发把 Job 锁替换成 Redis (Redisson) 了。不过说实话90% 的公司都遇不到这个瓶颈。9. 权限与多租户别用引擎自带的用户体系再次强调废弃 Flowable 的IdentityService公司都有现成的 Spring Security JWT 的 RBAC 模型何必再去引擎里维护一套用户查待办任务时直接拿 Spring Security 上下文里的用户 IDtaskService.createTaskQuery().taskAssignee(SecurityUtils.getCurrentUserId()).orderByTaskCreateTime().desc().list();如果是 SaaS 多租户场景Flowable 原生支持tenantId。部署时带上租户 ID查询和推进时千万别忘了加.processInstanceTenantId(tenant_001)。数据量大的话建议用 MyBatis 拦截器在 SQL 层面强制追加租户条件防越权。10. 生产级避坑事务、死锁与异常这是区分“玩具”和“生产级”的分水岭。10.1 事务边界冲突Flowable 的 API 底层都是 Command 模式包在它自己的事务拦截器里。如果你的 ServiceTask 里调了个慢 RPC会导致 Flowable 的数据库事务长时间不提交。解法耗时操作必须异步化或者在 ServiceTask 里只发个 MQ 消息让消费者去处理。10.2 死锁预防如果在同一个Transactional方法里流程 A 触发流程 B流程 B 又反过来更新流程 A 的变量极大概率死锁。解法别在同一个事务里嵌套调多个流程推进 API。用 Spring 的AsyncEventListener或者 MQ 把动作解耦。10.3 异常补偿ServiceTask 调外部系统比如扣库存失败了别傻乎乎地抛异常让流程回滚外部系统可能已经部分成功了。实战做法别太迷信 BPMN 的补偿事件太复杂。直接在 JavaDelegate 里try-catch把错误码塞进流程变量比如errorCode INSUFFICIENT_BALANCE让流程自然走到下一个排他网关根据错误码路由到“异常处理节点”或“人工干预节点”。简单粗暴且极好排查。工作流引擎这东西入门容易精通难。核心就一句话把流程引擎当个纯粹的状态机来用。业务逻辑、权限、耗时操作全给它剥离出去。别想着让它包揽一切它只是个引擎不是业务系统本身。理清了这个边界Flowable 在你手里才能发挥出真正的威力。 福利时间如果你正在备战面试或者想要学习其他知识给大家推荐一个宝藏知识库作者整理了一些列 Java 程序员需要掌握的核心知识有需要的自取不谢。知识库地址https://farerboy.com/
返回列表