一、简介

  后台业务入口类Controller,对于入参的合法性校验,可以简单粗暴的写出一堆的 if 判断,如下:

@RestController
@RequestMapping("user")
public class UserController { @PostMapping("saveUser")
public String saveUser(UserInfoVo userInfoVo){
if(StrUtil.isBlank(userInfoVo.getUserName())){
return "userName is not null";
}
if(StrUtil.isBlank(userInfoVo.getPwd())){
return "pwd is not null";
}
return "save success";
}
}

二、重要说明

   2.1、springboot在2.3之后,spring-boot-starter-web的依赖项已经去除了validate依赖,推荐导入依赖:

     <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

   2.2、关于 @Valid 和 @Validated

    @Validated 是Spring Validation验证框架对JSR-303规范的一个扩展, javax提供@Valid 是标准的JSR-303规范。使用基本无区别,但是在Group分组使用上还是使用 @Validated方便。

    嵌套验证上,必须在待验证的vo中的嵌套实体属性上增加@Valid。

三、实验出真知

  3.1、牛刀小试

    定义VO,在需要校验的字段上加上相应注解

@Data
public class UserInfoVo {
@NotBlank(message = "userName is not null")
private String userName;
@NotNull(message = "age is not null")
private Integer age;
@NotBlank(message = "pwd is not null")
private String pwd;
}

   Controller入参加上@Valid、校验结果BindingResult:

@RestController
@RequestMapping("user")
public class UserController { @PostMapping("saveUser")
public String saveUser(@Valid UserInfoVo userInfoVo, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return bindingResult.getAllErrors().get(0).getDefaultMessage();
}
return "save success";
} }

  通过工具访问,可看到如果入参不符合,会有相应message返回

  

以上就完成了一个最简单的优雅校验过程,其中内置常用校验类型:

空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY. Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false 长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included. 日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则 数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage; @CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=) 

  3.2、第一次改版

  入参很多的情况下,可能会同时产生多个不同的错误校验,那么如果每次只是返回一个错误提示,每次客户端改一个,那么体验是极差的。基于此,封装返回类,提供统一返回。

@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class ResponseData<T> { @NonNull
private Integer code;
@NonNull
private String message;
private T data; public static ResponseData success() {
return new ResponseData(HttpStatus.OK.value(), "SUCCESS");
} public static ResponseData success(Object data) {
ResponseData entity = success();
entity.setData(data);
return entity;
} public static ResponseData fail(Integer code, String msg) {
return new ResponseData(code, msg);
} public static ResponseData fail(Integer code, String msg, Object data) {
ResponseData entity = fail(code, msg);
entity.setData(data);
return entity;
}
}
@RestController
@RequestMapping("user")
public class UserController { @PostMapping("saveUser")
public ResponseData saveUser(@Valid UserInfoVo userInfoVo, BindingResult bindingResult){
if(bindingResult.hasErrors()){
List<String> collect = bindingResult.getFieldErrors().stream().map(item -> item.getDefaultMessage()).collect(Collectors.toList());
return ResponseData.fail(900, "req param invalid", collect);
}
return ResponseData.success();
}
}

  3.3、第二次改版

    上述改版,已经能够一次性的返回所有未校验通过异常,但是,每个方法中都这么来一遍,还是挺麻烦的。下面利用统一异常处理参数校验,改造完成后Controller中专注于业务处理即可

@RestController
@RequestMapping("user")
public class UserController { @PostMapping("saveUser")
public ResponseData saveUser(@Valid UserInfoVo userInfoVo){
return ResponseData.success();
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Integer BAD_REQUEST_CODE = 900;
private static final String BAD_REQUEST_MSG = "req param invalid"; @ExceptionHandler(BindException.class)
public ResponseData bindExceptionHandler(BindException exception){
List<String> collect = exception.getAllErrors().stream().map(item -> item.getDefaultMessage())
.collect(Collectors.toList());
return ResponseData.fail(BAD_REQUEST_CODE, BAD_REQUEST_MSG, collect);
} @ExceptionHandler(Exception.class)
public ResponseData exceptionHandler(Exception exception){
return ResponseData.fail(500, exception.getMessage());
}
}

