SpringBoot + Low-Code + JSON 表单引擎:5 分钟配置一套审批流,告别重复 CRUD
前言
在企业级应用开发中,审批流是一个高频需求。无论是请假申请、费用报销,还是采购审批,都需要一套完整的表单和流程系统。传统开发模式下,每个审批流都需要单独开发表单页面、验证逻辑、数据存储和流程控制,不仅耗时耗力,还容易出现重复造轮子的情况。今天,我将和大家分享一个基于SpringBoot的低代码表单引擎解决方案,通过JSON配置,实现5分钟配置一套审批流,彻底告别重复的CRUD开发。
为什么需要低代码表单引擎?
1. 开发效率问题
传统审批流开发需要经历以下步骤:
- 设计表单UI界面
- 实现前端交互逻辑
- 开发后端API接口
- 编写数据验证逻辑
- 集成工作流引擎
- 实现审批节点配置
- 部署和测试
整个过程可能需要几天甚至几周时间,而且每个新流程都要重复这些步骤。
2. 维护成本高昂
随着业务发展,表单字段经常需要调整,流程节点需要变更,每次修改都需要开发人员介入,增加了维护成本和响应时间。
3. 业务人员参与度低
业务人员无法直接参与表单和流程的设计,只能被动接受开发结果,导致最终产品与实际需求存在偏差。
核心技术方案
1. 架构设计
我们的解决方案采用以下核心技术栈:
- Spring Boot: 快速开发框架
- JSON Schema: 定义表单结构
- Activiti: 工作流引擎
- Thymeleaf: 模板引擎用于动态表单渲染
- Redis: 缓存表单定义和流程定义
- MySQL: 数据持久化存储
2. 核心组件
表单定义(FormDefinition)
{
"formKey": "leave_application",
"formName": "请假申请",
"formTitle": "员工请假申请表",
"formSchema": {
"type": "object",
"properties": {
"employeeName": {
"type": "string",
"title": "员工姓名",
"required": true
},
"department": {
"type": "string",
"title": "所属部门",
"enum": ["技术部", "市场部", "人事部", "财务部"],
"enumNames": ["技术部", "市场部", "人事部", "财务部"]
},
"leaveType": {
"type": "string",
"title": "请假类型",
"enum": ["年假", "病假", "事假", "婚假", "产假"],
"enumNames": ["年假", "病假", "事假", "婚假", "产假"]
},
"startDate": {
"type": "string",
"format": "date",
"title": "开始日期"
},
"endDate": {
"type": "string",
"format": "date",
"title": "结束日期"
},
"reason": {
"type": "string",
"title": "请假原因",
"maxLength": 200
}
},
"required": ["employeeName", "leaveType", "startDate", "endDate"]
},
"formUiSchema": {
"employeeName": {
"ui:widget": "input"
},
"department": {
"ui:widget": "select"
},
"leaveType": {
"ui:widget": "radio"
},
"reason": {
"ui:widget": "textarea"
}
},
"processDefinitionKey": "leave_approval_process"
}
流程定义(ApprovalProcess)
{
"processKey": "leave_approval_process",
"processName": "请假审批流程",
"processDefinition": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...",
"description": "员工请假申请审批流程"
}
3. 核心实现
表单渲染服务
@Service
@Slf4j
public class FormRenderingService {
/**
* 根据表单定义渲染HTML表单
*/
public String renderForm(FormDefinition formDefinition) {
try {
JSONObject schema = JSON.parseObject(formDefinition.getFormSchema());
JSONObject uiSchema = JSON.parseObject(formDefinition.getFormUiSchema());
StringBuilder html = new StringBuilder();
html.append("<form id=\"").append(formDefinition.getFormKey()).append("\" class=\"low-code-form\">\n");
// 添加表单标题
if (formDefinition.getFormTitle() != null && !formDefinition.getFormTitle().isEmpty()) {
html.append(" <h3>").append(formDefinition.getFormTitle()).append("</h3>\n");
}
// 解析schema中的属性并生成对应的HTML元素
if (schema.containsKey("properties")) {
JSONObject properties = schema.getJSONObject("properties");
for (String fieldName : properties.keySet()) {
JSONObject fieldSchema = properties.getJSONObject(fieldName);
JSONObject fieldUiSchema = uiSchema != null ?
uiSchema.getJSONObject(fieldName) : null;
String fieldHtml = generateFieldHtml(fieldName, fieldSchema, fieldUiSchema);
html.append(fieldHtml).append("\n");
}
}
// 添加提交按钮
html.append(" <div class=\"form-actions\">\n");
html.append(" <button type=\"submit\" class=\"btn btn-primary\">提交</button>\n");
html.append(" <button type=\"reset\" class=\"btn btn-secondary\">重置</button>\n");
html.append(" </div>\n");
html.append("</form>\n");
return html.toString();
} catch (Exception e) {
log.error("渲染表单失败: {}", formDefinition.getFormKey(), e);
return "<div class=\"error\">表单渲染失败</div>";
}
}
/**
* 根据字段类型生成HTML元素
*/
private String generateFieldHtml(String fieldName, JSONObject fieldSchema, JSONObject fieldUiSchema) {
StringBuilder fieldHtml = new StringBuilder();
// 获取字段标签
String label = fieldSchema.getString("title");
if (label == null || label.isEmpty()) {
label = fieldName;
}
// 获取字段类型
String type = fieldSchema.getString("type");
String widget = fieldUiSchema != null ?
fieldUiSchema.getString("ui:widget") : null;
// 生成字段HTML
fieldHtml.append(" <div class=\"form-group\">\n");
fieldHtml.append(" <label for=\"").append(fieldName).append("\">")
.append(label).append("</label>\n");
if ("string".equals(type)) {
if ("textarea".equals(widget)) {
// 文本域
fieldHtml.append(" <textarea id=\"").append(fieldName)
.append("\" name=\"").append(fieldName).append("\"")
.append(" class=\"form-control\"");
// 添加验证规则
addValidationAttributes(fieldHtml, fieldSchema);
fieldHtml.append(">");
if (fieldSchema.containsKey("default")) {
fieldHtml.append(fieldSchema.getString("default"));
}
fieldHtml.append("</textarea>\n");
} else {
// 输入框
fieldHtml.append(" <input type=\"text\" id=\"").append(fieldName)
.append("\" name=\"").append(fieldName).append("\"")
.append(" class=\"form-control\"");
// 添加默认值
if (fieldSchema.containsKey("default")) {
fieldHtml.append(" value=\"").append(fieldSchema.getString("default")).append("\"");
}
// 添加验证规则
addValidationAttributes(fieldHtml, fieldSchema);
fieldHtml.append(" />\n");
}
}
// ... 其他类型的字段处理
fieldHtml.append(" </div>\n");
return fieldHtml.toString();
}
}
表单验证服务
@Service
@Slf4j
public class FormValidationService {
/**
* 根据表单定义验证表单数据
*/
public ValidationResult validateFormData(String formKey, String formData, FormDefinition formDefinition) {
ValidationResult result = new ValidationResult();
try {
JSONObject data = JSON.parseObject(formData);
JSONObject schema = JSON.parseObject(formDefinition.getFormSchema());
// 获取必填字段
if (schema.containsKey("required")) {
JSONArray requiredFields = schema.getJSONArray("required");
for (int i = 0; i < requiredFields.size(); i++) {
String fieldName = requiredFields.getString(i);
if (!data.containsKey(fieldName) || data.getString(fieldName) == null ||
data.getString(fieldName).trim().isEmpty()) {
result.addError(fieldName, fieldName + " 是必填字段");
}
}
}
// 验证字段类型和约束
if (schema.containsKey("properties")) {
JSONObject properties = schema.getJSONObject("properties");
for (String fieldName : properties.keySet()) {
JSONObject fieldSchema = properties.getJSONObject(fieldName);
Object fieldValue = data.get(fieldName);
if (fieldValue != null) {
validateField(fieldName, fieldValue, fieldSchema, result);
}
}
}
} catch (Exception e) {
log.error("验证表单数据失败: {}", formKey, e);
result.addError("system", "表单验证系统错误");
}
return result;
}
/**
* 验证单个字段
*/
private void validateField(String fieldName, Object value, JSONObject fieldSchema, ValidationResult result) {
String type = fieldSchema.getString("type");
if ("string".equals(type)) {
if (!(value instanceof String)) {
result.addError(fieldName, fieldName + " 必须是字符串类型");
return;
}
String strValue = (String) value;
// 长度验证
if (fieldSchema.containsKey("minLength")) {
int minLength = fieldSchema.getIntValue("minLength");
if (strValue.length() < minLength) {
result.addError(fieldName, fieldName + " 长度不能少于 " + minLength + " 个字符");
}
}
if (fieldSchema.containsKey("maxLength")) {
int maxLength = fieldSchema.getIntValue("maxLength");
if (strValue.length() > maxLength) {
result.addError(fieldName, fieldName + " 长度不能超过 " + maxLength + " 个字符");
}
}
// 格式验证
if (fieldSchema.containsKey("pattern")) {
String pattern = fieldSchema.getString("pattern");
if (!strValue.matches(pattern)) {
result.addError(fieldName, fieldName + " 格式不正确");
}
}
}
// ... 其他类型的字段验证
}
public static class ValidationResult {
private boolean valid = true;
private Map<String, String> errors = new HashMap<>();
public void addError(String field, String message) {
valid = false;
errors.put(field, message);
}
public boolean isValid() {
return valid;
}
public Map<String, String> getErrors() {
return errors;
}
}
}
工作流服务
@Service
@Slf4j
public class WorkflowService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
/**
* 启动审批流程实例
*/
@Transactional
public String startProcessInstance(String processKey, String businessKey, Map<String, Object> variables) {
try {
// 启动流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
processKey, businessKey, variables);
String processInstanceId = processInstance.getId();
log.info("启动审批流程实例: {}, 业务键: {}", processInstanceId, businessKey);
return processInstanceId;
} catch (Exception e) {
log.error("启动审批流程实例失败: {}", processKey, e);
throw new RuntimeException("启动审批流程实例失败", e);
}
}
/**
* 完成审批任务
*/
@Transactional
public void completeTask(String taskId, String assignee, Map<String, Object> variables) {
try {
// 设置办理人
taskService.setAssignee(taskId, assignee);
// 完成任务
taskService.complete(taskId, variables);
log.info("完成审批任务: {}, 办理人: {}", taskId, assignee);
} catch (Exception e) {
log.error("完成审批任务失败: {}", taskId, e);
throw new RuntimeException("完成审批任务失败", e);
}
}
/**
* 提交表单并启动审批流程
*/
@Transactional
public String submitFormWithWorkflow(String formKey, String formData, String submitter) {
// 1. 提交表单实例
FormInstance formInstance = formService.submitFormInstance(formKey, formData, submitter);
// 2. 获取表单关联的流程定义
FormDefinition formDefinition = formService.getFormDefinition(formKey);
if (formDefinition == null || formDefinition.getProcessDefinitionKey() == null) {
throw new RuntimeException("表单未关联审批流程或表单定义不存在: " + formKey);
}
// 3. 启动审批流程
Map<String, Object> variables = new HashMap<>();
variables.put("assignee", submitter);
variables.put("formInstanceKey", formInstance.getInstanceKey());
String processInstanceId = startProcessInstance(
formDefinition.getProcessDefinitionKey(),
formInstance.getInstanceKey(),
variables);
return processInstanceId;
}
}
实际应用案例
让我们以请假审批为例,看看如何在5分钟内配置一套完整的审批流。
第一步:定义表单结构(1分钟)
{
"formKey": "leave_app_001",
"formName": "请假申请",
"formTitle": "员工请假申请表",
"formSchema": {
"type": "object",
"properties": {
"employeeId": {
"type": "string",
"title": "工号",
"pattern": "^\\d{6}$",
"required": true
},
"employeeName": {
"type": "string",
"title": "姓名",
"required": true
},
"leaveType": {
"type": "string",
"title": "请假类型",
"enum": ["年假", "病假", "事假", "婚假", "产假"],
"enumNames": ["年假", "病假", "事假", "婚假", "产假"],
"required": true
},
"startTime": {
"type": "string",
"format": "date",
"title": "开始时间",
"required": true
},
"endTime": {
"type": "string",
"format": "date",
"title": "结束时间",
"required": true
},
"reason": {
"type": "string",
"title": "请假事由",
"maxLength": 200
}
},
"required": ["employeeId", "employeeName", "leaveType", "startTime", "endTime"]
},
"formUiSchema": {
"employeeId": { "ui:widget": "input" },
"employeeName": { "ui:widget": "input" },
"leaveType": { "ui:widget": "select" },
"reason": { "ui:widget": "textarea" }
},
"processDefinitionKey": "leave_approval_wf"
}
第二步:配置审批流程(2分钟)
使用Activiti工作流引擎定义审批流程:
<process id="leave_approval_wf" name="请假审批流程" isExecutable="true">
<startEvent id="startevent1" name="开始"></startEvent>
<userTask id="dept_leader_approve" name="部门领导审批" activiti:assignee="${deptLeader}">
<extensionElements>
<activiti:taskListener event="create" class="com.example.listener.TaskCreateListener" />
</extensionElements>
</userTask>
<exclusiveGateway id="check_duration" name="请假天数检查"></exclusiveGateway>
<userTask id="hr_approve" name="HR审批" activiti:assignee="hr_user"></userTask>
<endEvent id="endevent1" name="结束"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="dept_leader_approve"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="dept_leader_approve" targetRef="check_duration">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approvalResult == 'approved'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="check_duration" targetRef="hr_approve">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${duration > 3}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="check_duration" targetRef="endevent1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${duration <= 3}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" sourceRef="hr_approve" targetRef="endevent1"></sequenceFlow>
</process>
第三步:API调用创建(1分钟)
curl -X POST "http://localhost:8081/api/forms" \
-H "Content-Type: application/json" \
-d '{
"formKey": "leave_app_001",
"formName": "请假申请",
"formTitle": "员工请假申请表",
"formSchema": "...",
"formUiSchema": "...",
"processDefinitionKey": "leave_approval_wf",
"createdBy": "admin"
}'
第四步:表单渲染和使用(1分钟)
前端页面只需一行代码即可渲染表单:
<div id="dynamic-form-container">
<!-- 从后端API获取渲染后的表单HTML -->
${renderedForm}
</div>
核心优势
1. 极致开发效率
- 配置即开发:通过JSON配置即可完成表单开发
- 模板复用:相同类型的表单可以复用模板
- 可视化配置:提供可视化配置界面,业务人员也能参与
2. 强大的扩展性
- 字段类型丰富:支持文本、数字、日期、选择等多种类型
- 验证规则灵活:支持必填、长度、格式、范围等多种验证
- 流程自定义:支持复杂的审批流程配置
3. 降低维护成本
- 配置驱动:表单修改无需代码发布
- 版本管理:支持表单定义的版本控制
- 权限控制:细粒度的权限管理
最佳实践
1. 性能优化
- 使用Redis缓存表单定义,减少数据库查询
- 对表单渲染结果进行适当缓存
- 实现分页查询表单实例
2. 安全考虑
- 对表单数据进行严格验证
- 实现操作权限控制
- 记录操作日志
3. 监控和运维
- 监控表单提交成功率
- 跟踪审批流程执行情况
- 提供管理后台进行配置管理
总结
通过SpringBoot + Low-Code + JSON表单引擎的方案,我们成功实现了5分钟配置一套审批流的目标。这个方案不仅大幅提升了开发效率,还降低了维护成本,让业务人员能够更好地参与到表单和流程的设计中。
在实际项目中,这个方案已经帮助我们:
- 减少80%的重复开发工作
- 将表单配置时间从几天缩短到几分钟
- 提升业务响应速度,增强系统灵活性
低代码不是万能的,但在合适的场景下,它确实能发挥巨大价值。希望这个方案能对大家有所帮助,让我们一起告别重复的CRUD开发!
服务端技术精选 - 专注后端技术分享,与你一起成长!
标题:SpringBoot + Low-Code + JSON 表单引擎:5 分钟配置一套审批流,告别重复 CRUD
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/01/10/1768030502710.html
0 评论