低代码平台精髓:附加功能及其设计思路揭秘

1,839 阅读12分钟

本文主要介绍在低代码平台中,实现低代码组件、组件管理、前端编排、功能权限的相关方案,以及后端如何实现低代码的思路。

前端编排(可视化编程)

低代码平台解决了快速搭建项目、编写组件等问题,但是它没解决低成本实现组件之间逻辑,也就是说低代码平台没法做到零代码,不管怎么样,都需要写一部分高代码,为了方便不会写代码的用户(比如:产品),一些产品使用拖拉拽的方式生成代码,比如这样的:

image.png

它们的思路都一样,在页面上通过拖拉拽把一些程序节点(比如:条件、循环等)、功能(比如:请求数据、跳转页面等)放置到画布上,然后连线形成树状结构,编译成代码,在低代码平台中调用。展示部分很好实现,我重点说说编译和调试。

编译

在这个场景下的编译没那么复杂,只需要把页面上的节点、节点配置按顺序拼接成代码就行了,比如:现在有一个条件节点,它的配置是 this.state.role === 'admin',我们在编译这个节点的时候返回 if(this.state.role === 'admin'){} 就行了。条件节点可以是这样:

// 展示
class ConditionNode  extends Node{
    render(){
        return 组件展示形式
    }
}
// 编译
const conditionCompiler = (options)=>{
    if(conditons.length === 1){
        return `if(${options[0].condition}){${option.body}}`
    }
    return options.map(option=>`if(${option.condition}){${option.body}}`)
}

因为我们的产品是商用产品,没有开源,我找到了一个类似的开源产品:jsgp,这个产品是用于生成 js 代码的,在低代码中没必要做这么细,只需要参考它的编译功能就行了。

调试

代码生成了,没办法保证它没有问题,出了问题,如何调试定位问题?

lowcode engine 为例,它的设计态所见即所得,但是数据不能是真实的,因为很多时候需要用户交互才能得到数据,设计态的很多交互需要禁用,所以设计态调试没有意义,只能放在预览态做。我们通过在 url 中加 query 参数,新增一个调试态,可以在调试态看到编排生成的所有逻辑,用户可以选中某个逻辑调试。

调试怎么做?一个同事提出了用 yeild 和 generator,这个方案被我给否决了,原因是节点类型不同 generator 中 yeild 部分代码不好写,另外数据可能是批量的,不一定要一步步调试,我给了一个简单方案:基于日志,就像微服务架构链路追踪那样,我们可以记录每条数据经过了哪些节点,节点中配置是什么,节点处理结果是什么等等,用户可以选中某一条数据的日志查看

如果非要调试某一条数据,怎么办?生成的代码不方便调试,但是节点可以,我们把一个个节点包裹成一个个函数,用户选中某条数据点击一次单步调试,就执行一个函数,展示节点的输入输出。如果需要作用域引用的变量值怎么办?使用 ts 编译,获取作用域、符号表等等,根据我们的经验做到前面的日志,很多场景都能满足了,这一步不一定要做。

功能权限

先说功能权限,应用是通过低代码搭建的,我们往往希望能够通过简单的配置完成,并且大规模不修改已有组件代码。在权限管理系统中,权限由“权限名称”和“权限标识”组成,比如有一个用户管理页面,它包括新增用户、删除用户、查询用户三个功能权限,新增、删除一般通过按钮实现,查询一般通过导航进入,针对这两种权限我们给出如下思路:

按钮权限

我们可以在按钮的配置中加入权限配置,它是一个开关,开启时用户可以填写权限名称:

image.png

填写“权限名称”用户多了一步操作,很多情况下“权限名称”就是按钮内容,开放“权限名称”只是方便用户在权限系统中修改,在传给权限系统时,可以按照:权限名称>按钮内容>按钮ID 的顺序获取。

页面权限

页面的查询功能一般在导航中拦截,我们可以在进入某个路由前判断是否有当前页面的权限,如果没有返回“您没有当前页面权限,请联系管理员!”,另外还可以根据用户权限渲染左侧的菜单。比如一个页面有“用户管理”、“订单管理”等页面,我们可以在传递给权限系统时,把页面导航也传过去,这样就实现了页面级别权限注册。用户进入应用,获取当前用户权限,根据权限渲染左侧菜单,用户强制进入,在路由中拦截。

