ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

橙单低代码生成器:多租户工作流场景下省掉哪些重复劳动

橙单低代码生成器:多租户工作流场景下省掉哪些重复劳动

简介:橙单中台化低代码生成器是一套面向企业级Web应用开发的后台管理框架,适合需要快速搭建多应用、多租户、多渠道系统的中高级开发者与团队使用。它完整支持Flowable与Activiti工作流、在线表单、自定义数据同步与Job、多表关联及跨服务多表关联,并允许框架技术栈自由组合,同时提供两种高颜值流程编辑器,工单编号升级为高可靠性自动编码规则,支持动态多数据源与独立部署的common-flow-online、common-report服务。资源包共4772个文件,约32.01MB,以2199个java源码、533个vue组件、573个css与397个js前端资源为主,另含xml、yml、sql、ftl模板及dockerfile等配置与部署文件,结构完整。目前已有476人学习下载。借助该资源,读者可获得一套可直接运行的中台化低代码底座,用于研究多租户架构、工作流引擎集成、在线表单与报表打印编辑器的实现思路,并参考其数据同步与自动编码规则处理方式,快速落地自己的后台管理项目。

1. 橙单中台化低代码生成器:多租户工作流场景下,它到底能省掉哪些重复劳动

后台管理框架做多了会发现一个规律:真正吃掉工期的从来不是业务逻辑本身,而是那些每个项目都要重来一遍的“基建活”——多租户怎么隔离、工作流引擎怎么接、在线表单怎么动态渲染、打印模板怎么配、数据同步怎么做。橙单中台化低代码生成器就是冲着这批重复劳动来的。它把多应用、多租户、多渠道、工作流(Flowable 与 Activiti 双引擎)、在线表单、自定义数据同步、自定义 Job、多表关联、跨服务多表关联这些能力打包成一套可生成的底座,技术栈允许自由组合,不是锁死在某一个前端框架或某一种后端写法上。

适合谁用?如果你手上正在做或即将做一套带审批流、带多租户隔离、带动态表单的后台系统,又不想从零搭权限模型和流程引擎的胶水层,这个生成器值得拆开看。它内置了钉钉风格的流程编辑器,而且同时维护两种高颜值流程编辑器,流程工单编号升级成了自动编码规则计算,打印接口的处理机制也做过修复。下面按“它是什么 → 怎么跑起来 → 坑在哪 → 怎么用得更深”的顺序,把我拆这套东西的过程写清楚。

2. 拆开工程结构:多租户、工作流与在线表单是怎么串起来的

2.1 从 pom.xml 环境变量到多数据源,先看清骨架

拿到一个生成器,我习惯先看它的构建入口和配置分层,因为低代码生成器的“可配置程度”基本决定了它能覆盖多少场景。橙单的工程 pom.xml 支持环境变量注入,这一点在多环境部署时很关键——同一份构建产物,靠环境变量切换数据库连接、租户模式、流程引擎类型,不用为每个环境单独改配置文件再重新打包。

常见做法是在 pom 里用${env.XXX}占位,配合 CI 的变量注入。这样做的直接好处是:开发、测试、生产三套环境的差异被收敛到变量层,代码仓库里不出现任何环境相关的硬编码。多数据源的支持也是同一套思路——动态多数据源意味着你可以在运行时根据租户或业务线切换数据源,而不是在启动时就固定死。

<!-- pom.xml 中通过环境变量控制构建参数,避免环境硬编码 --> <properties> <!-- 数据库类型由环境变量注入,默认 mysql --> <db.type>${env.DB_TYPE}</db.type> <!-- 流程引擎选择:flowable 或 activiti --> <flow.engine>${env.FLOW_ENGINE}</flow.engine> </properties>

参数说明:DB_TYPE控制数据源驱动与方言,FLOW_ENGINE决定打包时引入哪套流程引擎依赖。逻辑上,这两个变量把“技术栈自由组合”落到了构建层——你不需要改代码,只需要在部署时给不同的值。

2.2 多租户隔离的三种粒度与选型理由

多租户不是一句“支持”就完事的,落地时要选隔离粒度。橙单这套框架里,租户隔离通常落在三个层面:数据行级(同一张表用 tenant_id 区分)、schema 级(每个租户独立 schema)、数据源级(每个租户独立库)。选哪种取决于租户数量和合规要求。

租户少、数据量小,行级隔离最省事,一张表加个 tenant_id 字段,所有查询自动拼条件。租户多、或者有数据物理隔离要求,就上 schema 级或数据源级。橙单的动态多数据源能力正好支撑后两种。我一般会先问清楚:租户数量级是多少、有没有“数据必须物理分开”的硬要求。如果答案是“几十个以内、没有硬要求”,行级隔离配合框架的自动拼接就够了,别过度设计。

