zoukankan      html  css  js  c++  java
  • 项目总结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

  • 相关阅读:
    121.买卖股票 求最大收益1 Best Time to Buy and Sell Stock
    409.求最长回文串的长度 LongestPalindrome
    202.快乐数 Happy Number
    459.(KMP)求字符串是否由模式重复构成 Repeated Substring Pattern
    326.是否为3的平方根 IsPowerOfThree
    231.是否为2的平方根 IsPowerOfTwo
    461.求两个数字转成二进制后的“汉明距离” Hamming Distance
    206.反转单链表 Reverse Linked List
    448. 数组中缺少的元素 Find All Numbers Disappeared in an Array
    常见表单元素处理
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/12925240.html
Copyright © 2011-2022 走看看