### 引言
对于一个web项目而言后端经常需要对前端参数进行校验,传统方式常常是在`controller`中使用大量`if else`进行参数合法性校验,这样做的缺点显而易见,便不在赘述。
### 解决方案
对于以上问题,`SpringBoot`项目我列举了三种处理方式:
对于前端传参的实体我们可以写这样一个类例如`UserParam`,然后使用`javax.validation`提供的注解进行参数条件限制
```java
@Data
public class UserParam {
private Integer id;
@NotBlank(message="用户名不能为空")
private String username;
@Pattern(regexp = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\\\.[a-zA-Z0-9_-]+)+$", message = "邮箱地址格式不正确")
private String email;
@Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$", message="手机号格式不正确")
private String telephone;
@Max(value=144, message="长度必须小于144个字符")
private String description;
}
```
基于这样一个类,提出了两种关于`aop`的方式和一种异常处理的方式
### AOP参数校验方式一
写一个`AOP`参数校验的类:
```java
@Aspect
@Component
public class ValidParamAdvice {
/**
* 定义切点,切点为xyz.guqing.storycard.controller包和子包里任意方法的执行
*/
@Pointcut("execution(* xyz.guqing.storycard.controller..*(..))")
public void controllerPointCut() {
}
@Around("controllerPointCut() && args(.., bindingResult)")
public Object doAround(ProceedingJoinPoint joinPoint, BindingResult bindingResult) throws Throwable {
// 收集并返回参数校验错误信息,这里根据实际返回结果类自己封装
if(bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>(16);
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
fieldErrors.forEach(fieldError -> {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return errors.toString();
}
return joinPoint.proceed(joinPoint.getArgs());
}
}
```
然后在`controller`中使用如下,只需要在参数`UserParam`前添加`@Valid`即可完成参数校验
```java
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@PostMapping("/advice-valid")
public String exceptionAdviceValidUser(@RequestBody @Valid UserParam userParam) {
return userParam.toString();
}
}
```
### AOP参数校验方式二
同样写一个`aop`参数处理类
```java
@Aspect
@Component
public class ValidParamAdvice {
/**
* 定义切点,切点为xyz.guqing.storycard.controller包和子包里任意方法的执行
*/
@Pointcut("execution(* xyz.guqing.storycard.controller..*(..))")
public void controllerPointCut() {
}
@Around("controllerPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
BindingResult bindingResult = null;
// 使用遍历参数的方式找到BindingResult
for(Object arg:joinPoint.getArgs()){
if(arg instanceof BindingResult){
bindingResult = (BindingResult) arg;
}
}
if(bindingResult != null && bindingResult.hasErrors()){
Map<String, String> errors = new HashMap<>(16);
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
fieldErrors.forEach(fieldError -> {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return errors.toString();
}
return joinPoint.proceed();
}
}
```
在`controller`中使用
```java
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@PostMapping("/aop-valid")
public String aopValidUser(@RequestBody @Valid UserParam userParam, BindingResult result) {
return userParam.toString();
}
}
```
如上,不同点在于不仅需要在`UserParam`参数前写`@Valid`,还需要多添加一个参数`BindingResult result`才可以
### 全局异常处理参数校验
写这样一个异常处理类
```java
@RestControllerAdvice
@Slf4j
public class ValidExceptionAdvice {
/**
* 当使用@Valid不带@RequestBody request参数时:
* 对象验证失败,验证将引发BindException而不是MethodArgumentNotValidException
* @param e 参数绑定异常
* @return 返回参数校验失败的错误信息
*/
@ExceptionHandler(BindException.class)
public Object validExceptionHandler(BindException e){
// 将错误的参数的详细信息封装到统一的返回实体
return validParam(e.getBindingResult());
}
/**
* 使用@Valid并且带有@RequestBody request参数时
* 参数教研失败将抛出MethodArgumentNotValidException异常,由此方法捕获处理
* @param e 方法参数校验失败的异常
* @return 返回校验失败错误信息
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object validExceptionHandler(MethodArgumentNotValidException e){
return validParam(e.getBindingResult());
}
private Object validParam(BindingResult bindResult) {
List<FieldError> fieldErrors = bindResult.getFieldErrors();
Map<String, String> map = new HashMap<>(16);
fieldErrors.forEach(fieldError -> {
map.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return map;
}
}
```
在`controller`中使用
```java
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@PostMapping("/advice-valid")
public String exceptionAdviceValidUser(@RequestBody @Valid UserParam userParam) {
return userParam.toString();
}
}
```
只需要在需要进行参数校验的参数`UserParam`前添加`@Valid`即可,当校验不通过时会被`ValidExceptionAdvice`类拦截处理
### @Valid注解说明
`java`的`JSR303`声明了`@Valid`这类接口,而`Hibernate-validator`对其进行了实现,因此具体注解使用参考[Hibernate Validator](http://hibernate.org/validator/documentation/),下面列举一些常见注解的使用说明:
| 注解 | 备注 |
| ------------------------- | ------------------------------------------------------------ |
| @Null | 只能为null |
| @NotNull | 必须不为null |
| @Max(value) | 必须为一个不大于 value 的数字 |
| @Min(value) | 必须为一个不小于 value 的数字 |
| @AssertFalse | 必须为false |
| @AssertTrue | 必须为true |
| @DecimalMax(value) | 必须为一个小于等于 value 的数字 |
| @DecimalMin(value) | 必须为一个大于等于 value 的数字 |
| @Digits(integer,fraction) | 必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
| @Past | 必须是 日期 ,且小于当前日期 |
| @Future | 必须是 日期 ,且为将来的日期 |
| @Size(max,min) | 字符长度必须在min到max之间 |
| @Pattern(regex=,flag=) | 必须符合指定的正则表达式 |
| @NotEmpty | 必须不为null且不为空(字符串长度不为0、集合大小不为0) |
| @NotBlank | 必须不为空(不为null、去除首位空格后长度不为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
| @Email | 必须为Email,也可以通过正则表达式和flag指定自定义的email格式 |

SpringBoot如何优雅的处理参数校验