
简介面向需要在JEECG敏捷开发平台中引入Flowable工作流引擎的Java全栈开发者这份源码指南提供了从后端到前端的完整集成方案。后端部分围绕新建子工程、配置数据库连接、权限设置展开尤其是覆盖Flowable获取当前用户逻辑使其与JEECG用户体系正确对接避免流程发起人、审批人识别出现偏差前端部分则聚焦下载Flowable包、调整配置文件、引入Vue组件等关键步骤使工作流交互界面能嵌入现有项目。资源共15个文件压缩包约23KB以7个Java源码文件为主搭配yml、xml、vue、js、html和Markdown文档既有可运行代码也有说明文档便于对照后端逻辑与前端改动位置。已有704人学习下载。借助其中的代码示例与配置说明熟悉JEECG和Flowable基础用法的开发者可以快速完成工作流模块落地减少集成排查成本同时为后续扩展流程定义、权限模型和前端交互提供清晰起点。建议具备Java基础、熟悉Spring Boot与数据库基本操作并了解JEECG框架结构时学习效果更佳。 最近在项目里接到一个有点头疼的需求JEECG低代码平台上跑的业务需要在上线前把审批流补上。说白了就是采购单、请假单、合同会签这些流程不能再靠人肉在群里来去。调研了一圈最后选了 flowable 作为流程引擎和 JEECG 做了一次从引擎启动、用户体系、流程部署到表单绑定的完整集成。整个过程比预想中麻烦尤其是 JEECG 这种自带 MyBatis-Plus、在线表单、多租户机制的平台和 flowable 这种会自管理数据表的引擎碰在一起冲突点远比“加个依赖”复杂得多。很多问题网上没有现成答案只能翻源码一步步查。这篇就把我的集成过程、选型逻辑和源码级排坑经验完整整理出来给准备在 JEECG 中做工作流集成的同学一个参考。1. 为什么最后选了 flowable 而不是 activiti1.1 项目现状对这个选择的影响先说项目背景。我们这套系统是基于 JEECG Boot 3.x 扩展的底层是 Spring Boot 2.6.x MyBatis-Plus前端是 vue3 那套。JEECG 本身有配套的积木工作流模块但那是基于 activiti 5.x 深度魔改的社区里维护力度相对有限而且和官方版本脱节想在上面做定制开发光熟悉那套改动就得花不少时间。对比了一下 activiti 和 flowable 的实际差异如下表对比维度flowable 6.xactiviti 5.x / 7.x社区活跃度更新频繁Issue 响应快创始团队分叉后活跃度分化API 风格更规范异步任务模型完整老版本 API 沉淀多但较老兼容 Spring Boot 2.x6.6/6.7 稳定支持7.x 对 Boot3 支持更好5.x 太旧表单集成内置表单 外部表单都可依赖外部表单能力偏弱与低代码平台结合流程变量和调用逻辑清晰易封装老版本侵入性较强另外还有两点权重比较高第一流程引擎未来肯定要支撑高并发审批任务flowable 的异步执行器和 job executor 机制比 activiti 5.x 时代的设计要完善得多第二我们前端打算用 vue-pure-admin 风格重构管理系统页面流程审批界面需要完全脱离 JEECG 默认的 activiti 插件 UI自己定制前端flowable 的 REST API 和引擎 API 在这方面更干净封装和对接都更容易。1.2 不直接用 JEECG 自带工作流插件的原因JEECG 自带的 activiti 积木工作流其实能用但它的问题很现实引擎版本太老而且要求表单必须挂在 JEECG 的 online 表单体系上。如果业务流程里需要弹窗输入、会签加签、多实例任务这些偏灵活的操作老版本 activiti 的扩展点用起来不够顺手。最重要的是我们团队接下来要在这个项目上做二次开发源码可读性和可维护性必须优先考虑flowable 的代码结构和注释质量明显更符合这个需求。还有一点是 flowable 原生支持 Spring Boot Starter 自动装配引擎启动、建表、部署流程文件一站式完成集成文档相对齐整。对于“低代码平台 流程引擎”的组合来说我更愿意要一个生态干净的引擎而不是一个和平台耦合很深的模块。2. 版本选型与依赖冲突集成前必须搞定的三件事2.1 版本矩阵的确定集成前的第一个问题就是版本怎么选。JEECG Boot 3.x 自身的 Spring Boot 版本是 2.6.x所以 flowable 必须选 6.x 系列。我最终选的是 flowable-spring-boot-starter 6.7.2这个版本对 Spring Boot 2.6 的兼容性比较稳而且 Maven 依赖树相对干净。这里必须提醒一点不要直接上 flowable 7.x。flowable 7 在设计上更多面向 Spring Boot 3 和 JDK 17如果强行引入到 JEECG 的 Spring Boot 2.6 环境里会出现一堆自动配置类不兼容的问题排查起来非常痛苦。选版本的时候别只盯着新先确认自己的 Spring Boot 版本是 2.x 还是 3.x。2.2 依赖冲突的根源MyBatis 体系的碰撞这个坑是这次集成里最折腾的一个。flowable 引擎内部使用 MyBatis 作为 ORM 框架而 JEECG 使用的是 MyBatis-Plus。两个框架底层都依赖 mybatis 核心包版本不同就会冲突就算版本兼容也会出现 MyBatis-Plus 的拦截器把 flowable 的 SQL 一起拦截的问题。我这边遇到的具体表现是flowable 引擎启动正常但一执行 ACT_RU_TASK 表查询就报 SQL 语法错误后来定位到是 MyBatis-Plus 3.5.x 的分页插件拦截了 flowable 的 SQL给 flowable 的 SQL 强行加上了 LIMIT 导致语法错乱。解决思路有两个方向一是用 Maven 的 exclusion 把 flowable 依赖树里不需要的 mybatis 相关依赖排除掉二是给 flowable 配置单独的数据源和 MyBatis 环境让它和业务系统的 MyBatis-Plus 彻底隔离。我最终选择了第二种方案用多数据源的方式把 flowable 的事务和 SQL 环境独立出来虽然配置多一点但后续维护时边界清晰很多。2.3 我实际使用的依赖清单核心依赖配置如下dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version6.7.2/version /dependency dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter-process/artifactId version6.7.2/version /dependency同时需要在依赖中排除 flowable 自带的 mybatis 相关包避免影响业务侧已有的 MyBatis-Plusdependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version6.7.2/version exclusions exclusion groupIdorg.mybatis/groupId artifactIdmybatis/artifactId /exclusion /exclusions /dependency这个排除本身不是必须的但如果你后续要用 flowable 的批量查询或自定义 entity 查询排除掉可以减少重复的 SQL Session 冲突。需要注意的是如果排除了 mybatisflowable 自己的 SQL 映射仍然能工作因为 flowable 内置了完整的 mapper XML不依赖外部的 mybatis 包版本。3. 引擎初始化与自动建表的完整配置3.1 配置文件怎么写得既完整又保险flowable 集成到 JEECG 中yml 配置是第一步也是最容易遗漏细节的一步。我用的是多数据源方案业务数据源保持 JEECG 原来的配置不变单独加了一个 flowable 数据源spring: datasource: dynamic: datasource: master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/jeecg_boot username: root password: xxxxxx flowable: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/flowable_db username: root password: xxxxxx flowable: database-schema-update: true db-history-used: true async-executor-activate: false history-level: audit check-process-definitions: false几个关键参数说明一下database-schema-update: true首次启动自动建表。flowable 会检查数据库里有没有 ACT_ 开头的表没有就自动执行建表脚本。这个参数在生产环境建议改成false通过 Flyway 或手工执行 SQL 来管理表结构。async-executor-activate: false关闭异步执行器。Flowable 默认会启动一个用于处理定时器、异步任务的 Job Executor。在没有配置独立线程池和 Quartz 模块的情况下这个异步执行器在低代码平台里容易造成多余的后台线程占用建议先关掉等流程复杂到需要定时器的时候再打开。check-process-definitions: false关闭启动时自动扫描部署流程文件。flowable 默认会去 classpath 下的processes/目录自动部署 BPMN 文件。在 JEECG 这种已经有自身资源管理逻辑的场景里自动部署容易造成重复部署和版本失控所以我选择手动部署后面会讲部署代码。3.2 如何验证引擎是否成功初始化配置完之后启动项目如果 flowable 成功初始化日志里会出现类似下面的输出Flowable 6.7.2 starting Flowable 6.7.2 engine created另外观察数据库如果出现ACT_RU_、ACT_HI_、ACT_GE_等前缀的表说明建表成功。注意ACT_ID_开头的表在 flowable 6.x 中默认也会建立它们对应身份管理模块的默认实现这一步对后面的用户体系集成有影响。这里的坑之一是如果数据库账号没有 DDL 建表权限启动会因为建表失败直接报错。所以授权时一定要确认账号有 CREATE、ALTER、INDEX 权限否则即使配置了database-schema-update: true也起不来。3.3 引擎对象的获取与二次配置引擎初始化后Spring 容器里会有ProcessEngine、RuntimeService、TaskService、HistoryService这些 Bean。如果你需要自定义引擎配置比如自定义 IdentityService 的实现类、全局事件监听器、命令拦截器可以通过实现ProcessEngineConfigurationConfigurer来追加配置Component public class FlowableEngineConfigurer implements ProcessEngineConfigurationConfigurer { Override public void configure(SpringProcessEngineConfiguration processEngineConfiguration) { processEngineConfiguration.setIdmEnabled(false); processEngineConfiguration.setEnableEagerExecutionTreeFetching(false); processEngineConfiguration.setJdbcMaxActiveConnections(20); } }setIdmEnabled(false)这个值得单独说。flowable 6.x 里有个独立的 IDM 身份管理模块默认会启用并且会创建ACT_ID_*表用于管理用户和组。但既然要和 JEECG 用户体系打通这个自带的 IDM 模块基本就用不上关掉可以少建一堆无用的表。这里我当时没关后续自定义用户管理器时IDM 模块和自定义实现之间出现了一些行为干扰排查了一阵子。4. 和 JEECG 用户体系打通的实现方案4.1 为什么不能直接用 flowable 自带用户表flowable 默认使用ACT_ID_USER和ACT_ID_GROUP表来保存用户和组。但 JEECG 的业务用户存在sys_user表角色存在sys_role表如果让业务系统维护两套用户数据同步就是灾难。比如用户改个部门审批流程里显示的还是旧部门用户离职了流程历史参与人还显示正常。正确做法是让 flowable 的用户查询逻辑走 JEECG 的用户体系而不是用 flowable 的默认表。这样流程引擎只负责流程实例和任务流转用户、部门、角色等主数据全部以 JEECG 为准。4.2 自定义身份管理器的实现思路flowable 提供了一套用户和组的管理接口核心是UserManager、GroupManager、UserEntityManager这些接口。默认实现基于ACT_ID_*表。要接入 JEECG 用户体系需要自定义实现这些接口并把自定义实现注册到 ProcessEngine 配置里。我之前采用的是替换UserManager和GroupManager的实现findUserById(String userId)从sys_user查询用户封装成 flowable 的UserEntity。findGroupById(String groupId)从sys_role查询角色封装成 flowable 的GroupEntity。findGroupsByUser(String userId)查询用户的所有角色用于流程引擎的组任务判定。示例代码大致如下Component public class JeecgUserManager extends UserManagerImpl { Autowired private ISysUserService sysUserService; Override public UserEntity findUserById(String userId) { SysUser sysUser sysUserService.getById(userId); if (sysUser null) { return null; } UserEntityImpl userEntity new UserEntityImpl(); userEntity.setId(sysUser.getId()); userEntity.setDisplayName(sysUser.getRealname()); userEntity.setEmail(sysUser.getEmail()); return userEntity; } }4.3 注册自定义管理器的关键代码光实现类还不够必须让 ProcessEngine 的配置指向这个实现Component public class FlowableIdentityConfigurer implements ProcessEngineConfigurationConfigurer { Autowired private JeecgUserManager jeecgUserManager; Autowired private JeecgGroupManager jeecgGroupManager; Override public void configure(SpringProcessEngineConfiguration configuration) { configuration.setUserManager(jeecgUserManager); configuration.setGroupManager(jeecgGroupManager); } }这里有一个容易踩的坑如果setIdmEnabled(false)不设置flowable 的 IDM 模块可能会覆盖自定义的 UserManager 配置导致你的实现没有生效。所以我最终把setIdmEnabled(false)和自定义 UserManager 两个操作放在同一个 Configurer 里顺序才稳定。4.4 任务候选人解析的实际体验用户体系打通后taskService.createTaskQuery().taskCandidateUser(userId)就能正常工作流程引擎在查询候选人的时候会走 JEECG 用户表来判断。这意味着候选人设置可以基于 JEECG 的角色编码或用户 ID审批任务列表和待办数量直接通过 flowable 的 TaskQuery 查询不再需要维护额外的冗余表。但要注意一点如果你在 BPMN 设计器里把候选人设置成了角色编码流程引擎在运行时会调用GroupManager去解析角色对应的用户。这个时候sys_role表和sys_user_role关联表的数据质量就非常重要如果存在历史脏数据或者用户被软删除了引擎在查询候选人时可能会返回空集合导致任务没有处理人。这个问题我们上线前清理过一次用户数据才解决。5. 流程定义部署与 online 表单绑定的落地5.1 流程文件放哪里、怎么部署flowable 默认会扫描 classpath 下的processes/目录并自动部署。但 JEECG 的模块通常是 jar 包方式引入业务方会持续修改 BPMN因此我建议把流程文件放到数据库或外部存储启动时手动部署。我的做法是在启动后执行一个部署命令扫描自定义目录中的 BPMN 文件Component public class FlowableDeployRunner implements CommandLineRunner { Autowired private RepositoryService repositoryService; Override public void run(String... args) { ListString bpmnFiles scanBpmnFiles(); for (String filePath : bpmnFiles) { Deployment deployment repositoryService.createDeployment() .addClasspathResource(filePath) .name(filePath) .deploy(); log.info(deployed flowable process: {}, deploymentId{}, filePath, deployment.getId()); } } }如果直接使用系统自带的部署工具界面也可以调用类似接口上传 BPMN。关键是要注意重复部署问题每次部署都会生成新的流程定义版本如果每次重启都部署一次版本号会不断累加。建议先根据processDefinitionKey查一下当前版本如果最新版本已经存在就跳过部署。5.2 把 JEECG online 表单和流程节点绑定起来这是低代码平台和工作流结合的核心点。流程的发起、审批、驳回每一环节都需要展示业务数据而这些业务数据在 JEECG 中就是 online 表单。我的绑定方案不是让 flowable 内置表单而是用流程变量的方式存表单标识和业务主键。具体做法在启动流程实例时写入表单变量MapString, Object variables new HashMap(); variables.put(formCode, purchase_order); variables.put(dataId, purchaseOrderId); variables.put(initiatorId, currentUserId); runtimeService.startProcessInstanceByKey(purchaseOrderProcess, businessKey, variables);审批人查看任务详情时从前端传入任务 ID后端通过TaskService找到对应的流程实例读取流程变量中的formCode和dataId然后调用 JEECG online 表单服务渲染数据。这样流程引擎和业务表单就实现了解耦流程只管流转状态业务表单数据始终以 JEECG 的 online 表单为准。5.3 发起审批的接口设计发起审批接口我封装成了统一入口前端不需要关心流程细节PostMapping(/process/start) public ResultString startProcess(RequestBody StartProcessVO vo) { String processKey vo.getProcessKey(); String businessKey vo.getBusinessKey(); MapString, Object vars vo.getVariables(); ProcessInstance processInstance runtimeService.startProcessInstanceByKey(processKey, businessKey, vars); return Result.ok(processInstance.getId()); }这里的businessKey建议直接用 JEECG 业务表的主键后面做流程实例和业务数据关联查询时非常方便。如果后续要做待办列表、已办列表、关联业务数据预览这个businessKey就是链接流程和业务的钥匙。5.4 一个需要提前设计的点驳回与会签如果业务只要求简单的“提交 - 审批 - 通过”上面的方案够用了。但真实场景里很少有流程这么简单驳回、会签、加签基本是标配。flowable 的驳回通常有两种做法在 BPMN 里显式画驳回连线设计时从审批节点画一条到发起节点的连线条件是“驳回”。这种方式直观但每条流程都要画维护成本高。通过命令动态跳转使用runtimeService.createChangeActivityStateBuilder().moveActivityIdTo(...)实现动态跳转。我建议新项目先走方案 2 做统一驳回因为 BPMN 文件不用为了驳回逻辑反复改版。等到流程数量多了、驳回规则变得特别复杂时再逐步沉淀为设计器里的标准模板。6. 源码级排查集成过程中遇到的四个坑6.1 坑一MyBatis-Plus 拦截器把 flowable 的 SQL 也拦了前面提到过分页插件干扰的问题这次具体说下排查思路。当时启动成功后调用taskService.createTaskQuery().list()就报 SQL 语法异常日志打印出来的 SQL 被追加了LIMIT明显不是我预期中 flowable 原生的 SQL。定位过程在TaskServiceImpl的findTasksByQueryCriteria方法上打断点发现走到 MyBatis 执行器时会话是 MyBatis-Plus 包装过的会话。查看 MyBatis-Plus 的InterceptorChain确认分页插件被全局注册了。将 flowable 数据源的SqlSessionFactory独立出来不经过 MyBatis-Plus 的拦截器链路。解决办法就是前面说的多数据源方案flowable 用自己的数据源和 SqlSessionFactory这就从根本解决了两个 ORM 环境的冲突。6.2 坑二jackson 序列化问题导致历史数据读取失败这个坑出现在 flowable 的HistoricActivityInstance查询上。flowable 在保存流程实例变量时会使用 jackson 进行 JSON 序列化。JEECG 自身也引入了 jackson但版本和 flowable 期望的 jackson 版本不完全一致导致我自定义的业务对象在存储为流程变量时序列化成功但反序列化失败报出InvalidDefinitionException或JsonMappingException。排查后发现是我的 POJO 里加了 Java 8 时间类型LocalDateTime而 flowable 默认使用老版本 jackson 的ObjectMapper无法处理 Java 8 时间类型。解决办法有两种给 flowable 的 ObjectMapper 注册 JavaTimeModule。流程变量尽量放基本数据类型和字符串不要放复杂业务对象。我最终选择了第二种业务数据只放formCode和dataId审批详情通过接口查询从根上规避了序列化兼容问题。6.3 坑三多数据源下的事务边界混乱flowable 引擎操作和业务操作如果在同一个事务里需要特别注意。比如发起审批时既要向业务表插入一条申请单数据又要启动一个流程实例必须保证两个操作要么都成功要么都失败。如果 flowable 走独立数据源Spring 事务默认只管理主数据源的事务flowable 的操作可能会脱离事务控制。解决方式是在业务服务方法上使用分布式事务或链式事务。轻量方案是先把业务数据落库再启动流程实例如果启动流程实例失败就人工发起一个补偿操作把业务数据标记为失效。这种方案对于多数企业审批系统来说足够稳定且不需要引入额外的分布式事务组件。如果后续有强一致需求再引入 Seata 之类的事务框架。6.4 坑四flowable 自动建表时数据库字符集问题MySQL 默认字符集如果是 latin1flowable 建表时会直接报错因为 ACT_GE_BYTEARRAY 表用于存流程定义二进制内容需要支持 UTF-8。这个问题在低代码平台环境中尤其容易出现因为运维在安装数据库时很少会主动把字符集改成 utf8mb4。解决方案很直接在初始化 flowable 数据库时执行CREATE DATABASE flowable_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;如果你在启动阶段才发现字符集问题可以先把库删了重建再重新启动项目。这里有个技巧flowable 建表脚本是幂等的但已经建错的表不会自动重建所以遇到字符集问题最省事的办法就是删除 flowable 库后重新初始化反正里面没有业务数据。6.5 排查中沉淀的一个通用方法论这几个坑排查下来我的感受是集成框架类组件时优先保证它的运行环境是独立的再考虑怎么和业务系统融合。不管是数据源、SqlSessionFactory还是事务管理器、ObjectMapper只要涉及和环境相关的组件能隔离就先隔离。很多冲突之所以难查是因为两个框架共享了同一个底层资源问题表现千奇百怪。把边界划清之后剩下的问题基本都能通过报错信息直接定位到源码位置。如果你也在做 JEECG 和 flowable 的集成建议按这个顺序落地先搞定多数据源和引擎启动再做用户体系替换然后跑通一个最简单的 BPMN 流程最后再设计驳回、会签和表单绑定。前两步打通之后后面就是搭积木的工作了。我在这次集成里最大的体会是与其等遇到问题再去搜现成的补丁不如从一开始就把运行边界划清楚后面的流程会顺畅得多。本文还有配套的精品资源点击获取