引言 {#引言}
对于一个web项目而言后端经常需要对前端参数进行校验,传统方式常常是在controller
中使用大量if else
进行参数合法性校验,这样做的缺点显而易见,便不在赘述。
解决方案 {#解决方案}
对于以上问题,SpringBoot
项目我列举了三种处理方式:
对于前端传参的实体我们可以写这样一个类例如UserParam
,然后使用javax.validation
提供的注解进行参数条件限制
@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参数校验方式一}
写一个AOP
参数校验的类:
@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
即可完成参数校验
@RestController @CrossOrigin @RequestMapping("/user") public class UserController {
@PostMapping("/advice-valid") public String exceptionAdviceValidUser(@RequestBody @Valid UserParam userParam) { return userParam.toString(); }
}
AOP参数校验方式二 {#aop参数校验方式二}
同样写一个aop
参数处理类
@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
中使用
@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
才可以
全局异常处理参数校验 {#全局异常处理参数校验}
写这样一个异常处理类
@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
中使用
@RestController @CrossOrigin @RequestMapping("/user") public class UserController {
@PostMapping("/advice-valid") public String exceptionAdviceValidUser(@RequestBody @Valid UserParam userParam) { return userParam.toString(); }
}
只需要在需要进行参数校验的参数UserParam
前添加@Valid
即可,当校验不通过时会被ValidExceptionAdvice
类拦截处理
@Valid注解说明 {#valid注解说明}
java
的JSR303
声明了@Valid
这类接口,而Hibernate-validator
对其进行了实现,因此具体注解使用参考Hibernate Validator,下面列举一些常见注解的使用说明:
| 注解 | 备注 | |---------------------------|------------------------------------------------------------------------| | @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格式 |