步骤

  1. 用户在页面拖入按钮,开启权限,js 在产物中保存权限标识(组件 id)、权限名称(权限名称>按钮内容>按钮ID);

image.png

2.用户修改权限名称,js 修改 permissionMap 内容,用户删除关闭权限、删除按钮,js 删除对应的权限信息,用户保存当前页面,js 读取权限信息,保存到后端;

3.给每个低代码搭建的应用配置一个“导航菜单”,菜单名称就是权限名称,菜单ID就是权限ID,修改菜单时更新权限信息;

4.发布应用时,把页面权限、菜单权限组合起来,提交给权限系统;

5.在发布态,根据权限渲染菜单、按钮,跳转时拦截页面;

低代码组件

低代码组件的设计、使用可以分为以下步骤:

image.png

我们先看一下低代码平台的一些逻辑:

  • 低代码平台通过拖拉拽、配置,会得到一份平台的产物(例如 lowcode enginejson),平台把这份产物渲染成页面;
  • 用户编写的高代码组件,可以通过一些方法,注册到低代码平台中(例如lowcode engineawait material.setAssets(await injectAssets(assets)););

低代码组件也是一种组件,我们要尽可能地复用平台已有的能力,我们可以这样想:

  • 首先是渲染部分,低代码平台能够渲染页面,我们只需要保证拖拉拽生成的组件和页面结构是一样的,就能够利用渲染能力;
  • 再看注册部分,注册只需要做到:在左侧列表渲染出图标、名称,建立映射,保证拖拉拽时能找到对应的组件,点击组件能够根据组件找到右侧面板。它和高代码组件还不一样,平台需要把这部分能力开放出来,让用户能够把产物注册成组件;
  • 拖拽设计可以复用页面设计器;
  • 属性暴露,暴露属性就是建立映射。这部分要做得好用一点,暴露的属性包括 页面state子组件属性,这部分最好做成可选(选择 页面state,某个子组件、某个属性,设置中文名称),代码中做一个包裹层,能够把“暴露的属性映射到真实组件上”、“完成state 默认值初始化”就行了;
  • 产物管理:参考 【组件管理】 模块;

组件管理

开发人员开发了一个组件库,出于一些需求,可能需要对组件列表进行管理,比如:

  • 有一个新开发的组件库,我们希望不改代码,直接通过界面注册进来;

  • 组件 A 新老版本不兼容,我们做了两个组件,希望拖拽时使用新组件,同时没修改的老项目不报错,这时候需要在组件列表中隐藏老组件;

  • 组件 B,在某个业务中用得非常多,我们希望把它放在“常用组件”分组中;

  • “分组 C” 只有一两个组件能够在业务中用到,但是这个分组中组件非常多,不好找,我们希望把用到的组件提取出来,放到其它分组;

这个时候,需要开发一个组件管理功能,完成这些功能就行了:

  • 高代码开发的组件库中的分组、每个分组下的组件,是不允许删除的,但是允许隐藏;
  • 允许调整分组顺序;
  • 允许新建分组,新建分组中的组件是可以新增、删除的,分组也能删除;

技术点:

  • 分组顺序可以通过拖拽修改;
  • 往新建分组中新增组件可以通过穿梭框、拖拽等实现;
  • 组件分组模块,根据分组信息渲染出组件列表;

接口中心

低代码平台需要对接第三方接口,这部分接口文档往往不在当前系统中,因此需要一个接口中心把第三方接口同步到当前系统,方便用户选择接口、绑定数据,它需要支持 swagger 文档,同时支持第三方推送接口信息和主动从第三方拉取接口信息两个功能。

跨域如何解决

低代码平台往往会调用第三方接口,这部分接口有可能在不同域名下,由此造成跨域的问题,我们希望有一个跨域方案,普通开发人员完全感知不到,具体做法如下:

  • 定义一个全局的 http 请求对象,要求平台所有 http 请求都使用它完成;

  • 发起请求时,这个对象偷偷地把请求 url 替换成 /http-proxy,把真实 url 保存到这个请求的 headers 中;

  • 本地环境,基于 viteconfigureServer 创建一个 http 服务,把请求转发到真实地址;

  • 生产环境,基于 nginx 代理 /http-proxy

