背景

笔者目前所在团队的代码年代已久,早年规范缺失导致现在维护成本激增,举一个深恶痛疾的例子就是方法参数使用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-schema和networknt/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-validator和https://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规范

Map传参优雅检验,试试json schema validator的更多相关文章

  1. restTemplate getForObject中map传参问题

    在使用restTemplate中getForObject的map传参形式时: 开始时我是这么调用的: RestTemplate rest = new RestTemplate(); Map<St ...

  2. RestTemplate post请求使用map传参 Controller 接收不到值的解决方案 postForObject方法源码解析.md

    结论 post方法中如果使用map传参,需要使用MultiValueMap来传递 RestTemplate 的 postForObject 方法有四个参数 String url => 顾名思义 ...

  3. python接口测试(post,get)-传参(data和json之间的区别)

    python接口测试如何正确传参: POST 传data:data是python字典格式:传参data=json.dumps(data)是字符串类型传参 #!/usr/bin/env python3 ...

  4. 02基于注解开发SpringMVC项目(jar包,异步,request,参数传递,多选的接收,Model传参,map传参,model传参,ajax,重定向,时间日期转换)

     1 所需jar包 项目结构如下: 2 web.xml配置文件的内容如下: <?xmlversion="1.0"encoding="UTF-8"?&g ...

  5. map传参上下文赋值的问题

    今天开发遇到一个问题就是声明一个map<String,String> param ,给param赋值,明明有结果但是就是返回为空:下面附上代码: 因为在一个大的循环中,param是公用赋值 ...

  6. HttpClient调用doGet、doPost、JSON传参及获得返回值

    调用 doPost:map传参 Map<String,Object> map = new HashMap<>(); map.put("test"," ...

  7. post参数的方法 json data 和特别的传参

    json格式传参: 那么久使用json的方式传参: json=payload data格式传参: 其他方式传参: 在webFormes里 value 的值不是普通的字符 要把value值先序列化在放入 ...

  8. jdbcTemplate传参使用Map或List

    List传参方式 举个例子 sql = "select * from table where id=? and param=?": sql中的参数要用?形式,然后使用list.ad ...

  9. WebApi传参总动员(四)

    前文介绍了Form Data 形式传参,本文介绍json传参. WebApi及Model: public class ValuesController : ApiController { [HttpP ...

随机推荐

  1. 解释 WEB 模块?

    Spring 的 WEB 模块是构建在 application context 模块基础之上,提供一个适 合 web 应用的上下文.这个模块也包括支持多种面向 web 的任务,如透明地处理 多个文件上 ...

  2. Python - 函数的五大参数

    Python的函数参数挺重要的,总结一下: (1)位置参数:没啥好说的,就是普通的参数. (2)默认参数: 参数形式:def power(x, n = 3): (在函数定义时通过对一个形参赋值的形式, ...

  3. chrome插件one-read开发1:准备

    阅读我的博客文章:chrome插件one-read开发:准备 前言 为啥要做这个,因为我原本想用chrome做一个书签管理的东西,但是很久没有碰过chrome的插件开发了.所以先做一个简单的,来熟悉下 ...

  4. python大佬养成计划----flask_bootstrap装饰网页

    flask_bootstrap Bootstrap 是 Twitter 开发的一个开源框架,它提供的用户界面组件可用于创建整洁且具有吸引力的网页,而且这些网页还能兼容所有现代 Web 浏览器. Boo ...

  5. ccf颁奖晚会

    感想: 首先,十分感谢学校给我们参加比赛的机会,给予我们这次难能可贵的学习机会,第一次在这么大型的赛场中展现自己,我们也不免有些紧张.从最开始在线上进行模型训练,到我们不远千里在江苏进行场地的勘察,到 ...

  6. 【Unity原神AR开发实战 2022】下载原神模型,PMX转FBX,导入到Unity,AR设置,测试应用程序,生成应用程序

    文章目录 一.前言 二.模型下载 1.官网下载 2.模之屋官方下载 3.第三方链接 三.pmx转fbx 1.Blender插件CATS的下载与安装 2.pmx模型的导入 四.Unity开发部分 1.V ...

  7. Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found 解决办法

    问题 每次vs重启都提示安装证书,即使执行dotnet dev-certs https --clean,dotnet dev-certs https -t然后重启vs也不行. 环境 win10, vs ...

  8. PyQt5 基本语法(四)

    目录 2. 输入控件(一) 2.1 纯键盘 2.1.1 QLineEdit 2.1.1.1 描述 2.1.1.2 控件创建 2.1.1.3 输出模式 2.1.1.4 提示字符串 2.1.1.5 清空按 ...

  9. numpy教程06---ndarray的进阶操作

    欢迎关注公众号[Python开发实战], 获取更多内容! 工具-numpy numpy是使用Python进行数据科学的基础库.numpy以一个强大的N维数组对象为中心,它还包含有用的线性代数,傅里叶变 ...

  10. Linux下安装Typora

    系统:Ubuntu16.0 下载链接:https://www.typora.io/#linux 参考链接:https://www.cnblogs.com/wenkangzero/p/13202415. ...