性能文章>Map传参优雅检验,试试json schema validator>

Map传参优雅检验,试试json schema validator原创

1月前
174203

背景

笔者目前所在团队的代码年代已久,早年规范缺失导致现在维护成本激增,举一个深恶痛疾的例子就是方法参数使用Map“一撸到底“,说多了都是泪,我常常在团队内自嘲“咱硬是把java写成了JavaScript、php”,代码灵活的让人怀疑人生,你根本不知道方法需要什么、返回什么,新人来了想快速上手不可能的,老老实实debug吧,另一方面,以往的校验大多数都是放在前端做的,后端几乎没有校验,所幸业务量没上来,没有引起不速之客的造访,要不程序员早被拉去祭天多少回了。

 

恰逢接到一个任务在团队内推广参数校验,希望能带来一些业内的最佳实践,开始我内心是拒绝的:“这么成熟的东西还需要普及什么呢,网上一搜一大篇”,罢了罢了,拿人钱财,从开始的抵触到后来的坦然,还是有不少收获,待我娓娓道来。

 

业内实践

1.简单粗暴的if else

if(a == null){

     return Result.failure(400,"a不能为空);

}

 

if(StringUtil.isEmpty(b)){

    return Result.failure(400,"b不能为空);

}

通俗易懂的校验方式,不使用框架,代码重复度会比较高,参数较少的简单场景可以这么用。

 

2.JSR规范+hibernate validator框架【成熟体系】

JSR提供了一套Bean校验规范的API,维护在包javax.validation.constraints下。该规范使用属性或者方法参数或者类上的一套简洁易用的注解来做参数校验。开发者在开发过程中,仅需在需要校验的地方加上形如@NotNull, @NotEmpty , @Email的注解,就可以将参数校验的重任委托给一些第三方校验框架来处理。

接入validation api及hibernate validator后,做校验就很easy了

@Entity
public class Blog {
      public Blog() {
   }
   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private long id;
   @NotNull
   @Size(min = 2, message = "Blog Title must have at least 2 characters")
   private String blogTitle;
   @NotBlank(message = "Blog Editor cannot be blank")
   private String blogEditor;
   @Email(message = "Email should be valid")
   private String blogEmail;
  
// Getters and Setters
}


@RestController
@RequestMapping("api/v1")
public class BlogController {
 
  
  @PostMapping("/blog")
  public Blog saveBlog(@Valid @RequestBody Blog savedBlog,BindingResult result) {
       if(result.hasErrors()){
         // 获取异常信息对象
            List<ObjectError> errors = result.getAllErrors();
            // 将异常信息输出
            for (ObjectError error : errors) {
                //执行自己的逻辑
            }
       }
   }

 

场景复杂,参数多,这时我们就需要借助框架来助力,减少重复工作量,框架久经验证,bug相对来讲较少。

 

想深入了解的,可以参考官方文档

Getting Started | Validating Form Input (spring.io)

 

3.json schema+json schema validator【新宠】

json schema 是用于验证 JSON 数据结构的强大工具,适用于表单灵活变动、controller层没有定义对象数据绑定情况下(我们现在的场景就是大量使用Map接收前端数据,没法使用JSR规范+hibernate validator框架

@RestController
@RequestMapping("api/v1")
public class BlogController {
 
  
   @PostMapping("/saveChnl")
  public void saveChnl(HttpServletRequest request) {
       Map<String,Object> chnl = 
      JsonUtils.toMap(request.getParameter("data"));
   }

鉴于此我们需要引入正统的json schema标准来解决历史问题,json schema已经有成熟的规范,不需要我们自己造轮子,后面重点介绍json schema这种方式。

 

json schema了解

1.认识json schema

json schema 是用于验证 JSON 数据结构的强大工具,简单来说就是通过定义一些规则来约束json数据的合法性,比如类型、是否必填、最大值、最小值、正则等,看一个具体的例子:

{
  "$schema":"http://json-schema.org/draft-07/schema#",
  "$id": "http://com.公司名.项目名.模块名.子模块/schemas/channel_add.json",
  "title":"门户模块-栏目编辑",
  "description":"门户模块-栏目编辑-json schema 配置信息",
  "type":"object",
  "properties":{
    "chnlcode":{
      "description":"门户编码",
      "type":"string"
    },
    "chnlid":{
      "type":"string",
      "description":"门户编码id"
    },
    "data": {
      "type": "object",
      "properties":{
        "disname":{
          "description":"显示名称",
          "type":"string"
        },
        "chnlorder":{
          "description":"排序",
          "type":"string"
        }
      },
      "required": [
        "vmuri"
      ]
    }
  },
  "message": {
    "required": "必填"
  },
  "required":[
    "chnlid",
    "chnlcode"
  ]
}
$schema: $schema关键字来声明将使用哪个版本的 JSON 架构规范,我们统一使用draft-07;
$id:唯一标识符,格式为url格式,我们约定格式为http://代码包标识/schemas/有意义的名称.json,比如 http://com.公司名.模块名.ec/schemas/channel_add.json 代表ec工程下频道添加json对象的schema;

title: 有意义的名称;

description:对title的补充;

type:类型,object代表对象,还可以为string,integer,array等

properties:对象的属性(键值对)是使用 properties关键字定义的,properties是一个对象,其中每个键是属性的名称,每个值是用于验证该属性的模式;

message:自定义的错误信息;

required:必填字段;

 

具体解释请参考:

1.什么是json schema

2.json-shcema规范

 

1.1 json-schema 版本选择

  根据一些社区的统计,draft-7是目前使用最广泛的版本,以史为鉴,我们也选择draft-07即可。

 

2.定义json schema

这一步我们开始定义符合自己要求的json schema,我们需要限制

1.chnlorder是一个数字;

2.indexCount是一个数字而且需要大于0。

 

要校验对象的数据结构如下(有删减):

{
  "disname": "优质供应商",
  "chnlcode": "exsupplier",
  "chnltype": "0",
  "chnldesc": "优质供应商",
  "chnlorder": "99",
  "extdata": {
    "indexCount": "12"
  }
}

chnlorder是参数对象的属性,indexCount是参数对象中嵌套对象extdata的属性。

最终形成一份这样的json schema

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://com.公司名.项目名.模块名.子模块/schemas/channel_add.json",
  "title": "门户模块-栏目编辑",
  "description": "门户模块-栏目编辑-json schema 配置信息",
  "type": "object",
  "properties": {
    "disname": {
      "description": "显示名称",
      "type": "string"
    },
    "chnlorder": {
      "description": "排序,正整数",
      "type": "string",
      "pattern": "^[1-9]\\d*$"
    },
    "extdata": {
      "type": "object",
      "properties": {
        "indexCount": {
          "description": "显示条数,空串或者正整数",
          "type": "string",
          "pattern": "^$|[1-9][0-9]*"
        }
      }
    }
  },
  "message": {
    "required": "必填"
  },
  "required": [
    "chnlcode"
  ]
}

 

  

3.选择一个趁手的json schema validator

第2步我们已经定义好了json schema,相当于制订了规范,现在还需要找到一个validator来识别规范,根据官方的介绍有以下备选项:

https://json-schema.org/implementations.html#validator-java

 

结合以下考量点:

1.受欢迎程度

start 过百的有everit-org/json-schemanetworknt/json-schema-validator,当然还有官方未提到的https://github.com/java-json-tools/json-schema-validator(start超过1.5k)

2.依赖的json库

everit-org/json-schema底层基于 org.json API ,意味着还需要引入新json库,而networknt/json-schema-validatorhttps://github.com/java-json-tools/json-schema-validator,底层基于jackson,正好项目中的JsonUtils也是基于jackson实现,不需要引入其他json库;

3.性能

根据性能测试networknt最优

4.近期是否有更新

https://github.com/java-json-tools/json-schema-validator最后一次更新在2020年,networknt最近还有更新;

5.json-schema的支持程度

https://github.com/java-json-tools/json-schema-validator只支持到draft4,而networknt支持到draft-2019-09-formerly-known-as-draft-8

 

综合对比,最终选择了networknt/json-schema-validator。

 

实践

经过前面的准备工作,我们已经定义了schema,选择了validator,现在开始实践到我们的代码中

1.JsonUtils工具类扩展原来的转换方法,增加验证逻辑

    /**
     * json string convert to map,有校验逻辑,如果校验不通过抛出异常
     */
    @SuppressWarnings("unchecked")
    public static <T> Map<String, Object> toMapValid(String jsonStr,String schemaPath) {
        if (StringUtil.isBlank(jsonStr)) {
            return null;
        }

        Assert.hasLength(schemaPath,"schemaPath不能为空");
        try {
            JsonNode jsonNode = objectMapper.readTree(jsonStr);
            Set<ValidationMessage> validationMessageSet =  JsonSchemaValidatorUtil.validate(jsonNode,schemaPath);
            if(!CollectionUtils.isEmpty(validationMessageSet)){
                for(ValidationMessage validationMessage : validationMessageSet){
                    throw new IllegalArgumentException("参数不合法:"+validationMessage.getMessage());
                }
            }
            return objectMapper.convertValue(jsonNode,Map.class);
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }


    public static Set<ValidationMessage> validate(JsonNode checkData,String schemaPath){
        schemaPath = "conf/validation/json/schema/"+schemaPath;
        JsonNode schemaJson = null;
        try {
            schemaJson = getJsonNodeFromClasspath(schemaPath);
        } catch (IOException e) {
            throw new IllegalArgumentException("查找schema失败,请检查"+schemaPath+"是否存在");
        }
        JsonSchema schema = getJsonSchemaFromJsonNodeAutomaticVersion(schemaJson);
        Set<ValidationMessage> errors = schema.validate(checkData);
        return errors;
    }
 

2.编写json schema文件

位置:src\main\resources\conf\validation\json\schema\jc-ec\xxx.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://公司名.项目名.模块名.子模块/schemas/channel_add_or_update.json",
  "title": "门户模块-栏目编辑",
  "description": "门户模块-栏目编辑-json schema配置信息,校验前端传递的data是否合法",
  "type": "object",
  "properties": {
    "disname": {
      "description": "显示名称",
      "type": "string"
    },
    "chnlorder": {
      "description": "排序,正整数",
      "type": "string",
      "pattern": "^[1-9]\\d*$"
    },
    "extdata": {
      "type": "object",
      "properties": {
        "indexCount": {
          "description": "显示条数,空串或者正整数",
          "type": "string",
          "pattern": "^$|^[1-9]\\d*$"
        }
      }
    }
  },
  "required": [
    "disname",
    "chnlcode"
  ]
}

3.业务代码中json转换方法切换为带有验证逻辑的

Map<String,Object> chnl = JsonUtils.toMapValid("jsonStr","ec/channel_add_or_update.json");

 

4.效果

 

 

总结

目前大量的校验集中在前端,后台代码鲜有校验,长此下去对系统的安全问题是很大的挑战,鉴于此开发应该加强后台代码的校验,推荐使用“JSR规范+hibernate validator框架“来实现校验功能,因为规则在java对象上可读性相对于json schema更高,新人的接受度也更高,如果是老代码,开发可以根据实际情况去抉择:

如果定义了对象接收参数,推荐使用JSR规范+hibernate validator框架。

如果采用Map接受json格式参数,推荐使用json schema validator。

 

推荐阅读

1.什么是json schema

2.json-shcema规范

 

 

分类:标签:
请先登录,感受更多精彩内容
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

Kafka的生产者优秀架构设计
前言 Kafka 是一个高吞吐量的分布式的发布订阅消息系统,在全世界都很流行,在大数据项目里面使用尤其频繁。笔者看过多个大数据开源产品的源码,感觉 Kafka 的源码是其中质量比较上乘的一个,这得益于
如此火爆的ZooKeeper,到底如何选主?
前言 前面一篇文章我们已经给大家讲解了ZooKeeper的核心的原理,这一篇我们重点分析ZooKeeper的Leader选举算法。Leader的选举是ZooKeeper的最重要技术之一,也是保证分布式
这么流行的ZooKeeper,原来是这样设计的!
为什么会有ZooKeeper 我们知道要写一个分布式应用是非常困难的,主要原因就是局部故障。一个消息通过网络在两个节点之间传递时,网络如果发生故障,发送方并不知道接收方是否接收到了这个消息。有可能是收
世界最优秀的分布式文件系统架构演进之路
前言 Hadoop是一个由Apache基金会所开发的分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。Hadoop实现了一个分布式文件系
微服务架构何去何从?
前言微服务架构模式经过5年多的发展,在各行各业如火如荼地应用和实践。如何在企业中优雅地设计微服务架构?是企业面对的一个重要问题。本文将讲述微服务架构1.0设计与实践以及面临问题和破局,最后讲述微服务架
笔记整理:技术架构涵盖内容和演变过程总结
前言架构,说的是开发用的框架吗?对于刚接触编程的新人来说,可能并不能很清楚的知道架构是怎么来的,都包括什么内容。如果非得说什么架构,那么可能就是目前在 IDEA 中打开的工程就是架构。抛开技术圈内的架
RocketMQ 在使用上的一些排坑和优化
前言:RocketMQ 在我们的项目中使用非常广泛,在使用的过程中,也遇到了很多的问题。比如没有多环境的隔离,在多个版本同时开发送测的情况下,互相干扰严重。RocketMQ 的投递可能会失败,导致丢失
年纪轻轻,为什么要搞中间件开发?“路怎么走,让你们自己挑”
作者:小傅哥博客:[https://bugstack.cn](https://bugstack.cn)沉淀、分享、成长,让自己和他人都能有所收获!😄 一、前言`年纪轻轻,为什么要搞中间件开发?`五年