【Gulimall】JSR303校验、集中处理异常、分组校验、自定义校验、错误收集时Duplicated key避免


先给个实体类的校验注解示意,其中@ListValue为自定义校验注解

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

	/**
	 * 品牌id
	 */
	@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
	@Null(message = "新增不能指定id",groups = {AddGroup.class})
	@TableId
	private Long brandId;
	/**
	 * 品牌名
	 */
	@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
	private String name;
	/**
	 * 品牌logo地址
	 */
	@NotBlank(groups = {AddGroup.class})
	@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
	private String logo;
	/**
	 * 介绍
	 */
	private String descript;
	/**
	 * 显示状态[0-不显示;1-显示]
	 */
//	@Pattern()
	@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
  	@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})
	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 JSR303

1.给Bean添加校验注解:都在javax.validation.constraints包中,并定义自己的message提示
默认message提示在:ValidationMessages.properties中定义

javax.validation.constraints.AssertFalse.message     = must be false
javax.validation.constraints.AssertTrue.message      = must be true
javax.validation.constraints.DecimalMax.message      = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message      = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message          = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Email.message           = must be a well-formed email address
javax.validation.constraints.Future.message          = must be a future date
javax.validation.constraints.FutureOrPresent.message = must be a date in the present or in the future
javax.validation.constraints.Max.message             = must be less than or equal to {value}
javax.validation.constraints.Min.message             = must be greater than or equal to {value}
javax.validation.constraints.Negative.message        = must be less than 0
javax.validation.constraints.NegativeOrZero.message  = must be less than or equal to 0
javax.validation.constraints.NotBlank.message        = must not be blank
javax.validation.constraints.NotEmpty.message        = must not be empty
javax.validation.constraints.NotNull.message         = must not be null
javax.validation.constraints.Null.message            = must be null
javax.validation.constraints.Past.message            = must be a past date
javax.validation.constraints.PastOrPresent.message   = must be a date in the past or in the present
javax.validation.constraints.Pattern.message         = must match "{regexp}"
javax.validation.constraints.Positive.message        = must be greater than 0
javax.validation.constraints.PositiveOrZero.message  = must be greater than or equal to 0
javax.validation.constraints.Size.message            = size must be between {min} and {max}

先来对比下几个@Not**

注解@NotNull@NotEmpty@NotBlank
被修饰的字段禁止为null不能为null或""不能为null,并且至少包含一个非空白字符
适用字段类型任何类型字符序列、集合、map、数组字符序列

PS:空白字符包括:空格" "、换行’\n’、制表’\t’、回车’\r’

2.开启校验功能@Valid效果:校验错误以后会有默认的响应;
但是这种返回的错误结果并不符合我们的业务需要。
3.给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果

 @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if( result.hasErrors()){
            Map<String,String> map=new HashMap<>();
            //1.获取错误的校验结果
            result.getFieldErrors().forEach((item)->{
                //获取发生错误时的message
                String message = item.getDefaultMessage();
                //获取发生错误的字段
                String field = item.getField();
                map.put(field,message);
            });
            return R.error(400,"提交的数据不合法").put("data",map);
        }else {
			brandService.save(brand);
        }

        return R.ok();
    }

这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理。

2 统一的异常处理

1.编写异常处理类,使用@ControllerAdvice。
2.使用@ExceptionHandler标注方法可以处理的异常。

