【工具】JSR-303后端参数校验框架的使用方法及说明

【工具】JSR-303后端参数校验框架的使用方法及说明

1. 统一校验需求

有一句话是这样说的——“前端防君子,后端防小人”。这句话说明参数的校验在前端和后端都是十分重要的。不懂技术的人一般在前端页面无法找到系统的漏洞,但是懂技术又不怀好意的人如果获取到了你后端接口的地址,而恰好你后端又没有对参数进行校验,那么就很有可能会被他人乘虚而入,对系统造成很大的影响。为了避免这一情况的发生,所有我们需要在后端也进行参数的校验。

那么我们又有一个问题了,参数校验的代码是写在Controller层还是写在Service层呢?

对于一般的参数,我们应该是放在Controller层中进行校验,而对于业务逻辑的校验则在Service层中进行。Contoller中校验请求参数的合法性,包括:必填项校验,数据格式校验,比如:是否是符合一定的日期格式,等。Service中要校验的是业务规则相关的内容,比如:已存在的用户不能重复新增。

早在JavaEE6规范中就定义了参数校验的规范,它就是 JSR-303,它定义了Bean Validation,即对bean属性进行校验。

SpringBoot提供了JSR-303的支持,它就是 spring-boot-starter-validation,它的底层使用Hibernate Validator,Hibernate Validator是Bean Validation 的参考实现。

所以,我们准备在Controller层使用spring-boot-starter-validation完成对请求参数的基本合法性进行校验。


2. 使用说明

2.1 引入依赖

我们在项目的pom文件中添加如下依赖:

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

2.2 规则说明

上面引入的依赖为我们提供了很多的注解,注解的使用规则如下:

2022091315452386

了解了使用的规则之后我们就可以开始使用了。


2.3 使用说明

1)场景描述

设定一个场景,我们需要添加一门课程,前端传入一系列的参数,我们后端使用dto去接收这些参数。同时我们还需要在Controller校验这些参数是否合法。

首先设定dto类代码如下所示:

@Data
public class AddCourseDto {

 @NotEmpty(message = "课程名称不能为空")
 private String name;

 @NotEmpty(message = "适用人群不能为空")
 @Size(message = "适用人群内容过少",min = 10)
 private String users;

 @NotEmpty(message = "课程分类不能为空")
 private String mt;

 @NotEmpty(message = "课程分类不能为空")
 private String st;

 @NotEmpty(message = "课程等级不能为空")
 private String grade;

 @NotEmpty(message = "收费规则不能为空")
 private String charge;
}

我们在这个dto中用到了两个校验注解 @NotEmpty@Size ,注解含义如上图所示。


2)定义接口

我们已经定义好了dto,然后我们需要在接口层对dto进行校验,校验的方法也很简单,只需要在接口的参数列表中添加一个 @Validated 注解。接口代码如下所示:

@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody @Validated AddCourseDto addCourseDto){
    //...
  return courseBaseInfoService.createCourseBase(companyId,addCourseDto);
}

如果传入的参数中有不合规范的,Spring会抛出一个 MethodArgumentNotValidException 异常,我们需要在统一异常处理器中捕获异常,解析出异常信息。


3)编写异常处理逻辑

我们在统一异常处理器中对异常进行捕获,编写异常处理逻辑:

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//状态码返回500
public RestErrorResponse doMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    BindingResult bindingResult = e.getBindingResult();
    //校验的错误信息
    List<FieldError> fieldErrors = bindingResult.getFieldErrors();
    //收集错误
    StringBuffer errors = new StringBuffer();
    fieldErrors.forEach(error -> {
        errors.append(error.getDefaultMessage()).append(",");
    });
    //将错误信息以json的形式返回
    return new RestErrorResponse(errors.toString());
}

我们使用httpcilent今昔测试,我们将name属性的值设为空。

POST {{content_host}}/course
Content-Type: application/json

{
  "mt": "1-1",
  "st": "1-1-1",
  "name": "",
  "pic": "",
  "teachmode": "200002",
  "users": "初级人员",
  "tags": "标签",
  "grade": "204001",
  "description": "",
  "charge": "201001",
  "price": 10,
  "originalPrice":100,
  "qq": "123",
  "wechat": "123",
  "phone": "123",
  "validDays": 365
}

进行测试:

返回信息:

image-20230225125047139

这时发现,参数校验已经生效了。但是我们这又会引发出另一个问题。那就是:

假如我们有两个接口的参数都包含了相同的dto,我们都需要对其进行校验,但是这两个接口对dto的校验规则又不一样,比如第一个接口要求dto的name属性不能为空,第二个接口却要求dto的name属性可以为空。那我们怎么办呢?我们是再创建一个和dto完全相同的类去重新编写校验规则吗?当然不是,Spring为我们提供了更好的解决方案——“分组校验”。


2.4 分组校验

有时候再同一个属性上设置一个校验规则并不能满足所有要求。

比如:订单编号由系统生成,在添加订单时要求订单编号为空,在更新订单时要求订单编写不能为空。此时就用到了分组校验,同一个属性定义多个校验规则属于不同的分组,比如:添加订单定义@NULL规则属于insert分组,更新订单定义@NotEmpty规则属于update分组,insert和update是分组的名称,是可以修改的。

1)分组

我们用class类型来表示不同的分组,所以我们定义不同的接口类型(空接口)表示不同的分组。代码如下所示:

public class ValidationGroups {

 public interface Inster{};
 public interface Update{};
 public interface Delete{};

}

2)指定分组

在dto中定义校验规则是指定分组,比如上面的name属性,我们可以为其指定两个分组。

@NotEmpty(groups = {ValidationGroups.Inster.class},message = "添加课程名称不能为空")
@NotEmpty(groups = {ValidationGroups.Update.class},message = "修改课程名称不能为空")
// @NotEmpty(message = "课程名称不能为空")
 private String name;

3)接口层引用分组

在Controller方法中启动校验规则指定要使用的分组名:

@PostMapping("/course")
public CourseBaseInfoDto createCourseBase(@RequestBody @Validated({ValidationGroups.Inster.class}) AddCourseDto addCourseDto){
    //。。。
  return courseBaseInfoService.createCourseBase(companyId,addCourseDto);
}

再次测试,由于这里指定了Insert分组,所以抛出 异常信息:添加课程名称不能为空。

如果修改分组为ValidationGroups.Update.class,异常信息为:修改课程名称不能为空。


2.5 定制校验规则注解

如果javax.validation.constraints包下的校验规则满足不了需求怎么办?

我们有两种方法:

  1. 手写校验代码。
  2. 自定义校验规则注解。

自定义校验规则注解的方法可以查看:自定义校验注解

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值