JRS303 做后端数据校验
1. 引入springboot整合jrs依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2. 普通校验
1、给bean添加校验注解
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@Null(message = "新增不能指定ID")
@NotNull(message = "修改必须指定ID")
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交")
private String name;
/**
* 品牌logo地址
*/
@NotBlank(message = "品牌logo地址必须提交")
@URL(message = "logo必须是一个合法的URL地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
//自定义注解
private Integer showStatus;
/**
* 检索首字母
*/
@NotBlank(message = "检索首字母必须提交")
@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须是一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull(message = "排序必须提交")
@Min(value = 0,message = "排序必须大于等于0")
private Integer sort;
}
2、在controller层方法的参数上开启校验功能
可以给校验的参数bean在controller的方法参数紧跟一个BindResult参数 可以自定义错误处理
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand,BindingResult bindingResult){
if(bindingResult.hasErrors()){
//获取校验的错误结果
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);
}else{
brandService.save(brand);
return R.ok();
}
brandService.save(brand);
return R.ok();
}
3、统一异常处理
避免在controller层处理异常,使用统一异常处理@RestControllerAdvice
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
//不添加bindingresult时异常会抛出
brandService.save(brand);
return R.ok();
}
新建异常处理类捕获处理异常
/**
* 集中处理所有异常
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.rwp.gulimail.product.controller")
public class GulimailExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleVaildException(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(BizCodeEnume.VALID_EXCEPTION.getCode(),BizCodeEnume.VALID_EXCEPTION.getMessage()).put("data",map);
}
//全局异常
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
return R.error(BizCodeEnume.UNKONW_EXCEPTION.getCode(),BizCodeEnume.UNKONW_EXCEPTION.getMessage());
}
}
3. 分组校验(多场景复杂校验)
1、新建场景接口作为标志(不做任何处理)
public interface AddGroup {
}
public interface UpdateGroup {
}
2、实体类的校验标签添加groups属性
@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地址
*/
@NotBlank(message = "品牌logo地址必须提交" ,groups = {
AddGroup.class})
@URL(message = "logo必须是一个合法的URL地址" ,groups = {
AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotBlank(message = "检索首字母必须提交" ,groups = {
AddGroup.class})
@Pattern(regexp = "^[a-zA-Z]$",message = "检索首字母必须是一个字母" ,groups = {
AddGroup.class,UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(message = "排序必须提交",groups = {
AddGroup.class})
@Min(value = 0,message = "排序必须大于等于0" ,groups = {
AddGroup.class,UpdateGroup.class})
private Integer sort;
}
3、controller的方法参数指定属于哪一个groups @Validated
默认没有指定分组的校验注解 在分组的校验情况下不生效,只会在@Valid生效
@RequestMapping("/save")
public R save(@Validated({
AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
4. 自定义校验
引入依赖
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
效果
给BrandEntity的showstatus属性添加自定义校验,目的:showstatus的值只能为0或1
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ..............
*/
/**
* 显示状态[0-不显示;1-显示]
*/
//自定义注解
@ListValue(values={
0,1},groups = {
AddGroup.class})
private Integer showStatus;
/**
* ..........
*/
}
1、编写一个自定义的检验注解
@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 {
//前三个是jrs303的规范
String message() default "{com.rwp.common.valid.ListValue.message}";
Class<?>[] groups() default {
};
Class<? extends Payload>[] payload() default {
};
int[] values() default {
}; //自己添加的属性
}
2、编写一个自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer>{
//泛型第一个为注解类型,第二个为注解标注的属性的类型
private Set<Integer> set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] values = constraintAnnotation.values();
for (int value: values
) {
set.add(value);
}
}
//判断是否校验成功
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
if(set.contains(integer)){
return true;
}else {
return false;
}
}
}
3、关联自定义的校验器和自定义的校验注解
1、校验器实现的接口泛型指明注解属性
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer>{
2、注解使用@Constraint添加校验器
@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 {