【AIBIZ低代码平台:能长出业务系统的平台】

不做业务系统,做「能长出业务系统」的平台:RuoYi-Vue3 + Flowable 低代码实践

一套模型驱动的低代码平台:数据模型在线定义、页面拖拽生成、流程画出来、业务动作配置起来。

核心理念只有一条:业务配置 + AI 辅助 = 应用 ------AI 产出配置,平台负责可靠执行(AI 这一半还在路上)。

文中的「员工入职」只是用它配置出来的一个 demo------真正想讲的是:换任何一个业务域,同样能配出来。

源码:Gitee(推荐)|GitHub


一、先明确它是什么

这不是一个人力资源系统,也不是 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。

灵活性体现在三处:

  1. 零工单改表:业务加字段,配置页加一行就完事;
  2. 多数据源 :模型带 dataSource,hr_db、crm_db、oa 各写各的库,底层动态路由;
  3. 模型不含业务语义 :同一套机制既能建 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 原生低代码」:不用设计动作模型,不用维护配置结构,业务怎么说,代码就怎么来。实际跑入职流程时,这个方案在五个地方同时塌了:

  1. 生成质量不稳定 。同一条业务描述生成两次,formData / execution.getVariable 的取值方式、变量命名都不一样;有时直接编造 dataService.xxx() 这种不存在的方法签名,部署后才发现。
  2. 上下文塞不下。要让模型写对,得把模型字段、可用服务方法、参数顺序、当前流程变量全塞进提示词。稍复杂一点就超长,模型开始「合理地」猜。
  3. 无法验证,只能靠跑。脚本没有静态检查,对错只有部署到流程里执行才知道;一旦出错是整条流程回滚,排查只剩堆栈日志。
  4. 不可审计。脚本是自由文本,没人能一眼看出它到底改了哪张表的哪个字段。合规检查、交接、复盘全部失效。
  5. 改一句话就要重新生成。业务改个字段名,就要重新生成、人工 diff、再部署,没有任何增量可言。

结论很清晰:AI 直接产出「自由文本程序」,适合开发者的一次性探索,不适合作为业务配置的长期形态。

于是思路反转:先把高频操作固化成结构化的动作 ,让「配置」这件事本身先成立;将来接 AI 时,它要产出的是配置 JSON 而不是脚本------JSON 能被 schema 校验、能定位到具体动作和字段、能被 diff、能在界面上可视化编辑;出错时是「第 3 个动作的 user_name 映射取不到值」,而不是「第 87 行脚本 NPE」。

一句话总结这个坑:不是 AI 不行,是让它输出不可校验的自由文本。 脚本入口我们一直保留(复杂逻辑、外部集成仍然可以写 Groovy,AI 辅助写脚本也依然好用),但它只是「开发者的加速器」,不是平台的配置模型。

目前仍在用的,就是这条路的「开发侧版本」:AI 生成脚本 → 人工通读、补 API、改字段名 → 贴进服务任务 → 跑流程验证。它确实比从零写快,但验证、审计、交接的成本一分没少,业务依然无法自己维护。这也是我判断它只能停在开发环节、不能升级为平台能力的原因。

6.2 为什么要动作链

服务任务里直接写 Groovy 脚本是可行的,但:

  1. 业务人员写不出来,技术人员也要反复查 API;
  2. 每个脚本独立演化,异常处理、日志口径都不统一;
  3. 改一行逻辑就要重新部署流程。

所以把数据服务的能力包装成可配置动作 ,配置以 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"
}

它比前两个多了三条硬要求,也正是这类能力必须由平台提供、而不是每个流程各自拼的原因:

  1. 幂等 :outTradeNo 必须是发号器生成、库里有唯一约束的业务单号,重复执行也只扣一次;
  2. 异步回调:回调要能定位到流程实例(业务表存单号 → 反查 processInstanceId → 触发信号或继续任务),不能同步等结果;
  3. 对账 :支付状态以回调/查询结果为准,流程里的 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 复核 → 服务任务「入职收尾」

「入职收尾」的动作链(全程零脚本):

  1. queryRecord:查申请单 → formData
  2. genSerial:发号 EMPyyyyMMdd000001
  3. setVariable:empNo = 表单工号 ?: 生成工号
  4. insertRecord:写员工档案
  5. invokeService:调 createUserAccount(...) 建系统账号
  6. updateRecord:回写申请单(工号、员工ID、状态=completed、更新人、更新时间)

