数据校验是用来判断输入的数据是否满足规定的要求的。
前端可用JS来校验,如用户名唯一性,生日格式,邮箱格式校验等。
前端做了数据校验,后端也做数据校验的原因是防止有人绕过前端界面,直接向后端发起请求,导致数据库内写入了脏数据。
JSR提供的校验注解:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator提供的校验注解:
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
1、添加校验注解
在属性上添加校验注解,用于校验输入的属性是否符合校验规则。
还可以在校验注解上自定义校验失败后显示的信息。如@Null(message = “新增时不能指定id”),当校验失败后,错误信息展示为message中内容。
/**
* 品牌
*
* @author xiao-wang
* @email
* @date 2022-03-09 10:19:41
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
@Null(message = "新增时不能指定id")
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名不能为空")
private String name;
/**
* 品牌logo地址
*/
@NotBlank
@URL(message = "url地址必须是一个有效的地址")
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
// @TableLogic(value = "1",delval = "0")
@NotNull(message = "显示状态不能为空")
private Integer showStatus;
/**
* 检索首字母
*/
@NotBlank
@Pattern(regexp = "^[a-zA-Z]$",groups = {message = "首字母必须为a-z或者A-Z中的一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull
private Integer sort;
}
2、在controller层添加@valid注解
只添加校验注解,校验是不会生效的。在controller层的某个方法上添加了@valid注解,校验才会生效。
@RestController
@RequestMapping("product/brand")
public class BrandController {
@Autowired
private BrandService brandService;
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
}
用postman测试,校验完成,出现提示信息。
{
"timestamp": "2022-03-31 02:15:05",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.brandEntity.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"brandEntity.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "品牌名不能为空",
"objectName": "brandEntity",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='brandEntity'. Error count: 6",
"path": "/product/brand/save"
}
但是返回的格式不是自己需要的。
要求返回的格式:
{
"msg": "提交数据不合法",
"code": 400,
"data": {
"校验失败属性名": "错误提示信息",
}
}
3、统一异常处理
为了规范异常返回的格式,返回一个R类型,R类型的格式为上述要求返回的格式
避免代码的冗余,新建一个类用来统一异常处理。
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionController {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
//表示能捕获的异常类型为MethodArgumentNotValidException
public R methodArgumentValidException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
//e.getBindingResult()表示校验结果
Map<String,String> errorsMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
//遍历校验结果中的错误信息
String field = item.getField();
//错误信息中的报错的属性名
String message = item.getDefaultMessage();
//报错的错误提示
errorsMap.put(field,message);
});
return R.error(400,"提交数据不合法").put("data",errorsMap);
}
}
有的属性在新增的时候必须为空,但是修改的时候必须不为空
所以,需要对属性进行分组校验。
1、创建好分组接口,并在实体类的属性上加上groups=哪个分组
在如下示例中,需要创建AddGroup、UpdateGroup这两个接口
然后新增的时候为空,就在@Null注解上添加groups = {AddGroup.class})
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@TableId
@Null(message = "新增时不能指定id",groups = {AddGroup.class})
@NotNull(message = "修改时必须指定id",groups = {UpdateGroup.class})
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名不能为空",groups = {AddGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "url地址必须是一个有效的地址",groups = {AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
// @TableLogic(value = "1",delval = "0")
@NotNull(groups = {AddGroup.class},message = "显示状态不能为空")
@ListValues(values = {0,1},groups = {AddGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotBlank(groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z]$",groups = {AddGroup.class, UpdateGroup.class},message = "首字母必须为a-z或者A-Z中的一个字母")
private String firstLetter;
/**
* 排序
*/
@NotNull(groups = {AddGroup.class})
private Integer sort;
}
2、controller层需要将@valid注解换成@Validated注解
由上可知,只添加注解是不会生效的,需要在controller层的方法内添加@Valid注解,但是@Valid注解没有分组功能,因此,换成@Validated注解,指定生效的是哪个分组。
@RestController
@RequestMapping("product/brand")
public class BrandController {
@Autowired
private BrandService brandService;
@RequestMapping("/save")
public R save(@Validated(value = {AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
}
1、自定义校验注解
自己添加一个注解,规定该属性值只能填入规定的0和1,否则会报错
@Constraint(validatedBy = {ListvaluesConstraintValidator.class})
//指定该注解由ListvaluesConstraintValidator.class来校验
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValues {
String message() default "{com.atguigu.common.valid.ListValues.message}";
//在resources中新建ValidationMessages.properties(必须为此名字),就可指定该注解的默认报错信息
//在ValidationMessages.properties中填写,com.atguigu.common.valid.ListValues.message = 请输入指定的值,
//当报错时,默认报错信息为“请输入指定值”
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] values() default {};
}
2、编写校验类
校验类实现真正的校验功能,initialize表示初始化功能,isValid判断是否校验成功。
public class ListvaluesConstraintValidator implements ConstraintValidator<ListValues,Integer> {
HashSet<Integer> set = new HashSet<Integer>();
public void initialize(ListValues constraintAnnotation) {
for (int value : constraintAnnotation.values()) {
set.add(value);
}
}
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
return set.contains(integer);
}
}