JSR303校验前端传递的数据方式
作者:龙域、白泽
介绍
JSR-303规范(Bean Validation规范)提供了对 Java EE 和 Java SE 中的 Java Bean 进行验证的方式。
该规范主要使用注解的方式来实现对 Java Bean 的验证功能。
作用
前端传递数据到后端时,可以使用其对Bean对象的属性进行合法性校验。
快速开始
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Java Bean
@Data @AllArgsConstructor @NoArgsConstructor @TableName("user") public class User { @TableId private Long id; @NotBlank(message = "用户名不能为空") @TableField("user_name") private String userName; @Size(max = 20,min = 6,message = "密码的长度必须在6-20位") @TableField("pass_word") private String passWord; @TableField("group_id") private Long groupId; @Max(value = 100, message = "城市编号不等大于100") @TableField("city_id") private Long cityId; @Email(message = "邮件不合法") @TableField("email") private String email; }
统一返回结果
统一响应结果枚举类
@Getter @AllArgsConstructor @ToString public enum ResponseEnum { SUCCESS(0, "成功"), ERROR(-1, "服务器内部错误"), private final Integer code; private final String message; }
统一结果返回类
@Data public class R { private Integer code; private String message; /** * 返回的数据 */ private Map<String, Object> data = new HashMap<>(); private R() { } public static R ok() { return setResult(ResponseEnum.SUCCESS); } public static R error() { return setResult(ResponseEnum.ERROR); } /** * 设置特定结果 */ public static R setResult(ResponseEnum responseEnum) { R r = new R(); r.setCode(responseEnum.getCode()); r.setMessage(responseEnum.getMessage()); return r; } /** * 设置响应消息 */ public R message(String message) { this.setMessage(message); return this; } public R code(Integer code) { this.setCode(code); return this; } public R data(String key, Object value) { this.data.put(key, value); return this; } public R data(Map<String, Object> map) { this.setData(map); return this; } }
方法一
Bean对象的下一个位置的参数写BindingResult对象,当JSR303校验失败后可以由BindResult对象捕获异常
controller层
@Valid注解后面的对象是要校验的Bean对象
Bean对象的下一个位置的参数写BindingResult对象
@RestController @RequestMapping("/user") public class UserController { @Resource private UserServiceImpl userService; @PostMapping public R addUser(@Valid @RequestBody User user, BindingResult bindingResult) { Map<String, Object> map = new HashMap<>(); // 判断是否有错误 if (bindingResult.hasErrors()) { // 如果有错误,遍历错误信息,添加到Map中 bindingResult.getFieldErrors().forEach((item) -> { // 获取错误提示 String message = item.getDefaultMessage(); // 获取错误的属性名称 String field = item.getField(); map.put(field, message); }); return R.error().message("参数信息错误").data(map); } userService.save(user); return R.ok(); } }
方法二
当接口非常多,每一个接口都要写校验非常麻烦,写一个统一异常处理的类来集中处理异常
统一异常处理类
@Slf4j @Component //Spring容易自动管理 @RestControllerAdvice //在controller层添加通知。当Controller层出现异常,这里的方法会捕获异常,返回错误信息(相当于服务降级) public class UnifiedExceptionHandler { /** * 参数校验异常处理 */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public R handleValidException(MethodArgumentNotValidException e) { log.error("数据校验出现问题{}, 异常类型{}", e.getMessage(), e.getClass()); BindingResult bindingResult = e.getBindingResult(); Map<String, Object> map = new HashMap<>(); bindingResult.getFieldErrors().forEach((item) -> { map.put(item.getField(), item.getDefaultMessage()); }); return R.error().message("参数信息错误").data(map); } }
controller层
接口方法的参数如果有BindResult对象,代表校验出错由BindResult接受异常
接口方法的参数没有BindResult对象,代表校验出错将抛出异常,被统一异常处理类的方法接受
接口方法的参数没有BindResult对象,也没有统一异常处理类的方法接受,就抛出400的异常
@PostMapping public R addUser(@Valid @RequestBody User user) { userService.save(user); return R.ok(); }
测试
校验成功的测试
请求体的JSON数据
{ "userName": "lixianchichi", "passWord": "104ee44", "groupId": 1, "cityId": 14, "email": "123456@qq.com" }
返回的响应信息
{ "code": 0, "message": "成功", "data": {} }
校验失败的测试
请求体的JSON数据
{ "userName": "lixianchichi", "passWord": "10444eeeeeeeeeeeeeeeeee44", "groupId": 1, "cityId": 1514, "email": "123456@qq.com" }
返回的响应信息
{ "code": -1, "message": "参数信息错误", "data": { "passWord": "密码的长度必须在6-20位", "cityId": "城市编号不等大于100" } }
方法一与方法二测试结果相同,都为如上结果
分组校验
使用场景:不同情况下的校验规则是不同的,如新增的时候自动生成Id,所以数据不需要携带Id,而修改的时候必须要携带Id(不同场景触发不同的校验条件)
创建valid包
包里面创建两个空接口InsertGroup和UpdateGroup,代表新增和修改两种环境。
Java Bean
每一个Bean校验注解都有一个groups属性,值是一个接口字节码对象的数据,用来指定环境。
@Data @AllArgsConstructor @NoArgsConstructor @TableName("user") public class User { @TableId // 更新的时候校验 @NotNull(message = "修改用户id不能为null" ,groups = {UpdateGroup.class}) // 新增的时候校验 @Null(message = "新增用户id必须为null" ,groups = {InsertGroup.class}) private Long id; // 新增和修改的时候都校验 @NotBlank(message = "用户名不能为空" ,groups = {InsertGroup.class, UpdateGroup.class}) @TableField("user_name") private String userName; ... // 修改的时候判断email是否合法,可以null,不报错(不传就不校验) @Email(message = "邮件不合法" ,groups = {UpdateGroup.class}) @TableField("email") private String email; }
controller层
使用@Validated代替@Valid注解,该注解可以指定环境(接口字节码对象),
如下:代表当前是新增的情况
@PostMapping public R addUser(@Validated({InsertGroup.class}) @RequestBody User user) { userService.save(user); return R.ok(); }
注意:没有指定groups属性的注解,在controller层指定环境的情况下,不会生效
测试
在新增环境,前端传递JSON对象如果带Id属性
@PostMapping public R addUser(@Validated({InsertGroup.class}) @RequestBody User user) { userService.save(user); return R.ok(); }
响应结果
{ "code": -1, "message": "参数信息错误", "data": { "id": "新增用户id必须为null" } }
在修改环境,前端传递JSON对象如果带Id属性
@PostMapping public R addUser(@Validated({UpdateGroup.class}) @RequestBody User user) { userService.save(user); return R.ok(); }
响应结果
{ "code": -1, "message": "参数信息错误", "data": { "id": "修改用户id不能为null" } }
自定义校验注解
实现功能:校验Bean的某个属性的字段只能是0和1
@Data @AllArgsConstructor @NoArgsConstructor @TableName("user") public class User { ... @TableField("group_id") @ListValue(vals = {0L, 1L}, groups = {UpdateGroup.class}) private Long groupId; ... }
自定义的校验注解
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @Documented // 指定校验器,可以指定多个校验器,会自动适配,如:此注解还有一个Double数组的属性,再添加一个Double类型的校验器,当我们使用注解的时候,我们给Double数组赋值,就会自动找Double类型的校验器校验 @Constraint(validatedBy = {ListValueCondtraintValidator.class}) public @interface ListValue { // 前三个属性都是每个JSR303注解必须有的 // 默认错误信息,从properties里获取值 String message() default "{com.lixianhe.valid.listValue.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; long[] vals() default {}; }
自定义校验器
/** * 自定义校验器 */ public class ListValueCondtraintValidator implements ConstraintValidator<ListValue, Long> { private Set<Long> set = new HashSet<>(); /** * 初始化方法 * * @param constraintAnnotation */ @Override public void initialize(ListValue constraintAnnotation) { long[] vals = constraintAnnotation.vals(); for (long val : vals) { set.add(val); } } /** * 判断是否校验成功 * * @param value 需要校验的值 * @param constraintValidatorContext * @return 校验结果 */ @Override public boolean isValid(Long value, ConstraintValidatorContext constraintValidatorContext) { return set.contains(value); } }
测试
当groupId传2的时候
{ "userName": "lixianchichi", "passWord": "104eefd454545546456565tygpl[per44", "groupId": 2, "cityId": 10 }
响应结果
{ "code": -1, "message": "参数信息错误", "data": { "groupId": "必须提交指定的值" } }
补充
Java Bean校验注解总结
限制 | 说明 |
---|---|
@Null | 限制只能为null |
@NotNull | 限制必须不为null |
@AssertFalse | 限制必须为false |
@AssertTrue | 限制必须为true |
@DecimalMax(value) | 限制必须为一个不大于指定值的数字 |
@DecimalMin(value) | 限制必须为一个不小于指定值的数字 |
@Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
@Future | 限制必须是一个将来的日期 |
@Max(value) | 限制必须为一个不大于指定值的数字 |
@Min(value) | 限制必须为一个不小于指定值的数字 |
@Past | 限制必须是一个过去的日期 |
@Pattern(value) | 限制必须符合指定的正则表达式 |
@Size(max,min) | 限制字符长度必须在min到max之间 |
@Past | 验证注解的元素值(日期类型)比当前时间早 |
@NotEmpty | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@NotBlank | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 | |
@URL | 校验是否位合法的URL |
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。