大数跨境
0
0

Spring Boot通过Filter + JSON Schema 验证数据合法性

Spring Boot通过Filter + JSON Schema 验证数据合法性 Spring全家桶实战案例
2025-08-21
0
导读:Spring Boot通过Filter + JSON Schema 验证数据合法性
Spring Boot 3实战案例锦集PDF电子书已更新至130篇!
图片

🎉🎉《Spring Boot实战案例合集》目前已更新159个案例,我们将持续不断的更新。文末有电子书目录。

💪💪永久更新承诺

我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务

💌💌如何获取
订阅我们的合集点我订阅,并通过私信联系我们,我们将第一时间将电子书发送给您。

→ 现在就订阅合集

环境:SpringBoot3.4.2



1. 简介

在核心业务逻辑处理之前对API请求数据进行验证,有助于尽早发现错误,确保应用程序行为可预测。对于处理JSON数据的API而言,JSON Schema提供了一种正式的方法来描述请求必须遵循的结构、数据类型和限制条件。这使得我们能够在将数据转换为Java对象之前,进行数据有效性检查。

在本篇文章中,我们将深入探讨如何借助Filter来实现数据有效性校验。Filter作为Spring Boot请求处理流程中的前端关卡,具有在请求早期介入处理的特性。通过Filter进行数据有效性校验,能够在数据还未进入后续的核心业务处理逻辑,甚至在JSON数据转换为Java对象之前,就及时发现数据存在的问题。一旦检测到数据不符合要求,便可立即返回相应的错误信息,避免无效数据进一步在系统中流转,从而有效提升系统的稳定性和处理效率。

2.实战案例

2.1 引入依赖

<!--该依赖用来生成要验证的json schema文件--><dependency>  <groupId>com.github.victools</groupId>  <artifactId>jsonschema-generator</artifactId>  <version>4.38.0</version></dependency><!--该依赖用来校验json数据--><dependency>  <groupId>com.networknt</groupId>  <artifactId>json-schema-validator</artifactId>  <version>1.5.6</version></dependency>

说明:

  • jsonschema-generator 可以根据Java对象自动生成对应的JSON Schema文件,方便开发者定义和验证JSON数据的结构

  • json-schema-validator 用于根据生成的JSON Schema文件来校验实际的JSON数据是否符合预期的结构和格式

2.2 定义Controller接口

声明实体对象

public class User {  private String name ;  private String password ;  private Integer age ;  private String email ;  // getters, setters, constructors}

Controller接口

@RestController@RequestMapping("/users")public class UserController {  @PostMapping("")  public ResponseEntity<?> create(@RequestBody User user) {    return ResponseEntity.ok(user) ;  }}

2.3 生成JSON Schema

接下来,我们将为上述 User 实体生成对应的 JSON Schema 文件。此步骤旨在为后续在 Filter 中进行数据校验工作做好充分准备。

public static String genUserSchema() {  // 创建SchemaGeneratorConfigBuilder,指定Schema版本  SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON) ;  // 设置必须的字段  configBuilder.forFields()    .withRequiredCheck(field -> "name".equals(field.getName()))    .withRequiredCheck(field -> "email".equals(field.getName()))    .withIgnoreCheck(field -> "password".equals(field.getName()));  // 构建SchemaGeneratorConfig  SchemaGeneratorConfig config = configBuilder.build();  // 创建SchemaGenerator实例  SchemaGenerator generator = new SchemaGenerator(config);  // 根据User类生成Schema内容  JsonNode jsonSchema = generator.generateSchema(User.class);  // 将JsonNode转换为ObjectNode以便修改  ObjectNode schemaObject = (ObjectNode) jsonSchema;  // 设置additionalProperties为false,禁止额外属性  schemaObject.put("additionalProperties"false);  // 打印生成的JSON Schema  return jsonSchema.toPrettyString() ;}

执行上面的方法生成JSON Schema如下:

我们将上面的内容保存到classpath: schemas/user-schema.json

2.4 定义Filter

public class ValidationJsonFilter extends OncePerRequestFilter {  private final JsonSchema schema;  private final ObjectMapper mapper ;  public ValidationJsonFilter(ObjectMapper mapper) {    InputStream is;    try {      is = new ClassPathResource("schemas/user-schema.json").getInputStream();    } catch (IOException e) {      throw new RuntimeException(e) ;    }    this.schema = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012).getSchema(is) ;    this.mapper = mapper ;  }  @Override  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)      throws ServletException, IOException {    if (request.getServletPath().equals("/users") && "POST".equalsIgnoreCase(request.getMethod())) {      String body = readBody(request);      final JsonNode node;      try {        node = mapper.readTree(body);      } catch (JsonProcessingException ex) {        response.setContentType("application/json;charset=utf-8");        String content = this.mapper.writeValueAsString(Map.of("code", -1"message""JSON数据错误"));        response.getWriter().println(content);        return;      }      Set<ValidationMessage> errors = schema.validate(node);      if (!errors.isEmpty()) {        response.setContentType("application/json;charset=utf-8");        List<String> list = errors.stream().map(ValidationMessage::getMessage).toList() ;        mapper.writeValue(response.getWriter(),            Map.of("code", -1"message", list)) ;        return;      }      filterChain.doFilter(new CachedBodyRequestWrapper(request, body), response);      return;    }    filterChain.doFilter(request, response);  }  private String readBody(HttpServletRequest request) throws IOException {    return StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8) ;  }}

