告别重复编码!SpringBoot + JSON Schema 动态表单开发
你是否也厌倦了每次产品提需求都要改表结构、写一堆重复的校验代码?是否也曾因为表单字段变化频繁而焦头烂额?今天,我要和大家分享一个能彻底改变你开发体验的技术方案:SpringBoot + JSON Schema 动态表单开发,让你告别重复编码,拥抱高效开发!
一、传统表单开发的痛点
在开始介绍解决方案之前,我们先来看看传统表单开发存在的问题:
1.1 表结构僵化
-- 传统用户表结构
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100),
phone VARCHAR(20),
age INT
);
当产品经理说要增加一个性别字段时,我们需要:
- 修改数据库表结构
- 修改对应的实体类
- 修改前端表单页面
- 增加字段校验逻辑
- 测试验证
如果再要增加一个地址字段呢?又要重复一遍上面的步骤。
1.2 校验逻辑重复
@PostMapping("/user")
public ResponseEntity<?> createUser(@RequestBody User user) {
// 字段校验逻辑
if (StringUtils.isBlank(user.getName())) {
return ResponseEntity.badRequest().body("姓名不能为空");
}
if (!isValidEmail(user.getEmail())) {
return ResponseEntity.badRequest().body("邮箱格式不正确");
}
if (user.getAge() <= 0 || user.getAge() > 150) {
return ResponseEntity.badRequest().body("年龄必须在1-150之间");
}
// 保存用户...
return ResponseEntity.ok().build();
}
每当字段发生变化时,这些校验逻辑都需要手动维护,不仅容易出错,而且代码冗余严重。
1.3 扩展性差
随着业务发展,不同类型的用户可能需要不同的字段:
- 普通用户:姓名、邮箱、电话
- 企业用户:公司名称、营业执照、法人代表
- VIP用户:积分、等级、专属客服
传统方式很难灵活应对这种差异化需求。
二、JSON Schema 动态表单的优势
面对这些问题,我们引入了 JSON Schema 动态表单方案:
2.1 什么是 JSON Schema?
JSON Schema 是一个基于 JSON 的数据描述格式,用于描述和校验 JSON 数据结构。它可以定义:
- 字段类型(字符串、数字、布尔值等)
- 字段约束(长度、范围、格式等)
- 必填字段
- 嵌套结构
2.2 方案架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端页面 │ │ 后端服务 │ │ 数据存储 │
│ (动态渲染) │◄──►│ (Schema校验) │◄──►│ (JSON存储) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Form Schema │ │ JSON Schema │ │ MongoDB │
│ (表单定义) │ │ (校验规则) │ │ (或JSON字段) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
2.3 核心优势
- 灵活性强:通过修改 Schema 即可调整表单结构,无需改动代码
- 校验统一:一套 Schema 同时用于前后端校验,保证一致性
- 扩展性好:支持不同类型表单的动态切换
- 维护成本低:业务人员也可参与表单配置
三、核心实现思路
3.1 Schema 定义示例
让我们先看一个用户信息表单的 Schema 定义:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "用户信息表单",
"properties": {
"name": {
"type": "string",
"title": "姓名",
"maxLength": 50,
"minLength": 2
},
"email": {
"type": "string",
"title": "邮箱",
"format": "email"
},
"phone": {
"type": "string",
"title": "手机号",
"pattern": "^1[3-9]\\d{9}$"
},
"age": {
"type": "integer",
"title": "年龄",
"minimum": 1,
"maximum": 150
},
"address": {
"type": "object",
"title": "地址信息",
"properties": {
"province": {
"type": "string",
"title": "省份"
},
"city": {
"type": "string",
"title": "城市"
},
"detail": {
"type": "string",
"title": "详细地址"
}
},
"required": ["province", "city"]
}
},
"required": ["name", "email"]
}
3.2 SpringBoot 集成实现
3.2.1 依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.0.87</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
3.2.2 Schema 校验服务
@Service
public class FormValidationService {
private final JsonSchemaFactory schemaFactory = JsonSchemaFactory.getInstance();
/**
* 根据 Schema 校验表单数据
*
* @param schema Schema定义
* @param formData 表单数据
* @return 校验结果
*/
public ValidationResult validateForm(JsonNode schema, JsonNode formData) {
try {
// 解析 Schema
JsonSchema jsonSchema = schemaFactory.getSchema(schema);
// 执行校验
Set<ValidationMessage> validationMessages = jsonSchema.validate(formData);
if (validationMessages.isEmpty()) {
return ValidationResult.success();
} else {
return ValidationResult.failure(validationMessages);
}
} catch (Exception e) {
return ValidationResult.error("Schema解析失败: " + e.getMessage());
}
}
/**
* 获取表单 Schema
*
* @param formType 表单类型
* @return Schema定义
*/
public JsonNode getFormSchema(String formType) {
// 实际项目中可以从数据库或配置中心获取
try {
String schemaJson = loadSchemaFromStorage(formType);
return objectMapper.readTree(schemaJson);
} catch (Exception e) {
throw new RuntimeException("获取Schema失败", e);
}
}
private String loadSchemaFromStorage(String formType) {
// 简化示例,实际应从数据库或配置中心加载
if ("user".equals(formType)) {
return "{\n" +
" \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n" +
" \"type\": \"object\",\n" +
" \"properties\": {\n" +
" \"name\": {\"type\": \"string\", \"minLength\": 2},\n" +
" \"email\": {\"type\": \"string\", \"format\": \"email\"}\n" +
" },\n" +
" \"required\": [\"name\", \"email\"]\n" +
"}";
}
return "{}";
}
}
3.2.3 表单控制器
@RestController
@RequestMapping("/api/forms")
public class DynamicFormController {
@Autowired
private FormValidationService validationService;
@Autowired
private FormDataService formDataService;
/**
* 提交表单数据
*
* @param formType 表单类型
* @param formData 表单数据
* @return 提交结果
*/
@PostMapping("/{formType}/submit")
public ResponseEntity<?> submitForm(
@PathVariable String formType,
@RequestBody JsonNode formData) {
try {
// 1. 获取对应表单的 Schema
JsonNode schema = validationService.getFormSchema(formType);
// 2. 校验表单数据
ValidationResult validationResult = validationService.validateForm(schema, formData);
if (!validationResult.isSuccess()) {
return ResponseEntity.badRequest()
.body(Map.of("errors", validationResult.getMessages()));
}
// 3. 保存表单数据
String dataId = formDataService.saveFormData(formType, formData);
return ResponseEntity.ok(Map.of("id", dataId, "message", "提交成功"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "提交失败: " + e.getMessage()));
}
}
/**
* 获取表单 Schema
*
* @param formType 表单类型
* @return Schema定义
*/
@GetMapping("/{formType}/schema")
public ResponseEntity<JsonNode> getFormSchema(@PathVariable String formType) {
try {
JsonNode schema = validationService.getFormSchema(formType);
return ResponseEntity.ok(schema);
} catch (Exception e) {
return ResponseEntity.notFound().build();
}
}
}
3.3 数据存储设计
对于动态表单数据,我们有两种存储方案:
方案一:JSON 字段存储(适用于关系型数据库)
CREATE TABLE form_data (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
form_type VARCHAR(50) NOT NULL,
form_data JSON NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
@Entity
@Table(name = "form_data")
public class FormData {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "form_type")
private String formType;
@Column(name = "form_data", columnDefinition = "json")
private String formData;
@CreationTimestamp
@Column(name = "created_at")
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// getters and setters...
}
方案二:MongoDB 存储(天然支持 JSON)
@Document(collection = "form_data")
public class MongoFormData {
@Id
private String id;
private String formType;
private Map<String, Object> formData;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// getters and setters...
}
四、实际应用案例
4.1 用户注册表单
{
"formType": "user_registration",
"schema": {
"type": "object",
"properties": {
"username": {
"type": "string",
"pattern": "^[a-zA-Z0-9_]{4,20}$",
"title": "用户名"
},
"password": {
"type": "string",
"minLength": 8,
"title": "密码"
},
"confirmPassword": {
"type": "string",
"const": "${password}",
"title": "确认密码"
},
"email": {
"type": "string",
"format": "email",
"title": "邮箱"
}
},
"required": ["username", "password", "confirmPassword", "email"]
}
}
4.2 问卷调查表单
{
"formType": "survey",
"schema": {
"type": "object",
"properties": {
"satisfaction": {
"type": "integer",
"minimum": 1,
"maximum": 5,
"title": "满意度评分"
},
"recommend": {
"type": "boolean",
"title": "是否会推荐给朋友"
},
"comments": {
"type": "string",
"maxLength": 500,
"title": "意见建议"
}
},
"required": ["satisfaction", "recommend"]
}
}
五、高级特性实现
5.1 条件校验
{
"type": "object",
"properties": {
"hasCar": {
"type": "boolean",
"title": "是否有车"
},
"carInfo": {
"type": "object",
"properties": {
"brand": {"type": "string"},
"model": {"type": "string"}
}
}
},
"if": {
"properties": {
"hasCar": {"const": true}
}
},
"then": {
"required": ["carInfo"]
}
}
5.2 动态选项加载
{
"type": "object",
"properties": {
"province": {
"type": "string",
"title": "省份",
"enum": ["${api:/api/options/provinces}"]
},
"city": {
"type": "string",
"title": "城市",
"enum": ["${api:/api/options/cities?province=${province}}"]
}
}
}
六、性能优化建议
6.1 Schema 缓存
@Component
public class SchemaCache {
private final Cache<String, JsonNode> schemaCache =
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public JsonNode getSchema(String formType) {
return schemaCache.get(formType, this::loadSchemaFromDatabase);
}
}
6.2 批量校验
@PostMapping("/batch-submit")
public ResponseEntity<?> batchSubmitForms(
@RequestBody List<FormSubmission> submissions) {
List<ValidationResult> results = new ArrayList<>();
// 批量校验
for (FormSubmission submission : submissions) {
JsonNode schema = schemaCache.get(submission.getFormType());
ValidationResult result = validationService.validateForm(
schema, submission.getFormData());
results.add(result);
}
// 统一处理结果
return ResponseEntity.ok(results);
}
七、总结
通过 SpringBoot + JSON Schema 的动态表单方案,我们实现了:
- 开发效率提升:告别重复编码,通过配置即可实现表单变更
- 维护成本降低:统一的校验规则,前后端一致性保证
- 扩展性增强:支持任意复杂度的表单结构
- 用户体验优化:实时校验反馈,减少提交失败
这套方案已经在多个项目中得到应用,显著提升了开发效率和系统灵活性。对于需要频繁变更表单结构的业务场景,强烈推荐大家尝试这种方案。
当然,任何技术方案都不是银弹,在实际应用中还需要根据具体业务场景进行调整和优化。希望今天的分享能给大家带来一些启发和帮助。
关注「服务端技术精选」,获取更多干货技术文章!
标题:告别重复编码!SpringBoot + JSON Schema 动态表单开发
作者:jiangyi
地址:http://www.jiangyi.space/articles/2025/12/22/1766415866918.html
- 一、传统表单开发的痛点
- 1.1 表结构僵化
- 1.2 校验逻辑重复
- 1.3 扩展性差
- 二、JSON Schema 动态表单的优势
- 2.1 什么是 JSON Schema?
- 2.2 方案架构
- 2.3 核心优势
- 三、核心实现思路
- 3.1 Schema 定义示例
- 3.2 SpringBoot 集成实现
- 3.2.1 依赖配置
- 3.2.2 Schema 校验服务
- 3.2.3 表单控制器
- 3.3 数据存储设计
- 方案一:JSON 字段存储(适用于关系型数据库)
- 方案二:MongoDB 存储(天然支持 JSON)
- 四、实际应用案例
- 4.1 用户注册表单
- 4.2 问卷调查表单
- 五、高级特性实现
- 5.1 条件校验
- 5.2 动态选项加载
- 六、性能优化建议
- 6.1 Schema 缓存
- 6.2 批量校验
- 七、总结
0 评论