我们的校验框架通常用于DTO层,用户从前端通过Json串传入值,后端对传入的Json封装到DTO层中,我们对DTO层的元素进行校验,比如邮箱,手机号等等。
首先需要引入bean校验需要的jar包:
<!-- Validation 相关依赖 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.16.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.6</version>
</dependency>
怎么用?看下面的例子
@Data
public class UserDTO implements Serializable {
/**
* serialVersionUID
*/
private static final long serialVersionUID = -5087359471567830690L;
/**
* 用户名
*/
@NotBlank(message = "用户名不能为空!",
groups = {InsertValidationGroup.class})
private String username;
/**
* 用户密码
*/
@NotBlank(message = "密码不能为空!",
groups = {InsertValidationGroup.class})
@Length(min = 6, max = 18,
message = "密码长度不能少于6位,不能多于18位!")
private String password;
/**
* 邮箱
*/
@NotEmpty(message = "邮箱不能为空!",
groups = {InsertValidationGroup.class})
@Email(message = "必须为有效邮箱!")
private String email;
/**
* 年龄
*/
@NotNull(message = "年龄不能为空!",
groups = {InsertValidationGroup.class})
@Max(value = 60, message = "年龄不能大于60岁!")
@Min(value = 18, message = "年龄不能小于18岁!")
private Integer age;
/**
* 手机号
*/
@NotBlank(message = "手机号不能为空!",
groups = {InsertValidationGroup.class})
private String phone;
/**
* 版本号
*/
@NotNull(message = "版本号不能为空!",
groups = {UpdateValidationGroup.class})
private Long version;
/**
* 创建时间
*/
private LocalDateTime created;
如果没有遵循校验框架的注解,就会抛出message中的异常信息。
常用的约束注解:
- 空值校验类: @Null, @NotNull, @NotEmpty, @NotBlank等
- 范围校验类: @Min, @Size, @Digits, @Future, @Negative等
- 其他校验类: @Email, @URL, @AssertTrue, @Pattern等
上面可以看到 g r o u p s = U p d a t e V a l i d a t i o n G r o u p . c l a s s groups = {UpdateValidationGroup.class} groups=UpdateValidationGroup.class的代码,这个是分组校验。也就是说,比如主键注册的时候的无需填入,可以为空值,不需要在插入的时候校验。比如上面的版本号(乐观锁),只是在更新的时候校验。控制层可以这么写:
public ResponseResult update(
@NotNull @PathVariable("id") Long id,
@Validated(UpdateValidationGroup.class)
@RequestBody UserDTO userDTO) {
int update = userService.update(id, userDTO);
if (update == 1) {
return ResponseResult.success("更新成功!");
} else {
return ResponseResult.failure(ErrorCodeEnum.UPDATE_FAILURE);
}
}
当然,还需要创建两个空的接口分组:
public interface UpdateValidationGroup {
}
public interface InsertValidationGroup {
}
我们还可以自定义校验注解:
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* 自定义手机号约束注解
*/
@Documented
// 注解的作用
@Target({ElementType.FIELD})
// 注解的保留策略
@Retention(RetentionPolicy.RUNTIME)
// 与非约束注解的不同之处
// 约束注解关联验证器类
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
// 约束注解验证时输出的信息
String message() default "手机号校验错误";
// 约束注解在验证时所属的组别
Class<?>[] groups() default {};
// 约束注解的有效负载
Class<? extends Payload>[] payload() default {};
}
/**
* 自定义手机号约束注解关联验证器
*/
public class PhoneValidator implements ConstraintValidator<Phone, String> {
/**
* 自定义校验逻辑方法
* @param s
* @param constraintValidatorContext
* @return
*/
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
// 演示demo
// 手机号验证规则 158...
String check = "158\\d{8}";
Pattern regex = Pattern.compile(check);
String phone = Optional.ofNullable(s).orElse("");
Matcher matcher = regex.matcher(phone);
return matcher.matches();
}
}
我们捕捉到了异常之后,下面对异常进行统一的处理:
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 拦截业务类异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(value = BusinessException.class)
public ResponseResult businessExceptionHandle(BusinessException e) {
log.error("捕捉到业务类异常:", e);
return ResponseResult.failure(e.getCode(), e.getMessage());
}
/**
* 拦截运行时异常
* @param e
*/
@ResponseBody
@ExceptionHandler(value = RuntimeException.class)
public ResponseResult runtimeExceptionHandle(RuntimeException e) {
log.error("捕捉到运行时异常:", e);
return ResponseResult.failure(
ErrorCodeEnum.UNKNOWN_ERROR.getCode(),
e.getMessage());
}
/**
* 捕捉系统级异常
* @param th
* @return
*/
@ResponseBody
@ExceptionHandler(value = Throwable.class)
public ResponseResult throwableHandle(Throwable th) {
log.error("捕捉Throwable异常:", th);
return ResponseResult.failure(
ErrorCodeEnum.SYSTEM_ERROR.getCode(),
th.getMessage());
}
}
统一的结果封装:
@Data
@ApiModel(
value = "统一返回结果实体",
description = "封装统一返回结果信息实体"
)
public class ResponseResult<T> implements Serializable {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 7813356989387725160L;
/**
* 是否成功
*/
@ApiModelProperty(
name = "success",
value = "是否成功",
required = true,
dataType = "Boolean"
)
private Boolean success;
/**
* 编码
*/
@ApiModelProperty(
name = "code",
value = "编码",
required = false,
dataType = "String"
)
private String code;
/**
* 描述信息
*/
@ApiModelProperty(
value = "描述信息"
)
private String message;
/**
* 结果
*/
@ApiModelProperty(
value = "泛型结果T"
)
private T result;
/**
* 成功
* @param result
* @param <T>
* @return
*/
public static <T> ResponseResult<T> success(T result) {
ResponseResult<T> responseResult = new ResponseResult<>();
responseResult.setSuccess(Boolean.TRUE);
responseResult.setResult(result);
return responseResult;
}
/**
* 失败
* @param code
* @param message
* @param <T>
* @return
*/
public static <T> ResponseResult<T> failure(String code, String message) {
ResponseResult<T> responseResult = new ResponseResult<>();
responseResult.setSuccess(Boolean.FALSE);
responseResult.setCode(code);
responseResult.setMessage(message);
return responseResult;
}
/**
* 失败
* @param codeEnum
* @param <T>
* @return
*/
public static <T> ResponseResult<T> failure(ErrorCodeEnum codeEnum) {
return failure(codeEnum.getCode(), codeEnum.getMessage());
}
}