跳到主要内容SpringBoot 低代码 JSON 表单引擎:快速配置审批流方案 | 极客日志JavaSaaSjava
SpringBoot 低代码 JSON 表单引擎:快速配置审批流方案
综述由AI生成介绍基于 SpringBoot、JSON Schema 和 Activiti 的低代码表单引擎解决方案。通过 JSON 配置定义表单结构和 UI,结合工作流引擎实现审批流程自动化。核心组件包括表单渲染服务、验证服务和工作流服务。方案支持动态字段生成、灵活验证规则及复杂流程配置。实际应用案例展示了请假审批流的快速搭建过程。该方案显著提升了开发效率,降低了维护成本,减少了重复 CRUD 开发,适用于企业级应用中的高频审批需求。
观心28 浏览 前言
在企业级应用开发中,审批流是一个高频需求。无论是请假申请、费用报销,还是采购审批,都需要一套完整的表单和流程系统。传统开发模式下,每个审批流都需要单独开发表单页面、验证逻辑、数据存储和流程控制,不仅耗时耗力,还容易出现重复造轮子的情况。今天,我将和大家分享一个基于 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 {
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\">");
if (formDefinition.getFormTitle() != null && !formDefinition.getFormTitle().isEmpty()) {
html.append(" <h3>").append(formDefinition.getFormTitle()).append("</h3>");
}
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);
}
}
html.append(" <div class=\"form-actions\">");
html.append(" <button type=\"submit\" class=\"btn btn-primary\">提交</button>");
html.append(" <button type=\"reset\" class=\"btn btn-secondary\">重置</button>");
html.append(" </div>");
html.append("</form>");
return html.toString();
} catch (Exception e) {
log.error("渲染表单失败:{}", formDefinition.getFormKey(), e);
return "<div class=\"error\">表单渲染失败</div>";
}
}
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;
fieldHtml.append(" <div class=\"form-group\">");
fieldHtml.append(" <label for=\"").append(fieldName).append("\">").append(label).append("</label>");
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>");
} 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>");
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) {
FormInstance formInstance = formService.submitFormInstance(formKey, formData, submitter);
FormDefinition formDefinition = formService.getFormDefinition(formKey);
if (formDefinition == null || formDefinition.getProcessDefinitionKey() == null) {
throw new RuntimeException("表单未关联审批流程或表单定义不存在:" + formKey);
}
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 分钟)
<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">
${renderedForm}
</div>
核心优势
1. 极致开发效率
- 配置即开发:通过 JSON 配置即可完成表单开发
- 模板复用:相同类型的表单可以复用模板
- 可视化配置:提供可视化配置界面,业务人员也能参与
2. 强大的扩展性
- 字段类型丰富:支持文本、数字、日期、选择等多种类型
- 验证规则灵活:支持必填、长度、格式、范围等多种验证
- 流程自定义:支持复杂的审批流程配置
3. 降低维护成本
- 配置驱动:表单修改无需代码发布
- 版本管理:支持表单定义的版本控制
- 权限控制:细粒度的权限管理
最佳实践
1. 性能优化
- 使用 Redis 缓存表单定义,减少数据库查询
- 对表单渲染结果进行适当缓存
- 实现分页查询表单实例
2. 安全考虑
- 对表单数据进行严格验证
- 实现操作权限控制
- 记录操作日志
3. 监控和运维
- 监控表单提交成功率
- 跟踪审批流程执行情况
- 提供管理后台进行配置管理
总结
通过 SpringBoot + Low-Code + JSON 表单引擎的方案,我们成功实现了 5 分钟配置一套审批流的目标。这个方案不仅大幅提升了开发效率,还降低了维护成本,让业务人员能够更好地参与到表单和流程的设计中。
- 减少 80% 的重复开发工作
- 将表单配置时间从几天缩短到几分钟
- 提升业务响应速度,增强系统灵活性
低代码不是万能的,但在合适的场景下,它确实能发挥巨大价值。希望这个方案能对大家有所帮助,让我们一起告别重复的 CRUD 开发!
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online