暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

优雅的统一异常处理和全局统一响应

IT学习道场 2022-06-07
583

全局响应支持显式响应和隐式响应,异常处理支持自定义异常和非自定义异常

废话不多说,直接上代码,优雅性,扩展性相当可以,嘿嘿

代码结构:


Http状态码枚举类,响应的httpcode,最好是统一管理

    /**
    * 描述:请求时的自定义查询状态码,主要参考Http状态码,但并不完全对应 <br>
    * 时间:2020/7/30 5:15 <br>
    * 作者:老王
    *
    */
    public enum HttpCode {
    /** 10系统异常 */
    SYS_EXCEPTION(10),
    /** 200请求成功 */
    OK(200),
    /** 207频繁操作 */
    MULTI_STATUS(207),
    /** 400请求参数出错 */
    BAD_REQUEST(400),
    /** 401没有登录 */
    UNAUTHORIZED(401),
    /** 4012没有登录-没token匹配-登录超时*/
    UNAUTHORIZED2(4012),
    /** 4013 用户被限制登录*/
    UNAUTHORIZED3(4013),
    /** 4014没有登录-没token匹配-异地登录*/
    UNAUTHORIZED4(4014),
    /** 402登录失败 */
    LOGIN_FAIL(402),
    /** 403没有权限 */
    FORBIDDEN(403),
    /** 404找不到页面 */
    NOT_FOUND(404),
    /** 405请求方法不能被用于请求相应的资源 */
    METHOD_NOT_ALLOWED(405),
    /** 406内容特性不满足 */
    NOT_ACCEPTABLE(406),
    /** 408请求超时 */
    REQUEST_TIMEOUT(408),
    /** 409发生冲突 */
    CONFLICT(409),
    /** 410已被删除 */
    GONE(410),
    /** 411没有定义长度 */
    LENGTH_REQUIRED(411),
    /** 412条件不满足 */
    PRECONDITION_FAILED(412),
    /** 413数据太大 */
    ENTITY_TOO_LARGE(413),
    /** 415不是服务器中所支持的格式 */
    UNSUPPORTED_MEDIA_TYPE(415),
    /** 421连接数过多 */
    TOO_MANY_CONNECTIONS(421),
    /** 423已被锁定 */
    LOCKED(423),
    /** 451法律不允许 */
    UNAVAILABLE_LEGAL(451),
    /** 500服务器出错 */
    SERVER_ERROR(500),
    /** 503服务器升级中,暂时不可用 */
    SERVICE_UNAVAILABLE(503),
    /** 501获取资源所需要的策略并没有被满足 */
    NOT_EXTENDED(510);



    public final Integer code;


    private HttpCode(Integer code) {
    this.code = code;
    }


    /**
    * Return the integer value of this status code.
    */
    public Integer code() {
    return this.code;
    }


    @Override
    public String toString() {
    return this.code.toString();
    }
    }

    json转化辅助类JsonUtil处理json和对象/列表/map之间的转换

      package com.utils;


      import com.fasterxml.jackson.annotation.JsonAutoDetect;
      import com.fasterxml.jackson.annotation.JsonInclude;
      import com.fasterxml.jackson.annotation.PropertyAccessor;
      import com.fasterxml.jackson.core.JsonFactory;
      import com.fasterxml.jackson.core.JsonGenerator;
      import com.fasterxml.jackson.core.JsonParser;
      import com.fasterxml.jackson.core.JsonProcessingException;
      import com.fasterxml.jackson.core.type.TypeReference;
      import com.fasterxml.jackson.databind.*;
      import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
      import java.io.IOException;
      import java.io.StringWriter;
      import java.util.List;
      import java.util.Map;
      /**
      * @author 老王
      * @date 2018/12/24 11:21 上午
      */
      public class JsonUtil {


      public static final ObjectMapper OBJECT_MAPPER = createObjectMapper();
      /**
      * 初始化ObjectMapper
      * @return
      */
      private static ObjectMapper createObjectMapper() {
      ObjectMapper objectMapper = new ObjectMapper();
      objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
      objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
      objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
      // 允许序列化空的POJO类(否则会抛出异常)
      objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
      // 对象的所有字段全部列入。NON_NULL:不返回 null 值字段
      objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
      // 取消java.util.Date, Calendar默认转换timestamps形式
      objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS , false);
      objectMapper.registerModule(new JavaTimeModule());
      // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
      objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
      return objectMapper;
      }


      public static String object2Json(Object o) {
      StringWriter sw = new StringWriter();
      JsonGenerator gen = null;
      try {
      gen = new JsonFactory().createGenerator(sw);
      OBJECT_MAPPER.writeValue(gen, o);
      } catch (IOException e) {
      throw new RuntimeException("不能序列化对象为Json", e);
      } finally {
      if (null != gen) {
      try {
      gen.close();
      } catch (IOException e) {
      throw new RuntimeException("不能序列化对象为Json", e);
      }
      }
      }
      return sw.toString();
      }


      /**
      * 对象转map
      * @param o 转行对象
      * @return
      */
      public static Map<String, Object> object2Map(Object o) {
      return OBJECT_MAPPER.convertValue(o,Map.class);
      }


      /**
      * map转java对象
      * @param map 参数map
      * @param clazz T字节对象
      * @param <T> 返回对象类型
      * @return
      */
      public static <T> T map2Object(Map map, Class<T> clazz) {
      return OBJECT_MAPPER.convertValue(map,clazz);
      }




      /**
      * 将 json 字段串转换为 对象.
      *
      * @param json 字符串
      * @param clazz 需要转换为的类
      * @return
      */
      public static <T> T json2Object(String json, Class<T> clazz) {
      try {
      return OBJECT_MAPPER.readValue(json, clazz);
      } catch (IOException e) {
      throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + json, e);
      }
      }


      /**
      * 将 json 字段串转换为 List.
      * @param json
      * @param clazz
      * @param <T>
      * @return
      * @throws IOException
      */
      public static <T> List<T> json2List(String json,Class<T> clazz) throws IOException {
      JavaType type = OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz);
      return OBJECT_MAPPER.readValue(json, type);
      }




      /**
      * 将 json 字段串转换为 数据.
      * @param json
      * @param clazz
      * @param <T>
      * @return
      * @throws IOException
      */
      public static <T> T[] json2Array(String json,Class<T[]> clazz) throws IOException {
      return OBJECT_MAPPER.readValue(json, clazz);


      }


      public static <T> T node2Object(JsonNode jsonNode, Class<T> clazz) {
      try {
      T t = OBJECT_MAPPER.treeToValue(jsonNode, clazz);
      return t;
      } catch (JsonProcessingException e) {
      throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + jsonNode.toString(), e);
      }
      }


      public static JsonNode object2Node(Object o) {
      try {
      if(o == null) {
      return OBJECT_MAPPER.createObjectNode();
      } else {
      return OBJECT_MAPPER.convertValue(o, JsonNode.class);
      }
      } catch (Exception e) {
      throw new RuntimeException("不能序列化对象为Json", e);
      }
      }


      /**
      * JsonNode转换为Java泛型对象,可以是各种类型。
      *
      * @param <T>
      * @param json String
      * @param tr TypeReference,例如: new TypeReference< List<FamousUser> >(){}
      * @return List对象列表
      */
      public static <T> T json2GenericObject(String json, TypeReference<T> tr) {
      if (json == null || "".equals(json)) {
      throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + json);
      } else {
      try {
      return (T) OBJECT_MAPPER.readValue(json, tr);
      } catch (Exception e) {
      throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + json, e);
      }
      }
      }


      }


      自定义的异常类

        package com.http.exception;


        import java.io.PrintWriter;
        import java.io.StringWriter;


        /**
        * 描述: 基础异常 <br>
        * 时间: 2020-02-07 16:18 <br>
        * 作者:老王
        */
        public class BaseException extends RuntimeException {
        private int code;
        private String message;
        public BaseException() {
        }
        public BaseException(int code, String message) {
        super(message);
        this.code = code;
        this.message = message;
        }
        public BaseException(String msg, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(msg, cause, enableSuppression, writableStackTrace);
        }


        public BaseException(String message, Throwable cause) {
        super(message, cause);
        }


        public BaseException(String message) {
        super(message);
        }


        public BaseException(Throwable cause) {
        super(cause);
        }


        public static String getStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        throwable.printStackTrace(pw);
        return sw.getBuffer().toString();
        }


        public int getCode() {
        return code;
        }


        public void setCode(int code) {
        this.code = code;
        }


        @Override
        public String getMessage() {
        return message;
        }


        public void setMessage(String message) {
        this.message = message;
        }


        @Override
        public String toString() {
        return "BaseException{" +
        "code=" + code +
        ", msg='" + message + '\'' +
        '}';
        }
        }
          package com.http.exception;




          import com.http.constant.HttpCode;


          /**
          * 描述: 业务异常 <br>
          * 时间: 2020-02-07 16:18 <br>
          * 作者:老王
          */
          public class BusinessException extends BaseException {
          private int code = HttpCode.SERVER_ERROR.code;
          private String message;
          public BusinessException() {
          }


          public BusinessException(int code, String message) {
          super(code, message);
          this.code = code;
          this.message = message;
          }


          public BusinessException(Throwable ex) {
          super(ex);
          }


          public BusinessException(String message) {
          this(HttpCode.SERVER_ERROR.code, message);
          }


          public BusinessException(String msg, Throwable ex) {
          super(msg, ex);
          }


          @Override
          public int getCode() {
          return code;
          }


          @Override
          public void setCode(int code) {
          this.code = code;
          }


          @Override
          public String getMessage() {
          return message;
          }


          public void setMessage(String message) {
          this.message = message;
          }
          }
            package com.http.exception;


            /**
            * 描述: BusinessException业务异常描述 <br>
            * 时间: 2020-02-24 15:16 <br>
            * 作者:老王
            */
            public enum BEMsg {
            user_login_name_exist("登录名已存在"),
            login_user_error("用户名不存在或密码错误"),
            vote_repeat("您已点赞过"),
            no_auth_error("您无权进行当前操作"),
            no_Sign_error("请登录后再进行当前操作"),
            ;


            public String message;


            BEMsg(String message) {
            this.message = message;
            }


            public String getMessage() {
            return message;
            }


            public void setMessage(String message) {
            this.message = message;
            }
            }


            下面就是统一响应和处理的代码

            全局结果响应实体Result

              package com.http.response;


              import java.io.Serializable;


              /**
              * 全局结果响应实体 <br>
              * 作者:老王 <br>
              * 时间:2019-01-24 10:33
              */
              public class Result<T> implements Serializable {
              /**响应的code码*/
              private int code;
              /**响应的success布尔值*/
              private boolean success;
              /**响应的message*/
              private String msg;
              /**响应的业务数据*/
              private T data;


              public Result(int code, String msg, T data){
              this.code=code;
              this.msg=msg;
              this.data=data;
              }


              public Result(int code, boolean success, String msg, T data) {
              this.code = code;
              this.success = success;
              this.msg = msg;
              this.data = data;
              }


              public int getCode() {
              return code;
              }


              public void setCode(int code) {
              this.code = code;
              }


              public String getMsg() {
              return msg;
              }


              public void setMsg(String msg) {
              this.msg = msg;
              }


              public T getData() {
              return data;
              }


              public void setData(T data) {
              this.data = data;
              }


              public boolean isSuccess() {
              return success;
              }


              public void setSuccess(boolean success) {
              this.success = success;
              }
              }




              响应的状态枚举

                package com.http.response;


                /**
                * 描述: 响应状态枚举 <br>
                * 时间: 2020-01-25 19:33 <br>
                * 作者:老王
                */
                public enum ResponseStateEnum {
                success("success"),
                error("error");
                public String value;


                ResponseStateEnum(String state) {
                this.value = state;
                }


                ResponseStateEnum() {
                }


                public String getValue() {
                return value;
                }


                public void setValue(String value) {
                this.value = value;
                }
                }


                全局结果响应建造者,方便构建result对象

                  package com.http.response;




                  import com.http.constant.HttpCode;


                  /**
                  * 全局结果响应建造者 <br>
                  * 作者:老王 <br>
                  * 时间:2019-01-24 10:33
                  */
                  public class ResultBuilder {


                  //自定义message 失败信息
                  public static <T> Result<T> err (String message) {
                  return new Result<T>(HttpCode.SERVER_ERROR.code,false, message,null);
                  }
                  //自定义message 失败信息
                  public static <T> Result<T> err (String message, T data) {
                  return new Result<T>(HttpCode.SERVER_ERROR.code, false, message, data);
                  }
                  //自定义message 失败信息
                  public static <T> Result<T> ok (String message) {
                  return new Result<T>(HttpCode.OK.code, true, message,null);
                  }
                  //自定义message 成功
                  public static <T> Result<T> ok (String message, T data) {
                  return new Result<T>(HttpCode.OK.code,true, message, data);
                  }
                  //自定义code,msg 返回数据
                  public static <T> Result<T> render(int code, boolean success, String msg) {
                  return new Result<T>(code, success, msg,null);
                  }
                  //自定义code,msg,data 返回数据
                  public static <T> Result<T> render(int code, boolean success, String msg, T data) {
                  return new Result<T>(code, success, msg, data);
                  }
                  }




                  全局结果异常响应实体,本质和Result一样,只是单独处理异常的数据响应结构

                    package com.http.response;
                    /**
                    * 全局结果异常响应实体<br>
                    * 作者:老王 <br>
                    * 时间:2019-01-24 10:33
                    */
                    public class ResultException {
                    /**响应码*/
                    private int code;
                    private boolean success = false;
                    /**响应消息提醒*/
                    private String msg;
                    /**请求的异常路径*/
                    private String url;


                    public ResultException(int code, String msg, String url) {
                    this.code = code;
                    this.msg = msg;
                    this.url = url;
                    }


                    public ResultException(int code, boolean success, String msg, String url) {
                    this.code = code;
                    this.success = success;
                    this.msg = msg;
                    this.url = url;
                    }


                    public int getCode() {
                    return code;
                    }


                    public void setCode(int code) {
                    this.code = code;
                    }


                    public String getMsg() {
                    return msg;
                    }


                    public void setMsg(String msg) {
                    this.msg = msg;
                    }


                    public String getUrl() {
                    return url;
                    }


                    public void setUrl(String url) {
                    this.url = url;
                    }


                    public boolean isSuccess() {
                    return success;
                    }


                    public void setSuccess(boolean success) {
                    this.success = success;
                    }
                    }


                    全局结果响应处理,全局异常处理

                      package com.http.response;


                      import cn.hutool.core.exceptions.ExceptionUtil;
                      import com.http.constant.HttpCode;
                      import com.http.exception.BusinessException;
                      import com.utils.JsonUtil;
                      import lombok.SneakyThrows;
                      import lombok.extern.slf4j.Slf4j;
                      import org.springframework.core.MethodParameter;
                      import org.springframework.http.MediaType;
                      import org.springframework.http.converter.HttpMessageConverter;
                      import org.springframework.http.server.ServerHttpRequest;
                      import org.springframework.http.server.ServerHttpResponse;
                      import org.springframework.validation.BindException;
                      import org.springframework.web.bind.MethodArgumentNotValidException;
                      import org.springframework.web.bind.annotation.ExceptionHandler;
                      import org.springframework.web.bind.annotation.RestControllerAdvice;
                      import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
                      import javax.servlet.http.HttpServletRequest;


                      /**
                      * 全局结果响应处理,全局异常处理 <br>
                      * 作者:老王 <br>
                      * 时间:2019-01-24 10:33
                      */
                      @Slf4j
                      @RestControllerAdvice
                      public class ResultAdvice implements ResponseBodyAdvice<Object> {




                      @Override
                      public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
                      return true;
                      }


                      /**
                      * 响应前重写处理
                      * @param o 方法响应返回实体
                      * @param methodParameter 方法参数
                      * @param mediaType
                      * @param aClass
                      * @param serverHttpRequest request
                      * @param serverHttpResponse response
                      * @return Object - 重写方法响应返回实体后的对象
                      */
                      @SneakyThrows
                      @Override
                      public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
                      return builderResult(o);
                      }


                      /**
                      * 重写方法返回对象响应结构
                      * @param o
                      * @return
                      */
                      private Object builderResult(Object o) {
                      //如果响应体Result,直接返回,不做处理
                      if (o instanceof Result) {
                      return o;
                      }
                      //返回数据data是String的时候需要做出处理,不然会报错
                      if (o instanceof String) {
                      Result<String> result = ResultBuilder.ok(ResponseStateEnum.success.value, o.toString());
                      return JsonUtil.object2Json(result);
                      }
                      //返回的数据若是异常返回信息,那么不做处理直接返回
                      if (o instanceof ResultException) {
                      return o;
                      }
                      return ResultBuilder.ok(ResponseStateEnum.success.value, o);
                      }


                      /**
                      * 先捕获异常 然后再把数据返回到ResponseBody中,
                      * 然后在Body中要返回数据的时候调用上面的拦截方法beforeBodyWrite()
                      */
                      @ExceptionHandler(value = Exception.class)
                      public Object handleException(Exception e, HttpServletRequest request) {
                      //此处返回json数据
                      //捕捉到的异常如果是自定义异常类,那么就返回自定义异常类中的错误码和错误信息
                      String stackExceptionMsg = ExceptionUtil.stacktraceToString(e);
                      //异常输出到日志
                      log.error(stackExceptionMsg);
                      //自定义基础异常
                      if (e instanceof BusinessException) {
                      return new ResultException(((BusinessException) e).getCode(), false, ((BusinessException) e).getMessage(), request.getRequestURL().toString());
                      //非法参数异常
                      } else if (e instanceof IllegalArgumentException) {
                      return new ResultException(HttpCode.BAD_REQUEST.code, false, "参数异常,请稍候再试", request.getRequestURL().toString());
                      //绑定异常
                      } else if (e instanceof BindException) {
                      return new ResultException(HttpCode.BAD_REQUEST.code, false, ((BindException) e).getBindingResult().getFieldError().getDefaultMessage(), request.getRequestURL().toString());
                      //方法参数异常验证异常
                      } else if (e instanceof MethodArgumentNotValidException) {
                      return new ResultException(HttpCode.BAD_REQUEST.code, false, ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage(), request.getRequestURL().toString());
                      }


                      //这里是除了自定义异常的其他异常信息
                      else {
                      return new ResultException(HttpCode.SERVER_ERROR.code, false, "系统异常请联系管理员", request.getRequestURL().toString());
                      }
                      }
                      }




                      这同加了@RestControllerAdvice后,就会在@RestController的控制器上返回时,对响应结果进行处理重写咯,

                      流程如下:

                      @RestController的控制器响应后 -->到加了@RestControllerAdviceResultAdvicebeforeBodyWrite 方法中重构响应体Result--> Result由 ResultBuilder 来构建

                      @ExceptionHandler(value&#32;=&#32;Exception.class)的方法

                      handleException上进行统一异常处理

                      hutool的ExceptionUtil控制日志输出,不然全局异常捕获,异常日志就无法输出,不利于问题排查,其他的都是自定义异常和其他异常的统一处理,处理成全局响应结果ResultException,比Result就多了一个url,之所以这么玩,是因为不同的处理我喜欢用不同的对象处理,相互不干扰,但是返回后的json格式还是一样的,前端可以用一个对象就可以接收


                      下面测试demo演示

                      User对象

                        package com.model;


                        import lombok.AllArgsConstructor;
                        import lombok.Data;
                        import lombok.NoArgsConstructor;


                        /**
                        * 描述: 用户model </br>
                        * 时间: 2020-12-14 9:59 </br>
                        * 作者:老王
                        */
                        @Data
                        @AllArgsConstructor
                        @NoArgsConstructor
                        public class User {


                        private Integer id;


                        private String userName;


                        private Integer age;
                        }


                          package com.controller;


                          import com.http.exception.BaseException;
                          import com.http.response.Result;
                          import com.http.response.ResultBuilder;
                          import com.model.User;
                          import org.springframework.web.bind.annotation.GetMapping;
                          import org.springframework.web.bind.annotation.RequestMapping;
                          import org.springframework.web.bind.annotation.RestController;


                          /**
                          * 描述: 统一结果响应测试控制器 <br>
                          * 时间: 2022-06-07 8:33 <br>
                          * 作者:老王
                          */
                          @RestController
                          @RequestMapping("/respTest")
                          public class RespTestController {
                          /**
                          * 隐式响应,即方法响应体就是业务实体,优点直观明了
                          */
                          @GetMapping("/getUser")
                          public User getUser(){
                          User user = new User(1, "IT学习道场", 18);
                          return user;
                          }


                          /**
                          * 显式响应,即方法响应体就是result,
                          * 优点可以自定义一些定制化的响应数据组装
                          * 但是需要自己构建Result对象
                          */
                          @GetMapping("/getResult")
                          public Result<User> getResult(){
                          User user = new User(1, "IT学习道场", 18);
                          //可以根据不同的业务处理渲染不同的Result,增加了灵活性
                          Result<User> result = ResultBuilder.render(200, true, "只是我自己构建的Result", user);
                          return result;
                          }


                          /**
                          * 测试统一异常处理(自定义异常)
                          */
                          @GetMapping("/testBaseException")
                          public User testBaseException(){
                          throw new BaseException(508, "触发自定义业务异常了");
                          }


                          /**
                          * 测试统一异常处理(非自定义异常)
                          */
                          @GetMapping("/testException")
                          public User testException(){
                          throw new RuntimeException("业务异常了");
                          }




                          }


                          public User getUser(){} 响应Result结构

                            {
                            "code": 200,
                            "success": true,
                            "msg": "success",
                            "data": {
                            "id": 1,
                            "userName": "IT学习道场",
                            "age": 18
                            }
                            }


                            public Result<User> getResult(){} 响应Result结构


                              {
                              "code": 200,
                              "success": true,
                              "msg": "只是我自己构建的Result",
                              "data": {
                              "id": 1,
                              "userName": "IT学习道场",
                              "age": 18
                              }
                              }

                              public User testBaseException(){} 响应Result结构

                                {
                                "code": 500,
                                "success": false,
                                "msg": "系统异常请联系管理员",
                                "url": "http://localhost:8080/respTest/testBaseException"
                                }

                                public User testException()响应Result结构

                                  {
                                  "code": 500,
                                  "success": false,
                                  "msg": "系统异常请联系管理员",
                                  "url": "http://localhost:8080/respTest/testException"
                                  }

                                  你学废了吗?

                                  文章转载自IT学习道场,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                  评论