在一个项目中,不仅前端要对用户输入的数据进行校验,避免发送不必要的请求,而且后端也要对数据进行对应的校验,因为操作不都是通过页面过来的。
前端
不是很了解
正则表达式
配合各种组件使用
后端
这里以Java为例,这里主要说声明式,JSR303校验。
常用的注解
@Null 验证对象是否为null
@NotNull 验证对象是否不为null
@Max 对Number和String的最大值进行限制
@Min 对Number和String的最小值进行限制
@NotBlank 检查字符串是否不为空,即不是null,也不是""
@URL 检查是否符合路径规则。
对数据进行校验
第一步引入对应的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.5.RELEASE</version> </dependency>
在实体类中的属性加上对应的注解
/** * 品牌名 */ @NotBlank(message = "name参数校验错误") private String name;
message是在校验不正确的时候,响应的信息。
在对应的Controller中使用@Valid注解
/** * 修改 */ @RequestMapping("/update") //@RequiresPermissions("product:brand:update") public R update(@Valid @RequestBody BrandEntity brand){ brandService.updateById(brand); return R.ok(); }
数据校验不通过,就会朝前断响应400 Bad Request,参数不正确。
当然,你也可以自定义返回的结果:
在要校验的参数后面,紧跟一个BindingResult ,就可以拿到校验的结果。
/** * 修改 */ @RequestMapping("/update") //@RequiresPermissions("product:brand:update") public R update(@Valid @RequestBody BrandEntity brand,BindingResult result){ brandService.updateById(brand); if(result.hasErrors()){ //1. 获得所有的错误 result.getFieldErrors().forEach((item) -> { //TODO:进行操作。 }) //TODO:返回自定义的信息即可。 } return R.ok(); }
这样写,显然不合适,每个Controller都有错误校验的话,重复代码太多了,而且这些代码都和我们业务代码耦合了。所以最佳实践:全局异常处理。
/** * 修改 */ @RequestMapping("/update") //@RequiresPermissions("product:brand:update") public R update(@Valid @RequestBody BrandEntity brand){ brandService.updateById(brand); return R.ok(); }
还是上面的一段代码
全局异常使用SpringBoot的ControllerAdvice注解。
编写一个类,加上@ControllerAdvice注解。
编写对应的方法,加上@ExceptionHandler注解,并写上能处理的异常。
在方法里写上对应的逻辑。
//集中处理所有异常 @Slf4j @RestControllerAdvice(basePackages = "com.atguigu.gulimail.product.app") public class GulimailExcdeptionControllerAdvice { @ExceptionHandler(value = MethodArgumentNotValidException.class) public R handleVaildException(MethodArgumentNotValidException e) { log.error("数据校验出现问题{},异常类型:{}", e.getMessage(), e.getClass()); BindingResult bindingResult = e.getBindingResult(); //TODO :进行操作即可 } @ExceptionHandler(value = Throwable.class) public R handleException(Throwable throwable) { log.error("错误", throwable); //TODO:进行操作即可。 } }
分组校验
对于一个实体类的属性,在不同的方法下,标准可能是不一样的,比如一个商品的ID,在创建时应该是@Null(前端不用传,数据库自己生成),但在更新修改方法中就得是@NotNull了,所以分组校验就十分重要了。
那注解就可以这样写:
/** * 品牌id */ @NotNull(message = "修改id不能为空",groups = {UpdateGroup.class}) @Null(message = "新增id要为空") private Long brandId;
groups接受一个数组,数组里面的是接口,自己定义接口即可,不用做任何操作,就相当于一个标识。
@Controller层的@valid注解不支持分组,这是就得使用@valiated(Spring提供的注解)注解了。
/** * 修改 */ @RequestMapping("/update") //@RequiresPermissions("product:brand:update") public R update(@Validated({UpdateStatusGroup.class}) @RequestBody BrandEntity brand){ brandService.updateById(brand); return R.ok(); }
{}里的就是校验分组的标识。
注意
@NotBlank在分组情况下不生效,只有在@Validated情况下生效
自定义注解:
先看一下@NotBlank注解:
@Documented @Constraint( validatedBy = {} )//指定的校验方法。 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) //触发的时机 @Repeatable(List.class) public @interface NotBlank { String message() default "{javax.validation.constraints.NotBlank.message}"; //默认去找的信息 Class<?>[] groups() default {}; //分组情况 Class<? extends Payload>[] payload() default {}; //上面三个是必须要有的。 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface List { NotBlank[] value(); } }
自己写一个类
首先需要引入对应的依赖:
<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency>
编写一个注解
@Documented @Constraint( validatedBy = {ListValueConstraintValidator.class} ) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) public @interface ListValue { String message() default "{com.atguigu.common.valid.ListValue.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int[] vals() default {}; }
String message() default "{com.atguigu.common.valid.ListValue.message}"; 是默认错误信息的路径地址,在resource包下创建一个properties文件即可。
ValidationMessages.properties
com.atguigu.common.valid.ListValue.message=信息有误
创建处理方法
这两个泛型分别是:注解和需要添加注解的数据的类型。
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> { private Set<Integer> set = new HashSet<>(); //初始化方法 @Override public void initialize(ListValue constraintAnnotation) { int[] vals = constraintAnnotation.vals(); for(int val : vals){ set.add(val); } } //判断是否校验成功 @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { return set.contains(value); } }
使用注解:
/** * 显示状态[0-不显示;1-显示] */ @ListValue(vals = {0,1}) private Integer showStatus;
在ListValueConstraintValidator的initialize方法中,会将vals里的所有值放到set集合中,然后isValid判断实体属性是否在这个set里面,这里可以根据自己的逻辑修改。