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@Slf4jpublicclassFormRenderingService{/** * 根据表单定义渲染HTML表单 */publicStringrenderForm(FormDefinition formDefinition){try{JSONObject schema = JSON.parseObject(formDefinition.getFormSchema());JSONObject uiSchema = JSON.parseObject(formDefinition.getFormUiSchema());StringBuilder html =newStringBuilder(); 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元素 */privateStringgenerateFieldHtml(String fieldName,JSONObject fieldSchema,JSONObject fieldUiSchema){StringBuilder fieldHtml =newStringBuilder();// 获取字段标签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@Slf4jpublicclassFormValidationService{/** * 根据表单定义验证表单数据 */publicValidationResultvalidateFormData(String formKey,String formData,FormDefinition formDefinition){ValidationResult result =newValidationResult();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;}/** * 验证单个字段 */privatevoidvalidateField(String fieldName,Object value,JSONObject fieldSchema,ValidationResult result){String type = fieldSchema.getString("type");if("string".equals(type)){if(!(value instanceofString)){ 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 +" 格式不正确");}}}// ... 其他类型的字段验证}publicstaticclassValidationResult{privateboolean valid =true;privateMap<String,String> errors =newHashMap<>();publicvoidaddError(String field,String message){ valid =false; errors.put(field, message);}publicbooleanisValid(){return valid;}publicMap<String,String>getErrors(){return errors;}}}
工作流服务
@Service@Slf4jpublicclassWorkflowService{@AutowiredprivateRuntimeService runtimeService;@AutowiredprivateTaskService taskService;/** * 启动审批流程实例 */@TransactionalpublicStringstartProcessInstance(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);thrownewRuntimeException("启动审批流程实例失败", e);}}/** * 完成审批任务 */@TransactionalpublicvoidcompleteTask(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);thrownewRuntimeException("完成审批任务失败", e);}}/** * 提交表单并启动审批流程 */@TransactionalpublicStringsubmitFormWithWorkflow(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){thrownewRuntimeException("表单未关联审批流程或表单定义不存在: "+ formKey);}// 3. 启动审批流程Map<String,Object> variables =newHashMap<>(); 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工作流引擎定义审批流程:

<processid="leave_approval_wf"name="请假审批流程"isExecutable="true"><startEventid="startevent1"name="开始"></startEvent><userTaskid="dept_leader_approve"name="部门领导审批"activiti:assignee="${deptLeader}"><extensionElements><activiti:taskListenerevent="create"class="com.example.listener.TaskCreateListener"/></extensionElements></userTask><exclusiveGatewayid="check_duration"name="请假天数检查"></exclusiveGateway><userTaskid="hr_approve"name="HR审批"activiti:assignee="hr_user"></userTask><endEventid="endevent1"name="结束"></endEvent><sequenceFlowid="flow1"sourceRef="startevent1"targetRef="dept_leader_approve"></sequenceFlow><sequenceFlowid="flow2"sourceRef="dept_leader_approve"targetRef="check_duration"><conditionExpressionxsi:type="tFormalExpression"><![CDATA[${approvalResult == 'approved'}]]></conditionExpression></sequenceFlow><sequenceFlowid="flow3"sourceRef="check_duration"targetRef="hr_approve"><conditionExpressionxsi:type="tFormalExpression"><![CDATA[${duration > 3}]]></conditionExpression></sequenceFlow><sequenceFlowid="flow4"sourceRef="check_duration"targetRef="endevent1"><conditionExpressionxsi:type="tFormalExpression"><![CDATA[${duration <= 3}]]></conditionExpression></sequenceFlow><sequenceFlowid="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分钟)

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

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

核心优势

1. 极致开发效率

  • 配置即开发:通过JSON配置即可完成表单开发
  • 模板复用:相同类型的表单可以复用模板
  • 可视化配置:提供可视化配置界面,业务人员也能参与

2. 强大的扩展性

  • 字段类型丰富:支持文本、数字、日期、选择等多种类型
  • 验证规则灵活:支持必填、长度、格式、范围等多种验证
  • 流程自定义:支持复杂的审批流程配置

3. 降低维护成本

  • 配置驱动:表单修改无需代码发布
  • 版本管理:支持表单定义的版本控制
  • 权限控制:细粒度的权限管理

最佳实践

1. 性能优化

  • 使用Redis缓存表单定义,减少数据库查询
  • 对表单渲染结果进行适当缓存
  • 实现分页查询表单实例

2. 安全考虑

  • 对表单数据进行严格验证
  • 实现操作权限控制
  • 记录操作日志

3. 监控和运维

  • 监控表单提交成功率
  • 跟踪审批流程执行情况
  • 提供管理后台进行配置管理

总结

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

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

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

低代码不是万能的,但在合适的场景下,它确实能发挥巨大价值。希望这个方案能对大家有所帮助,让我们一起告别重复的CRUD开发!


服务端技术精选 - 专注后端技术分享,与你一起成长!

Read more

AI绘画新选择:Z-Image-Turbo与Stable Diffusion对比体验

AI绘画新选择:Z-Image-Turbo与Stable Diffusion对比体验 如果你最近在AI绘画工具间反复横跳——等SDXL模型下载到怀疑人生、调参调到显存报警、生成一张图要喝三杯咖啡,那今天这篇实测可能就是你一直在找的“那个开关”。我们不聊参数量、不讲训练方法,就用最真实的工作流场景:同一台RTX 4090D机器、同一个提示词、同一张显示器,把Z-Image-Turbo和Stable Diffusion XL(SDXL 1.0)面对面拉出来比一比。不是理论对比,是手按回车键后,看谁先弹出那张图。 1. 先说结论:快不是噱头,是实打实的体验差 Z-Image-Turbo不是“又一个扩散模型”,它是为生产环境重新定义“生成”这个动作的工具。而Stable Diffusion,依然是那个你熟悉、信赖、但越来越像“需要定期保养的老朋友”的经典方案。它们的区别,不是“好与坏”,而是“快刀切菜”和“慢火炖汤”的分工差异。

Flutter 三方库 llm_json_stream 的鸿蒙化适配指南 - 掌控 LLM 流式 JSON 解析、大模型解析实战、鸿蒙级精密 AIGC 专家

Flutter 三方库 llm_json_stream 的鸿蒙化适配指南 - 掌控 LLM 流式 JSON 解析、大模型解析实战、鸿蒙级精密 AIGC 专家

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 llm_json_stream 的鸿蒙化适配指南 - 掌控 LLM 流式 JSON 解析、大模型解析实战、鸿蒙级精密 AIGC 专家 在鸿蒙跨平台应用执行大型语言模型(LLM)的流式交互(如实时获取大模型生成的结构化 JSON 数据、处理非完整的 JSON 片段解析或是实现一个具备极致反馈速度的 AI 驱动表单)时,如果依赖传统的 jsonDecode,极易在处理“不完整字符串(Chunk)”、“语法中断”或“非预期的文本噪声”时陷入解析异常死循环。如果你追求的是一种完全对齐流式解析规范、支持实时恢复 JSON 结构且具备极致容错性能的方案。今天我们要深度解析的 llm_json_stream—

【论文阅读103】pinn-review-科学机器学习中的物理信息神经网络:现状与展望

【论文阅读103】pinn-review-科学机器学习中的物理信息神经网络:现状与展望

科学机器学习中的物理信息神经网络:现状与展望 作者:Salvatore Cuomo¹ · Vincenzo Schiano Di Cola² · Fabio Giampaolo¹ · Gianluigi Rozza³ · Maziar Raissi⁴ · Francesco Piccialli¹ 在线发表:2022年7月26日 摘要 物理信息神经网络(Physics-Informed Neural Networks,PINNs)是一类将模型方程(如偏微分方程,PDE)直接嵌入神经网络结构中的神经网络(NN)。目前,PINNs 已被广泛用于求解偏微分方程、分数阶方程、积分-微分方程以及随机偏微分方程。这一新兴方法作为一种多任务学习框架出现,在该框架中,神经网络不仅需要拟合观测数据,还需最小化 PDE 残差。 本文对物理信息神经网络相关文献进行了全面综述:研究的主要目标是阐明这类网络的特征、优势与局限性。同时,本文还涵盖了更广义的基于配点法(collocation-based)的物理约束神经网络研究,包括从最初的基础 PINN(

【无人机控制】无人机控制系统的MATLAB仿真实现,包含快速扩展随机树(RRT)路径规划、B样条轨迹生成及线性规划(LP)轨迹优化

✅作者简介:热爱科研的Matlab仿真开发者,擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 🍎 往期回顾关注个人主页:Matlab科研工作室  👇 关注我领取海量matlab电子书和数学建模资料  🍊个人信条:格物致知,完整Matlab代码获取及仿真咨询内容私信。 🔥 内容介绍 无人机自主飞行控制系统的核心目标是在复杂环境中实现 “安全路径规划 - 平滑轨迹生成 - 最优轨迹跟踪” 的闭环控制。本文设计的系统包含三大核心模块:快速扩展随机树(RRT)路径规划(解决 “无碰撞路径搜索” 问题)、B 样条轨迹生成(解决 “路径平滑化” 问题)、线性规划(LP)轨迹优化(解决 “动态约束适配” 问题),辅以传感器数据融合与姿态控制模块,形成完整的自主飞行解决方案。 系统工作流程如下: 1. 环境感知模块获取障碍物信息与飞行任务(起点、终点、飞行区域边界); 2. RRT 路径规划模块在障碍物环境中搜索无碰撞初始路径;