由于要多次读取请求body,所以我们必须对request对象进行保证

public class CachedBodyRequestWrapper extends HttpServletRequestWrapper {  private final String cachedBody;  public CachedBodyRequestWrapper(HttpServletRequest request, String cachedBody) {    super(request);    this.cachedBody = cachedBody;  }  public ServletInputStream getInputStream() throws IOException {    byte[] bytes = cachedBody.getBytes();    return new CachedBodyServletInputStream(new ByteArrayInputStream(bytes));  }  public BufferedReader getReader() throws IOException {    return new BufferedReader(new InputStreamReader(this.getInputStream()));  }  private static class CachedBodyServletInputStream extends ServletInputStream {    private final InputStream inputStream;    public CachedBodyServletInputStream(InputStream inputStream) {      this.inputStream = inputStream;    }    public boolean isFinished() {      try {        return inputStream.available() == 0;      } catch (IOException e) {return false;}    }    public boolean isReady() {return true;}    public void setReadListener(ReadListener readListener) {throw new UnsupportedOperationException();}    public int read() throws IOException {return inputStream.read();}  }}

最后,注册Filter

@BeanFilterRegistrationBean<ValidationJsonFiltervalidationJsonFilter(ObjectMapper mapper) {  FilterRegistrationBean<ValidationJsonFilter> reg = new FilterRegistrationBean<>() ;  reg.setFilter(new ValidationJsonFilter(mapper)) ;  reg.setUrlPatterns(List.of("/*")) ;  return reg ;}

2.5 测试

不输入任何属性

输出必填字段后

我们再添加其它字段,如:password或user不存在的字段



以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

一个接口,N 种实现?Spring Boot 中的10种切换方式

Tika 与 Spring Boot 的完美结合:支持任意文档解析的神器

高级开发!Spring Boot 乐观锁正确处理的3种方案,第三种方案最优雅

性能分析!利用Spring AOP构建方法执行耗时树状图

高级开发!Controller接口参数与响应结果四种记录方式,第四种对性能无任何影响

Spring Boot 优雅处理 JSON动态属性

Spring Boot 记录Controller接口请求日志7种方式,第六种性能极高

高级开发!基于Spring Boot自定义注解@Interceptor动态注册拦截器,非常强大

@HttpExchange 强势登场,彻底终结 Feign 时代!

惊呆了!Controller接口返回值支持17种逆天类型

全栈开发!Spring Boot 文件下载的N种神级姿势

优雅!Spring Boot 动态任务调度,灵活又强大

图片
图片
图片
图片
图片
图片
图片
图片
图片

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832