跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaSaaSjava

SpringBoot 低代码 JSON 表单引擎与审批流实现

一种基于 SpringBoot、JSON Schema 和 Activiti 工作流引擎的低代码表单引擎解决方案。该方案允许通过 JSON 配置快速定义表单结构和审批流程,替代传统的硬编码开发模式。核心功能包括动态表单渲染、数据验证、流程实例启动与任务完成。文章详细阐述了架构设计、核心组件实现及实际案例,展示了如何通过配置在几分钟内完成一套审批流,从而显著提升开发效率,降低维护成本,并支持业务人员参与流程设计。

DevOpsTeam发布于 2026/4/6更新于 2026/10/26106 浏览

前言

在企业级应用开发中,审批流是一个高频需求。无论是请假申请、费用报销,还是采购审批,都需要一套完整的表单和流程系统。传统开发模式下,每个审批流都需要单独开发表单页面、验证逻辑、数据存储和流程控制,不仅耗时耗力,还容易出现重复造轮子的情况。本文将分享一个基于 SpringBoot 的低代码表单引擎解决方案,通过 JSON 配置,实现快速配置审批流,提升开发效率。

为什么需要低代码表单引擎?

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"
:
[
"技术部"
,
"市场部"
,
"人事部"
,
"财务部"
]
}
,
"leaveType"
:
{
"type"
:
"string"
,
"title"
:
"请假类型"
,
"enum"
:
[
"年假"
,
"病假"
,
"事假"
,
"婚假"
,
"产假"
]
}
,
"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;
    }
}

实际应用案例

让我们以请假审批为例,看看如何配置一套完整的审批流。

第一步:定义表单结构
{
  "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": ["年假", "病假", "事假", "婚假", "产假"], "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"
}
第二步:配置审批流程

使用 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 调用创建
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" }'
第四步:表单渲染和使用

前端页面只需一行代码即可渲染表单:

<div id="dynamic-form-container">
  <!-- 从后端 API 获取渲染后的表单 HTML -->
  ${renderedForm}
</div>

核心优势

1. 极致开发效率
  • 配置即开发:通过 JSON 配置即可完成表单开发
  • 模板复用:相同类型的表单可以复用模板
  • 可视化配置:提供可视化配置界面,业务人员也能参与
2. 强大的扩展性
  • 字段类型丰富:支持文本、数字、日期、选择等多种类型
  • 验证规则灵活:支持必填、长度、格式、范围等多种验证
  • 流程自定义:支持复杂的审批流程配置
3. 降低维护成本
  • 配置驱动:表单修改无需代码发布
  • 版本管理:支持表单定义的版本控制
  • 权限控制:细粒度的权限管理

最佳实践

1. 性能优化
  • 使用 Redis 缓存表单定义,减少数据库查询
  • 对表单渲染结果进行适当缓存
  • 实现分页查询表单实例
2. 安全考虑
  • 对表单数据进行严格验证
  • 实现操作权限控制
  • 记录操作日志
3. 监控和运维
  • 监控表单提交成功率
  • 跟踪审批流程执行情况
  • 提供管理后台进行配置管理

总结

通过 SpringBoot + Low-Code + JSON 表单引擎的方案,我们成功实现了快速配置审批流的目标。这个方案不仅大幅提升了开发效率,还降低了维护成本,让业务人员能够更好地参与到表单和流程的设计中。

在实际项目中,这个方案已经帮助我们:

  • 减少 80% 的重复开发工作
  • 将表单配置时间从几天缩短到几分钟
  • 提升业务响应速度,增强系统灵活性

低代码不是万能的,但在合适的场景下,它确实能发挥巨大价值。希望这个方案能对大家有所帮助。

目录

  1. 前言
  2. 为什么需要低代码表单引擎?
  3. 1. 开发效率问题
  4. 2. 维护成本高昂
  5. 3. 业务人员参与度低
  6. 核心技术方案
  7. 1. 架构设计
  8. 2. 核心组件
  9. 表单定义(FormDefinition)
  10. 流程定义(ApprovalProcess)
  11. 3. 核心实现
  12. 表单渲染服务
  13. 表单验证服务
  14. 工作流服务
  15. 实际应用案例
  16. 第一步:定义表单结构
  17. 第二步:配置审批流程
  18. 第三步:API 调用创建
  19. 第四步:表单渲染和使用
  20. 核心优势
  21. 1. 极致开发效率
  22. 2. 强大的扩展性
  23. 3. 降低维护成本
  24. 最佳实践
  25. 1. 性能优化
  26. 2. 安全考虑
  27. 3. 监控和运维
  28. 总结

更多推荐文章

查看全部
  • 接入第三方 OpenAI 兼容模型到 GitHub Copilot
  • 2026 前端跨端框架选型指南
  • MySQL 内置函数实战指南:日期、字符串与数学运算
  • Java WebSocket 核心原理与实战应用
  • 大模型 Agent 开发指南:基于 LangChain 的实战应用
  • 基于 STM32 的智能家居安防系统设计与实现
  • 大模型技术原理详解:从 Transformer 到 RLHF
  • OpenWebUI 集成 SearXNG 实现本地大模型联网搜索
  • RabbitMQ 消费端限流:basicQos 配置实例与经验
  • 基于 Zynq FPGA 的雷龙 SD NAND 测试实践
  • Python 使用 Turtle 库绘制十种图形示例
  • Neo4j 图数据库安装与 Cypher 查询实战指南
  • HTTP 协议基础与 HTTPS 原理
  • AI 大模型对会计工作与财务软件的影响分析
  • C++ STL 容器适配器:stack 与 queue 剖析
  • 攻防世界 Web 题解:Lottery 与 ics-05 漏洞分析
  • 大模型学习路线:从原理到工程化落地实践
  • C++ 函数重载:核心规则、实现细节与实战
  • RuoYi-Vue-Plus AI 智能编程助手配置方案
  • Rust 异步代码的测试与调试实践

相关免费在线工具

  • 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