JSR303数据校验异常处理-19

导读:本篇文章讲解 JSR303数据校验异常处理-19,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一:异常处理——JSR303

1.在product下新建exception包

在这里插入图片描述

2.在exception包下新建GulimailExceptionControllerAdvice类

3.在类上添加@ControllerAdvice注解

@ControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
  • @ControllerAdvice:集中处理异常
  • basePackages = “com.sysg.gulimail.product.controller”:表示在当前目录下的controller出现的异常都需要处理

4.编写handleValidException方法,用来接收所有数据校验异常

package com.sysg.gulimail.product.exception;
import com.sysg.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
 * @author 77916
 * @ControllerAdvice 集中处理异常
 */
@Slf4j
//@ControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
//@ResponseBody
@RestControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
public class GulimailExceptionControllerAdvice {
    @ExceptionHandler(value = Exception.class)
    public R handleValidException(Exception e){
       log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
       return R.error();
    }
}
  • @ExceptionHandler(value = Exception.class):用来标注所有异常都可以处理
  • Exception e:用来接收从controller抛出的异常
  • @Slf4j:此注解用来接收日志,记录日志
  • @ResponseBody:将结果转化为字符串返回回去
  • @RestControllerAdvice:就是@ResponseBody和@ControllerAdvice的合体
    在这里插入图片描述
    注:此时虽然获取到异常,但不知道具体是哪个字段的异常,所以我们需要完善方法

5.优化GulimailExceptionControllerAdvice类

1)完善数据异常处理方法handleValidException

@Slf4j
//@ControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
//@ResponseBody
@RestControllerAdvice(basePackages = "com.sysg.gulimail.product.controller")
public class GulimailExceptionControllerAdvice {
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
       log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
        //获取到具体的异常返回结果
        BindingResult result = e.getBindingResult();
        HashMap<String, String> errorMap = new HashMap<>(16);
        //将错误信息遍历出来,获取错误字段和错误信息
        result.getFieldErrors().forEach((fieldError)->{
            //校验错误的字段
            String field = fieldError.getField();
            //校验错误的信息
            String message = fieldError.getDefaultMessage();
            errorMap.put(field, message);
        });
        return R.error(400,"数据校验出现问题").put("data",errorMap);
    }
}
  • MethodArgumentNotValidException:用来直接将具体那方面的异常获取到
  • e.getBindingResult():将返回的错误结果获取到
  • 当能精确匹配到异常类型的时候使用,MethodArgumentNotValidException
    在这里插入图片描述
    此时校验异常处理方法就可以正常返回错误信息了
    2)新建其他异常处理方法handleException方法

6.在common项目下做状态码处理

1)在common下新建exception包
在这里插入图片描述
2)在exception下新建BizCodeEnume枚举,用来处理状态码

package com.sysg.common.exception;

/**
 * 状态码枚举
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为 5 为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知 异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 * 10: 通用
 * 001:参数格式校验
 * 11: 商品
 * 12: 订单
 * 13: 购物车
 * 14: 物流
 * @author 77916
 */
public enum BizCodeEnume {
    //参数校验
    VALID_EXCEPTION(10001,"参数格式校验失败"),
    //系统未知异常
    UNKNOW_EXCEPTION(10000,"参数未知异常");
    
    private int code;
    private String msg;
    //构造器
    BizCodeEnume(int code,String msg){
        this.code = code;
        this.msg = msg;
    }
    public int getCode(){
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

1)修改GulimailExceptionControllerAdvice的handleValidException方法,其返回的状态码用枚举获取。

 /**
     * 用来处理数据校验异常
     * 当能精确匹配到异常类型的时候使用
     * @param e
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
       log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
        //获取到具体的异常返回结果
        BindingResult result = e.getBindingResult();
        HashMap<String, String> errorMap = new HashMap<>(16);
        //将错误信息遍历出来,获取错误字段和错误信息
        result.getFieldErrors().forEach((fieldError)->{
            //校验错误的字段
            String field = fieldError.getField();
            //校验错误的信息
            String message = fieldError.getDefaultMessage();
            errorMap.put(field, message);
        });
        return R.error(BizCodeEnume.VALID_EXCEPTION.getCode(),BizCodeEnume.VALID_EXCEPTION.getMsg()).put("data",errorMap);
    }

2)修改GulimailExceptionControllerAdvice的handleException方法,其返回的状态码用枚举获取。

    /**
     * 用来处理其他所有异常
     * @param throwable
     * @return
     */
    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){
       return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
    }

二:分组校验——多场景的复杂校验

使用场景:新增和修改的时候传递的参数不一样,校验的时候有所区别

1.在common下新建valid包

1)新建AddGroup接口,用来处理新增时候的业务字段校验
2)新建UpdateGroup接口,用来处理编辑时候的业务字段校验
在这里插入图片描述

