前言
在我们开发项目的时候数据校验是一个麻烦的事情,所说前端也会进行数据校验,但是我们后台也需要统一校验一下,但是这样的话,我也不是很好去找报错信息,这里我们可以做一个那个字段异常我们就返回那个字段异常的消息
接下来开始操作,开干
首先我们需要了解一下JSR-303是个啥玩意
这里给一个JSR-303官方文档
这个玩意是一套对于javaBean进行校验的javaAPI规范
简单的数据校验
如果你是springboot 2.x的版本的话
我们需要引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
然后我们就可以在Entity中,对我们需要校验的字段添加注解
例如: 字段不能为空,则在相对应得字段上面添加@NotEmpty
这个字段(这个字段是针对字符串得 @NotNull
(int类型)),我们还可以自定义对错误得说明 @NotEmpty (message = "XX必须提交")
类似于这样得
@NotNull(message = "排序字段不能为空")
@Min(value = 0, message = "排序字段必须大于等于0") // 这个字段标识我这个最小值为0
private Integer sort;
然后我们还在在controller
层中对需要校验得内同添加注解 @Valid
这样就会后台就会自动去校验我们每个字段前端传过来得数据,根据我们后台定义得规则
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
//....
}
如果我们需要对校验结果得异常信息进行记录,并且返回给前端:请看下面操作
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand , BindingResult result){
if (result.hasErrors()){
Map<String, String> map = new HashMap<>();
result.getFieldErrors().forEach((item) ->{
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();
}
BindingResult result 这个字段必须加上,因为在这里面记录了校验结果得消息,通过getField()和getDefaultMessage()分别得到字段名和校验不通过给出的信息(我们自定义的消息)
这样我们就完成了最基本得数据校验功能,这里面还有很多校验得规则或者方法,我们可以去查看官方文档进行选择我们所需要得
对于复杂场景得数据校验
这个按照每个业务得不同对数据要求不同进行数据校验,很显然上面得提供得方法并不满足我们这个场景得数据校验需求,所以这里我们提出了分组校验得方式
理解 :这里既然说出了分组,其实就是一个标识,这个数据校验应该使用哪一种校验规则
首先我们要创建出两个分组:(这个分组就是创建一个空接口,便于我们标识)
/**
* description: 新增数据校验分组标识
* @author : 沐光
* @date : 2023-12-04
*/
public interface AddGroup {
}
/**
* description: 修改数据校验分组
* @author : 沐光
* @date : 2023-12-04
*/
public interface UpdateGroup {
}
接下来修改实体类里面得数据校验规则:(给个示例)
@NotNull(message = "修改必须传入id", groups = {UpdateGroup.class,UpdateStatusGroup.class})
@Null(message = "新增不能指定id", groups = {AddGroup.class})
@TableId
private Long brandId;
controller : 注解修改为@Validated(AddGroup.class)
:并且指定那个分组校验
@RequestMapping("/save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand , BindingResult result){
if (result.hasErrors()){
Map<String, String> map = new HashMap<>();
result.getFieldErrors().forEach((item) ->{
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();
}
注意:
如果我们使用了分组校验,但是实体类里面得注解也都必须指定分组,不然不会生效,还有控制层里面得注解也都必须指定分组
统一异常处理
到这里发现我们无论使用那种校验,他的异常信息处理好像代码都是重复,可以抽取出来,做一个全局得统一异常处理
这段代码都是重复得
if (result.hasErrors()){
Map<String, String> map = new HashMap<>();
result.getFieldErrors().forEach((item) ->{
String message = item.getDefaultMessage();
String field = item.getField(); // 获取校验错误的名字
map.put(field,message);
});
return R.error(400, "提交的数据不合法").put("data", map);
}
接下来我们抽取成为一个统一处理异常信息并且统一返回得信息
创建一个统一处理得类
/**
* description : 字段数据校验统一的异常处理类 JSR303
* @author : 沐光
* @date : 2023-12-04
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.muguang.controller")// 这个包名就是我们要直接捕获那个包里面得异常信息
public class MallExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e) {
log.error("数据校验出现问题:{}, 异常类型:{}", e.getMessage(), e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String, String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach((item) -> {
String message = item.getDefaultMessage(); // 错误的默认信息
String field = item.getField(); // 出现校验错误的字段
map.put(field, message);
});
return R.error(400, "数据校验错误")
.put("data", map);
}
// 这里我们用来捕获其他错误,错误信息将会输出在控制台
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("出现异常的方法及其信息"+ throwable.getMessage(), throwable.getClass());
return R.error(400, "系统错误");
}
}
controller
@RequestMapping("/save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
这样我们得异常信息统一处理返回就完成
自定义数据校验规则以及校验器
首先我们得创建一个注解
ListValue
和一个自定义校验器ListValueConstraintValidator
这个注解要符合JRS-303
标准,必须要有三个字段:
- 字段 message 定义:返回的错误消息。用于当 JavaBean 验证失败后,返回该内容
- 字段 groups 定义:该元素指定与约束声明关联的处理组。比如设我们的昵称,我们可能会设定多个限制,只允许英文,长度在 10 个以内。以 yyds_开头…登等, 像这样有多重限制的规则可以定义在一个组内 NamesGroups,当然默认位空,该规则就是单独的。
- 方法 payload() 定义:有效负载元素,该元素指定与约束声明关联的有效负载。这一方法通常是指定该注解只能够添加到哪个字段上去。默认是为空的,可以添加在任何字段上
/**
* description: 自定义校验注解
*
* @author : 沐光
* @date : 2023-12-04
*/
@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.muguang.common.valid.ListValue.message}";// 这里就是你的自定义注解得包路径获取里面得message
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() default {};
}
自定义校验器
/**
* description : 自定义校验器
* @author : 沐光
* @date : 2023-12-04
*/
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);
}
}
// 判断是否校验成功
/**
* description 判断是否校验成功
* @param value 需要校验的值
* @param context
* @return boolean
* @author 沐光
* @date 2023/12/4
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
自定义注解和校验器进行关联,在注解里面指定你的校验器
还有一个配置:我们需要在这里添加一个配置文件,名为:ValidationMessages.properties
指定我们需要返回得消息
com.muguang.common.valid.ListValue.message= 自定义返回消息
entity : 使用自定义注解
@ListValue(vals = {0,1}, groups = {AddGroup.class})
private Integer showStatus;
自定义注解使用起来和上面得方式一样得就是我们要去配置一些东西,创建一些类,到这里就结束了,还有很多东西没有写出来可以去官方网站看看JSR-303官方文档,写的不是很好,大家多给点意见。