JSR303后端校验详解

前言

数据校验是用来判断输入的数据是否满足规定的要求的。
前端可用JS来校验,如用户名唯一性,生日格式,邮箱格式校验等。
前端做了数据校验,后端也做数据校验的原因是防止有人绕过前端界面,直接向后端发起请求,导致数据库内写入了脏数据。

常用注解

JSR提供的校验注解:         
@Null   被注释的元素必须为 null    
@NotNull    被注释的元素必须不为 null    
@AssertTrue     被注释的元素必须为 true    
@AssertFalse    被注释的元素必须为 false    
@Min(value)     被注释的元素必须是一个数字,其值必须大于等于指定的最小值    
@Max(value)     被注释的元素必须是一个数字,其值必须小于等于指定的最大值    
@DecimalMin(value)  被注释的元素必须是一个数字,其值必须大于等于指定的最小值    
@DecimalMax(value)  被注释的元素必须是一个数字,其值必须小于等于指定的最大值    
@Size(max=, min=)   被注释的元素的大小必须在指定的范围内    
@Digits (integer, fraction)     被注释的元素必须是一个数字,其值必须在可接受的范围内    
@Past   被注释的元素必须是一个过去的日期    
@Future     被注释的元素必须是一个将来的日期    
@Pattern(regex=,flag=)  被注释的元素必须符合指定的正则表达式    


Hibernate Validator提供的校验注解:  
@NotBlank(message =)   验证字符串非null,且长度必须大于0    
@Email  被注释的元素必须是电子邮箱地址    
@Length(min=,max=)  被注释的字符串的大小必须在指定的范围内    
@NotEmpty   被注释的字符串的必须非空    
@Range(min=,max=,message=)  被注释的元素必须在合适的范围内

实战

1、添加校验注解

在属性上添加校验注解,用于校验输入的属性是否符合校验规则。

还可以在校验注解上自定义校验失败后显示的信息。如@Null(message = “新增时不能指定id”),当校验失败后,错误信息展示为message中内容。

/**
 * 品牌
 * 
 * @author xiao-wang
 * @email 
 * @date 2022-03-09 10:19:41
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	@Null(message = "新增时不能指定id")
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名不能为空")
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotBlank
	@URL(message = "url地址必须是一个有效的地址")
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
//	@TableLogic(value = "1",delval = "0")
	@NotNull(message = "显示状态不能为空")
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotBlank
	@Pattern(regexp = "^[a-zA-Z]$",groups = {message = "首字母必须为a-z或者A-Z中的一个字母")
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull
	private Integer sort;

}

2、在controller层添加@valid注解
只添加校验注解,校验是不会生效的。在controller层的某个方法上添加了@valid注解,校验才会生效。

@RestController
@RequestMapping("product/brand")
public class BrandController {
    @Autowired
    private BrandService brandService;
 	@RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }
 }

用postman测试,校验完成,出现提示信息。

{
    "timestamp": "2022-03-31 02:15:05",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotBlank.brandEntity.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "brandEntity.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "品牌名不能为空",
            "objectName": "brandEntity",
            "field": "name",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        }
    ],
    "message": "Validation failed for object='brandEntity'. Error count: 6",
    "path": "/product/brand/save"
}

但是返回的格式不是自己需要的。

要求返回的格式

{
    "msg": "提交数据不合法",
    "code": 400,
    "data": {
        "校验失败属性名": "错误提示信息",
    }
}

3、统一异常处理

为了规范异常返回的格式,返回一个R类型,R类型的格式为上述要求返回的格式

避免代码的冗余,新建一个类用来统一异常处理。

@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionController {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    //表示能捕获的异常类型为MethodArgumentNotValidException
    public R methodArgumentValidException(MethodArgumentNotValidException e){
        log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        //e.getBindingResult()表示校验结果
        Map<String,String> errorsMap  = new HashMap<>();
        bindingResult.getFieldErrors().forEach((item)->{
        //遍历校验结果中的错误信息
            String field = item.getField();
            //错误信息中的报错的属性名
            String message = item.getDefaultMessage();
            //报错的错误提示
            errorsMap.put(field,message);
        });
        return R.error(400,"提交数据不合法").put("data",errorsMap);
    }
}

分组校验

有的属性在新增的时候必须为空,但是修改的时候必须不为空

所以,需要对属性进行分组校验。

1、创建好分组接口,并在实体类的属性上加上groups=哪个分组

在如下示例中,需要创建AddGroup、UpdateGroup这两个接口
然后新增的时候为空,就在@Null注解上添加groups = {AddGroup.class})

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 品牌id
	 */
	@TableId
	@Null(message = "新增时不能指定id",groups = {AddGroup.class})
	@NotNull(message = "修改时必须指定id",groups = {UpdateGroup.class})
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class})
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotBlank(groups = {AddGroup.class})
	@URL(message = "url地址必须是一个有效的地址",groups = {AddGroup.class,UpdateGroup.class})
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
//	@TableLogic(value = "1",delval = "0")
	@NotNull(groups = {AddGroup.class},message = "显示状态不能为空")
	@ListValues(values = {0,1},groups = {AddGroup.class})
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotBlank(groups = {AddGroup.class})
	@Pattern(regexp = "^[a-zA-Z]$",groups = {AddGroup.class, UpdateGroup.class},message = "首字母必须为a-z或者A-Z中的一个字母")
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull(groups = {AddGroup.class})
	private Integer sort;

}

2、controller层需要将@valid注解换成@Validated注解

由上可知,只添加注解是不会生效的,需要在controller层的方法内添加@Valid注解,但是@Valid注解没有分组功能,因此,换成@Validated注解,指定生效的是哪个分组。

@RestController
@RequestMapping("product/brand")
public class BrandController {
    @Autowired
    private BrandService brandService;
 	@RequestMapping("/save")
    public R save(@Validated(value = {AddGroup.class}) @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }
 }

自定义校验

1、自定义校验注解
自己添加一个注解,规定该属性值只能填入规定的0和1,否则会报错
在这里插入图片描述

@Constraint(validatedBy = {ListvaluesConstraintValidator.class})
//指定该注解由ListvaluesConstraintValidator.class来校验
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValues {

    String message() default "{com.atguigu.common.valid.ListValues.message}";
	//在resources中新建ValidationMessages.properties(必须为此名字),就可指定该注解的默认报错信息
	//在ValidationMessages.properties中填写,com.atguigu.common.valid.ListValues.message = 请输入指定的值,
	//当报错时,默认报错信息为“请输入指定值”
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int[] values() default {};
}

2、编写校验类

校验类实现真正的校验功能,initialize表示初始化功能,isValid判断是否校验成功。

public class ListvaluesConstraintValidator implements ConstraintValidator<ListValues,Integer> {
    HashSet<Integer> set = new HashSet<Integer>();
    public void initialize(ListValues constraintAnnotation) {

        for (int value : constraintAnnotation.values()) {
            set.add(value);
        }
    }

    public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(integer);
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值