• 项目总结63:使用Spring AOP和BindingResult实现对接口的请求数据校验,并用@ExceptionHandler返回校验结果


    项目总结63:使用Spring AOP和BindingResult实现对接口的请求数据校验,并用@ExceptionHandler返回校验结果

    问题

      合格的接口,应该在接口的内部对请求参数进行校验,但是在接口内部通过业务代码进行校验,显得十分冗余,参数越多,代码就越混乱;

      思考:可以将接口请求参数的校验封装成一个全局的方法,进行统一处理。

    目的

      使用Spring AOP 和 @ExceptionHandler 对接口的数据校验进行全局封装,从而做到只要在请求数据的实体类中进行注解说明,就可以进行数据校验;

      具体可以:

        1- 避免在接口内部,通过代码方式进行冗余的数据校验;比如:if(data is empty){retur ...}

        2- 可以在请求数据的实体类中进行注解说明,比如:@NotEmpty(message = "手机号不能为空");就可以进行数据校验;

        3- 可以将具体的校验结果直接返回给前端;比如: {...,"msg": "手机号不能为空",...}

    解决思路

      1- 用@Valid 和 BindingResult分析请求参数校验情况

      2- 用AOP对BindingResult中的校验结果进行处理,如果校验出问题,抛出异常;

      3- 使用@ExceptionHandler注解捕获校验异常,并返回给前端

    具体实现方案(源码示例)(以修改登陆密码接口为例)

    第一步:用@Valid 和 BindingResult分析请求参数校验

    具体逻辑:被@Validated的实体类,会自动根据实体类中的参数的@NotEmpty注解,进行数据校验;并将数据校验结果封装到BindingResult类中;如果检验有问题,BindingResult数据如下

      源码如下:

      1- controller层接口

    
    
    import javax.validation.Valid;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.*;

    @RestController @RequestMapping(value
    ="/api") public class ApiLoginController extends ApiBaseController { @ValidAnn //AOP切入点 @PostMapping(value="/password/update") public ResultModel<Object> updatePassword(@Valid @RequestBody UpdatePasswordReq updatePasswordReq, BindingResult bindingResult){ //1-校验验证码 //2-更新密码 return ResultUtil.success(); } }

      2- 请求数据实体类

    //使用了lombok
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UpdatePasswordReq {
    
        @NotEmpty(message = "手机号不能为空")
        private String mobile;
    
        @NotEmpty(message = "新密码1不能为空")
        private String newPassword;
    
        @NotEmpty(message = "新密码2不能为空")
        private String newPasswordAgain;
    
        @NotEmpty(message = "验证码不能为空")
        private String code;
    }

      3- 返回参数实体类

    @Data
    public class ResultModel<T> {
    
        // Http网络码
        private Integer code;
        // 提示消息
        private String  msg;
        // 数据
        private T data;
    }

      4- 返回结果通用类和异常枚举

    /**
     * 工具类
     *
     * @author cjm
     * @date 2018/2/1
     */
    public class ResultUtil {
    
        /**
         * 返回异常信息
         */
        public static ResultModel exception(ResultEnum enums) {
            ResultModel model = new ResultModel();
            model.setCode(enums.getCode());
            model.setMsg(enums.getMessage());
            return model;
        }
        /**
         * 返回成功信息(只包含提示语)
         */
        public static ResultModel success() {
            ResultModel model = new ResultModel();
            model.setCode(ResultEnum.SUCCESS.getCode());
            model.setMsg(ResultEnum.SUCCESS.getMessage());
            return model;
        }
    
    
    }
    
    
    public enum ResultEnum {
        SUCCESS(200, "success"), 
        FAIL(400, "fail"), 
        VISIT_NOT_AUTHORIZED(401, "未授权"), 
        ARGUMENT_ERROR(402,"参数错误"), 
        ARGUMENT_EXCEPTION(407, "参数存在异常"), 
        ARGUMENT_TOKEN_EMPTY(409, "Token为空"), 
        ARGUMENT_TOKEN_INVALID(410, "Token无效"), 
        SERVER_ERROR(501, "服务端异常"), 
        SERVER_SQL_ERROR(503,"数据库操作出现异常"), 
        SERVER_DATA_REPEAT(504, "服务器数据已存在"), 
        SERVER_DATA_NOTEXIST(505,"数据不存在"), 
        SERVER_DATA_STATUS_ERROR(506, "数据状态错误"), 
        SERVER_SMS_SEND_FAIL(701, "短信发送失败"),
    
    
        ;
        
    
    
        private int code;
        private String message;
    
        private ResultEnum(int code, String message) {
            this.code = code;
            this.message = message;
        }
    
        public int getCode() {
            return code;
        }
    
        public String getMessage() {
            return message;
        }
    }

    第二步:用AOP对BindingResult中的校验结果进行处理,如果校验出问题,抛出异常;

    具体实现:AOP根据@ValidAnn定位到每一个需要数据校验的接口,使用环绕通知,处理BindingResult类的结果,如果数据校验有问题,将校验结果交给ServerException,并且抛出ServerException异常

      源码如下

      1- ValidAnn 注解,用户AOP定位

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ValidAnn {
    
    }

      2- 环绕AOP类,处理判断并处理校验结果,如果校验出问题,则抛出ServerException;

    @Aspect
    @Component
    @Order(1)
    public class ValidAop {
        /**
         * 所有controller方法都会执行此切面
         * 用于检验参数
         */
        @Pointcut("@annotation(com.hs.annotation.ValidAnn)")
        public void validAop() {
        }
    
        /**
         * 对切面进行字段验证
         *
         * @param joinPoint 切点
         * @return Object
         * @throws Throwable
         */
        @Around("validAop()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            Object[] args = joinPoint.getArgs();
            List<String> errorList = new ArrayList<>();
            if (args != null) {
                Arrays.stream(args)
                        .forEach(arg -> {
                            if (arg instanceof BindingResult) {
                                BindingResult result = (BindingResult) arg;
                                if (result.hasErrors()) {
                                    result.getAllErrors()
                                            .forEach(err -> {
                                                errorList.add(err.getDefaultMessage());
                                            });
                                    throw new ServerException(Joiner.on(" || ").join(errorList));
                                }
                            }
                        });
            }
            return joinPoint.proceed();
        }
    }

      3- ServerException,当校验出问题时,抛出当前异常

    public class ServerException extends RuntimeException {
    
        private Integer code;
    
        private Object Data;
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public Object getData() {
            return Data;
        }
    
        public void setData(Object data) {
            Data = data;
        }
    
        public ServerException() {
            super();
        }
    
        public ServerException(Integer code, String message, Object data) {
            super(message);
            this.code = code;
            this.Data = data;
        }
    
        public ServerException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    
        public ServerException(String message) {
            super(message);
            this.code = ResultEnum.ARGUMENT_ERROR.getCode();
        }
    
        public ServerException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public ServerException(Throwable cause) {
            super(cause);
        }
    
        protected ServerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }

    第三步:使用@ExceptionHandler注解捕获校验异常,并返回给前端

    具体实现:@ControllerAdvice和@ExceptionHandler拦截异常并统一处理。在GlobalExceptionHandler 类中使用@ExceptionHandler(value = ServerException.class)专门捕获ServerException异常,并且将结果封装到返回类中,返回给前端

      源码如下:

      1- GlobalExceptionHandler 类,用于捕获异常,并作相关处理

    @ControllerAdvice //使用 @ControllerAdvice 实现全局异常处理
    @ResponseBody
    public class GlobalExceptionHandler {
        
        protected Logger logger = Logger.getLogger(this.getClass());
    
    
        /**
         * 自定义异常捕获
         *
         * @param exception 捕获的异常
         * @return 返回信息
         */
        @ExceptionHandler(value = ServerException.class)
        public ResultModel exceptionHandler(ServerException exception) {
            logger.warn(exception);
            ResultModel model = new ResultModel();
            model.setCode(exception.getCode());
            model.setMsg(exception.getMessage());
            model.setData(exception.getData());
            //3-以Json格式返回数据
            return model;
        }
    }

    测试结果

    END

  • 相关阅读:
    iOS之富文本总结
    iOS文字编辑之富文本介绍
    【原创】【小技巧】如何阻止每次手机链接到电脑上都会自动弹出图片和iTunes
    iOS枚举
    根据两点经纬度计算距离
    iOS设计模式之懒加载
    【小技巧】【源码】iOS开发中常见的小技巧,小知识点
    重要信息!Xcode 8 不支持第三方插件
    iOS开发 适配iOS10以及Xcode8
    【源码】模拟炮弹动画效果,平移动画
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/12925240.html
Copyright © 2020-2023  润新知