2.groups——注解上添加什么时候需要什么注解校验

	/**
	 * 品牌id
	 */
	@Null(message = "新增不能指定id",groups = {AddGroup.class})
	@NotNull(message = "修改必须指定id",groups = {UpdateGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message="品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
  • 使用groups用来标注哪些字段需要分组处理,给注解上添加什么时候需要什么注解校验
  • 指定其在那种情况下触发

3.@Validated——指定我们要校验那一组

1)在controller的保存方法上添加**@Validated({AddGroup.class})**表示只有在保存方法的时候会触发校验

   /**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }

2)更新方法同样如此

    /**
     * 修改
     */
    @RequestMapping("/update")
    //@RequiresPermissions("product:brand:update")
   public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
        brandService.updateById(brand);
        return R.ok();
    }

3)当没有添加分组校验的注解,默认是不起作用的

/**
	 * 品牌logo地址
	 * @URL 必须是一个url地址
	 */
	@NotBlank(groups = {AddGroup.class})
	@URL(message="logo必须是一个合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
	private String logo;
  • @NotBlank(groups = {AddGroup.class}):url新增的时候不能为空,且必须符合url的校验。
  • @URL(message=“logo必须是一个合法的url地址”,groups = {AddGroup.class,UpdateGroup.class}):新增的时候不可以为空,且必须符合url校验。修改的时候可以为空,但不为空的时候一定要符合url校验。

4)其他字段处理方法类似

package com.sysg.gulimail.product.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.sysg.common.valid.AddGroup;
import com.sysg.common.valid.UpdateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.*;
/**
 * 品牌
 * 
 * @author sysg
 * @email sunlightcs@gmail.com
 * @date 2022-03-03 16:15:44
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
	private static final long serialVersionUID = 1L;
	/**
	 * 品牌id
	 */
	@Null(message = "新增不能指定id",groups = {AddGroup.class})
	@NotNull(message = "修改必须指定id",groups = {UpdateGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message="品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
	/**
	 * 品牌logo地址
	 * @URL 必须是一个url地址
	 */
	@NotBlank(groups = {AddGroup.class})
	@URL(message="logo必须是一个合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
	private Integer showStatus;
	/**
	 * 检索首字母
	 */
	@NotEmpty(groups = {AddGroup.class})
	@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups = {AddGroup.class,UpdateGroup.class})
	private String firstLetter;
	/**
	 * 排序
	 */
	@NotNull(groups = {AddGroup.class})
	@Min(value = 0,message = "排序必须大于等于0",groups = {AddGroup.class,UpdateGroup.class})
	private Integer sort;
}

三:自定义校验注解

1.编写自定义校验注解

1)自定义注解JSR303必须拥有三个属性

  • message,信息以及默认去哪里取,一般是校验注解的全类名
  • groups,要支持分组校验功能
  • payload,负载信息

2)标写源信息数据

  • @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }):表明注解可以标注在什么位置,方法上,类上,属性,参数,构造器等
  • @Retention(RUNTIME):校验注解在什么时候生效,在运行时生效
  • @Constraint(validatedBy = { }):校验注解通过哪个校验器进行校验,在validatedBy里面指定校验器

3)在common的resource目录下新建ValidationMessages.properties配置文件
作用:用来取com.sysg.common.valid.ListValue.message的值,配置错误消息

com.sysg.common.valid.ListValue.message=必须提交指定的值

4)指定注解的values,可以是一个数组

int[] vals() default { };

2.编写自定义校验器——ConstraintValidator

1)在valid包下新建ListValueConstraintValidator接口
在这里插入图片描述
2)实现ConstraintValidator<ListValue,Integer>接口

  • ListValue表示需要校验的注解
  • Integer表示传入字段的类型

3)实现ConstraintValidator接口自带的方法

    private Set<Integer> set = new HashSet<>();
    /**
     * 初始化方法
     * @param constraintAnnotation
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int i = 0; i < vals.length; i++) {
            set.add(vals[i]);
        }
    }
  • initialize的参数ListValue ,可以获取到添加ListValue注解的详细信息
  • 将获取到的值,遍历以后封装到set集合当中,这个值就是@ListValue(vals={0,1},groups = {AddGroup.class})的0和1
 /**
     * 判断是否校验成功
     * @param value 需要校验的值
     * @param context  校验的上下文环境信息
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
  • value 需要校验的值
  • context 校验的上下文环境信息
  • 总结:将获取到的值和注上定义的值进行比较,然后返回
package com.sysg.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

/**
 * @author 77916
 */
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

    private Set<Integer> set = new HashSet<>();
    /**
     * 初始化方法
     * @param constraintAnnotation
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int i = 0; i < vals.length; i++) {
            set.add(vals[i]);
        }
    }

    /**
     * 判断是否校验成功
     * @param value 需要校验的值
     * @param context  校验的上下文环境信息
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}

3.关联自定义校验器和自定义校验注解

package com.sysg.common.valid;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @author 77916
 */
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "{com.sysg.common.valid.ListValue.message}";

    Class<?>[] groups() default { };

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

    int[] vals() default { };
}

1)在ListValue注解里面,通过@Constraint注解声明给什么东西进行校验

@Constraint(validatedBy = {ListValueConstraintValidator.class})

2)@Constraint也可以指定多个不同的校验器进行校验,用于针对传递的参数类型有变化时。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/84130.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!