不做业务系统,做「能长出业务系统」的平台:RuoYi-Vue3 + Flowable 低代码实践
一套模型驱动的低代码平台:数据模型在线定义、页面拖拽生成、流程画出来、业务动作配置起来。
核心理念只有一条:业务配置 + AI 辅助 = 应用 ------AI 产出配置,平台负责可靠执行(AI 这一半还在路上)。
文中的「员工入职」只是用它配置出来的一个 demo------真正想讲的是:换任何一个业务域,同样能配出来。
一、先明确它是什么
这不是一个人力资源系统,也不是 OA。
它是一套四层抽象的配置平台:
| 层 | 你定义什么 | 平台产出什么 |
|---|---|---|
| 数据层 | 模型 + 字段 | 自动建表、增删改查 API、分页、多数据源 |
| 页面层 | 组件 + 布局(JSON) | 可运行的 PC 页面、表单、列表、查询 |
| 流程层 | BPMN 图 + 表单绑定 | 发起、审批、待办、已办、抄送、子流程 |
| 动作层 | 动作链(JSON) | 服务节点里的真实业务逻辑 |
这四层之间没有硬编码的业务语义:hr_employee 和 crm_customer 对平台来说是同一种东西,入职审批 和 合同会签 也是同一张图。
所以判断它是不是「低代码平台」而不是「一个 HR 系统」,看一个标准就够了:
把 HR 相关的模型、页面、流程全部删掉,平台还能不能从零配出另一套业务系统?
答案是能------这就是下面所有设计的目标。
1.1 核心理念:业务配置 + AI 辅助 = 应用
整套平台只押注一个公式:
业务配置 + AI 辅助 = 应用
业务配置承担「确定性的 80%」------模型、页面、流程、动作。它是结构化的、可校验的、可回滚的、可审计的,业务人员自己能改,改完立刻生效。
AI 辅助是「不确定性的 20%」的解法------把一句人话翻译成配置草稿:按需求生成模型字段、生成表单布局、生成动作链、补齐字段映射、解释报错。它加速的是「从想法到第一版配置」,而不是替代平台的执行模型。
现状说明(先把边界说清) :目前 AI 只被用在开发环节 ------让模型生成 Groovy 脚本,开发者校对后手工贴进服务任务执行。它既不进入平台执行链路,也不面向业务人员。
至于「AI 直接产出配置」,仍是方向,尚未实现。具体怎么接、为什么这样接,放在第十一节展开。
一句话:平台负责「可靠」,AI 负责「省事」。 把 AI 放进执行链路里得到的是不可控;把 AI 放在「生成配置」这一步,才有机会拿到可控的提效。
二、技术栈与模块边界
| 层 | 技术 |
|---|---|
| 前端 | Vue 3 + Vite + Element Plus + bpmn-js 18 |
| 后端 | Spring Boot + JDK 17 + MyBatis |
| 引擎 | Flowable(BPMN 2.0) |
| 存储 | MySQL(业务库可多数据源)+ Redis |
| 底座 | RuoYi-Vue3 / RuoYi(用户、部门、角色、字典、权限) |
ruoyi-lowcode 平台内核
├─ 数据模型(模型/字段/索引) 在线 DDL,模型即表
├─ 页面定义(JSON 快照) 页面设计器产物
├─ 动态数据服务 多数据源、动态 SQL、分页、单条查询
└─ 运行态接口 /lowcode/runtime/*
ruoyi-flowable 流程能力
├─ 定义 / 部署 / 发起 / 审批 / 待办
├─ 流程表单对接低代码页面
└─ 动作链执行(服务任务的业务动作)
前端
├─ lowcode/model 数据模型管理
├─ lowcode/page 页面设计器 + 运行态渲染
└─ flowable 流程设计器、发起页、待办页
页面、流程、动作全是 JSON;真正干活的只有「数据服务」和「动作链」

图1:平台外壳只有三个入口------建模型、画页面、配流程,业务全部在配置里长出来。
三、第一层:模型即表,字段即配置
字段定义存在 fields_json 里:
json
{
"fields": [
{ "fieldName": "biz_no", "fieldLabel": "业务编号", "fieldType": "varchar", "fieldLength": 50, "nullable": false },
{ "fieldName": "amount", "fieldLabel": "金额", "fieldType": "decimal", "fieldLength": 12, "fieldScale": 2 },
{ "fieldName": "status", "fieldLabel": "状态", "fieldType": "varchar", "fieldLength": 20, "defaultValue": "draft" }
]
}
保存后由 DDL 服务对比物理表差异,自动 CREATE TABLE / ALTER TABLE ADD COLUMN。
灵活性体现在三处:
- 零工单改表:业务加字段,配置页加一行就完事;
- 多数据源 :模型带
dataSource,hr_db、crm_db、oa各写各的库,底层动态路由; - 模型不含业务语义 :同一套机制既能建
hr_employee,也能建crm_customer、asset_device。
踩坑:
default数据源查不到配置时会直接抛异常。现在对default显式回退主库并缓存,避免「新建模型必须手配数据源」这种反直觉的约束。


图2:字段列表就是表结构------加一行、保存,DDL 自动比对并 ALTER TABLE。
四、第二层:页面只是 JSON,渲染器决定形态
4.1 设计器能力
- 基础组件:输入、数字、日期、组合框(选项来源支持字典 / 模型 / SQL)
- 业务组件:部门、用户、角色用户(都是可复用元组件,与业务无关)
- 布局组件:表格布局 (
rows/cols栅格 + 合并单元格)、子表 - 数据组件:列表查询(列配置、条件查询、分页、新增按钮联动页面)
- 动作组件:按钮(执行流程 / 关闭 / 跳转页面)
页面 JSON 片段(表格布局,本质是栅格容器):
json
{
"type": "table", "rows": 5, "cols": 4,
"children": [
{ "type": "title", "label": "金额:", "rowIdx": 2, "colIdx": 1 },
{ "type": "number", "field": "amount", "required": true, "rowIdx": 2, "colIdx": 2 }
],
"mergeInfo": [{ "row": 5, "col": 1, "rowspan": 1, "colspan": 4 }]
}
4.2 一套 JSON,四种渲染场景
| 场景 | 组件 | 特点 |
|---|---|---|
| 设计画布 | FormCanvas |
网格渲染 + 选中拖拽 |
| 运行页面 | DynamicPage |
真实数据、真实交互 |
| 流程发起页 | ProcessStart |
提交后写入流程变量 |
| 流程待办页 | ProcessTodo |
只读/可编辑切换、审批操作 |
为了保证「设计态 = 运行态」,四个入口共用同一套网格定位逻辑:
js
const isHiddenByMerge = (comp, rowIdx, colIdx) =>
(comp.mergeInfo || []).some(m =>
rowIdx > m.row && rowIdx < m.row + m.rowspan &&
colIdx > m.col && colIdx < m.col + m.colspan)
踩坑:待办页早期把表格布局的
children当成el-table的列来渲染,页面上出现「金额:/ 单行输入框」这种表头。后来统一成网格,一处修改四处受益------这正是「渲染器统一」的价值。
4.3 组件是元组件,不是业务组件
「部门选择」组件不关心你在做人事还是做合同,它只做一件事:给一个部门 ID,展示部门名称。
这就带来一个隐蔽但很关键的要求------回显必须只依赖 ID:
- 部门:批量
formLookup/deptNames - 用户:批量
formLookup/userNames - 字典:
getDicts+ 本地缓存
为什么不用现成的 /system/dept/list?因为那个接口带菜单权限和部门数据范围过滤,普通业务用户调用会拿到空列表,于是字段回显成裸 ID。平台组件不能依赖「管理员权限」才能正常工作。

图3:拖出来的是组件,存下来的是 JSON;同一个页面 JSON 在设计、预览、发起、待办四处渲染。
五、第三层:流程不绑定业务
5.1 表单与流程解耦
流程节点只存一个 pageCode,指向低代码页面。同一个页面可以被多个流程复用,同一个流程也可以在不同节点换不同页面。
流程变量与表单天然打通:表单字段写入 formData.xxx,同时进流程变量,后续节点可直接引用。
5.2 子流程即复用单元
会签、多级审批、跨部门流程,一律用 callActivity 拆成子流程,主流程保持骨架清晰。
这里有个必须注意的点:子流程是独立的 processInstanceId ,只通过 superProcessInstanceId 与父流程关联。所以「补审批人」这类操作必须递归:
java
private void assignAllUnassignedTasks(String processInstanceId, Map<String, Object> variables) {
assignTasksForProcessInstance(processInstanceId, variables);
List<ProcessInstance> children = runtimeService.createProcessInstanceQuery()
.superProcessInstanceId(processInstanceId)
.list();
for (ProcessInstance child : children) {
assignAllUnassignedTasks(child.getId(), variables);
}
}
不递归的后果很典型:日志安静地打印「总任务数量: 0」,审批人为 null,流程卡住。

图4:分支画在图上,条件填在连线上;排他网关必须配默认流兜底。
六、第四层:动作链------平台灵活性的核心
6.1 弯路的起点:让 AI 按业务描述直接生成 Groovy 脚本
在动作链之前,我最初的想法非常直接:
服务任务里不就是一个脚本文本框吗?业务人员把需求描述丢给大模型(DeepSeek),让它生成一段 Groovy 脚本,贴进去就能跑。平台只需要提供「脚本 + 变量」两个东西。
听起来是「AI 原生低代码」:不用设计动作模型,不用维护配置结构,业务怎么说,代码就怎么来。实际跑入职流程时,这个方案在五个地方同时塌了:
- 生成质量不稳定 。同一条业务描述生成两次,
formData/execution.getVariable的取值方式、变量命名都不一样;有时直接编造dataService.xxx()这种不存在的方法签名,部署后才发现。 - 上下文塞不下。要让模型写对,得把模型字段、可用服务方法、参数顺序、当前流程变量全塞进提示词。稍复杂一点就超长,模型开始「合理地」猜。
- 无法验证,只能靠跑。脚本没有静态检查,对错只有部署到流程里执行才知道;一旦出错是整条流程回滚,排查只剩堆栈日志。
- 不可审计。脚本是自由文本,没人能一眼看出它到底改了哪张表的哪个字段。合规检查、交接、复盘全部失效。
- 改一句话就要重新生成。业务改个字段名,就要重新生成、人工 diff、再部署,没有任何增量可言。
结论很清晰:AI 直接产出「自由文本程序」,适合开发者的一次性探索,不适合作为业务配置的长期形态。
于是思路反转:先把高频操作固化成结构化的动作 ,让「配置」这件事本身先成立;将来接 AI 时,它要产出的是配置 JSON 而不是脚本------JSON 能被 schema 校验、能定位到具体动作和字段、能被 diff、能在界面上可视化编辑;出错时是「第 3 个动作的 user_name 映射取不到值」,而不是「第 87 行脚本 NPE」。
一句话总结这个坑:不是 AI 不行,是让它输出不可校验的自由文本。 脚本入口我们一直保留(复杂逻辑、外部集成仍然可以写 Groovy,AI 辅助写脚本也依然好用),但它只是「开发者的加速器」,不是平台的配置模型。
目前仍在用的,就是这条路的「开发侧版本」:AI 生成脚本 → 人工通读、补 API、改字段名 → 贴进服务任务 → 跑流程验证。它确实比从零写快,但验证、审计、交接的成本一分没少,业务依然无法自己维护。这也是我判断它只能停在开发环节、不能升级为平台能力的原因。
6.2 为什么要动作链
服务任务里直接写 Groovy 脚本是可行的,但:
- 业务人员写不出来,技术人员也要反复查 API;
- 每个脚本独立演化,异常处理、日志口径都不统一;
- 改一行逻辑就要重新部署流程。
所以把数据服务的能力包装成可配置动作 ,配置以 JSON 存进 BPMN 扩展属性 flowable:actionConfig,与脚本共存(flowable:scriptTaskConfig),执行顺序「先动作、后脚本」,两者都空则报错。
6.3 六种动作,覆盖绝大多数服务节点
| 动作 | 作用 | 与业务无关的例子 |
|---|---|---|
insertRecord |
插入记录,可回写主键 | 建客户、建工单、建资产 |
updateRecord |
按主键更新 | 回写状态、写审批人 |
genSerial |
Redis 发号 | 合同号、工单号、资产编号 |
queryRecord |
查询单条存变量 | 反查申请单、反查客户 |
invokeService |
反射调用 Spring Bean | 建账号、调第三方、发消息 |
setVariable |
表达式计算写变量 | 金额汇总、编号兜底 |
参数来源统一五种:formField / variable / constant / expression / sysvar。
其中 formField 有个小设计:只填字段名,执行时先取 formData.xxx,取不到再回退顶层流程变量。业务人员不需要理解「表单上下文」和「流程变量」的区别。
一段真实配置(节选,注意没有一个字段名是 HR 专属的):
json
{
"actions": [
{
"type": "genSerial",
"dsCode": "biz_db",
"prefix": "CT",
"outputVar": "bizNo",
"skipCondition": "${not empty formData.biz_no}"
},
{
"type": "insertRecord",
"dsCode": "biz_db",
"modelCode": "crm_contract",
"outputVar": "contractId",
"mappings": [
{ "field": "contract_no", "source": "variable", "value": "bizNo" },
{ "field": "customer_id", "source": "formField", "value": "customer_id" },
{ "field": "amount", "source": "formField", "value": "amount" }
]
},
{
"type": "updateRecord",
"dsCode": "biz_db",
"modelCode": "crm_contract",
"idValue": "${contractId}",
"mappings": [
{ "field": "status", "source": "constant", "value": "approved" },
{ "field": "update_by", "source": "sysvar", "value": "currentUser" },
{ "field": "update_time", "source": "sysvar", "value": "now" }
]
}
]
}

图5:业务只填「字段 → 取值来源」,五种来源(表单字段 / 变量 / 常量 / 表达式 / 系统变量)覆盖绝大多数场景。
6.4 策略模式,让动作可插拔
动作类型会持续增加,所以执行器里没有 switch:
java
public interface LowcodeActionHandler {
String getType();
void execute(LowcodeActionContext ctx, Map<String, Object> action);
}
@Component
public class LowcodeActionExecutor {
private final Map<String, LowcodeActionHandler> handlers;
public LowcodeActionExecutor(List<LowcodeActionHandler> list) {
this.handlers = list.stream()
.collect(Collectors.toMap(LowcodeActionHandler::getType, h -> h));
}
public void execute(...) {
for (Map<String, Object> action : actions) {
LowcodeActionHandler handler = handlers.get(str(action.get("type")));
if (handler == null) {
throw new IllegalStateException("未知动作类型: " + action.get("type"));
}
handler.execute(ctx, action);
}
}
}
新增动作 = 新增一个 @Component,内核零改动。
6.5 分支不藏进 JSON
条件分支用 BPMN 排他网关 ,动作链只保留单条动作的 skipCondition 守卫:
json
{ "type": "insertRecord", "skipCondition": "${empty formData.customer_id}" }
理由很现实:分支画在图上,产品、测试、运维都能看懂;藏在动作链的 JSON 里,出问题时只有写它的人能排查。
6.6 动作能扩到哪:文件、HTTP、支付
支撑「平台而不是业务系统」这个判断的,不是动作数量多,而是加动作不用改内核。按同一套模式往下扩,最容易遇到的三类场景是文件、HTTP、支付。
文件处理(file)
审批流几乎绕不开附件:合同盖章件归档、发票附件打包、模板导出、多份材料合并成一个 PDF。
json
{
"type": "file",
"op": "copy",
"sourceVar": "formData.contractFile",
"targetDir": "/archive/${year}/${month}/",
"outputVar": "archivePath"
}
可变的部分全部收敛到配置:op(copy / move / zip / pdfMerge / exportTemplate)、来源(表单字段或流程变量里的文件 ID / URL)、落点(支持变量插值的目录模板)、输出变量。换一个业务,只是换参数。
HTTP 包装(http)
短信、企微/钉钉通知、ERP 对接、第三方查重,本质都是「发一个请求,把响应里的某个字段写进流程变量」。
json
{
"type": "http",
"method": "POST",
"url": "${config.erpHost}/api/employee",
"headers": { "Content-Type": "application/json" },
"body": {
"empNo": "${empNo}",
"name": "${formData.name}",
"deptId": "${formData.deptment_id}"
},
"timeoutMs": 5000,
"failStrategy": "failFast",
"resultMappings": [
{ "path": "$.data.id", "var": "erpEmployeeId" }
]
}
有三件事必须由平台统一处理,而不是交给每个配置者:
- 鉴权:token / 签名 / 证书放在数据源或系统配置里,不落进流程 JSON;
- 失败策略 :
failFast(中断并回滚)或ignore(记日志继续),否则总有人把失败吞掉; - 结果映射:用 JSONPath 把响应映射成流程变量,后续动作和网关条件可直接引用。
支付(pay)
付款申请、预付款、报销打款,本质是「用业务单号发起一笔支付,再等回调」。
json
{
"type": "pay",
"provider": "bank",
"outTradeNo": "${payNo}",
"amount": "${formData.amount}",
"payee": { "account": "${formData.payeeAccount}", "name": "${formData.payeeName}" },
"outputVar": "payResult"
}
它比前两个多了三条硬要求,也正是这类能力必须由平台提供、而不是每个流程各自拼的原因:
- 幂等 :
outTradeNo必须是发号器生成、库里有唯一约束的业务单号,重复执行也只扣一次; - 异步回调:回调要能定位到流程实例(业务表存单号 → 反查 processInstanceId → 触发信号或继续任务),不能同步等结果;
- 对账 :支付状态以回调/查询结果为准,流程里的
payResult只是快照。
扩展一个动作要写多少代码
以 HTTP 为例,全部工作就是实现接口 + 打上 @Component,执行器自动纳管:
java
@Component
public class HttpActionHandler implements LowcodeActionHandler {
@Override
public String getType() { return "http"; }
@Override
public void execute(LowcodeActionContext ctx, Map<String, Object> action) {
String url = ctx.resolveString(action.get("url")); // 支持 ${} 插值
Map<String, Object> body = ctx.resolveMap(action.get("body"));
HttpResult result = httpClient.post(url, body, timeoutOf(action));
if (!result.isOk() && "failFast".equals(action.get("failStrategy"))) {
throw new IllegalStateException("HTTP 动作失败: " + result.getMessage());
}
ctx.writeOutputVariables(action.get("resultMappings"), result.getJson());
}
}
内核、执行器、前端配置面板都不需要改------前端只是多一个动作类型选项和对应字段。
七、平台级基础设施:那些「不炫但决定能不能用」的细节
7.1 多数据源与动态 SQL
数据服务对外只暴露几个方法,内部支持指定数据源:
java
List<Map<String, Object>> queryListBySql(String dsCode, String sql, Map<String, Object> params);
Map<String, Object> selectOne(String dsCode, String sql);
Long insertReturnId(String dsCode, String modelCode, Map<String, Object> data);
模型、动作、页面都带 dsCode,业务库可以物理隔离,而平台代码不需要感知。
7.2 Redis 发号器
key = lowcode:seq:{prefix}:{yyyyMMdd}
- 先
increment,返回 1 说明是当天首号或 Redis 被清空,用数据库MAX(流水号)回填; - TTL 两天,自动回收;
- 兼顾「不打库」与「不重号」。
java
long seq = redis.increment(key);
if (seq == 1L) {
long base = queryDbMaxSeq(prefix, datePart);
seq = redis.increment(key, base);
}
7.3 配置不能丢
流程设计器的属性面板有个经典坑:改动只在「切换选中元素」时写回 businessObject,用户点一下空白再点回同一条连线,配置就被旧值覆盖了。
修复方案是双保险:防抖自动保存 + 内存缓存优先回读。配置丢失这类问题,用户不会认为是「操作顺序」的问题,只会认为平台不可靠。
八、Demo:用同一套能力配出一个 HR 入职流程
这段用来证明平台能用,而不是用来限定平台只能做这个。
模型 :hr_onboard(申请单)、hr_employee(员工档案)
页面 :employ_info(表格布局,8 个字段 + 提交按钮)
流程:开始 → 服务任务「保存申请单」→ 子流程「入职审批」→ 网关 → HR 复核 → 服务任务「入职收尾」
「入职收尾」的动作链(全程零脚本):
queryRecord:查申请单 →formDatagenSerial:发号EMPyyyyMMdd000001setVariable:empNo = 表单工号 ?: 生成工号insertRecord:写员工档案invokeService:调createUserAccount(...)建系统账号updateRecord:回写申请单(工号、员工ID、状态=completed、更新人、更新时间)
配置完这一套,改需求只需要改映射,不需要发版。
同样的六个动作,换一套模型和页面,就是另一套系统:
| 业务 | 模型 | 动作链 |
|---|---|---|
| 合同审批 | crm_contract / crm_customer |
发合同号 → 建合同 → 更新客户状态 → 通知 |
| 报销 | fin_expense / fin_voucher |
发单号 → 写报销单 → 汇总金额 → 生成凭证 → 回写状态 |
| 设备巡检 | asset_device / asset_inspect |
查设备 → 建巡检单 → 触发工单 → 更新设备状态 |
| 采购申请 | pur_request / pur_order |
发单号 → 建申请 → 审批通过转订单 → 回写 |
这些都不需要改平台代码------如果哪天某个场景需要改内核,那说明抽象还不够。

图6:一个字段没写前端代码、一个接口没写后端代码,跑起来就是完整的审批系统。
九、踩坑合集(都是真事)
JSON_ARRAY_APPEND报 Invalid JSON path :MySQL 对$.fields的处理不达预期,改为字符串拼接(LEFT+LOCATE(']', REVERSE(...))+RIGHT)。default数据源不存在:动态数据源查不到配置直接抛异常,改为回退主库并缓存。- 反射调用拿不到参数名 :
DefaultParameterNameDiscoverer依赖编译参数,pom.xml必须开<parameters>true</parameters>。 - Groovy 语法混进 EL 表达式 :动作里的
${a ?: b}被 Flowable 的 JUEL 解析,在:处抛TreeBuilderException,接口 500。执行前归一化:去掉variables.前缀,a ?: b改写为(a != null && a != '' ? a : b)。 - 映射填了不存在的字段 :报
Column 'xxx' cannot be null,排查成本极高。现在任何取不到值的映射都打 WARN,直接告诉你哪个field / source / value没取到。 - 子流程审批人为 null:只给主流程补人,忘了子流程是独立实例,改为递归。
- BPMN 扩展属性丢失:见 7.3,防抖自动保存 + 缓存回读。
- 部门/用户组件回显显示 ID:权限接口返回空导致翻译失败,改用只校验登录的批量查询接口。
- 模型字段漏配 :物理表有列、
fields_json里没有,动作配置下拉里就选不到,先补模型。 fieldsJson存成对象 :{"fields":[...]}直接喂给el-table,报data.includes is not a function,整个弹窗白屏。前后端都加了格式归一化。
十、设计取舍
AI 生成脚本 vs 配置动作:让大模型直接输出 Groovy 是最初的方案,也是最大的一个坑(见 6.1)。不是模型不行,而是「自由文本程序」不可校验、不可审计、不可增量修改。现在把 AI 的输出从代码改成结构化 JSON 配置,校验、diff、定位都有落点。
动作链 vs 纯脚本 :动作链覆盖 80% 的「查、改、发号、写变量」,剩下 20%(复杂计算、外部集成)交给脚本和 invokeService。共存,不二选一。
动作配置内嵌 BPMN,不单独建表:配置跟着流程定义版本走,回滚流程即回滚配置,也不需要维护「配置与流程版本不一致」这种状态。
动作类型不建表:考虑过「动作表 + 反射调用」的表驱动方案,结论是参数签名不统一、输出变量无法统一、没有编译期检查。最终选策略模式 + 可选的「调用服务」动作。
分支必须画在图上:排他网关是唯一分支入口。
组件保持业务无感:部门、用户、字典这类组件只认 ID,不认业务场景,才能被所有业务复用。
十一、现状与未来方向
已完成:数据模型(在线 DDL / 多数据源)、页面设计器、四种场景统一渲染、查询组件(字典/部门/用户翻译)、流程设计器、动作链六种动作、Redis 发号、服务任务 Groovy 脚本入口(AI 生成后人工校对贴入)。
推进中:
- 文件、HTTP、支付三类扩展动作(沿用同一套 Handler 模式,内核不改);
- AI 生成「动作链配置」:以模型字段和服务方法作为受约束的上下文,输出 JSON 而非脚本,生成后先做 schema 校验再落到流程(当前仅做到「AI 生成脚本 + 人工改」,尚未产出结构化配置);
- 动作链的事务边界与失败重试;
- 页面设计器的移动端适配;
- 流程版本迁移(老实例平滑切换到新定义);
- 行级数据权限(当前只到页面/接口级);
- 动作市场的雏形(把常用动作组合沉淀成可复用模板)。
11.1 未来方向:AI 的输出物是配置,不是代码
这是我认为最值得做、也最容易被做错的一块。
公式里「AI 辅助」这一半,落地方式只有一种:让 AI 产出配置,而不是代码。
业务描述 ──AI 辅助(规划中)──> 配置(模型/页面/流程/动作) ──平台执行(已实现)──> 应用
注意箭头的方向:AI 只出现在「业务描述 → 配置」这一段,不进执行链路。执行永远由平台按 JSON 走,模型、页面、流程、动作都是确定性的。
为什么必须把输出物定成配置:
- 生成物可验证 。配置有 schema,错了能指出「第 3 个动作的
user_name映射无效」,而不是给你一段看起来对、跑起来 500 的脚本(这个坑见 6.1)。 - 人能接得住。生成结果落到可视化编辑器里,业务/实施可以确认、微调、再保存,而不是面对一堆无法阅读的文本。
- 平台不被 AI 绑架。即使 AI 一直不接入,配置照样能手工配出来------AI 是加速器,不是依赖项。
具体形态会是:在设计器里加一个「用自然语言描述」的入口,把当前模型字段、可用服务方法、动作 schema 作为受约束上下文一起喂给模型;模型只允许输出符合 schema 的 JSON;生成结果先做校验,再以「草稿」形式回填到设计器,由人确认后保存。
难点不在调用模型,而在把上下文约束住:字段名必须来自模型、方法签名必须来自注册表、动作类型必须来自枚举。约束越硬,生成越可用;约束一松,就又回到了「AI 写脚本」那条老路。
先把配置能力做扎实,再谈接 AI。顺序反了,得到的是一个看起来很聪明、却没人敢上生产的系统。
十二、常见问题
12.1 业务逻辑遇到分支、循环怎么处理?
分支交给流程,不交给脚本。
判断条件写在排他网关的连线上,条件表达式引用流程变量或表单字段(设计器里有字段下拉,不用手敲),并且必须配一条默认流 兜底,否则条件全不满足时流程会卡住。动作层面只保留轻量守卫(skipCondition),用来处理「满足条件才执行这个动作」,不承担编排职责。
好处是分支在图上看得见:谁都能一眼看出「金额 > 5000 走总监审批」,而不是埋在某段 if/else 里。
循环分两种,用的不是同一个东西。
| 场景 | 做法 |
|---|---|
| 同一个任务重复多轮(会签、多人依次审批) | BPMN 多实例(multiInstanceLoopCharacteristics),集合来自流程变量 |
| 多条业务数据逐条处理(如批量生成账号) | 「查询记录」动作输出列表 → 多实例子流程,每次迭代处理一条 |
要点是:集合必须是变量 (查询动作的输出),循环体内只放动作和网关,不要把「遍历 + 处理」写成一段脚本------那样循环就重新变成黑盒了。
目前平台没有提供「动作内部循环」这种语法,也是刻意的:循环本质是编排,应该留在 BPMN 上。
12.2 业务逻辑如何复用?
按复用范围分三层,别混用:
| 复用范围 | 手段 | 说明 |
|---|---|---|
| 单流程内多个节点 | 动作链复制/模板 | 同一组动作(查、改、发通知)直接复用配置 |
| 跨流程 | 子流程(callActivity) | 变量显式映射进/出,父流程与子流程各自独立实例 |
| 跨系统/复杂逻辑 | invokeService + Spring Bean |
动作只负责传参,逻辑写在 Bean 里,可单测、可复用 |
经验判断:能写成纯函数的放 Bean,需要审批参与的放子流程,只是数据读写的放动作链。
另外,模型和页面本身也是可复用的资产:一个「部门 + 用户 + 字典」的表单可以挂在多个流程上,页面 JSON 由设计、预览、发起、待办四处共用(见第四节)。
12.3 业务逻辑变化怎么办?
优先改配置,不新增代码。
- 加字段:模型里加字段 → 在线 DDL 加列 → 页面拖一个组件 → 流程动作里补一条映射。全程不发版。
- 改判断:改网关连线上的条件表达式,或者调整默认流。
- 改写入逻辑:改动作的映射/常量/表达式;复杂变化才动脚本。
- 流程结构大改:保存即生成新版本,已运行的实例仍走旧版本,互不干扰;需要时再单独做实例迁移。
配置层面的原则是「只增不删」:加字段而不是改字段语义,加分支而不是改写原有分支。历史数据才不会因为一次「优化」而失真。
如果变化只是一次性过渡(比如临时对接一个外部系统),直接写 Groovy,别为了「配置化」硬造动作------动作是给长期存在、会被反复配置的逻辑用的。
12.4 动作不够怎么办?
三条路,按成本从低到高:
invokeService兜底:只要能拿到 Spring Bean,就能在动作里直接调用,不用写新动作。这是最快的扩展方式,适合「偶尔用一次」。- 写一个 Handler :实现
LowcodeActionHandler的getType()/execute(),加@Component,执行器构造时会自动注册,后端内核和前端框架都不用改;前端动作类型下拉里补一项即可。 - Groovy 脚本:完全兜底,适合一次性、探索性、或者逻辑过于特殊的场景。
判断标准很简单:
| 情况 | 选择 |
|---|---|
| 只用一次、以后不会再配 | Groovy 脚本 |
| 会跨多个流程复用、但不需要业务配置 | Bean + invokeService |
| 要暴露给业务人员在界面上配 | 写 Handler,做成标准动作 |
刻意没有做「动作配置表 + 反射调用」那一套:表驱动看起来扩展性好,但方法签名不统一、输出变量无法约束、还没有编译期检查,最后会退化成「在数据库里写代码」。
十三、开源地址
# Gitee 国内地址(推荐)
git clone https://gitee.com/longsebo/aibiz.git
# GitHub 同步镜像
git clone https://github.com/longsebo/aibiz.git
环境要求:JDK 17、MySQL 8、Redis、Maven 3.8+、Node 18+。后端启动 ruoyi-admin,前端在 RuoYi-Vue3-master 下 npm install && npm run dev。
欢迎提 Issue 交流,尤其是「换一个业务域能不能配出来」这类实际验证。
结语
低代码平台真正的难点不在拖拽,而在把变化收敛到配置里:表单变、流程变、业务规则变,都不该变成一次发版。
这套系统里我最满意的是动作链:它把服务任务从「一段只有作者看得懂的脚本」,变成了业务人员能看懂、能自己改的配置项,同时保留写代码的出口。
判断一个平台好不好,我只有一个标准:删掉现有业务,它还剩下什么。 剩下的是模型、页面、流程、动作这四层抽象------那才是平台本身。
而搭建顺序也回到了最初那个公式:业务配置 + AI 辅助 = 应用。
目前只完成了左半边------配置先站住,AI 还在路上。但顺序不能反:先有一个可校验、可回滚、人接得住的配置模型,AI 才有地方落脚。
身高lxb 体重2011 尺寸 0121
拼音数字合一起可找到我愉快