@Slf4j  //日志记录下 这是lombok下的注解
//@ResponseBody //将所有方法返回以json方式
//@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
//    @ExceptionHandler(value = Exception.class)
//    public R handleValidException(Exception e) {
//        log.error("数据校验异常:类型{},信息{}", e.getClass(),e.getMessage());  //得出校验异常类型是MethodArgumentNotValidException
//        return R.error(400,"校验出错");
//    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class) //处理指定异常
    public R handleValidException(MethodArgumentNotValidException e) {
        log.error("数据校验异常:类型{},信息{}", e.getClass(),e.getMessage());
        BindingResult bindingResult = e.getBindingResult();
        Map<String, String> map = new HashMap<>();
        bindingResult.getFieldErrors().forEach(item->{
            String field = item.getField();
            String defaultMessage = item.getDefaultMessage();
            map.put(field, defaultMessage);
        });

        return R.error(BazCodeEnum.VALID_EXCEPTION.getCode(),BazCodeEnum.VALID_EXCEPTION.getMsg())  //BazCodeEnum为自定义的枚举类,规范提示
                .put("data",map);
    }

    @ExceptionHandler(value = Throwable.class)  //任意异常
    public R handleValidException(Throwable e) {
        log.error("异常:类型{},信息{},detail:{}", e.getClass(),e.getMessage(),e);

        return R.error(BazCodeEnum.UNKNOWN_EXCEPTION.getCode(),BazCodeEnum.UNKNOWN_EXCEPTION.getMsg());
    }
}

这里的BazCodeEnum是枚举类(定义在gulimall-common下)


public enum BazCodeEnum {
    UNKNOWN_EXCEPTION(10000, "系统未知异常"),  //这些错误码得实现统一好
    VALID_EXCEPTION(10001, "数据格式校验异常");

    private int code;
    private String msg;
    BazCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public int getCode() {
        return code;
    }
    public String getMsg() {
        return msg;
    }
}

3 分组校验(多场景的复杂校验)

1.@NotBlank(message = “品牌名必须提交”,groups = {AddGroup.class,UpdateGroup.class})给校验注解标注什么情况需要进行校验
而AddGroup.class,UpdateGroup.class是定义的空接口

package com.atguigu.common.valid;
/**
 * 校验分组用
 */
public interface AddGroup {
}

2.@Validated({AddGroup.class}) 指定当前校验用的分组

    @RequestMapping("/save")
    //@RequiresPermissions("product:brand:save")
//    public R save(@Valid @RequestBody BrandEntity brand/**, BindingResult result*/) {
        public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/**, BindingResult result*/) {  //指定校验分组@Validated
//        if (result.hasErrors()) {
//            Map<String, String> map = new HashMap<>();
//            result.getFieldErrors().forEach((item)->{
//                String defaultMessage = item.getDefaultMessage();
//                String field = item.getField(); //错误的字段名
//                map.put(field, defaultMessage);
//            });
//            return R.error(400, "提交的数据不合法").put("data",map);
//        } else {
//
//            brandService.save(brand);
//        }
        brandService.save(brand);
        
        return R.ok();
    }

PS:默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;指定分组的注解只会在对应分组下有效

4 自定义校验

1.编写一个自定义的校验注解(仿照别的校验注解编写

@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class }) //自定义校验注解绑定自定义校验器 【可以指定多个不同的校验器,适配不同类型的校验】
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
    String message() default "{com.atguigu.common.valid.ListValue.message}"; //错误提示信息获取来源,在配置文件中指定
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
    int[] values() default {};
}

仿照上面的错误默认消息提示,新建配置文件ValidationMessages.properties

com.atguigu.common.valid.ListValue.message= xu must submit the special value

2.编写一个自定义的校验器 ConstraintValidator

public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
    private Set<Integer> set = new HashSet<>();

    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] values = constraintAnnotation.values(); //获取注解给的值
        for (Integer val : values) {
            set.add(val);
        }
    }

    /**
     *
     * @param value 需要校验的值
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {

        return set.contains(value);
    }
}

5 单字段多校验注解error合并

在这里插入图片描述接收四个字段出现七个错误,因为有的字段标了不止一个注解

Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));

上面的方式收集是不可行的,会报Duplicated key,后缀个合并函数方可

@param mergeFunction a merge function, used to resolve collisions
between
* values associated with the same key, as supplied
* to {@link Map#merge(Object, Object, BiFunction)}

Map<String, String> errors = result.getFieldErrors().stream().collect(
                    Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage,(v1,v2)->v1+","+v2));
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空•物语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值