资讯详情

RuoYi集成Flowable工作流引擎的OA审批实战指南

📅 2026/9/11 22:10:53 | 华诺云谱 👁 阅读
RuoYi集成Flowable工作流引擎的OA审批实战指南
简介一套面向Java开发者的OA办公系统项目源码基于RuoYi框架集成Flowable工作流引擎实现适合需要学习SpringBootMyBatis实战或快速搭建后台管理系统的读者。项目可在Eclipse与IntelliJ IDEA中直接运行前后端技术栈包括Layui、Ajax、Json以及SpringBoot、MyBatis推荐JDK1.8MavenMySQL环境。系统包含管理员与用户两种角色覆盖登录注册、工作计划、待办已办、通知公告、公章申请、项目管理、流程管理、权限设置、定时任务等典型OA模块业务完整度较高。资源压缩包共2000个文件其中HTML与JS前端页面约占多数另有259个Java源文件、SQL数据库脚本及CSS、JSON、XML等配置整体大小321.39MB便于按目录结构查阅学习。内含完整数据库脚本和前后端分离逻辑可快速启动企业级后台系统原型已有375人学习适合希望从零理解工作流集成与权限设计的中级Java开发者。1. OA 的流程不只是状态字段RuoYi 和 Flowable 各管哪一段OA 项目最常在验收前一个月出问题请假单能提交但流程走到一半卡死报销单审批通过后列表里状态还是“审批中”一个人请假部门主管和人事同时看到待办谁批都生效。这些问题大多不是表单字段设计不够也不是 SQL 写错而是把“审批流程”硬编码进了业务表的状态字段。RuoYi 提供了部门、用户、角色、菜单、代码生成这些组织与管理能力但它不会替你管理流程节点Flowable 是 BPMN 2.0 规范的工作流引擎负责流程定义、任务分派、驳回、会签。两者通过 Spring Boot 拼在一起形成“RuoYi 管组织与数据权限、Flowable 管审批流转、业务表只存业务数据”的三角结构。这套组合也是目前 Java 生态里做 OA 类系统最常见的技术选型正文按集成建表、部署发起、审批回退、表单绑定、踩坑验证的顺序展开。2. Flowable 的表和 RuoYi 的业务表怎么分家从 ACT_ 前缀说起2.1 Flowable 都需要创建哪些表四组前缀各管一摊Flowable 启动后会创建 60 多张表第一次接触的人看到这个数量通常会犹豫以为是引错了依赖。其实按表名前缀划分只有四组职责非常明确ACT_RE_ 存流程定义和模型ACT_RU_ 存运行中的实例与任务ACT_HI_ 存审批历史和足迹ACT_GE_ 存通用数据。RuoYi 自带的业务表以 sys_ 为前缀两者互不干扰。前缀表族典型表职责ACT_RE_流程定义存储ACT_RE_DEPLOYMENT、ACT_RE_PROCDEF部署包、流程定义及版本ACT_RU_运行时数据ACT_RU_EXECUTION、ACT_RU_TASK、ACT_RU_VARIABLE、ACT_RU_IDENTITYLINK运行中的实例、任务、变量、参与者ACT_HI_历史数据ACT_HI_PROCINST、ACT_HI_TASKINST、ACT_HI_ACTINST、ACT_HI_VARINST已结束实例、任务足迹、审批记录ACT_GE_通用数据ACT_GE_BYTEARRAY、ACT_GE_PROPERTY部署资源字节流、引擎属性这四组表里ACTHI_ 和 ACT_RU_ 是日常运维关注的重点。审批中的任务一定在 ACT_RU_TASK 里有记录流程结束后会写到 ACT_HI_PROCINSTRuoYi 的业务表不会知道这些细节它只需要记住一个流程实例 ID。2.2 自动建表配置一步到位但生产环境要改掉RuoYi 的数据源默认走 Druid集成 Flowable 时不需要单独配置数据源让 Flowable 与 RuoYi 共用业务库即可。首次启动时用自动建表最省事配置如下flowable: database-schema-update: true async-executor-activate: false history: full db-history-used: true check-process-definitions: true process-definition-location-prefix: classpath:/processes/database-schema-update: true表示启动时自动创建或升级 ACT_ 表第一次跑通项目可以开着生产环境建议改为 false用官方 SQL 脚本在发布流程里手动初始化避免引擎在启动时做不可控的 DDL。async-executor-activate: false很关键没有定时器事件、异步消息的流程不需要激活异步执行器置为 false 可以减少一批后台线程。history: full表示记录全部历史数据审批意见、变量变更都能追溯如果项目对历史要求不高可以改成 audit 减少存储量。process-definition-location-prefix让 Flowable 启动时扫描 classpath 下指定目录里的 BPMN 文件并自动部署本地开发和测试环境非常方便。MySQL 8 环境下最容易踩的坑是启动日志没有报错但 ACT_ 表一张都没建。原因是驱动拿不到正确的 catalog需要在 RuoYi 的 Druid URL 上追加参数url: jdbc:mysql://localhost:3306/ry_oa?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8nullCatalogMeansCurrenttruenullCatalogMeansCurrenttrue告诉 MySQL 驱动在 catalog 为空时使用当前数据库Flowable 的表结构初始化语句才能落到ry_oa库而不是报 “Table doesnt exist” 或建到奇怪的位置。这个参数也是网上搜“flowable 工作流数据库报错”时最常看到的解药。2.3 RuoYi 业务表怎么挂流程proc_inst_id 和 status 是标配Flowable 不管业务数据RuoYi 的代码生成器也不懂流程节点两边的连接点就是流程实例 ID。以请假单为例业务表最少要有三个与流程相关的字段proc_inst_id存 Flowable 的流程实例 IDstatus存业务自己的审批状态apply_user_id存发起人。流程发起后用businessKey记录业务主键反过来在历史表里也能通过BUSINESS_KEY_查到这张请假单。CREATE TABLE ob_leave ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT COMMENT 申请人, leave_type VARCHAR(20) COMMENT 事假/病假/年假, days DECIMAL(4,1) COMMENT 请假天数, reason VARCHAR(500) COMMENT 请假事由, proc_inst_id VARCHAR(64) COMMENT Flowable流程实例ID, status CHAR(1) DEFAULT 0 COMMENT 0草稿 1审批中 2通过 3驳回, create_time DATETIME, KEY idx_proc_inst_id (proc_inst_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT请假业务表;proc_inst_id上要建索引因为待办列表和详情回显都要靠它关联任务表没有索引的话数据量上来之后关联查询会明显变慢。status只反映业务视角的状态它和 Flowable 里的任务状态是两回事前者给列表页展示用后者驱动流程走向。2.4 部署一次后 BPMN 变更怎么生效靠版本号而不是覆盖Flowable 每次部署同一 key 的 BPMN 文件都会生成新版本ACT_RE_PROCDEF里的VERSION_会递增。老版本的流程实例继续按老定义走完新发起的流程使用最新版本。这个机制避免了“流程改到一半存量单子全乱掉”的问题。RuoYi 这类管理后台里版本管理一般不做成可视化界面直接用部署文件命名区分即可例如leave-process-v1.bpmn20.xml、leave-process-v2.bpmn20.xml。3. Spring Boot 集成 RuoYi 的流程最小闭环部署、发起、审批3.1 依赖选择flowable-spring-boot-starter-process 就够了RuoYi 分离版常见基于 Spring Boot 2.5 或 2.7搭配 Flowable 6.7.2 是比较成熟的选择如果项目已经升级到 Spring Boot 3则要使用 Flowable 7.x。依赖放到 ruoyi-framework 模块或独立的 OA 模块均可dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter-process/artifactId version6.7.2/version /dependency不需要引入flowable-ui-modeler那是独立的 Web 应用和 RuoYi 的前后端分离结构不好融合。流程设计可以用 bpmn-js 在前端画图将生成的 XML 传给后端部署也可以先用手头的 BPMN 设计器导出 XML 再放到 resources 目录。starter-process会带来RepositoryService、RuntimeService、TaskService、HistoryService一组 Spring Bean直接注入使用不需要手动初始化 ProcessEngine。3.2 最小可跑的 BPMN请假超过 3 天走总经理审批Flowable 的流程文件后缀可以是.bpmn20.xml放在src/main/resources/processes/下配合前面的process-definition-location-prefix配置项目启动时自动部署。一个能覆盖“条件分支、多人审批”的最小请假流程如下?xml version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:flowablehttp://flowable.org/bpmn targetNamespacehttp://flowable.org/bpmn process idleaveProcess name请假审批 isExecutabletrue startEvent idstartEvent flowable:initiatorapplyUserId/ sequenceFlow idflow1 sourceRefstartEvent targetRefdeptUserTask/ userTask iddeptUserTask name部门审批 flowable:candidateGroupsdeptLeader/ sequenceFlow idflow2 sourceRefdeptUserTask targetRefgateway1/ exclusiveGateway idgateway1/ sequenceFlow idflow3 sourceRefgateway1 targetRefmanagerUserTask conditionExpression xsi:typetFormalExpression${days 3}/conditionExpression /sequenceFlow sequenceFlow idflow4 sourceRefgateway1 targetRefendEvent conditionExpression xsi:typetFormalExpression${days 3}/conditionExpression /sequenceFlow userTask idmanagerUserTask name总经理审批 flowable:candidateGroupsmanager/ sequenceFlow idflow5 sourceRefmanagerUserTask targetRefendEvent/ endEvent idendEvent/ /process /definitionsuserTask上的candidateGroups定义谁能处理这个任务deptLeader和manager是组名不是 RuoYi 的用户名。exclusiveGateway根据流程变量days的值决定走总经理审批还是直接结束days必须在发起流程时传入变量。3.3 发起流程把 RuoYi 当前用户和业务主键传进去发起流程的代码放在 Service 层通过runtimeService.startProcessInstanceByKey启动。流程定义 ID 是上面的leaveProcess业务主键作为businessKey传给引擎方便以后从 ACT_HI_PROCINST 反查业务单public void startLeaveProcess(ObLeave leave) { MapString, Object vars new HashMap(); vars.put(days, leave.getDays()); vars.put(applyUserId, SecurityUtils.getUserId()); vars.put(applyUserName, SecurityUtils.getUsername()); ProcessInstance pi runtimeService.startProcessInstanceByKey( leaveProcess, leave.getId().toString(), vars); leave.setProcInstId(pi.getId()); leave.setStatus(1); leaveMapper.updateProcInstId(leave); }startProcessInstanceByKey的三个参数分别是流程定义 key、业务 key、流程变量。days会在网关条件表达式里被读取applyUserId和applyUserName用于记录发起人审批节点如果需要按发起人查部门负责人可以在这一步先查好放入变量。businessKey这里传的是业务表主键字符串引擎不会解析它但查询历史时会非常有用。3.4 待办查询和审批RuoYi 的分页插件可以继续用RuoYi 使用 PageHelper 做分页查询待办任务时照常用startPage()再调用taskService.createTaskQuery()public TableDataInfo getTodoList(String userId, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListTask tasks taskService.createTaskQuery() .taskCandidateOrAssigned(userId) .processDefinitionKey(leaveProcess) .orderByTaskCreateTime().desc() .list(); return getDataTable(convertTaskToVo(tasks)); }taskCandidateOrAssigned会同时匹配已经分配给这个人的任务assignee和这个人所在候选组里的任务candidate适合“同一节点多人可处理”的场景。如果流程里用的是 candidateUsers 而不是 candidateGroups这里可以直接换成taskCandidateUser(userId)。审批动作本质上是往任务上写审批意见和变量然后调用completepublic void completeTask(String taskId, String approved, String comment) { taskService.addComment(taskId, null, comment); MapString, Object vars new HashMap(); vars.put(approved, Y.equals(approved) ? Y : N); taskService.complete(taskId, vars); }addComment把审批意见写入历史前端详情页可以按任务 ID 查出来展示。complete是让流程继续往下走的唯一入口流程走到哪个节点完全由 BPMN 定义决定后端代码里不需要再写 if/else 判断下一级是谁。3.5 驳回不能删流程实例用变量控制网关走向很多从零开始写 OA 的人会想到用runtimeService.deleteProcessInstance模拟驳回这是错误做法。删除实例会把整个流程痕迹抹掉审批历史也没了。常见做法是在 BPMN 里设计排他网关审批人驳回时传入approvedN网关条件判断后走“驳回结束”或“退回发起节点”的路径。更复杂的退回上一节点操作Flowable 支持changeActivityId动态跳转但需要在 Service 层做节点校验写清楚“当前节点能否退到目标节点”否则容易把流程实例改乱。4. 表单数据进流程变量RuoYi 代码生成器与 Flowable 变量的绑定4.1 业务表单和流程表单是两套数据先分清一个原则RuoYi 代码生成器按业务表生成列表页、表单页、Controller、Service、Mapper它解决的是“CRUD 页面快速开发”的问题。但流程表单的要求不一样每个节点的字段可见性可能不同审批人要在表单里填写意见发起人提交的数据要跟着实例走。常见做法是业务数据存业务表流程变量只存流程走向需要的控制字段。days存业务表但也要在发起时同步成流程变量因为网关表达式${days 3}读的是变量不是数据库。4.2 提交时业务入表、变量入流程前后端各做一半RuoYi 前端提交表单后把需要参与流程判断的字段单独放在variables对象里传给后端const data { id: this.form.id, days: this.form.days, reason: this.form.reason, variables: { days: this.form.days, approved: Y } }; saveAndStart(data).then(res { this.$modal.msgSuccess(流程已发起); });后端接收后先保存业务数据再带着variables启动流程PostMapping(/leave/start) public AjaxResult saveAndStart(RequestBody ObLeaveBody body) { ObLeave leave obLeaveService.saveBusiness(body); MapString, Object vars new HashMap(); if (body.getVariables() ! null) { vars.putAll(body.getVariables()); } vars.putIfAbsent(applyUserId, SecurityUtils.getUserId()); ProcessInstance pi runtimeService.startProcessInstanceByKey( leaveProcess, leave.getId().toString(), vars); return AjaxResult.success(流程已发起, pi.getId()); }代码里vars.putIfAbsent保证前端传的变量不会被后端覆盖applyUserId缺失时自动拿当前登录用户补上。businessKey用业务主键后面流程结束回写状态时通过BusinessKey找到对应业务记录并更新status。4.3 会签和或签用多实例与候选组实现会签需要多个人全部审批完流程才继续走。BPMN 里用multiInstanceLoopCharacteristics实现给任务节点配置一个审批人列表变量userTask idmultiApproveTask name会签审批 multiInstanceLoopCharacteristics isSequentialfalse flowable:collectionassigneeList elementVariableassignee completionCondition${nrOfCompletedInstances nrOfInstances}/completionCondition /multiInstanceLoopCharacteristics /userTaskassigneeList是流程变量里的 List 类型来自 RuoYi 的部门人员查询结果。isSequentialfalse表示并行会签所有人同时批completionCondition控制完成条件默认全部通过才算过。或签更简单用candidateGroups的候选人组谁先处理完任务就归谁。4.4 RuoYi 的 DataScope 不能直接用流程表没有部门字段RuoYi 的数据权限注解DataScope是在 Mapper 层拼接部门数据权限 SQL但 ACT_RU_TASK 里没有dept_id、create_by这类 RuoYi 约定字段所以不能直接对 Flowable 表使用数据权限过滤。可靠做法是分两步先按当前用户查出他可见的业务表数据再把这些数据的proc_inst_id集合拿去匹配 Flowable 任务表public ListTaskVO getTodoListWithDataScope(Long userId, Integer pageNum, Integer pageSize) { ListTask tasks taskService.createTaskQuery() .taskCandidateOrAssigned(userId.toString()) .orderByTaskCreateTime().desc() .listPage((pageNum - 1) * pageSize, pageSize); ListString procInstIds tasks.stream() .map(Task::getProcessInstanceId) .collect(Collectors.toList()); if (procInstIds.isEmpty()) { return new ArrayList(); } ListObLeave leaves leaveMapper.selectByProcInstIds(procInstIds); SetLong visibleIds leaves.stream() .map(ObLeave::getId) .collect(Collectors.toSet()); return tasks.stream() .filter(t - visibleIds.contains(Long.valueOf(t.getBusinessKey()))) .map(t - convertToVO(t, leaves)) .collect(Collectors.toList()); }这个思路是先取任务再用业务表做权限过滤。leaveMapper.selectByProcInstIds的 Mapper 方法上可以加DataScope注解让 RuoYi 自动拼上部门过滤条件这样数据权限边界仍然由 RuoYi 控制Flowable 只负责找出候选人任务。4.5 流程结束状态回写用事件监听器而不是在 complete 后写审批流最后一步执行完complete流程实例在引擎侧已经结束此时如果只在前端代码里更新业务状态异步场景下很容易漏更新。推荐注册一个FlowableEventListener监听流程结束事件在回调里根据businessKey回写业务表状态Component public class ProcessEndListener implements FlowableEventListener { Resource private ObLeaveMapper obLeaveMapper; Override public void onEvent(FlowableEvent event) { FlowableEntityEvent entityEvent (FlowableEntityEvent) event; ProcessInstanceEntity instanceEntity (ProcessInstanceEntity) entityEvent.getEntity(); String businessKey instanceEntity.getBusinessKey(); if (businessKey ! null) { Long leaveId Long.valueOf(businessKey); obLeaveMapper.updateStatus(leaveId, 2); } } Override public boolean isFailOnException() { return false; } Override public boolean isFireOnTransactionLifecycleEvent() { return false; } Override public String getOnTransaction() { return null; } }isFailOnException返回 false 表示监听器抛异常不会影响流程事务回写失败可以由日志告警兜底。监听器需要注册到引擎配置在 RuoYi 中通过ProcessEngineConfigurationConfigurer追加即可这样流程无论走哪条分支结束状态都会落到业务表。5. RuoYi Flowable 集成避坑从版本冲突到数据权限5.1 版本搭配要提前定Spring Boot 3 配 Flowable 6 直接启动失败RuoYi 框架目前有基于 Spring Boot 2.x 和 3.x 的分支选 Flowable 时一定要先看 Spring Boot 大版本。Flowable 6.x 基于 javax 命名空间遇到 Spring Boot 3 的 jakarta 会直接 Bean 创建失败错误信息通常是ClassNotFoundException: javax.xml.bind.*。Spring Boot 版本Flowable 版本备注2.5.x6.7.2若依分离版常见组合2.7.x6.7.2 或 7.0.0兼容性较好3.x7.0.0必须用 7.x注意依赖版本对齐5.2 ACT_ 表没自动创建先检查 Druid URL 和数据库账号权限启动日志里没有报错但数据库里看不到 ACT_ 表最常见的是 MySQL 8 的nullCatalogMeansCurrent问题另外还要确认数据库账号有 CREATE 权限。database-schema-update: true只在第一次启动时建表如果项目之前用旧账号启动过并失败表目录可能已经不完整此时需要手动执行官方 SQL 脚本或删除相关表重新建。5.3 申请人看不到待办candidateGroups 里的组名和 RuoYi 角色要对上BPMN 里的candidateGroups是流程角色组RuoYi 里一个用户对多个角色角色标识存sys_role.role_key。如果两边名称不一致比如流程里写deptLeaderRuoYi 角色表里维护的是dept_manager任务就不会出现在任何人的待办列表里。建议建一张映射表或统一角色编码规范部署流程前用脚本检查 BPMN 里的组名在sys_role中是否存在。5.4 历史表膨胀定时清理要按子表到主表顺序删Flowable 的history: full会记录每个节点、每个变量的完整变更跑几年后 ACT_HI_ 系列表会非常大。清理已结束实例的历史数据时要按从子表到主表的顺序删除否则外键约束会报错DELETE FROM ACT_HI_VARINST WHERE PROC_INST_ID_ IN (SELECT ID_ FROM ACT_HI_PROCINST WHERE END_TIME_ IS NOT NULL); DELETE FROM ACT_HI_ACTINST WHERE PROC_INST_ID_ IN (SELECT ID_ FROM ACT_HI_PROCINST WHERE END_TIME_ IS NOT NULL); DELETE FROM ACT_HI_TASKINST WHERE PROC_INST_ID_ IN (SELECT ID_ FROM ACT_HI_PROCINST WHERE END_TIME_ IS NOT NULL); DELETE FROM ACT_HI_PROCINST WHERE END_TIME_ IS NOT NULL;生产环境不建议手写定时 SQL 直接删可以基于 Flowable 的 History Job 或者自研幂等任务按业务保留期限分批清理避免大事务锁表。5.5 验证一套流程是否真正跑通看三张表和一条 SQL集成完成后用一个真实的请假单走完全流程检查三个指标发起后 ACT_RU_TASK 里出现当前审批人的任务审批通过后 ACT_HI_PROCINST 的 END_TIME_ 不是 NULL业务表 ob_leave 的 status 变成 2。用一条 SQL 同时观察流程侧和业务侧SELECT p.ID_ AS 实例ID, p.BUSINESS_KEY_ AS 业务单号, p.START_TIME_ AS 发起时间, p.END_TIME_ AS 结束时间, t.NAME_ AS 当前节点 FROM ACT_HI_PROCINST p LEFT JOIN ACT_RU_TASK t ON t.PROC_INST_ID_ p.ID_ ORDER BY p.START_TIME_ DESC LIMIT 10;END_TIME_为 NULL 表示流程还在运行当前节点显示的是引擎此刻停在哪配合业务列表页的审批状态一起看就能区分是流程没走完还是状态没回写。这个查询是判断集成是否正常最直接的入口建议直接保存为开发期常用 SQL。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。