低代码表单引擎刷新机制

一、背景

在低代码产品的研发过程中,表单引擎扮演着至关重要的角色。它涉及前后端的数据解析和触发刷新能力,以满足用户在选择单选框等操作时带出其他数据计算的需求。然而,这些计算往往无法通过前端简单计算得出,需要后端执行脚本或数据库查询来实现。因此,设计高效的表单引擎刷新机制显得尤为重要。

具体业务场景参考如下:

分数子表:

学生的成绩页面有多个子表,分别用于填写各项成绩:

  • 单元测试分数:例如 85、90、78。
  • 作业分数:例如 88、92。
  • 期末考试分数:例如 95。

总分和均分:

页面展示该学生的 总分均分,会根据分数子表的变动实时刷新。

逻辑:

  • 当教师在子表中新增或修改一个分数时,系统会触发刷新逻辑。
  • 总分 为所有分数的累加结果。
  • 均分 为所有分数的平均值(保留两位小数)。
  • 系统需要保证前后端通信高效,避免不必要的全量刷新。

二、刷新模式选型

在选择前后端交互模式时,我们考虑了多种常见的刷新机制,并进行了对比分析。

1.定时刷新模式:前端设定一个频率来刷新页面。然而,在低代码表单中,这个频率通常较高,导致服务器资源消耗过大。


2.用户触发模式:在页面中设定一个按钮,让用户通过点击按钮来刷新页面。这种模式对服务端较为友好,因为用户刷新的频率不会非常频繁。但缺点是用户交互和数据响应不够友好。


3.刷新点触发模式:服务端主动解析会触发刷新的点,以及低代码维护者设定的刷新点(触发其他字段刷新机制的字段的信息后续简称刷新点)。当用户每次改动相关的触发点时,前端发起请求获取刷新结果,并解析到页面中。这种模式对用户较为友好,每次刷新的结果都能及时反应到页面中,同时服务端的消耗也较为可控。


在我们的常见场景中,刷新的及时性和交互友好度更为重要,最后选择了刷新点触发模式作为我们的刷新机制。

|----------|---------|-------|---------|
| | 服务器资源消耗 | 刷新及时性 | 用户交互友好度 |
| 定时刷新模式 | 高 | 低 | 中 |
| 用户触发刷新模式 | 低 | 中 | 低 |
| 刷新点触发模式 | 中 | 高 | 中 |

三、解析刷新点 算法

在确定了交互选型之后,服务端需要根据交互逻辑来逐步实现功能点。首先,服务端需要解析所有会触发刷新逻辑的字段。我们采用的方案是,通过解析DSL(领域特定语言)中所有引用了当前字段的其他字段,并写入当前字段的标识来实现。例如,如果A字段的可见条件是B字段为1时,我们会给B字段加上刷新A字段的标识。当前端解析到B字段需要刷新其他字段时,会将B字段作为刷新点发送请求到后端刷新当前页面。

四、服务端刷新点算法

为了高效处理刷新请求,服务端提供了批量处理刷新点的接口。算法采用递归遍历刷新点,并写入刷新点的图结构来实现刷新点的计算以及回归。递归采用广度优先的逻辑,以更加贴近用户的交互顺序逻辑。每计算完刷新点的结果,系统会解析结果,并将结果也解析成新的解析点进入图结构中,后继续进行广度优先的遍历。同时,系统对无效或者重复解析的数据进行剪枝,以提高计算的有效性。

五、伪代码

