低代码表单引擎刷新机制

一、背景

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

具体业务场景参考如下:

分数子表:

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

  • 单元测试分数:例如 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的全民数智化解决方案,包括连接器工厂、业务编排、数据集成等核心能力,带动全员进行数字化创新。

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

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

相关推荐
喝拿铁写前端5 小时前
前端与 AI 结合的 10 个可能路径图谱
前端·人工智能
codingandsleeping5 小时前
浏览器的缓存机制
前端·后端
追逐时光者5 小时前
面试官问:你知道 C# 单例模式有哪几种常用的实现方式?
后端·.net
Asthenia04126 小时前
Numpy:数组生成/modf/sum/输出格式规则
后端
Asthenia04126 小时前
NumPy:数组加法/数组比较/数组重塑/数组切片
后端
Asthenia04126 小时前
Numpy:limspace/arange/数组基本属性分析
后端
Asthenia04126 小时前
Java中线程暂停的分析与JVM和Linux的协作流程
后端
Asthenia04126 小时前
Seata TCC 模式:RootContext与TCC专属的BusinessActionContext与TCC注解详解
后端
自珍JAVA6 小时前
【代码】zip压缩文件密码暴力破解
后端
灵感__idea6 小时前
JavaScript高级程序设计(第5版):扎实的基本功是唯一捷径
前端·javascript·程序员