  3.4、分组校验

  通常,存在场景:name参数在注册接口必须非空,但是修改接口无所谓。那么此时,分组group很好解决问题

/**
* @author cfang 2020/9/23 10:47
*
* 关于 extends Default
* 继承 Default 的话,所有定义校验规则的都会校验
* 不继承的话,则只校验加了group信息的校验字段
*/
public interface UserGroup extends Default{
} @Data
public class UserInfoVo { @NotBlank(message = "userName is not null", groups = UserGroup.class)
private String userName;
@NotNull(message = "age is not null")
private Integer age;
@NotBlank(message = "pwd is not null")
private String pwd;
} @RestController
@RequestMapping("user")
public class UserController { @PostMapping("saveUser")
public ResponseData saveUser(@Validated({UserGroup.class}) UserInfoVo userInfoVo){
return ResponseData.success();
} @PostMapping("updateUser")
public ResponseData updateUser(@Valid UserInfoVo userInfoVo){
return ResponseData.success();
} @InitBinder
public void init(HttpServletRequest request, DataBinder dataBinder){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false); //是否校验转化日期格式,true-转化日期,false-参数错误则直接异常。eg. 2020-55-10, true->2024-10-10 , false-异常报错。默认值true
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
} }

  3.5、递归校验

@Data
public class AddressInfoVo { @NotBlank(message = "street is not null")
private String street;
} @Data
public class UserInfoVo { @NotBlank(message = "userName is not null", groups = UserGroup.class)
private String userName;
@NotNull(message = "age is not null")
private Integer age;
@NotBlank(message = "pwd is not null")
private String pwd;
@Past(message = "predate is invalid")
@NotNull(message = "predate is not null")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date preDate;
@Valid
private AddressInfoVo infoVo;
} @RestController
@RequestMapping("user")
public class UserController { @PostMapping("saveUser")
public ResponseData saveUser(@Validated({UserGroup.class}) @RequestBody UserInfoVo userInfoVo){
return ResponseData.success();
} @PostMapping("updateUser")
public ResponseData updateUser(@Valid @RequestBody UserInfoVo userInfoVo){
return ResponseData.success();
} @InitBinder
public void init(HttpServletRequest request, DataBinder dataBinder){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
} @RestControllerAdvice
public class GlobalExceptionHandler {
private static final Integer BAD_REQUEST_CODE = 900;
private static final String BAD_REQUEST_MSG = "req param invalid"; @ExceptionHandler(BindException.class)
public ResponseData bindExceptionHandler(BindException exception){
List<String> collect = exception.getAllErrors().stream().map(item -> item.getDefaultMessage())
.collect(Collectors.toList());
return ResponseData.fail(BAD_REQUEST_CODE, BAD_REQUEST_MSG, collect);
} @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseData argInvalidExceptionHandler(MethodArgumentNotValidException exception){
List<String> collect = exception.getBindingResult().getFieldErrors().stream().map(item -> item.getDefaultMessage())
.collect(Collectors.toList());
return ResponseData.fail(BAD_REQUEST_CODE, BAD_REQUEST_MSG, collect);
} @ExceptionHandler(Exception.class)
public ResponseData exceptionHandler(Exception exception){
return ResponseData.fail(500, exception.getMessage());
}
}

  

SpringValid优雅校验入参的更多相关文章

  1. spring boot中使用javax.validation以及org.hibernate.validator校验入参

    这里springboot用的版本是:<version>2.1.1.RELEASE</version> 自带了hibernate.validator,所以不用添加额外依赖 1.创 ...

  2. @RequestBody配合@Valid 校验入参参数

    自定义一个Controller import com.example.demo.pojo.User; import org.springframework.web.bind.annotation.Po ...

  3. Egg.js 中入参的校验

    日常作业中免不了频繁处理 GET/POST 的入参,你当然可以每个 action 中都重复地去做这些事情, 从 query 或 body 取出入参, 对可选的入参进行判空, 处理入参的类型转换, 对入 ...