复制代码
import java.util.*;`

`public` `class` `FieldRefreshLogic` `{`

    `// 解析字段依赖关系`
    `public` `static Map<String, List<String>>` `parseDependencies(List<Field> fields)` `{`
`        Map<String, List<String>> dependencyMap =` `new` `HashMap<>();`
        `for` `(Field field : fields)` `{`
            `for` `(String referencedField : field.getConditions())` `{`
`                dependencyMap`
                    `.computeIfAbsent(referencedField, k ->` `new` `ArrayList<>())`
                    `.add(field.getName());`
            `}`
        `}`
        `return dependencyMap;`
    `}`

    `// 标记刷新逻辑`
    `public` `static Map<String, List<String>>` `markRefreshFields(Map<String, List<String>> dependencyMap)` `{`
`        Map<String, List<String>> refreshMap =` `new` `HashMap<>();`
        `for` `(Map.Entry<String, List<String>> entry : dependencyMap.entrySet())` `{`
`            refreshMap.put(entry.getKey(), entry.getValue());`
        `}`
        `return refreshMap;`
    `}`

    `// 服务端处理刷新请求`
    `public` `static Map<String, Object>` `handleRefreshRequest(`
`            Map<String, List<String>> refreshMap, String changedField)` `{`
        `if` `(refreshMap.containsKey(changedField))` `{`
`            List<String> fieldsToRefresh = refreshMap.get(changedField);`
            `// 重新计算字段值(这里需要补充具体的计算逻辑)`
            `return` `recalculateFields(fieldsToRefresh);`
        `}`
        `return Collections.emptyMap();`
    `}`

    `// 模拟字段重新计算逻辑`
    `public` `static Map<String, Object>` `recalculateFields(List<String> fieldsToRefresh)` `{`
`        Map<String, Object> refreshedData =` `new` `HashMap<>();`
        `for` `(String field : fieldsToRefresh)` `{`
            `// 示例:设置新的值(实际逻辑根据需求实现)`
`            refreshedData.put(field,` `"newValue");`
        `}`
        `return refreshedData;`
    `}`

    `// 前端触发刷新逻辑`
    `public` `static` `void` `frontendOnFieldChange(String changedField)` `{`
        `// 模拟发送请求到后端`
`        Map<String, Object> response =` `handleRefreshRequest(refreshMap, changedField);`
        `if` `(!response.isEmpty())` `{`
            `// 更新前端页面`
            `updateUI(response);`
        `}`
    `}`

    `// 模拟更新前端 UI`
    `public` `static` `void` `updateUI(Map<String, Object> data)` `{`
`        data.forEach((key, value)` `-> System.out.println("Field: "` `+ key +` `", New Value: "` `+ value));`
    `}`

    `// 示例字段类`
    `static` `class` `Field` `{`
        `private String name;`
        `private List<String> conditions;`

        `public` `Field(String name, List<String> conditions)` `{`
            `this.name = name;`
            `this.conditions = conditions;`
        `}`

        `public String getName()` `{`
            `return name;`
        `}`

        `public List<String>` `getConditions()` `{`
            `return conditions;`
        `}`
    `}`

    `// 主程序`
    `public` `static` `void` `main(String[] args)` `{`
        `// 模拟 DSL 字段`
`        List<Field> fields = Arrays.asList(`
                `new` `Field("A", Arrays.asList("B")),`
                `new` `Field("C", Arrays.asList("B"))`
        `);`

        `// 服务端初始化`
`        Map<String, List<String>> dependencyMap =` `parseDependencies(fields);`
`        Map<String, List<String>> refreshMap =` `markRefreshFields(dependencyMap);`

        `// 模拟前端触发字段变化`
`        String changedField =` `"B";`
        `frontendOnFieldChange(changedField);`
    `}`
`}`
`

算法思路

广度优先递归

五、总结

本文全面介绍了低代码表单引擎的刷新机制设计思路,从背景介绍到刷新模式选型、解析刷新点以及服务端刷新点算法等方面进行了详细阐述。通过对比分析不同刷新模式的优缺点,我们最终选择了刷新点触发模式,并给出了具体的实现步骤和算法逻辑。这一设计思路不仅提高了用户交互的友好性,还确保了服务端资源的合理利用。


**作者介绍:**七巧低代码是以业务应用搭建为核心的aPaaS低代码应用平台,为客户提供aPaaS+iPaaS的全民数智化解决方案,包括连接器工厂、业务编排、数据集成等核心能力,带动全员进行数字化创新。

官网:道一云七巧 - 高效的低代码开发平台 | 快速搭建个性化应用

获取更多技术资料:点击这里

相关推荐
uzong1 小时前
技术故障复盘模版
后端
GetcharZp1 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
加班是不可能的,除非双倍日工资1 小时前
css预编译器实现星空背景图
前端·css·vue3
桦说编程2 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研2 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi2 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip2 小时前
vite和webpack打包结构控制
前端·javascript
excel3 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国3 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼3 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin