文章目录
- JSR303数据校验
- 引入依赖和简介
- 配置验证规则
- 开启验证
- BindResult
- 校验的统一异常处理
- JSR303分组校验
- 自定义校验注解
JSR303数据校验
引入依赖和简介
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
里面依赖了hibernate-validator 在非空处理方式上提供了@NotNull,@NotBlank和@NotEmpty
1 @NotNull
注解元素禁止为null,能够接收任何类型
2 @NotEmpty
该注解修饰的字段不能为null或""
支持以下几种类型:
-
字符序列(字符序列长度的计算)
-
集合长度的计算
-
map长度的计算
-
数组长度的计算
3 @NotBlank
该注解不能为null,并且至少包含一个非空格字符。接收字符序列。
配置验证规则
/**
* 品牌
* @author WSKH
* @email 1187560563@qq.com
* @date 2022-01-03 18:28:04
*/
@Data
@ToString
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须非空")
private String name;
/**
* 品牌logo地址
*/
@NotEmpty(message = "品牌logo地址不能为空")
// @URL(message = "品牌logo地址必须是合法的URL")
private String logo;
/**
* 介绍
*/
@NotEmpty(message = "描述信息不能为空")
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(message = "显示状态不能为null")
private Integer showStatus;
/**
* 检索首字母
*/
@Pattern(regexp = "[a-zA-Z]",message = "检索首字母必须是a-z或A-Z")
@NotEmpty(message = "检索首字母不能为空")
private String firstLetter;
/**
* 排序
*/
@NotNull(message = "排序信息不能为null")
private Integer sort;
}
开启验证
在需要验证的controller参数前加上注解,即可开启验证
用postMan进行测试
测试结果如下:
BindResult
给校验的Bean后,紧跟一个BindResult,就可以获取到校验的结果。拿到校验的结果,就可以自定义的封装。
@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)->{
// 2.获取发生错误时的message
String message = item.getDefaultMessage();
// 3.获取发生错误的属性名
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}else {
// 如果校验通过 就进行保存操作
brandService.save(brand);
return R.ok();
}
}
再用PostMan进行测试看看
比刚刚整齐好看多了!
校验的统一异常处理
可以使用SpringMvc所提供的@ControllerAdvice,通过“basePackages”能够说明处理哪些路径下的异常。
0 将原来controller上的BindResult去掉
1 抽取一个异常处理类
// 开启日志功能
@Slf4j
// 使用SpringMvc所提供的@ControllerAdvice,通过“basePackages”能够说明处理哪些路径下的异常。
@RestControllerAdvice(basePackages = "com.wskh.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class) // 也可以返回ModelAndView
public R handleValidException(MethodArgumentNotValidException exception){
Map<String,String> map=new HashMap<>();
// 获取数据校验的错误结果
BindingResult bindingResult = exception.getBindingResult();
bindingResult.getFieldErrors().forEach(fieldError -> {
String message = fieldError.getDefaultMessage();
String field = fieldError.getField();
map.put(field,message);
});
log.error("数据校验出现问题{},异常类型{}",exception.getMessage(),exception.getClass());
return R.error(400,"数据校验出现问题").put("data",map);
}
}
2 默认异常处理
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("未知异常{},异常类型{}",throwable.getMessage(),throwable.getClass());
return R.error(400,"数据校验出现问题");
}
3 错误状态码和错误信息规范化声明
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*/
public enum BizCodeEnum {
UNKNOW_EXEPTION(10000,"系统未知异常"),
VALID_EXCEPTION( 10001,"参数格式校验失败");
private int code;
private String msg;
BizCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
4 使用错误状态码和状态信息
// 开启日志功能
@Slf4j
// 使用SpringMvc所提供的@ControllerAdvice,通过“basePackages”能够说明处理哪些路径下的异常。
@RestControllerAdvice(basePackages = "com.wskh.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class) // 也可以返回ModelAndView
public R handleValidException(MethodArgumentNotValidException exception){
Map<String,String> map=new HashMap<>();
// 获取数据校验的错误结果
BindingResult bindingResult = exception.getBindingResult();
bindingResult.getFieldErrors().forEach(fieldError -> {
String message = fieldError.getDefaultMessage();
String field = fieldError.getField();
map.put(field,message);
});
log.error("数据校验出现问题{},异常类型{}",exception.getMessage(),exception.getClass());
return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",map);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("未知异常{},异常类型{}",throwable.getMessage(),throwable.getClass());
return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),BizCodeEnum.UNKNOW_EXEPTION.getMsg());
}
}
5 测试下看看
错误状态码和错误信息都已经按照我们自定义的输出了
JSR303分组校验
分组校验常常用于完成多场景的复杂校验
如:指定在更新和添加的时候,都需要进行校验。新增时不需要带id,修改时必须带id
1 在common模块下创建一个文件夹vaild,里面创建两个空的接口AddGroup和UpdateGroup
2 在请求传入参数对象中的校验注解中指定其生效的groups
3 在controller中加入@Validated注解,并指定其对应的分组
4 测试下看看
指定id,测试新增产品
不指定id,测试修改产品
自定义校验注解
场景:要校验showStatus的01状态,可以用正则,但我们可以利用其他方式解决
1 添加依赖
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
2 common模块中新建空接口UpdateStatusGroup,代表只校验showStatus的分组
3 编写自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set=new HashSet<>();
@Override
public void initialize(ListValue constraintAnnotation) {
int[] value = constraintAnnotation.value();
for (int i : value) {
set.add(i);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 如果所传参数在指定的set集合中,才允许校验通过
return set.contains(value);
}
}
4 编写自定义的校验注解
@Documented
// 关联校验器和校验注解(一个校验注解可以匹配多个校验器)
@Constraint(validatedBy = { ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
// 使用该属性去Validation.properties中取
String message() default "{com.wskh.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] value() default {};
}
5 在common模块的resources文件夹下,创建Validation.properties文件,在里面定义校验失败要返回的msg
6 将注解应用在实例对象的显示状态属性上,并设置分组
7 新增一个只用来修改显示状态的controller方法,分组设置为UpdateStatusGroup.class
/**
* 修改显示状态
*/
@RequestMapping("/update/status")
// @RequiresPermissions("product:brand:update")
public R updateStatus(@Validated(UpdateStatusGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
8 测试一下看看
先试一下不输入显示状态
再试一下输入显示状态为3,会不会成功报错
这里可以看到已经报错了,而且是按照配置文件中的msg来报的,只不过由于项目的编码格式问题,导致出现了乱码
解决方案:在idea设置中,将项目编码格式全部设置为UTF-8
如果改了之后还是这样。那就用英文吧…