低代码表单引擎刷新机制
一、背景
在低代码产品的研发过程中,表单引擎扮演着至关重要的角色。它涉及前后端的数据解析和触发刷新能力,以满足用户在选择单选框等操作时带出其他数据计算的需求。然而,这些计算往往无法通过前端简单计算得出,需要后端执行脚本或数据库查询来实现。因此,设计高效的表单引擎刷新机制显得尤为重要。
具体业务场景参考如下:
分数子表:
学生的成绩页面有多个子表,分别用于填写各项成绩:
- 单元测试分数:例如 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的全民数智化解决方案,包括连接器工厂、业务编排、数据集成等核心能力,带动全员进行数字化创新。
官网:道一云七巧 - 高效的低代码开发平台 | 快速搭建个性化应用
获取更多技术资料:点击这里