先给个实体类的校验注解示意,其中@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));