简介:本项目基于 RuoYi-Vue-Plus 二次开发,扩展 Flowable 工作流能力,面向 Java 后端开发者、工作流学习者及毕业设计人群,解决在线表单设计与可视化流程编排的需求。资源包共 1198 个文件,约 10.81MB,以 502 个 Java 文件承载后端业务与工作流引擎逻辑,248 个 JS 与 132 个 Vue 文件构成前端交互界面,另有 53 个 XML、26 个 SQL 及 18 个 VM 模板支撑流程定义、数据库脚本与代码生成,并包含少量 yml、scss、ftl 等配置与样式资源。目前已有 2369 人学习下载。项目采用 MIT 开源协议,个人与企业均可免费使用,脚手架功能同步 RuoYi-Vue-Plus 更新,可帮助读者快速理解 Flowable 与在线表单的整合方式,掌握流程建模、表单渲染及后台管理模块的组织结构,适合作为学习与二次开发的参考底包。
1. 从若依到 Flowable:这套二次开发资源到底能省多少事
如果你正在找一个能直接跑起来的工作流后台管理底座,又不想从零搭权限、菜单、租户、代码生成那一整套,那这套基于 RuoYi-Vue-Plus 二次开发、把 Flowable 工作流引擎揉进后台管理的源码包,值得你花一个下午拆一遍。它解决的不是“工作流是什么”这种概念问题,而是“我明天就要给业务方演示一个能画表单、能配流程、能审批、能看流转记录的完整系统”这种落地问题。适合谁?适合手里有若依基础、被 Flowable 原生 API 折磨过、或者接私活需要快速交付审批模块的开发者。我见过太多人卡在“流程定义部署成功但任务查不出来”这种玄学问题上,这套东西的价值就在于把 BPMN 设计器、在线表单、业务表单挂载、流程实例管理这些高频场景提前串好了。它不是官方文档,是一份能让你少走弯路的工程化参考。
2. 环境搭建与工程结构:把 Flowable 塞进若依的正确姿势
2.1 依赖版本与数据库选型
这套二次开发的核心思路是“若依管业务,Flowable 管流转”,所以第一步不是急着看流程代码,而是把版本对齐。RuoYi-Vue-Plus 本身对 Spring Boot 版本有要求,Flowable 6.x 和 7.x 在 API 上有差异,常见做法是锁定 Flowable 6.8.0 配合 Spring Boot 2.7.x,这样和若依的 MyBatis-Plus、Sa-Token 冲突最少。数据库方面,MySQL 8.0 是主流选择,但要注意 Flowable 自带的ACT_表默认用 InnoDB 和 utf8mb4,如果若依的库已经是 utf8mb4_general_ci,建表时别让 Flowable 自动改成 utf8mb4_unicode_ci,否则关联查询会报排序规则混用错误。
我一般会先检查pom.xml里有没有显式排除 Flowable 自带的 MyBatis 依赖,因为若依用的是 MyBatis-Plus,两者共存时容易在启动阶段报SqlSessionFactory冲突。正确做法是引入flowable-spring-boot-starter-process后,排除mybatis传递依赖,只保留 Flowable 引擎本身。
<!-- 在 ruoyi-flowable 模块的 pom.xml 中 --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter-process</artifactId> <version>6.8.0</version> <exclusions> <!-- 排除自带 MyBatis,避免与 MyBatis-Plus 冲突 --> <exclusion> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </exclusion> </exclusions> </dependency>参数说明:version必须和若依主 pom 里定义的flowable.version保持一致,不要单独升级某个子模块。排除 MyBatis 后,Flowable 的ACT_表操作会走 MyBatis-Plus 的 SqlSession,但需要额外配置flowable.database-schema-update: true让引擎自动建表。第一次启动建议设为true,生产环境改成false并手动执行 SQL 脚本。
2.2 在线表单设计器的数据落点
在线表单设计是这套资源的亮点,但很多人没搞明白表单数据到底存哪。它没有用 Flowable 自带的 Form 引擎,而是走若依的“动态表单”思路:表单结构存sys_form表,字段定义存 JSON,流程节点通过form_id关联。这样做的好处是表单可以独立于流程版本复用,坏处是流程回退时表单数据不会自动回滚,需要业务代码自己处理。
配置步骤分三步:第一,在application.yml里打开表单设计器开关;第二,执行ry_flowable.sql里的sys_form和sys_form_data建表语句;第三,在流程模型设计器的“表单设置”里选择“业务表单”并填入表单 ID。
# application.yml 关键配置 flowable: # 关闭异步执行器,避免若依线程池冲突 async-executor-activate: false # 自动更新数据库表结构 database-schema-update: true # 历史级别,audit 够用,full 太吃存储 history-level: audit # 关闭 DMN 和 CMMN,只留 BPMN dmn-enabled: false cmmn-enabled: false逻辑说明:async-executor-activate设为 false 是因为若依本身有ThreadPoolTaskExecutor,Flowable 再起一个异步执行器容易在定时任务触发时抢线程,导致流程节点卡在“异步待执行”状态。history-level选 audit 能记录所有活动实例和历史任务,但不会存变量快照,对审批场景足够。如果你要做数据审计追溯,改成 full,但 MySQL 的ACT_HI_DETAIL表会膨胀得很快,建议配合定时清理。
2.3 前端流程设计器集成方式
前端用的是bpmn-js加自定义属性面板,不是 Flowable 官方的 Angular 设计器。若依前端是 Vue2 + Element UI,所以设计器被封装成ProcessDesigner组件,通过 iframe 或直接引入的方式挂到路由上。我一般会检查src/views/flowable/process目录下的index.vue,看它有没有在mounted里调this.$refs.designer.init(),如果没调,画布是空白的。
常见坑是 bpmn-js 的版本和 Flowable 的 BPMN 命名空间不匹配。比如 bpmn-js 8.x 默认导出bpmn:Process,而 Flowable 6.8 解析时要求flowable:process扩展属性。解决办法是在设计器的moddleExtensions里注册 Flowable 的 JSON 描述文件,这个文件通常在src/components/ProcessDesigner/flowable.json。
// ProcessDesigner 组件初始化片段 import BpmnModeler from 'bpmn-js/lib/Modeler' import flowableModdle from './flowable.json' this.bpmnModeler = new BpmnModeler({ container: this.$refs.canvas, moddleExtensions: { flowable: flowableModdle // 关键:让设计器识别 flowable: 前缀 } })参数说明:flowableModdle定义了flowable:assignee、flowable:candidateUsers、flowable:formKey等属性的解析规则。没有它,你画的任务节点保存后,后端解析 XML 时会丢掉审批人配置,导致流程启动后任务没有办理人,直接卡死。初始化完成后,记得调this.bpmnModeler.importXML(xmlStr)加载已有流程,否则每次打开都是新画布。
3. 流程定义与任务审批:从 BPMN 文件到待办列表的完整链路
3.1 流程模型部署与版本控制
在若依里,流程模型先存sys_flow_model表,部署时再转成 BPMN XML 推给 Flowable。部署接口通常是POST /flowable/model/deploy/{modelId},内部调repositoryService.createDeployment().addString()。这里有个血泪经验:如果同一个模型重复部署,Flowable 不会覆盖旧版本,而是生成新版本号,但若依的sys_flow_model表只记录最新版,导致历史版本在ACT_RE_PROCDEF里越积越多。我一般会在部署前先调repositoryService.deleteDeployment(deploymentId, true)删掉旧部署,或者用tenantId隔离不同业务线。
// 部署流程模型的核心逻辑 public String deployModel(String modelId) { FlowModel model = flowModelMapper.selectById(modelId); // 先查询是否已有部署记录 Deployment existing = repositoryService.createDeploymentQuery() .deploymentTenantId(model.getTenantId()) .deploymentName(model.getModelName()) .singleResult(); if (existing != null) { // 级联删除旧部署,包括运行中的实例 repositoryService.deleteDeployment(existing.getId(), true); } Deployment deployment = repositoryService.createDeployment() .name(model.getModelName()) .tenantId(model.getTenantId()) .addString(model.getModelName() + ".bpmn20.xml", model.getModelXml()) .deploy(); return deployment.getId(); }逻辑说明:deleteDeployment(id, true)的第二个参数是cascade,设为 true 会删掉关联的流程实例、任务和历史数据。如果业务不允许删运行中实例,改成 false 并先挂起旧版本。tenantId在多租户场景下必须传,否则不同租户的同名流程会互相覆盖。部署成功后,ACT_RE_PROCDEF表会新增一条记录,KEY_字段就是流程定义 key,启动流程时要用它。
3.2 启动流程实例与业务表单关联
启动流程不是简单调runtimeService.startProcessInstanceByKey(),因为若依的业务数据要挂到流程实例上。常见做法是先把业务数据存进自己的业务表,拿到主键后作为businessKey传给 Flowable,同时把表单数据塞进variables。
// 启动流程并绑定业务表单 public void startProcess(StartProcessDTO dto) { // 1. 保存业务数据 BizLeave leave = new BizLeave(); leave.setDays(dto.getDays()); leave.setReason(dto.getReason()); bizLeaveMapper.insert(leave); // 2. 构建流程变量 Map<String, Object> variables = new HashMap<>(); variables.put("businessKey", leave.getId()); variables.put("formData", dto.getFormData()); // 在线表单的 JSON 数据 variables.put("initiator", SecurityUtils.getUsername()); // 3. 启动流程 ProcessInstance instance = runtimeService.startProcessInstanceByKey( dto.getProcessKey(), leave.getId().toString(), // businessKey variables ); // 4. 回写流程实例 ID 到业务表 leave.setProcessInstanceId(instance.getId()); bizLeaveMapper.updateById(leave); }参数说明:businessKey用业务主键,方便后续用runtimeService.createProcessInstanceQuery().processInstanceBusinessKey()反查。variables里的formData如果太大,建议存到业务表而不是流程变量,因为 Flowable 会把变量序列化进ACT_RU_VARIABLE,大字段会拖慢查询。initiator用于后续任务分配时判断发起人,配合flowable:initiator属性使用。
3.3 待办任务查询与审批提交
待办列表是审批系统的门面,查得慢或者查不准,业务方直接开骂。若依的待办接口一般走taskService.createTaskQuery().taskAssignee(username),但实际场景里还有候选人、候选组、委托任务,所以要用taskCandidateOrAssigned。
// 分页查询待办任务 public IPage<TaskVO> selectTodoList(Page<TaskVO> page, String username) { TaskQuery query = taskService.createTaskQuery() .taskCandidateOrAssigned(username) // 覆盖候选人、候选组、直接指派 .orderByTaskCreateTime().desc(); long total = query.count(); List<Task> tasks = query.listPage((int) page.getCurrent() - 1, (int) page.getSize()); List<TaskVO> voList = tasks.stream().map(task -> { TaskVO vo = new TaskVO(); vo.setTaskId(task.getId()); vo.setTaskName(task.getName()); vo.setCreateTime(task.getCreateTime()); // 查业务表单数据 String businessKey = runtimeService .createProcessInstanceQuery() .processInstanceId(task.getProcessInstanceId()) .singleResult() .getBusinessKey(); vo.setBusinessData(bizService.getByBusinessKey(businessKey)); return vo; }).collect(Collectors.toList()); return page.setRecords(voList).setTotal(total); }逻辑说明:taskCandidateOrAssigned是 Flowable 提供的组合查询,等价于assignee = username OR candidateUser = username OR candidateGroup IN (用户所属组)。注意它不会自动包含“委托任务”,如果用了delegationState,要额外加.or().taskDelegationState(...)。分页用listPage而不是list,避免全表加载。审批提交时调taskService.complete(taskId, variables),如果节点有网关条件,变量必须包含网关判断所需的字段,否则会报Unknown property。
4. 避坑与排查:那些让流程卡死的配置细节
4.1 流程启动后任务查不到
现象:流程实例启动成功,ACT_RU_EXECUTION有记录,但待办列表为空,ACT_RU_TASK表里也没有任务。原因通常是 BPMN 里第一个用户任务的assignee写成了固定值但用户不存在,或者candidateGroups填了若依里不存在的角色 key。解决:先查ACT_RU_TASK确认任务是否生成,如果没有,检查 BPMN XML 里flowable:assignee的值是否和sys_user.user_name一致;如果有任务但查不到,检查taskCandidateOrAssigned里的用户名是否带租户前缀。
4.2 在线表单保存后流程节点读不到数据
现象:表单设计器里配了字段,流程启动时也传了formData,但审批页面显示空白。原因多半是表单 ID 和流程节点的formKey没对上。Flowable 的formKey存在ACT_RU_TASK.FORM_KEY_字段,若依的在线表单靠这个字段去sys_form表查结构。解决:在流程设计器里选中用户任务节点,在属性面板的“表单”选项卡里选择“业务表单”并填入正确的表单 ID,保存后重新部署。部署后可以查ACT_RU_TASK的FORM_KEY_确认。
4.3 审批通过后流程不往下走
现象:调taskService.complete()没报错,但流程实例停在当前节点,ACT_RU_EXECUTION的IS_ACTIVE_变成 0。原因通常是排他网关的条件表达式引用了不存在的变量,或者变量类型不匹配。比如网关条件写${days > 3},但days传的是字符串"5",Flowable 会抛PropertyNotFoundException并挂起执行。解决:在complete之前打印variables的 key 和类型,确保数值型变量用Integer或Double,不要用String。网关条件里用days > 3而不是days > "3"。
4.4 历史任务查询慢到超时
现象:流程跑了几百个实例后,历史任务列表加载超过 10 秒。原因一般是ACT_HI_TASKINST和ACT_HI_PROCINST没建索引,或者history-level设成了 full 导致ACT_HI_DETAIL过大。解决:在ACT_HI_TASKINST的PROC_INST_ID_、ASSIGNEE_、START_TIME_上建联合索引;把history-level从 full 改成 audit;定期归档ACT_HI_*表,比如只保留最近 6 个月数据。
4.5 多租户下流程定义串租户
现象:A 租户部署的流程,B 租户也能启动。原因是在部署和查询时没传tenantId,Flowable 默认租户为空字符串,所有租户共享。解决:部署时调.tenantId(tenantId),启动时调runtimeService.startProcessInstanceByKeyAndTenantId(key, businessKey, tenantId, variables),查询时加.taskTenantId(tenantId)。若依的SecurityUtils里能拿到当前租户 ID,封装一个工具方法统一注入。
5. 进阶技巧:用流程变量做动态审批人与条件网关
5.1 动态审批人:从角色到具体用户的运行时解析
固定审批人只适合 demo,真实业务里审批人往往取决于发起人部门、金额大小、请假天数。这套资源支持在 BPMN 里写flowable:assignee="${approver}",然后在启动或完成任务时把approver塞进变量。但更灵活的做法是用TaskListener在任务创建时动态计算。
// 自定义任务监听器,动态设置审批人 @Component("dynamicAssigneeListener") public class DynamicAssigneeListener implements TaskListener { @Override public void notify(DelegateTask delegateTask) { String initiator = (String) delegateTask.getVariable("initiator"); Integer days = (Integer) delegateTask.getVariable("days"); // 根据发起人和天数决定审批人 String approver; if (days > 3) { approver = userService.findDeptLeader(initiator); } else { approver = userService.findDirectLeader(initiator); } delegateTask.setAssignee(approver); } }逻辑说明:监听器通过@Component注册成 Spring Bean,在 BPMN 的用户任务里配flowable:taskListener的expression为${dynamicAssigneeListener}。delegateTask.getVariable能拿到流程启动时的变量,setAssignee会覆盖 BPMN 里的静态配置。注意监听器里不要做远程调用,否则任务创建会阻塞,建议只查本地库或缓存。
5.2 条件网关的表达式写法与调试
条件网关是流程分支的核心,但表达式写错时 Flowable 的报错信息很不友好。我一般会先在单元测试里用runtimeService.startProcessInstanceByKey跑一遍,看ACT_RU_EXECUTION的ACT_ID_落在哪个节点。表达式里用${amount > 1000}而不是${amount > 1000 ? true : false},Flowable 的 JUEL 解析器对三元运算符支持不好。如果变量是字符串比较,用${type == 'leave'},单引号不能少。
<!-- BPMN 排他网关片段 --> <exclusiveGateway id="gateway1" /> <sequenceFlow id="flow1" sourceRef="gateway1" targetRef="task_leader"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${days <= 3}]]> </conditionExpression> </sequenceFlow> <sequenceFlow id="flow2" sourceRef="gateway1" targetRef="task_manager"> <conditionExpression xsi:type="tFormalExpression"> <![CDATA[${days > 3}]]> </conditionExpression> </sequenceFlow>参数说明:CDATA包裹表达式是为了避免 XML 解析器把>和<当成标签。days必须是数值类型,如果从表单传过来是字符串,在启动流程前用Integer.parseInt转一下。网关的默认流用default="flow1"指定,避免所有条件都不满足时抛异常。
5.3 流程实例的挂起、激活与终止
业务里经常需要临时挂起流程,比如发起人撤回、管理员冻结。Flowable 提供runtimeService.suspendProcessInstanceById和activateProcessInstanceById,但挂起后任务查不到,审批页面会报 404。我一般会在业务表加一个status字段,挂起时同步更新,前端根据状态显示“已挂起”而不是报错。终止流程用runtimeService.deleteProcessInstance(instanceId, reason),会级联删掉运行中任务,但历史数据保留。
// 挂起流程实例 public void suspend(String instanceId) { runtimeService.suspendProcessInstanceById(instanceId); bizService.updateStatusByInstanceId(instanceId, "SUSPENDED"); } // 激活流程实例 public void activate(String instanceId) { runtimeService.activateProcessInstanceById(instanceId); bizService.updateStatusByInstanceId(instanceId, "ACTIVE"); }逻辑说明:挂起后taskService.createTaskQuery().taskAssignee()查不到该实例的任务,但historyService还能查历史。激活后任务恢复。注意挂起不会触发边界事件,如果流程里有定时边界事件,挂起期间定时器暂停,激活后继续计时。
5.4 验证流程是否跑通的最小闭环
拆完这套资源后,我习惯用一个最小闭环验证:建一个“请假”流程,包含发起、部门审批、人事审批三个节点,表单只留天数和理由两个字段。部署后启动实例,用发起人账号提交,切换审批人账号查待办,逐节点 complete,最后查ACT_HI_PROCINST的END_TIME_是否有值。如果END_TIME_为空,说明还有活动任务;如果DELETE_REASON_有值,说明被终止了。这个闭环跑通,基本能确认引擎、表单、权限、租户都没大问题。
从那以后我每次拿到新的工作流二次开发包,都强制走一遍这个最小闭环,不跑通不看业务代码。希望帮到你。
本文还有配套的精品资源,点击获取