配置完这一套,改需求只需要改映射,不需要发版。

同样的六个动作,换一套模型和页面,就是另一套系统:

业务 模型 动作链
合同审批 crm_contract / crm_customer 发合同号 → 建合同 → 更新客户状态 → 通知
报销 fin_expense / fin_voucher 发单号 → 写报销单 → 汇总金额 → 生成凭证 → 回写状态
设备巡检 asset_device / asset_inspect 查设备 → 建巡检单 → 触发工单 → 更新设备状态
采购申请 pur_request / pur_order 发单号 → 建申请 → 审批通过转订单 → 回写

这些都不需要改平台代码------如果哪天某个场景需要改内核,那说明抽象还不够。

图6:一个字段没写前端代码、一个接口没写后端代码,跑起来就是完整的审批系统。


九、踩坑合集(都是真事)

  1. JSON_ARRAY_APPEND 报 Invalid JSON path :MySQL 对 $.fields 的处理不达预期,改为字符串拼接(LEFT + LOCATE(']', REVERSE(...)) + RIGHT)。
  2. default 数据源不存在:动态数据源查不到配置直接抛异常,改为回退主库并缓存。
  3. 反射调用拿不到参数名 :DefaultParameterNameDiscoverer 依赖编译参数,pom.xml 必须开 <parameters>true</parameters>。
  4. Groovy 语法混进 EL 表达式 :动作里的 ${a ?: b} 被 Flowable 的 JUEL 解析,在 : 处抛 TreeBuilderException,接口 500。执行前归一化:去掉 variables. 前缀,a ?: b 改写为 (a != null && a != '' ? a : b)。
  5. 映射填了不存在的字段 :报 Column 'xxx' cannot be null,排查成本极高。现在任何取不到值的映射都打 WARN,直接告诉你哪个 field / source / value 没取到。
  6. 子流程审批人为 null:只给主流程补人,忘了子流程是独立实例,改为递归。
  7. BPMN 扩展属性丢失:见 7.3,防抖自动保存 + 缓存回读。
  8. 部门/用户组件回显显示 ID:权限接口返回空导致翻译失败,改用只校验登录的批量查询接口。
  9. 模型字段漏配 :物理表有列、fields_json 里没有,动作配置下拉里就选不到,先补模型。
  10. 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 走,模型、页面、流程、动作都是确定性的。

为什么必须把输出物定成配置:

  1. 生成物可验证 。配置有 schema,错了能指出「第 3 个动作的 user_name 映射无效」,而不是给你一段看起来对、跑起来 500 的脚本(这个坑见 6.1)。
  2. 人能接得住。生成结果落到可视化编辑器里,业务/实施可以确认、微调、再保存,而不是面对一堆无法阅读的文本。
  3. 平台不被 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 动作不够怎么办?

三条路,按成本从低到高:

  1. invokeService 兜底:只要能拿到 Spring Bean,就能在动作里直接调用,不用写新动作。这是最快的扩展方式,适合「偶尔用一次」。
  2. 写一个 Handler :实现 LowcodeActionHandler 的 getType() / execute(),加 @Component,执行器构造时会自动注册,后端内核和前端框架都不用改;前端动作类型下拉里补一项即可。
  3. 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

拼音数字合一起可找到我愉快

相关推荐
AI_Gump8 小时前
Harness篇|DeepSeek Harness 上手机:五条安卓移植路线与 App 内 Linux 拆解
android·linux·人工智能·智能手机·deepseek
Eyfcom14 小时前
〖物流低代码实战 01〗物流页面可视化搭建的底层架构:协议、引擎与产物的三重奏
低代码·架构·快递系统·物料体系·物流低代码实战
疯华整猫14 小时前
否决 Flutter 提案那天,我发现自己也说不清 Framework 管什么
android
传奇开心果编程1 天前
【Flutter踩坑排雷】第25课:ScrollController 误用坑|跳转失效、监听泄漏与“控制器未附加”
android·flutter·ios·移动开发·跨平台·dart·声明式ui
慕容雪_1 天前
Capacitor8-Android打包完整手册
android·打包·capacitor
阿杰m1 天前
Android源码第十三章:Bluetooth——沿一次发现走到控制器
android
booboosui1 天前
派活给另一个 AI:前后两次文件夹快照,就是交付清单
android
普通人62 天前
MySQL_FIND_IN_SET函数详解
android·数据库·mysql
好的佩奇2 天前
Dart 之集合元素
android·flutter·dart