后端低码设计器

低代码在前端有很多方案了,后端还是不好做,我们开发了模型设计器、流程设计器、逻辑设计器,能够覆盖到大部分场景,但是功能超出了,还是缺少兜底逻辑,这部分可能要用 DSL 实现,做法是:传入一个 DSL,生成接口,具体参考 “低代码平台精髓:低代码平台的反思”

模型设计器

很多情况下,低代码平台需要搭建业务管理系统,一般需要单表、多表的增加、删除、修改、查询、导入导出,因此需要一个系统,能够根据输入的模型生成特定接口,类似于 headless CMS,但是最好别像 strapi 那样生成代码。输入的模型包括

  • 模型标识(数据库表名)、模型名称(表中文描述);
  • 字段标识、字段名称,字段类型、校验信息等;

其中字段类型可以自己抽象一层,多加一些信息,比如:字符串,当长度不输入时默认是 255,转换为 varchar,当长度大于 4000 时,转换为 text,还有校验信息、是否必填,这部分信息最终可以用于自动生成表单。

输入模型信息后,自动生成 CRUD 接口,excel 导入导出也是常见功能,可以这样设计:

导入:

  • 增加一个公共接口,接受 excel、excel 描述信息(比如:{A:"name"} 指把 A列转换为 name 字段)、批量新增的接口信息;
{
    file:File,
    meta:{
        startLine: 2,
        bathSize: 500,
        template:{A:"name",B:"age"}
    },
    api:{
        apiId:"batchCreateUser",
        url:"/user",
        method:"POST",
        body:{
            data:[
             {
                filedName:"name",
                type:"string",
                regExp:"[a-zA-Z]",
                minLength: 8,
                maxLength: 16
              },
               {
                filedName:"age",
                type:"number",
                min: 0,
                max: 150
              }
            ]
        }
    }
}
  • 这个接口根据 meta 从第二行开始解析 excel,把 A 列映射成 name,把 B 列映射成 age,映射过程中,使用 apibody 部分强行转换类型;
  • 解析完整的 excel,得到一个数组,然后调用 batchCreateUser ,将数据填写到 body.data 中(有可能需要组装其它参数,总之就是按照接口,发起一个个 http 请求),完成批量更新;
  • 有的接口可能会有批量提交数据大小限制,可以使用基于 metabathSize 解析提交;

导出:

导出和导入类似:

  • 需要传入批量查询接口、查询条件、分页参数、每次查询最大条数等:
{
    meta:{
        template:{
            "name": "A",
            "age": "B"
        },
        pageSize:["query","size"],
        pageNumber:["query","current"],
        responsePath:["body","list"],
        maxBatchSize: 5000,
    },
    api:{
        apiId:"listUsers",
        url:"/users",
        method:"GET",
    }
}

  • 根据这些信息,设置分页参数的当前页为 1(例子中在 url 后面拼接 size=1),查询条数为查询最大条数(例子中在 url 后面拼接 current=5000),调用查询接口;
  • 得到返回值,然后根据 responsePath 解析返回值,得到一个数组;
  • 再根据 meta 中的 template 将数据追加到 excel 中;
  • 将分页参数的当前页为 2,继续上述步骤,直到查询不到数据;

流程设计器

很多产品需要工作流引擎,因此还需要根据 flowable 或者 activiti 定制一个流程设计器。

逻辑设计器

通过建设模型设计器、流程设计器,以及能覆盖到后端的一部分场景了,但是目前还没有兜底逻辑,如果通过前面两个设计器都无法完成,该怎么办?我们开发了逻辑设计器,和前端编排差不多,不过编排的是后端逻辑,最终生成后端代码。它包括如下功能:

  • 有常见语法节点,比如:条件、循环等;
  • 有常用功能的封装,比如:发起请求、后端鉴权等;
  • 支持编写脚本语言(比如 jspython) ,在后端完成对数据的处理;