这里有个容易翻车的点:行级隔离下,所有自定义 SQL 都必须带上租户条件,框架的自动拼接只覆盖它自己生成的查询。手写的关联查询一旦漏了 tenant_id,就是跨租户数据泄露。血泪经验是,在代码审查清单里把“自定义 SQL 是否带租户条件”列成强制项。

2.3 工作流引擎接入:Flowable 与 Activiti 的取舍

橙单同时支持 Flowable 和 Activiti,这不是为了堆功能,而是因为存量项目里两套引擎都有大量在用。新项目我倾向 Flowable,它的异步执行器和历史数据模型更成熟;但如果团队已有 Activiti 的流程定义和运维经验,硬切 Flowable 的迁移成本不低,这时候保留 Activiti 更务实。

接入流程大致是:定义流程模型 → 部署到引擎 → 业务表单与流程节点绑定 → 工单编号走自动编码规则 → 审批流转。橙单把流程工单编号升级成了自动编码规则计算,这个改动看着小,实际很关键。早期很多项目用时间戳或数据库自增做工单号,并发一高就重复或者断号。自动编码规则计算把编号生成收敛成一套可配置的规则,支持前缀、日期段、流水号位数,可靠性比手写高得多。

// 流程部署的典型调用,引擎类型由配置决定 // flowEngine 由环境变量 FLOW_ENGINE 注入,运行时多态 ProcessEngine engine = flowEngine.getProcessEngine(); repositoryService.createDeployment() .addClasspathResource("processes/leave-approval.bpmn20.xml") .name("leave-approval") .deploy();

逻辑说明:部署动作本身两套引擎 API 接近,差异在后续的运行时服务和历史服务。参数上,addClasspathResource指向 BPMN 文件路径,name是部署名,重复部署同名流程会生成新版本而不是覆盖,这点在迭代流程时要留意——旧版本实例还在跑,新版本只对新实例生效。

2.4 在线表单与流程编辑器的绑定关系

在线表单是低代码生成器的门面。橙单的在线表单和工作流是打通的:表单字段可以映射到流程变量,流程节点可以控制表单字段的读写权限。钉钉风格的流程编辑器让非技术人员也能拖拽出审批链,两种流程编辑器并存意味着你可以按项目调性选。

绑定关系上,我一般这样组织:表单定义存一份 JSON schema,流程节点通过字段 key 引用 schema 里的字段。这样表单改版不影响流程定义,流程调整也不动表单结构。跨服务多表关联的场景下,表单字段可能来自不同服务的接口,这时候要在表单的数据源配置里声明远程调用,而不是在表单渲染时临时拼。

提示:在线表单的字段 key 一旦被流程引用,就不要随意改名。改名等于断链,流程变量取不到值,表现为审批页面字段空白,排查起来很费时间。

3. 跑通一套最小可用流程:从建租户到工单打印

3.1 建租户、配数据源、初始化菜单

最小可用流程的第一步是把租户和数据源配好。橙单的动态多数据源支持运行时不重启切换,所以建租户时可以顺带指定它用哪个数据源。步骤上:新建租户 → 绑定数据源 → 初始化该租户的菜单和权限 → 建第一个用户。

菜单初始化这块,生成器一般会提供模板。我习惯先只初始化最小菜单集,把流程跑通再逐步加菜单,避免一上来菜单太多、权限配错还找不到问题在哪。数据源绑定后要验证连通性,别等到跑流程才发现连不上。

-- 租户与数据源绑定关系,示意表结构 INSERT INTO sys_tenant (tenant_id, tenant_name, datasource_key, status) VALUES ('t001', '演示租户', 'ds_main', 1); -- datasource_key 对应动态数据源配置里的 key,运行时据此路由

参数说明:datasource_key是路由的关键,必须和动态数据源配置里的 key 完全一致,大小写敏感。status为 1 表示启用。逻辑上,后续所有该租户的请求都会带上 tenant_id,框架据此选数据源并拼租户条件。

3.2 用流程编辑器画一条审批链

流程编辑器里画审批链,核心是把“谁在什么条件下审批”表达清楚。橙单的钉钉风格编辑器支持条件分支、会签、或签。画的时候注意:条件表达式的变量名要和表单字段 key 对齐,否则条件永远为假,流程卡在第一个节点。

我一般先画一条最简的“提交 → 一级审批 → 结束”,跑通后再加条件分支和会签。这样出问题时范围小,容易定位。流程画完要部署,部署后建议用测试账号走一遍完整流转,确认每个节点的表单权限和变量传递都对。