  4. springboot项目中接口入参的简单校验

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  5. SpringBoot2 参数管理实践,入参出参与校验

    一.参数管理 在编程系统中,为了能写出良好的代码,会根据是各种设计模式.原则.约束等去规范代码,从而提高代码的可读性.复用性.可修改,实际上个人觉得,如果写出的代码很好,即别人修改也无法破坏原作者的思 ...

  6. list对象数组,xpath复杂定位校验,POST入参为number数组,POST入参为JSON对象数组

    list对象数组: POST入参为number数组: {    "typeIds":[1,2,3]} POST入参为JSON对象数组,举例: [{    "itemId& ...

  7. c++调用python系列(1): 结构体作为入参及返回结构体

    最近在打算用python作测试用例以便对游戏服务器进行功能测试以及压力测试; 因为服务器是用c++写的,采用的TCP协议,当前的架构是打算用python构造结构体,传送给c++层进行socket发送给 ...

  8. java接口入参模板化,适用于企业化服务远程调度模板化的场景,接口入参实现高度可配置化

    需求:远程服务接口模板化配置提供接入服务 模板接口分为三个模块:功能路由.参数校验.模板入库 路由:这里的实现方式很简单,就是根据业务标识找到对应的处理方法 参数校验: 参数校验这步涉及模板和校验类两 ...

  9. Web API入参,响应规范

    入参绑定 入参应该定义成实体,而不是多个参数,方便扩展.[FromBody]和[FromUrl]特性也最好加上. public ActionResult<Pet> Create([From ...

随机推荐

  1. 强化学习模型实现RL-Adventure

    源代码:https://github.com/higgsfield/RL-Adventure 在Pytorch1.4.0上解决bug后的复现版本:https://github.com/lucifer2 ...

  2. SQL语句组合查询 UNION

    1.使用UNION UNION 可以涉及编写多条SELECT语句,首先看看单条语句 第一条SELECT语句把Illinois,Indiana,Michigan等州的缩写传递给IN子句,检索出这些州的所 ...

  3. Java数据结构——二叉树的遍历(汇总)

    二叉树的遍历分为深度优先遍历(DFS)和广度优先遍历(BFS) DFS遍历主要有: 前序遍历 中序遍历 后序遍历 一.递归实现DFSNode.java: public class Node { pri ...

  4. 超级码力编程赛带着6万奖金和1200件T恤向你跑来了~

    炎炎夏日,总是感觉很疲劳,提不起一点精神怎么办?是时候参加一场比赛来唤醒你的激情了!阿里云超级码力在线编程大赛震撼携手全国数百所高校震撼来袭. 它来了,它来了,它带着60000现金和1200件T恤向你 ...

  5. 消息型中间件之RabbitMQ集群

    在上一篇博客中我们简单的介绍了下rabbitmq简介,安装配置相关指令的说明以及rabbitmqctl的相关子命令的说明:回顾请参考https://www.cnblogs.com/qiuhom-187 ...

  6. Java面试题(Java Web篇)

    Java Web 64.jsp 和 servlet 有什么区别? jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将 ...

  7. C++算法 线段树

    线段树这个算法,看起来非常高端,而且很有用处,所以还是讲一下下吧. 温馨提示:写线段树前请做好写码5分钟,调试一辈子的准备^-^ 啊直接步入正题…… 首先我们考虑一个题目:有一个序列,要做到单点修改单 ...

  8. e3mall商城总结13之订单确认(有BUG)

    说在前面的话 上一节说了购物车的生成,本节主要说了在购物车的列表上去结算,从而生成一个未支付的订单,生成的订单默认状态为1, 题目说的BUG是因为所有数据都是通过前端向后端生成的,包括订单的金额.因此 ...

  9. Java/后端学习路线

    点赞再看,养成习惯,微信搜一搜[三太子敖丙]关注这个喜欢写情怀的程序员. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的系 ...

  10. Android开发之获取APP的应用程序名称以及版本名称信息java工具类

    //跟App相关的辅助类 public class AppUtils { private AppUtils() { /* cannot be instantiated */ throw new Uns ...