低代码平台表单引擎与业务事件设计实践
低代码平台表单引擎与业务事件设计实践
一、什么是低代码?它能做什么?
低代码(Low-Code)是一种通过可视化拖拽、配置和少量代码开发应用的方式。它极大地降低了开发门槛,让业务人员也能参与到应用搭建中。低代码平台通常具备以下能力:
- 可视化表单设计:通过拖拽组件快速搭建业务表单。
- 流程编排:配置化定义审批流、业务流转。
- 业务规则配置:通过“事件-动作-条件”方式实现复杂业务逻辑。
- 数据集成:对接外部系统、数据库,实现数据互通。
- 快速上线与迭代:大幅缩短开发周期,支持敏捷变更。
低代码平台适用于OA审批、报销、请假、合同、CRM等大量表单驱动的业务场景。
二、请假系统案例介绍
为了更好地理解低代码平台的能力,我们以“请假申请系统”为例,介绍其功能和业务逻辑。
2.1 主要功能
- 员工提交请假申请,填写请假人、请假时间、请假原因等信息。
- 系统自动带出直属领导作为审批人。
- 请假天数超过3天时,自动增加二级、三级审批人。
- 审批人可在系统中审批,审批结果自动流转到下一级。
- 审批通过/拒绝后,自动通知相关人员。
2.2 业务流程
- 员工发起请假申请,填写表单。
- 系统根据申请人自动带出一级审批人。
- 若请假天数>3天,自动增加二级、三级审批人。
- 审批人依次审批,全部通过后流程结束。
- 审批拒绝则流程回退,通知申请人。
三、表单元数据、实例数据与业务事件联动设计
3.1 表单元数据(Meta)如何设计
表单元数据描述了表单的结构、字段、字段属性和字段事件。每个字段可以挂载多个业务事件(如 onChange、onBlur、onSubmit 等),事件与字段通过 eventId 关联。
表单元数据(MongoDB示例):
{
"_id": "form_001",
"appId": "app_001",
"name": "请假申请表",
"fields": [
{
"fieldId": "f1",
"name": "applicant",
"type": "input",
"label": "请假人",
"required": true,
"events": [
{
"eventId": "event_001"
}
]
},
{
"fieldId": "f2",
"name": "manager1",
"type": "input",
"label": "第一审批人",
"required": true
},
{
"fieldId": "f3",
"name": "manager2",
"type": "input",
"label": "第二审批人",
"required": true
},
{
"fieldId": "f4",
"name": "manager3",
"type": "input",
"label": "第三审批人",
"required": false
},
{
"fieldId": "f5",
"name": "reason",
"type": "textarea",
"label": "请假原因",
"required": true
},
{
"fieldId": "f6",
"name": "startTime",
"type": "datetime",
"label": "请假开始时间",
"required": true
},
{
"fieldId": "f7",
"name": "endTime",
"type": "datetime",
"label": "请假结束时间",
"required": true
},
{
"fieldId": "f8",
"name": "result1",
"type": "select",
"label": "第一审批结果",
"required": false
},
{
"fieldId": "f9",
"name": "result2",
"type": "select",
"label": "第二审批结果",
"required": false
},
{
"fieldId": "f10",
"name": "result3",
"type": "select",
"label": "第三审批结果",
"required": false
}
],
"formEvents": [
{
"eventId": "event_002"
}
],
"createdAt": "...",
"updatedAt": "..."
}
说明:
- 每个字段可配置 events,events 里存 eventId,eventId 指向业务事件配置。
- formEvents 是表单级别的事件(如提交时触发)。
3.2 表单实例数据(Data)如何保存(宽表结构)
表单实例数据采用宽表结构,每个字段单独一列,便于查询和统计。
MySQL表结构(宽表):
CREATE TABLE form_leave (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
app_id VARCHAR(64),
form_id VARCHAR(64),
user_id VARCHAR(64),
applicant VARCHAR(64),
manager1 VARCHAR(64),
manager2 VARCHAR(64),
manager3 VARCHAR(64),
reason VARCHAR(255),
start_time DATETIME,
end_time DATETIME,
result1 VARCHAR(32),
result2 VARCHAR(32),
result3 VARCHAR(32),
status VARCHAR(32),
current_node VARCHAR(64),
created_at DATETIME,
updated_at DATETIME
);
示例数据:
id | app_id | form_id | user_id | applicant | manager1 | manager2 | manager3 | reason | start_time | end_time | result1 | result2 | result3 | status | current_node | created_at | updated_at |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | app_001 | form_001 | u_001 | 张三 | 李四 | 王五 | 赵六 | 家中有事 | 2024-06-01 09:00:00 | 2024-06-03 18:00:00 | 同意 | 同意 | pending | manager1 | 2024-06-10 10:00:00 | 2024-06-10 10:00:00 |
3.3 业务事件与数据节点的联动
业务事件是低代码平台的核心,描述了“何时触发、触发什么、如何处理”。
业务事件配置(MongoDB示例):
{
"_id": "event_001",
"formId": "form_001",
"trigger": "onChange",
"sourceField": "applicant",
"action": "setValue",
"targetField": "manager1",
"strategy": "fromEmployeeTable",
"params": {
"sourceField": "applicant",
"lookupTable": "employee",
"lookupField": "manager1"
}
}
说明:
- trigger:事件触发时机(如 onChange、onSubmit、onApprove 等)
- sourceField:事件源字段
- action:动作类型(如 setValue、validate、block、branch、aggregate 等)
- targetField:目标字段
- strategy:具体的原子策略(如 fromEmployeeTable、calculate、validateRule 等)
- params:策略参数
字段与事件的联动:
- 在表单元数据的字段 events 中,配置 eventId。
- 前端监听字段变化,触发事件,后端根据 eventId 查找事件配置,执行对应策略。
四、业务事件与原子策略的实现(详细Java代码)
4.1 业务事件类型举例
- 字段赋值(setValue):如 applicant 变更时自动填充 manager1/manager2
- 字段校验(validate):如请假天数不能超过 10 天
- 节点新增(addNode):如并签时动态增加审批节点
- 节点聚合(aggregate):如并签节点全部通过后流转
- 流程推进(advance):如审批通过后流转到下一个节点
- 流程阻塞(block):如审批未通过阻塞流程
- 分支判断(branch):如请假天数大于 3 天需三级审批
- 消息通知(notify):如审批人收到待办通知
- 数据计算(calculate):如自动计算请假天数
- 回退(rollback):如审批拒绝回退到上一步
4.2 详细Java实现
4.2.1 事件上下文对象
public class EventContext {
private Map<String, Object> fieldValues = new HashMap<>();
private Map<String, Object> extra = new HashMap<>();
public Object getFieldValue(String fieldName) {
return fieldValues.get(fieldName);
}
public void setFieldValue(String fieldName, Object value) {
fieldValues.put(fieldName, value);
}
public Map<String, Object> getAllFieldValues() {
return fieldValues;
}
public void setExtra(String key, Object value) {
extra.put(key, value);
}
public Object getExtra(String key) {
return extra.get(key);
}
}
4.2.2 原子策略接口
public interface AtomicStrategy {
void execute(EventContext context, Map<String, Object> params);
}
4.2.3 具体策略实现
// 字段赋值策略:根据申请人自动带出直属领导
public class SetValueStrategy implements AtomicStrategy {
@Override
public void execute(EventContext context, Map<String, Object> params) {
String sourceField = (String) params.get("sourceField");
String targetField = (String) params.get("targetField");
String lookupTable = (String) params.get("lookupTable");
String lookupField = (String) params.get("lookupField");
String applicant = (String) context.getFieldValue(sourceField);
// 假设EmployeeService.getManager(applicant)能查到直属领导
String manager = EmployeeService.getManager(applicant);
context.setFieldValue(targetField, manager);
}
}
// 字段校验策略:请假天数不能超过10天
public class ValidateStrategy implements AtomicStrategy {
@Override
public void execute(EventContext context, Map<String, Object> params) {
String startField = (String) params.get("startField");
String endField = (String) params.get("endField");
int maxDays = (int) params.getOrDefault("maxDays", 10);
LocalDateTime start = (LocalDateTime) context.getFieldValue(startField);
LocalDateTime end = (LocalDateTime) context.getFieldValue(endField);
long days = Duration.between(start, end).toDays();
if (days > maxDays) {
throw new RuntimeException("请假天数不能超过" + maxDays + "天");
}
}
}
// 分支判断策略:请假天数大于3天需三级审批
public class BranchStrategy implements AtomicStrategy {
@Override
public void execute(EventContext context, Map<String, Object> params) {
String startField = (String) params.get("startField");
String endField = (String) params.get("endField");
int threshold = (int) params.getOrDefault("threshold", 3);
LocalDateTime start = (LocalDateTime) context.getFieldValue(startField);
LocalDateTime end = (LocalDateTime) context.getFieldValue(endField);
long days = Duration.between(start, end).toDays();
if (days > threshold) {
context.setFieldValue("needManager3", true);
} else {
context.setFieldValue("needManager3", false);
}
}
}
// 消息通知策略
public class NotifyStrategy implements AtomicStrategy {
@Override
public void execute(EventContext context, Map<String, Object> params) {
String userField = (String) params.get("userField");
String message = (String) params.get("message");
String userId = (String) context.getFieldValue(userField);
NotificationService.send(userId, message);
}
}
// 更多策略可按需扩展...
4.2.4 策略工厂
public class StrategyFactory {
private static final Map<String, AtomicStrategy> STRATEGY_MAP = new HashMap<>();
static {
STRATEGY_MAP.put("setValue", new SetValueStrategy());
STRATEGY_MAP.put("validate", new ValidateStrategy());
STRATEGY_MAP.put("branch", new BranchStrategy());
STRATEGY_MAP.put("notify", new NotifyStrategy());
// ...注册其他策略
}
public static AtomicStrategy getStrategy(String strategyName) {
return STRATEGY_MAP.get(strategyName);
}
}
4.2.5 事件配置对象
public class EventConfig {
private String strategy;
private Map<String, Object> params;
// ...getter/setter
}
4.2.6 事件引擎
public class EventEngine {
public void handleEvent(EventConfig eventConfig, EventContext context) {
String strategyName = eventConfig.getStrategy();
Map<String, Object> params = eventConfig.getParams();
AtomicStrategy strategy = StrategyFactory.getStrategy(strategyName);
if (strategy != null) {
strategy.execute(context, params);
} else {
throw new RuntimeException("未找到策略: " + strategyName);
}
}
}
4.2.7 事件驱动示例
// 假设前端触发 applicant 字段 onChange,后端收到事件
EventConfig eventConfig = eventConfigRepository.findById("event_001");
EventContext context = new EventContext();
context.setFieldValue("applicant", "张三");
EventEngine engine = new EventEngine();
engine.handleEvent(eventConfig, context);
// manager1 字段会被自动赋值
五、架构设计总结
- 表单元数据:存储表单结构、字段、字段事件,字段与事件通过 eventId 关联,支持灵活扩展。
- 表单实例数据:采用宽表结构,每个字段单独一列,便于查询和统计。
- 业务事件:存储事件触发条件、动作、目标、策略、参数等,支持灵活配置和复用。
- 原子策略:每个业务事件由一个或多个原子策略组成,策略可扩展、可复用,解耦业务逻辑。
- 事件引擎:根据事件配置,动态组装并执行策略链,实现“配置即逻辑”,大幅提升开发效率。
- 工厂+策略模式:Java 端通过工厂+策略模式实现原子策略的注册与调用,保证系统高扩展性和解耦性。
六、常见业务事件场景举例
- 字段变更自动赋值
- 字段变更自动校验
- 字段变更动态显示/隐藏其他字段
- 审批节点并签/或签
- 审批节点动态新增/聚合
- 审批拒绝回退
- 流程自动推进
- 消息/通知推送
- 数据自动计算
- 数据自动填充(如带出员工信息)
七、数据结构建议(最终版)
7.1 表单元数据(MongoDB)
{
"_id": "form_001",
"appId": "app_001",
"name": "请假申请表",
"fields": [
{
"fieldId": "f1",
"name": "applicant",
"type": "input",
"label": "请假人",
"required": true,
"events": [
{
"eventId": "event_001"
}
]
},
{
"fieldId": "f2",
"name": "manager1",
"type": "input",
"label": "第一审批人",
"required": true
},
{
"fieldId": "f3",
"name": "manager2",
"type": "input",
"label": "第二审批人",
"required": true
},
{
"fieldId": "f4",
"name": "manager3",
"type": "input",
"label": "第三审批人",
"required": false
},
{
"fieldId": "f5",
"name": "reason",
"type": "textarea",
"label": "请假原因",
"required": true
},
{
"fieldId": "f6",
"name": "startTime",
"type": "datetime",
"label": "请假开始时间",
"required": true
},
{
"fieldId": "f7",
"name": "endTime",
"type": "datetime",
"label": "请假结束时间",
"required": true
},
{
"fieldId": "f8",
"name": "result1",
"type": "select",
"label": "第一审批结果",
"required": false
},
{
"fieldId": "f9",
"name": "result2",
"type": "select",
"label": "第二审批结果",
"required": false
},
{
"fieldId": "f10",
"name": "result3",
"type": "select",
"label": "第三审批结果",
"required": false
}
],
"formEvents": [
{
"eventId": "event_002"
}
],
"createdAt": "...",
"updatedAt": "..."
}
7.2 表单实例数据(MySQL,宽表结构)
CREATE TABLE form_leave (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
app_id VARCHAR(64),
form_id VARCHAR(64),
user_id VARCHAR(64),
applicant VARCHAR(64),
manager1 VARCHAR(64),
manager2 VARCHAR(64),
manager3 VARCHAR(64),
reason VARCHAR(255),
start_time DATETIME,
end_time DATETIME,
result1 VARCHAR(32),
result2 VARCHAR(32),
result3 VARCHAR(32),
status VARCHAR(32),
current_node VARCHAR(64),
created_at DATETIME,
updated_at DATETIME
);
7.3 业务事件(MongoDB)
{
"_id": "event_001",
"formId": "form_001",
"trigger": "onChange",
"sourceField": "applicant",
"action": "setValue",
"targetField": "manager1",
"strategy": "fromEmployeeTable",
"params": {
"sourceField": "applicant",
"lookupTable": "employee",
"lookupField": "manager1"
}
}
八、结语
通过本案例,我们可以看到低代码平台在表单引擎、业务事件、原子策略等方面的强大能力。通过“元数据+事件+策略”驱动的架构,极大提升了业务灵活性和开发效率。未来,低代码平台将成为企业数字化转型的重要基础设施。