ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Flowable低代码引擎源码解析与Spring Boot集成实战

Flowable低代码引擎源码解析与Spring Boot集成实战 简介这是一份基于 Flowable 的低代码开源工作流引擎设计源码为 Java 开发者、架构师以及低代码平台研究人员提供一套可二次开发的工程参考。项目包含完整的前后端分离设计261 个 Java 源文件承担流程引擎集成与业务逻辑229 个 JavaScript 文件实现前端交互70 个 CSS 文件用于界面样式另有 59 个 SVG 图像、8 个 XML 配置和 8 个 SQL 数据库脚本可用于流程定义、审批节点、表单设计等场景。包体共 662 个文件压缩后仅 6.36MB目录结构清晰适合快速搭建流程中心原型或深入阅读源码目前已有 1103 人学习下载。通过这套源码读者可以拿到开箱即用的流程设计器前端模块、后端接口示例和数据库初始化脚本。同时从节点包装、审批步骤、错误提示等界面组件中可以理解低代码工作流的建模思路与工程拆分方式便于在此基础上扩展自己的业务模块。1. 这套 Flowable 低代码引擎源码先看文件清单再谈架构如果你以为 Flowable 就是几个 jar 包、写段 XML 跑起来完事那拿到这份源码的第一反应大概率是懵662 个文件里Java 只有 261 个JavaScript 却有 229 个CSS 还占了 70 个。一个“工作流引擎”为什么前端文件接近一半答案在这套源码的定位里——它不是裸的 Flowable 二次封装而是一个把 BPMN 建模、流程设计器、表单渲染、运行时管理全部收进来的低代码工作流平台。设计器里拖一个审批节点生成的是 JSON引擎最终要执行的却是 BPMN 的 XML中间这一层转换和兜底逻辑才是这套源码值得拆的地方。适合两类人一是要在若依这类脚手架里快速集成审批流的 Java 工程师二是已经在用 Flowable 但被流程定义文件管理、节点参数传递折腾过的后端开发。下面从存储设计开始往里拆。2. Flowable 表结构与存储设计ACT_ 三组表的低代码化改造2.1 按文件构成反推项目边界拿到一个源码包别急着跑。先把文件类型摊开看261 个 Java 文件对应 Controller、Service、Mapper 和流程定义构建逻辑229 个 JS 文件里绝大部分是流程设计器的节点拖拽、属性面板和表单渲染剩下的是管理后台的列表与统计70 个 CSS 里能直接看到addNode-82269853.css、step3-d5ef26d9.css、nodeWrap-8798d817.css这类命名说明设计器的增加节点、步骤三、节点包裹层都有独立样式模块。8 个 XML 大概率是 MyBatis Mapper 和 Spring 配置8 个 SQL 是引擎表和低代码扩展表的建表脚本。这套结构是一个典型重前端、轻 BPMN 生成层的低代码引擎复杂交互全放在设计器后端只做模型转换和工作流 API 透传。2.2 ACT_ 三组核心表哪些是低代码引擎自己该管的Flowable 的数据库表分为三类理解这个边界能解决flowable 都需要创建那些表的疑问。ACT_RE_*是流程定义与部署的静态资源表ACT_RU_*是运行时表流程结束即清理ACT_HI_*是历史表记录审批轨迹。低代码引擎一般不直接碰历史表和身份表身份走自己的用户体系历史通过查询接口返回。真正需要扩展的是流程定义和运行时节点配置。表名归属在低代码引擎中的作用ACT_RE_DEPLOYMENT静态资源一次部署的元数据designer 导出的 BPMN 通过它落库ACT_RE_PROCDEF静态资源部署后生成的流程定义是发起流程的唯一依据ACT_RU_EXECUTION运行时流程实例的当前执行树排查流程卡在哪个环节先查它ACT_RU_TASK运行时当前待办任务低代码审批列表直接映射这张表ACT_HI_PROCINST历史已结束流程的归档报表和管理台的我发起的都查它ACT_HI_TASKINST历史每个任务节点的办理记录含开始、结束时间和办理人低代码引擎自己扩展的表一般在ACT_前缀之外常见做法是加LC_FLOW_NODE节点配置、LC_FLOW_FORM表单定义、LC_FLOW_CATEGORY流程分类Node 表存设计器生成的节点 JSONForm 表存表单字段和节点 ID 的绑定关系。这样设计的好处是BPMN 文件里只写审批链路业务表单和节点参数作为扩展字段存在业务侧改表单不需要重新部署流程。2.3 数据库初始化的两条路线与参数权衡先看源码里的 8 个 SQL 文件里面会有flowable.mysql.sql这类官方建表脚本和低代码扩展表脚本。初始化时有两种做法-- 方法一让 Flowable 自建表开发环境 SET flowable.database-schema-updatetrue -- 引擎启动时自动检测并创建 ACT_ 前缀的所有表 -- 方法二手动执行官方 SQL 后关闭自动建表生产环境 -- 先执行 flowable.mysql.sql 创建 ACT_ 表 -- 再执行扩展表 SQL 创建 LC_FLOW_NODE / LC_FLOW_FORM SET flowable.database-schema-updatefalse我一般会在生产环境锁死database-schema-updatefalse因为一旦引擎升级自动建表会尝试对老表做 ALTER遇到大数据量表直接锁表非常被动。同时强烈建议给低代码扩展表单独建一个 schema 或用统一前缀后期做读写分离、归档历史数据都方便。排查问题时最常用的诊断 SQL 是查流程实例走到了哪个节点SELECT p.PROC_INST_ID_, d.NAME_ AS DEPLOYMENT_NAME, p.START_TIME_, r.ACT_ID_ AS CURRENT_NODE FROM ACT_RU_EXECUTION p LEFT JOIN ACT_RE_DEPLOYMENT d ON p.DEPLOY_ID_ d.ID_ LEFT JOIN ACT_RU_EXECUTION r ON r.PROC_INST_ID_ p.PROC_INST_ID_ AND r.PARENT_ID_ IS NULL WHERE p.PARENT_ID_ IS NULL ORDER BY p.START_TIME_ DESC LIMIT 10;这个查询把流程实例、部署名称和当前停留节点拼在一张结果集里PARENT_ID_ IS NULL取的是流程实例的根执行流避免把并行分支的子执行流混进来。如果CURRENT_NODE是exclusiveGateway说明流程卡在排他网关的出口判断上优先检查分支条件表达式的变量名是否和发起时传入的参数一致。3. 从设计器 JSON 到 BPMN 流程定义JS 节点模型的转换实现3.1 设计器保存的节点 JSON 数据结构低代码引擎的核心输出不是 BPMN 文件而是一棵节点树。打开源码里 JS 侧的设计器模块会发现每个节点被抽象成统一的 JSON 对象。比如一个请假审批流程通过拖拽生成的节点序列是开始 → 部门经理审批 → 人事复核 → 结束对应存储结构如下{ processKey: leave_approval, processName: 请假审批, nodes: [ { nodeId: start, nodeType: start, nodeName: 开始, next: [deptManagerTask] }, { nodeId: deptManagerTask, nodeType: userTask, nodeName: 部门经理审批, assigneeType: role, assigneeValue: manager, next: [hrReviewTask] }, { nodeId: hrReviewTask, nodeType: userTask, nodeName: 人事复核, assigneeType: user, assigneeValue: ${hrUserId}, next: [end] }, { nodeId: end, nodeType: end, nodeName: 结束, next: [] } ] }next数组是画布上连线关系的映射assigneeType和assigneeValue是低代码层对办理人的封装设计器右侧属性面板改的指定角色还是指定用户最终都固化在这里。前端拿到这份 JSON 后通过递归遍历节点树渲染出连线addNode-82269853.css这种样式文件控制的正是节点卡片和连线的视觉呈现。数据一定要落到流程表单里刷新之后才不丢配置。3.2 Java 端把 JSON 转成 BpmnModel 并生成 XML流程定义最终要落到 Flowable 的 BpmnModel。源码里 Java 端的转换逻辑不是一个一个拼 XML 字符串而是用 Flowable 提供的模型对象构建这样生成的 BPMN 文件语义最干净。我自己做这块时习惯写一个BpmnBuilder工具类核心逻辑如下import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.*; public BpmnModel buildBpmnModel(String processKey, ListNodeVO nodes) { Process process new Process(); process.setId(processKey); process.setName(processKey); // 遍历设计器传来的节点列表按类型创建对应 Flowable 元素 for (NodeVO node : nodes) { if (start.equals(node.getNodeType())) { StartEvent startEvent new StartEvent(); startEvent.setId(node.getNodeId()); startEvent.setName(node.getNodeName()); process.addFlowElement(startEvent); } else if (userTask.equals(node.getNodeType())) { UserTask userTask new UserTask(); userTask.setId(node.getNodeId()); userTask.setName(node.getNodeName()); userTask.setAssignee(node.getAssigneeValue()); process.addFlowElement(userTask); } else if (end.equals(node.getNodeType())) { EndEvent endEvent new EndEvent(); endEvent.setId(node.getNodeId()); endEvent.setName(node.getNodeName()); process.addFlowElement(endEvent); } } // 根据 next 连线关系生成 SequenceFlow for (NodeVO node : nodes) { String sourceId node.getNodeId(); for (String targetId : node.getNext()) { SequenceFlow flow new SequenceFlow(sourceId, targetId); flow.setName(flow_ sourceId _ targetId); process.addFlowElement(flow); } } BpmnModel model new BpmnModel(); model.addProcess(process); return model; }Process是 BPMN 的容器对象StartEvent、UserTask、EndEvent分别对应开始、审批、结束节点SequenceFlow的构造参数是sourceRef和targetRef对应我们 JSON 里的nodeId和next指向。注意setAssignee这里支持${hrUserId}这类表达式写法Flowable 在任务创建时会把流程变量里 key 为hrUserId的值解析成实际办理人这是低代码层和引擎层的重要衔接点。之后转成 XML 就交给官方转换器BpmnXMLConverter converter new BpmnXMLConverter(); byte[] bpmnBytes converter.convertToXML(bpmnModel); // 将 byte[] 以 .bpmn20.xml 后缀部署到 RepositoryServiceconvertToXML生产出的 BPMN 文件是标准格式可以直接用 Flowable 官方设计器打开二次编辑这在项目里是刚需。常见的坑是设计和后端只互传可视化节点丢了连线关系结果部署出来的流程是断头路点上发起后直接抛No outgoing sequence flow异常。所以保存动作完成前最好前后端各校验一遍节点可达性从 start 节点出发能否到达 end 节点。3.3 表单渲染与 CSS 模块在流程配置中的角色流程设计器除了画流程还要绑定每个节点要展示的表单字段。这部分功能对应源码里的step3-d5ef26d9.css流程定义的第三步配置节点选中后右侧面板加载表单字段列表通过拖拽或复选把字段绑定到当前任务节点。运行时待办页面根据节点拿到表单 ID再去渲染动态表单。这是低代码引擎相对传统 BPM 系统最具差异化的点——每个审批环节看到的字段组合可以完全不同而不用写死页面。后端存储上节点和表单的绑定关系就落在扩展表LC_FLOW_NODE里表里至少要有NODE_ID、FORM_ID、PROCESS_KEY三个字段一个节点可以绑定多个表单按SORT_NO排序展示。这套设计决定了一个流程模板从配置到上线不需要任何 Java 代码变更。4. flowable 整合 Spring Boot 实战部署、审批和数据库报错排解4.1 application.yml 里的参数先对齐任何 Flowable 项目启动前先花两分钟核对application.yml。低代码引擎里因为要同时建 ACT_ 表和 LC_ 扩展表配置比较容易出问题我一般会这样写spring: datasource: url: jdbc:mysql://127.0.0.1:3306/flowable_lowcode?useUnicodetruecharacterEncodingutf8nullCatalogMeansCurrenttrue username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver flowable: database-schema-update: true db-history-used: true history-level: audit async-executor-activate: falsehistory-level有 none、activity、audit、full 四档低代码平台至少要audit否则审批历史里看不到流程变量变化很多统计报表会失去数据源。async-executor-activate在低代码场景建议关掉因为流程里带业务回调时异步执行器会脱离业务线程事务边界变模糊运维排错也难除非你的引擎单独配置了消息队列。4.2 部署、发起、待办、审批的完整链路代码这一节把完整链路串起来所有方法都来自ProcessEngine的各个 Service不依赖额外中间件照着这个顺序写就能跑通一条请假流// 1. 部署流程定义将设计器生成的 bpmn 文件注入引擎 Autowired private RepositoryService repositoryService; Deployment deployment repositoryService.createDeployment() .addClasspathResource(processes/leave_approval.bpmn20.xml) .name(请假审批流程) .deploy(); System.out.println(Deployment ID: deployment.getId()); // 2. 发起流程通过流程定义 key 启动一个实例 Autowired private RuntimeService runtimeService; MapString, Object variables new HashMap(); variables.put(hrUserId, 10086L); variables.put(days, 3); variables.put(reason, 年假); ProcessInstance instance runtimeService .startProcessInstanceByKey(leave_approval, variables); // 3. 查询待办查部门经理拥有的可审批任务 Autowired private TaskService taskService; ListTask tasks taskService.createTaskQuery() .taskAssignee(10001) .processDefinitionKey(leave_approval) .orderByTaskCreateTime().desc() .list(); // 4. 审批通过完成任务并把审批意见写进流程变量 MapString, Object taskVars new HashMap(); taskVars.put(approved, true); taskVars.put(comment, 同意请人事复核); taskService.complete(task.getId(), taskVars);startProcessInstanceByKey用的是ACT_RE_PROCDEF.KEY_不是刚才 Deployment 的 ID这点容易搞混。taskAssignee的入参是办理人 ID对应的是节点定义时指定的办理人值。如果在第 3 步没有接表现层的用户体系低代码引擎一般会在 TaskListener 里根据节点的assigneeType去查业务用户表替换变量这样taskAssignee才能精确匹配。complete方法的第二个参数会合并进流程变量后续排他网关的分支判断比如approved true就是从这批变量里取值所以网关条件里的变量名必须和这里 put 的 key 完全一致。4.3 数据库报错三个高频位置与处理方式搜flowable 工作流数据库报错能翻出一堆帖子实际集中在三个点上。第一是 MySQL 8 以下驱动类名写错# 错误信息ClassNotFoundException com.mysql.jdbc.Driver # 解决方案driver-class-name 改成 com.mysql.cj.jdbc.Driver第二是连接串缺少nullCatalogMeansCurrenttrue后果非常隐蔽——Flowable 在初始化时扫描数据库表结构如果这个参数缺失MySQL 连接器默认按nullcatalog 扫描元数据可能把同库其他项目的表误判为需要管理的表某些极端情况下 DELETE 操作会扩到别的业务表。低代码引擎扩展了 LC_ 表最容易触发这个问题所以这个参数必须显式加上。第三是引擎版本升级后ACT_GE_PROPERTY表里的版本号不匹配-- 查看当前引擎版本 SELECT NAME_, VALUE_ FROM ACT_GE_PROPERTY WHERE NAME_ schema.version; -- 正确执行官方升级脚本前不要手动改 VALUE_很多时候项目启动失败不是代码问题而是换了个 Flowable 版本后旧库里的ACT_GE_PROPERTY.VALUE_还停留在旧版本引擎启动时做版本校验不通过直接抛异常。解决路径只有一个先备份再执行对应版本的flowable.upgrade.sql不要手工改表。5. 低代码 Flowable 的进阶边界监听器绑定、表前缀与若依集成5.1 用动态 ExecutionListener 解耦业务逻辑低代码平台不能让用户每次调整业务逻辑都改 Java 代码。我在处理节点前后置动作时会给 BPMN 节点统一挂一个动态 Listener通过流程变量指定要调用的 Spring Bean 名称做到流程配置层面的插件化public class DynamicBizListener implements ExecutionListener { Override public void notify(DelegateExecution execution) { String handlerName (String) execution.getVariable(_bizHandler); if (handlerName null) { return; } ApplicationContext context SpringUtil.getApplicationContext(); Object handler context.getBean(handlerName); // 反射调用统一接口 handle(DelegateExecution) ((BizHandler) handler).handle(execution); } }这个写法的价值在于节点本身固定挂DynamicBizListener实际执行的逻辑通过设计器属性面板填 Bean 名来切换比如发送企微通知、更新业务单据状态都做成独立的BizHandler实现类。事务边界就在handle方法上和流程引擎共用同一个事务要么全成要么全回滚。5.2 表前缀配置与若依集成的两个关键点多租户或复用数据库时给 Flowable 表加前缀能避免和现有系统表冲突。配置只在 ProcessEngine 创建时生效改配置需要重建引擎ProcessEngineConfiguration config ProcessEngineConfiguration .createProcessEngineConfigurationFromResourceDefault(); config.setDatabaseTablePrefix(LC_); ProcessEngine engine config.buildProcessEngine();设置后表名变为LC_ACT_RU_TASK这类形式建表脚本也会自动带上前缀。还有热门的若依 flowable 集成方案两个坑最值得注意一是若依自带多数据源路由Flowable 的 Service 必须和业务数据源走同一个事务管理器否则出现任务已流转但业务状态没更新的脏数据二是若依的拦截器会拦截动态表单请求要在shiroConfig里放行设计器相关的 URL否则前端能打开画布但保存节点时直接被拦截表现成数据写不进去。验证集成的唯一标准是在设计器里走到导出 BPMN并成功部署之后ACT_RE_PROCDEF表里能看到新增记录流程才算真正可用。本文还有配套的精品资源点击获取
返回列表