参考:https://www.jianshu.com/p/554533f88370
(1)可以通过简单的注解校验 Bean 属性,比如 @NotNull、@Null 等。
(2)可以通过 Group 分组自定义需要执行校验的属性。
(3)可以自定义注解并指定校验规则。
(4)支持基于 JSR 303 的实现,比如 Hibernate Validator(额外添加一些注解)。
1、在相关的 Bean 上标注需要处理的注解,并指定需要提示的信息(若不指定,会从默认配置文件中读取默认的信息)。
2、在相关的方法上,使用 @Valid 注解(或者 @Validated 指定组名)标记需要被校验的数据,否则会不生效。
注意:检测到数据异常后,系统会向外抛出异常,如果做了统一异常处理,可以根据 postman 测试的结果,找到控制台打印出的相应的异常,并处理。
@RestControllerAdvice
与 @ExceptionHandler
),处理检测结果1、给Bean添加校验注解:javax.validation.constraints,并定义自己的message提示
import javax.validation.constraints.*;/*** 品牌* * @author zhengyuzhu* @email 2977429967@qq.com* @date 2023-02-25 23:11:31*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@NotNull(message = "修改必须指定品牌id")@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名必须提交")private String name;}
2、修改 Controller 方法,使用 @Valid 注解标记需要检测的数据、比如在常见的增加和修改方法上,这个时候需要将前台传来的数据插入数据库
@RestController
@RequestMapping("product/brand")
public class BrandController {@Autowiredprivate BrandService brandService;/*** 保存新添加品牌*/@RequestMapping("/save")public R save(@Validated @RequestBody BrandEntity brand){brandService.save(brand);return R.ok();}/*** 修改*/@RequestMapping("/update")public R update(@Validated @RequestBody BrandEntity brand){brandService.updateDetail(brand);return R.ok();}}
提示:这个时候如果插入的数据不符合规范,就会抛出异常、为了避免代码冗余。可以使用全局异常来进行处理
3、集中处理异常
可以使用 BindingResult 去处理捕获到的数据并进行相关处理
/*** 集中处理所有异常*/
@Slf4j@RestControllerAdvice(basePackages = "com.zyz.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {@ExceptionHandler(value= MethodArgumentNotValidException.class)public R handleVaildException(MethodArgumentNotValidException e){log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());BindingResult bindingResult = e.getBindingResult();Map errorMap = new HashMap<>();// 获取校验结果,遍历获取捕获到的每个校验结果bindingResult.getFieldErrors().forEach((fieldError)->{// 存储得到的校验结果errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());});return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);}@ExceptionHandler(value = Throwable.class)public R handleException(Throwable throwable){log.error("错误:",throwable);return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());}}
如果出现多个方法,都需要校验 Bean,且校验规则不同的时候,怎么办呢?
就比如下方,修改品牌ID的时候,提交为空的时候,提示用户的应该是修改ID不能为空。新增的时候,应该是提示用户,新增ID不能为空。但是你也可以直接写ID不能为空,但总感觉缺少点啥。
分组校验就可以去解决该问题,每个分组指定不同的校验规则,不同的方法执行不同的分组,就可以得到不同的校验结果。 也就是说,定义好分组后,在校验的时候,就可以去找对应的分组进行相关的信息提示。
/*** 品牌id*/@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})@Null(message = "新增不能指定id",groups = {AddGroup.class})@TableIdprivate Long brandId;
1、定义一个空接口,用于指定分组,内部不需要任何实现。
2、指定 注解时,通过 groups 指定分组。用于指定在某个分组条件下,才去执行校验规则。
3、在相关的业务方法上,通过 @Validated 注解指定分组,去指定校验。
注:使用分组校验后,Bean 注解上若不指定分组,则不会执行校验规则。
1 创建两个新增和修改接口
public interface AddGroup {
}public interface UpdateGroup {
}
2 指定 注解时,通过 groups 指定分组
比如,这里的品牌id定义两个分组,一个是增加时候的校验,一个是修改时候的校验,对应不同的校验规则。也可以将通用的校验规则,以分组的时候同时管理多个。
/*** 品牌id*/@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})@Null(message = "新增不能指定id",groups = {AddGroup.class})@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})private String name;
3、在相关的业务方法上,通过 @Validated 注解指定分组,去指定校验。
/*** 保存新添加品牌*/@RequestMapping("/save")public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){brandService.save(brand);return R.ok();}/*** 修改*/@RequestMapping("/update")public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){brandService.updateDetail(brand);return R.ok();}
当上面的注解满足不了业务需求时,可以自定义校验注解,自定义校验规则。
1、需要自定义一个校验注解。可以创建一个 ValidationMessages.properties 用于保存默认的 message 信息。
2、需要自定义一个校验器,即自定义校验规则。实现 ConstraintValidator 接口,并重写相关方法。
注:
initialize
方法用于初始化,可以获取 自定义的属性的值。isValid
方法用于校验,可以获取到实际的值,然后与自定义的属性值进行比较。
@Constraint(validatedBy = {TestValidConstraintValidator.class})
如下例,自定义一个校验规则,判断数据是否是 0,1。当传来的数据不是这两种之一,校验不通过。
/*** @author zyz*/
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {/*** 添加校验* @return*/String message() default "{com.zyz.common.valid.ListValue.message}";Class>[] groups() default { };Class extends Payload>[] payload() default { };int[] vals() default { };
}
创建一个文件ValidationMessages.properties
。当不符合自定义规则校验的时候,message可以获取到对应的信息
配置文件内容:com.zyz.common.valid.ListValue.message=必须提交指定的值啊
/*** @author zyz*//*** 实现 ConstraintValidator 接口,* 其中 ConstraintValidator 的泛型,一个需要指定自定义的注解,一个需要指定需要获取的值的类型。* 比如:* ConstraintValidator 中* ListValue 表示自定义注解* String 表示获取的值的类型* 即定义规则,判断一个 String 的值的长度是否满足条件*/
public class ListValueConstraintValidator implements ConstraintValidator {private Set set = new HashSet<>();/*** 初始化方法*/@Overridepublic void initialize(ListValue constraintAnnotation) {int[] vals = constraintAnnotation.vals();for (int val : vals) {set.add(val);}}//判断是否校验成功/**** @param value 需要校验的值* @param context* @return*/@Overridepublic boolean isValid(Integer value, ConstraintValidatorContext context) {return set.contains(value);}
}
使用自定义注解,这里的@ListValue
就是使用的自定义注解,当前端传来状态的值不是对应数据。就会走自定义校验判断、提示对应的信息。这里也使用了分组的形式。
/*** 显示状态[0-不显示;1-显示]*/@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})private Integer showStatus;
方法中使用
这里使用了分组,就会自动校验
/*** 保存新添加品牌*/@RequestMapping("/save")public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){brandService.save(brand);return R.ok();}
资料参考:JSR 303