ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

若依集成Flowable工作流引擎与在线表单二次开发实战

若依集成Flowable工作流引擎与在线表单二次开发实战 简介本资源是基于 RuoYi-Vue-Plus 二次开发的工作流后台管理项目面向 Java 后端开发者、全栈工程师及需要在线表单与流程编排能力的团队重点解决 Flowable 工作流场景下的流程设计与表单定制问题。压缩包共 1198 个文件约 10.81MB以 502 个 Java 源码、248 个 JavaScript、132 个 Vue 组件为主辅以 53 个 XML 配置、26 个 SQL 脚本、18 个 Velocity 模板及若干 SCSS、YML、Dockerfile 等覆盖后端服务、前端页面、数据库初始化与容器化部署等模块。目前已有 2369 人学习下载。项目采用 MIT 开源协议个人与企业均可免费使用脚手架功能同步 RuoYi-Vue-Plus 更新可帮助读者快速理解 Flowable 与在线表单设计的整合思路掌握流程建模、表单渲染与后台权限管理的实现方式适合作为学习参考与毕业设计基础工程。需注意项目仍处开发阶段工作流流程尚存不足目前仅推荐用于学习等个人用途。1. 从若依到 Flowable二次开发工作流引擎的选型账很多团队在若依RuoYi-Vue-Plus上做完业务模块后都会撞上同一堵墙审批流写死在业务表里加一个节点就要改一次代码。我见过最夸张的一个项目请假流程从「部门经理审批」改成「部门经理 HR 双签」前后动了 7 个文件、发了 3 次版。这时候把 Flowable 引进来配合在线表单设计本质上是把「流程结构」从代码里抽出来变成可配置的数据。RuoYi-Vue-Plus 本身提供了成熟的权限、租户、代码生成底座Flowable 负责流程编排两者拼起来就是一套能落地的低代码工作流方案。这篇笔记面向的是已经用过若依、想在自己项目里把 Flowable 跑通并接上在线表单的开发者从依赖引入、表结构、表单渲染一路讲到踩过的坑能照着复现。2. 把 Flowable 塞进 RuoYi-Vue-Plus依赖、表结构与最小可跑流程2.1 为什么选 Flowable 而不是 Activiti、Camunda先说选型账。Activiti 6 之后社区活跃度下滑7 的 API 又和 6 有断层Camunda 7 功能强但商业版边界清晰社区版在复杂网关场景下文档偏企业向Flowable 6.x 是 Activiti 6 的分支API 稳定、中文资料多、Spring Boot Starter 开箱即用最关键的是它和若依这种「单体 多模块」的 Maven 结构能直接对齐。我一般会锁 Flowable 6.8.0 这个版本原因是它和 Spring Boot 2.7.x 兼容性经过大量项目验证再往上 7.x 对 JDK 和 Spring 版本要求更严若依老项目升级成本高。选型还要看一个隐性指标流程定义文件BPMN 2.0 XML的解析容错。Flowable 对不规范 XML 的报错信息比 Activiti 清晰比如漏了bpmn:startEvent的id它会直接告诉你哪一行哪个元素缺属性而不是抛一个空指针。这个细节在调试在线设计器生成的 XML 时能省大量时间。2.2 依赖引入与多模块拆分若依是典型的多模块结构ruoyi-admin、ruoyi-system、ruoyi-common各司其职。Flowable 相关代码我建议单独建一个ruoyi-flowable模块避免污染原有业务模块。父 pom 里统一管理版本!-- 父 pom.xml 的 dependencyManagement 中 -- dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version6.8.0/version /dependency !-- 在线表单设计器常用到的 JSON 处理 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependencyruoyi-flowable模块的 pom 里引入 starter同时依赖ruoyi-common拿工具类和统一返回体。这里有个容易翻车的点Flowable starter 默认会扫描所有processes/目录下的 BPMN 文件并自动部署如果你把设计器生成的临时 XML 也丢进去启动时会因为重复部署报错。我的做法是关掉自动部署改由业务代码手动调用repositoryService.createDeployment()。# application.yml 中关闭自动部署 flowable: check-process-definitions: false database-schema-update: true async-executor-activate: truedatabase-schema-update: true让 Flowable 首次启动时自动建表生产环境建议改成false并用 SQL 脚本手动执行避免版本升级时表结构被意外改动。async-executor-activate控制异步任务执行器如果流程里有定时边界事件或异步服务任务必须为true。2.3 Flowable 表结构分组与若依数据源共存Flowable 默认会建 30 多张表按前缀分五组ACT_RE_*是流程定义和部署信息ACT_RU_*是运行时实例和任务ACT_HI_*是历史数据ACT_ID_*是用户组ACT_GE_*是通用属性。和若依的sys_user、sys_dept共存时关键是把 Flowable 的用户体系桥接到若依上而不是让 Flowable 自己管用户。我一般会实现IdentityService的适配层把sys_user的查询包装成 Flowable 能识别的UserQuery。但更省事的做法是流程里的候选人、候选组直接用若依的userId和roleKey字符串在任务查询时用taskCandidateUser和taskCandidateGroup过滤不碰 Flowable 自带的ACT_ID_*表。这样用户数据只有一份权限逻辑也统一在若依侧。// 查询某用户待办任务直接用若依 userId 作为 candidateUser ListTask tasks taskService.createTaskQuery() .taskCandidateOrAssigned(String.valueOf(userId)) .orderByTaskCreateTime().desc() .list();taskCandidateOrAssigned同时覆盖「候选人」和「已签收」两种状态避免待办列表漏数据。参数传字符串是因为 Flowable 内部按字符串匹配若依的userId是 Long转一下即可。2.4 最小可跑流程一个请假审批的 BPMN 与启动代码先不碰在线设计器手写一个最简 BPMN 验证链路。流程定义文件放resources/processes/leave.bpmn20.xml核心节点是开始事件、用户任务、结束事件。用户任务的assignee先用固定值跑通后再换成动态表达式。process idleaveProcess name请假审批 startEvent idstart/ sequenceFlow sourceRefstart targetRefdeptLeader/ userTask iddeptLeader name部门经理审批 flowable:assignee${deptLeaderId}/ sequenceFlow sourceRefdeptLeader targetRefend/ endEvent idend/ /process启动流程时把deptLeaderId作为变量传进去MapString, Object vars new HashMap(); vars.put(deptLeaderId, 1001); // 若依里的部门经理 userId ProcessInstance instance runtimeService.startProcessInstanceByKey(leaveProcess, vars);startProcessInstanceByKey的第二个参数是流程变量${deptLeaderId}会在进入用户任务时被解析。如果变量没传Flowable 会抛Unknown property used in expression这是新手最常见的报错排查时先看变量名拼写和传入时机。3. 在线表单设计从 JSON Schema 到流程节点绑定3.1 表单设计器的技术选型与数据格式在线表单设计器市面上有几种路子用 form-generator 这类开源组件、基于 JSON Schema 自研、或者直接上 Formily。若依生态里 form-generator 出现频率最高它输出的是一份 JSON 数组每个元素描述一个字段的组件类型、label、校验规则。我一般会在这份 JSON 外面再包一层业务元数据形成「表单定义」{ formKey: leaveForm, version: 1, fields: [ {type: input, field: reason, label: 请假事由, required: true}, {type: date, field: startDate, label: 开始日期, required: true}, {type: number, field: days, label: 天数, required: true} ] }formKey是表单和流程节点的绑定键version用于表单改版后旧流程实例仍能渲染历史版本。这个 version 字段是我踩坑后加的表单字段删了一个历史审批记录渲染时直接报错因为渲染器找不到对应组件。3.2 表单与流程节点的绑定方式绑定有两种常见做法。一种是在 BPMN 的用户任务上写扩展属性比如flowable:formKeyleaveForm流程走到该节点时前端根据 formKey 拉表单定义渲染。另一种是把绑定关系存在业务表里用processDefId taskDefKey做联合索引。我倾向第二种因为 BPMN 改一次就要重新部署绑定关系写在 XML 里改起来麻烦放数据库里可以热更新。CREATE TABLE wf_form_binding ( id BIGINT PRIMARY KEY AUTO_INCREMENT, process_def_id VARCHAR(64) NOT NULL, task_def_key VARCHAR(64) NOT NULL, form_key VARCHAR(64) NOT NULL, form_version INT DEFAULT 1, UNIQUE KEY uk_def_task (process_def_id, task_def_key) );process_def_id是 Flowable 的流程定义 ID格式类似leaveProcess:1:2504带版本号。task_def_key是 BPMN 里用户任务的 id。联合唯一索引保证一个节点只绑一份表单。查询时用processDefinitionId和当前taskDefinitionKey去查拿不到就回退到流程级默认表单。3.3 表单数据的存储与回显表单数据不能直接塞进 Flowable 的变量表因为ACT_RU_VARIABLE的TEXT_字段长度有限复杂表单会截断。我的做法是业务表存全量 JSONFlowable 变量只存一个业务主键。// 提交表单时 String businessKey leaveService.saveForm(formDataJson); // 业务表落库 MapString, Object vars new HashMap(); vars.put(businessKey, businessKey); taskService.complete(taskId, vars);回显时用businessKey查业务表拿 JSON再交给前端渲染器。这样流程引擎只负责流转数据归属清晰。注意businessKey在启动流程时就要通过runtimeService.startProcessInstanceByKey(key, businessKey, vars)传进去而不是等到第一个任务完成才传否则历史流程查询会缺主键。3.4 动态候选人用若依角色和部门算审批人固定 assignee 只能跑通 demo真实场景要按发起人部门找经理。常见做法是用 Flowable 的表达式调用 Spring BeanuserTask iddeptLeader name部门经理审批 flowable:assignee${userService.findDeptLeader(initiator)}/Component(userService) public class UserService { public String findDeptLeader(String initiator) { // 查若依 sys_user 拿 deptId再查该部门负责人 SysUser user userMapper.selectUserById(Long.valueOf(initiator)); SysDept dept deptMapper.selectDeptById(user.getDeptId()); return String.valueOf(dept.getLeaderUserId()); } }initiator是启动流程时传入的变量值是若依 userId。表达式里的 Bean 名要和Component里的一致否则报Unknown property used in expression。这个方法里如果部门没配负责人要返回一个兜底 userId 或抛业务异常别返回 null否则任务会变成无主任务谁都查不到。4. 流程流转与业务解耦监听器、网关与驳回实现4.1 执行监听器与任务监听器的分工Flowable 的监听器分两类ExecutionListener挂在流程流转的连线上TaskListener挂在用户任务上。分工要清楚任务创建时改候选人、发通知用TaskListener的create事件流程节点进出时写业务日志、更新业务状态用ExecutionListener的start和end。public class TaskCreateListener implements TaskListener { Override public void notify(DelegateTask task) { String initiator (String) task.getVariable(initiator); // 动态设置候选人覆盖 BPMN 里的静态配置 task.addCandidateUser(initiator); } }监听器里不要做重耗时操作比如远程调用、大批量查询。Flowable 的监听器执行在流程事务内耗时过长会拖长事务、锁住ACT_RU_*表。发通知这类操作我一般丢到异步线程或消息队列监听器里只写一条待发送记录。4.2 排他网关与条件表达式的写法排他网关exclusiveGateway用来做分支判断比如请假天数大于 3 天要走总经理审批。条件写在连线的conditionExpression上exclusiveGateway iddayCheck/ sequenceFlow sourceRefdayCheck targetRefgmApprove conditionExpression xsi:typetFormalExpression ${days 3} /conditionExpression /sequenceFlow sequenceFlow sourceRefdayCheck targetRefend conditionExpression xsi:typetFormalExpression ${days 3} /conditionExpression /sequenceFlowdays必须是数值类型如果表单提交上来是字符串 5表达式比较会抛类型转换异常。前端提交时要做类型转换或者在后端统一把表单 JSON 转成 Map 时按字段类型解析。另外所有分支条件要覆盖全集否则 Flowable 会抛No outgoing sequence flow found这是网关最常见的报错。4.3 驳回与自由跳转的实现思路Flowable 原生没有「驳回」概念只有changeState和moveActivityIdTo这类底层 API。常见做法是用runtimeService.createChangeActivityStateBuilder()把当前活动节点改到目标节点runtimeService.createChangeActivityStateBuilder() .processInstanceId(instanceId) .moveActivityIdTo(currentTaskDefKey, targetTaskDefKey) .changeState();moveActivityIdTo会把当前节点取消激活目标节点。注意目标节点如果是用户任务会重新触发TaskListener的 create 事件候选人会重新计算。驳回时要把目标节点的表单数据回填否则审批人看到的是空表单。我的做法是驳回时把当前表单 JSON 存一份快照到历史表回填时按节点顺序取最近一次。4.4 流程与业务状态的一致性流程状态和业务状态是两套东西容易不一致。比如流程已经结束业务表状态还是「审批中」。我一般用ExecutionListener在end事件里更新业务状态同时加一张对账表定时扫描流程已结束但业务状态未更新的记录。public class ProcessEndListener implements ExecutionListener { Override public void notify(DelegateExecution execution) { String businessKey execution.getProcessInstanceBusinessKey(); leaveService.updateStatus(businessKey, APPROVED); } }getProcessInstanceBusinessKey()拿到的就是启动时传的 businessKey。监听器里更新业务表要用同一个事务如果业务更新失败流程状态回滚保证一致性。别用异步异步会丢一致性。5. 避坑与排查二次开发工作流最容易翻车的 5 个点5.1 现象启动流程报「Unknown property used in expression」原因BPMN 里用了${xxx}表达式但启动时没传对应变量或者变量名拼写不一致。Flowable 表达式解析是大小写敏感的。解决启动前打印变量 Map 的 keySet和 BPMN 里的表达式逐个比对。动态 assignee 的表达式要确认对应的 Spring Bean 已经注册Bean 名默认是类名首字母小写。5.2 现象待办列表查不到任务原因候选人设置用了addCandidateUser但查询用了taskAssignee两者匹配不上。或者用户 ID 类型不一致存的是 Long查的是 String。解决统一用taskCandidateOrAssigned查询用户 ID 统一转成 String 再传入。检查ACT_RU_IDENTITYLINK表里USER_ID_字段的实际值和查询参数对比。5.3 现象流程部署后旧版本实例还在跑新版本不生效原因Flowable 按processDefinitionKey部署同 key 多次部署会生成多版本新启动的实例默认用最新版但已运行的实例仍绑旧版。解决这是正常行为不要试图改已运行实例的版本。如果必须让旧实例走新流程只能终止后重新发起。部署时用repositoryService.createDeployment().name()带上业务标识方便在ACT_RE_DEPLOYMENT里追溯。5.4 现象表单 JSON 存进流程变量后被截断原因ACT_RU_VARIABLE的TEXT_字段在部分数据库下长度有限大 JSON 会被截断或报错。解决流程变量只存 businessKey全量 JSON 存业务表。如果确实需要存小段 JSON改用LONG_字段或BYTEARRAY_但更推荐前者。5.5 现象并发审批时任务重复签收或状态错乱原因多个审批人同时打开待办都点了「签收」Flowable 的claim没有做幂等后一次会覆盖前一次。解决签收前先查taskService.createTaskQuery().taskId(taskId).taskUnassigned()确认未签收再 claim。或者用乐观锁在业务层加版本号控制。高并发场景下建议用taskService.claim的返回值判断返回空表示已被签收。6. 进阶技巧用流程变量做动态表单权限与字段级控制表单字段的读写权限如果写死在设计器里流程走到不同节点就没法变。我的做法是在表单定义里给每个字段加一个permission表达式运行时根据流程变量计算。{ field: salary, label: 薪资, permission: ${node hr ? edit : readonly} }前端渲染时把流程变量注入表达式引擎比如用expr-eval或后端算好返回算出每个字段是edit、readonly还是hidden。这样同一份表单在不同节点呈现不同形态不用为每个节点单独做表单。验证方法发起一个流程走到 HR 节点时用 HR 账号打开确认薪资字段可编辑走到经理节点时用经理账号打开确认同一字段只读。如果表达式没生效先检查流程变量node是否在进入节点时被正确设置我一般用ExecutionListener的start事件往变量里写当前节点标识。public class NodeFlagListener implements ExecutionListener { Override public void notify(DelegateExecution execution) { execution.setVariable(node, execution.getCurrentActivityId()); } }getCurrentActivityId()返回的是 BPMN 里节点的 id和表单权限表达式里的值要对齐。这个变量会随流程流转不断覆盖所以表达式里读到的永远是当前节点的值。还有一个技巧是字段级的历史留痕。表单提交时把当前节点的字段权限快照存一份审批历史里就能还原「当时这个人看到的是什么」。我吃过亏一个薪资字段在经理节点是只读但经理通过接口直接改了值事后查不到是谁改的。加了快照后每次提交都记录字段值和权限状态对账时一目了然。这套方案值不值得做取决于你的流程变更频率。如果一年改不了两次流程硬编码也能忍如果业务部门三天两头要调审批链把 Flowable 和在线表单接进若依就是一次性投入、长期省事。我自己的习惯是新项目直接上老项目先拿一个边缘流程试点跑顺了再铺开。希望帮到你。本文还有配套的精品资源点击获取
返回列表