3.3 工单编号走自动编码规则

工单编号从手写升级到自动编码规则计算,是这套生成器里我比较认可的一处改动。配置一条编码规则:前缀 + 日期段 + 流水号。流水号的并发安全由框架保证,不用自己写锁。

// 自动编码规则配置示意 CodeRule rule = new CodeRule(); rule.setPrefix("WF"); rule.setDatePattern("yyyyMMdd"); rule.setSeqLength(4); // 流水号位数 rule.setResetCycle("DAILY"); // 每日重置 String orderNo = codeRuleService.generate(rule); // 输出形如 WF202501010001

逻辑说明:seqLength决定流水号补零位数,resetCycle决定流水号何时归零。参数上,如果业务量一天超过 9999 单,seqLength要相应加大,否则会溢出。自动编码的可靠性来自框架内部的原子操作,比在业务代码里select max + 1安全得多。

3.4 打印接口的处理机制与前端配合

打印接口这块,橙单做过处理机制的修复,前端也有对应改动。打印的本质是把业务数据填充到模板,再转成可打印格式。修复后的机制更强调前后端职责分离:后端负责数据组装和模板渲染,前端负责预览和触发打印。

我踩过的坑是:后端模板改了字段名,前端预览还在用旧字段,结果打印出来是空白。所以打印模板的字段变更要前后端同步,最好把模板字段清单纳入版本管理。打印接口的入参一般是业务单据 ID 加模板 ID,返回渲染后的内容或文件流。

注意:打印接口的权限要单独校验,不能只靠前端隐藏按钮。否则绕过前端直接调接口就能打印未授权的单据。

4. 避坑与排查:多租户、流程引擎、打印接口的高频翻车点

4.1 现象:跨租户能查到别人的数据

原因:自定义 SQL 没带 tenant_id 条件,框架的自动拼接只覆盖它生成的查询,手写 SQL 是盲区。

解决:把所有手写 SQL 纳入审查清单,强制带租户条件;能不用手写就不用,优先用框架的查询构造器。上线前用两个租户的账号交叉验证,确认查不到对方数据。

4.2 现象:流程部署后新流程不生效

原因:同名流程重复部署会生成新版本,但运行中的旧实例仍走旧版本;或者流程定义缓存没刷新。

解决:确认新实例是否走新版本;必要时清理流程定义缓存。迭代流程时,建议用新流程 key 而不是覆盖同名流程,避免新旧实例混在一起。

4.3 现象:工单编号重复或断号

原因:早期用手写编号逻辑,并发下select max + 1出现竞态;或者编码规则的流水号位数不够,溢出后回绕。

解决:统一走自动编码规则计算,别在业务代码里自己生成编号;根据业务峰值调大seqLength,并确认resetCycle符合业务预期。

4.4 现象:打印出来字段空白

原因:模板字段名和业务数据字段名不一致,或者前端预览用的字段和后端渲染用的字段不同步。

解决:把模板字段清单纳入版本管理,前后端字段变更同步;打印前先用一条真实数据验证渲染结果,别只看预览。

4.5 现象:动态多数据源切换后连不上

原因:datasource_key大小写不一致,或者数据源配置在运行时没刷新。

解决:核对 key 的拼写和大小写;确认动态数据源支持运行时刷新,必要时触发一次配置重载。新建租户后先做连通性验证,再往下走。

5. 进阶用法:把 common-flow-online 和 common-report 拆成独立服务

橙单支持生成common-flow-online和common-report位于独立的服务,这是从“单体生成器”往“服务化”走的一步。拆出去的好处是:流程在线编辑和报表打印这两块相对独立的能力,可以单独扩容、单独部署,不和主业务服务抢资源。代价是服务间调用和事务边界要重新设计。

拆分的判断标准我一般看两点:这两块能力的负载是否和主业务差异大;团队是否有独立维护它们的精力。如果流程编辑和报表打印的访问量远低于主业务,拆出去反而增加运维成本,那就先不拆。如果它们本身就是高频入口,拆分值得做。

拆分后,跨服务多表关联的场景会更常见。这时候要注意:跨服务查询没有本地事务保证,要么用最终一致性,要么把关联数据冗余到本地。我倾向冗余关键字段到本地,查询走本地,异步同步冗余数据,避免每次查询都跨服务调用。

验证拆分是否成功,我习惯看三个指标:服务间调用的平均耗时、跨服务查询的失败率、以及拆分后主服务的资源占用是否下降。如果拆分后调用耗时明显上升、失败率没降,说明拆早了。

从那以后我每次做服务拆分,都强制先跑一遍负载对比和调用链验证,确认收益大于成本再动手。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进