SpringBoot 统一异常处理及延伸

为什么需要统一异常处理

在日常开发中,前后端需要规约一个清晰的规则,比如返回什么code是正常返回,什么code表示用户未登录,什么code是后端报错,这样后端返回了相应的code,前端会对不同的code做一些不同的处理,比如返回的code表示用户未登录,则前端直接强制用户去登录。可是如果每次检测到用户未登录,则手工设置code = 1XXX,然后返回给前端,这样做很明显不太好,说不准哪次就设置错了,就算每次都设置是对的,也会导致大量重复无用的代码,很不优雅。

解决方案

利用Spring的统一异常处理,其实可以很优雅的解决这个问题,这里只说一下我司处理的方式,虽然未必有多好,但起码项目跑了大半年,也没因为这个出啥问题。

  1. 首先定义一个通用返回的基类

public class BaseResponse {

   // 正常返回
   public static int CODE_SUCCESS = 1000;
   // 用户未登录
   public static int CODE_NOT_LOGIN = 1001;
   // 未定义的错误,比如 某个地方空指针导致整个接口挂了
   public static int CODE_ERROR = 1100;

   protected int code = CODE_SUCCESS;
   // 错误提示
   protected String msg; 

   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;
       this.code = CODE_ERROR;
   }

}

这里只是简单的定义了3种code,正常返回、未登录和未定义错误。
再定义一个一般接口返回的类


public class CommonResponse extends BaseResponse {

   public CommonResponse() {
   }

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

   public CommonResponse(T result) {
       this.result = result;
       this.code = BaseResponse.CODE_SUCCESS;
   }

   private T result;

   public T getResult() {
       return result;
   }

   public void setResult(T result) {
       this.result = result;
   }

}

这样的返回类可以应对大部分的接口返回,当然,有些特殊的接口返回可以再写一个继承自 BaseResponse 的返回。但无论如何,只要返回给前端的Response,都必须继承自 BaseResponse,这样前端就可以先去判断code,再根据code的值做下一步操作。

  1. 定义一个自定义的异常

public class LindianException extends Exception {

    // 未登录
    public static final int ERROR_SESSION_NOT_FOUND = 1001;

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

    private int code;
    private String msg;

    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;
    }

}

这里定义一个自定义异常,做个简单的例子,只定义一个未登录异常。

  1. 接下来就是定义全局异常

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    private final static Logger logger = Logger.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = LindianException.class)
    public CommonResponse catchLindianException(LindianException exception) throws Exception {
        CommonResponse commonResponse;
        int errorCode = exception.getCode();
        switch (errorCode) {
            case LindianException.ERROR_SESSION_NOT_FOUND:
                commonResponse = new CommonResponse(BaseResponse.CODE_SESSION_NOT_FOUND, exception.getMsg());
                break;
            default:
                commonResponse = new CommonResponse(BaseResponse.CODE_ERROR, "未知错误");
        }
        return commonResponse;
    }

    @ExceptionHandler(value = RuntimeException.class)
    public CommonResponse catchRunTimeException(RuntimeException exception) throws Exception {
        logger.error("controller-error: ", exception);
        CommonResponse commonResponse = new CommonResponse(BaseResponse.CODE_ERROR, exception.getMessage());
        return commonResponse;
    }

}

首先捕捉上面自定义的 LindianException,当捕捉到 LindianException,判断相应的错误信息,返回对应的 CommonResponse。
除了捕捉 LindianException 以外,接口中抛出的 RuntimeException 也在此捕获到,如果不在此捕捉的话,返回的接口的http状态即为500,这肯定是我们不想看见的,这样捕捉的话,我们就能控制返回给前端一个正确的json串,以便于前端做统一的处理。

  1. 定义基类Controller

public class BaseController {

    protected WxUserInfo getLoginUser() throws LindianException {
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        if (requestAttributes != null) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            String rdSession = request.getHeader("rd-session");
            if (StringUtils.isBlank(rdSession)) {
                throw new LindianException(LindianException.ERROR_SESSION_NOT_FOUND, "用户未登录");
            }
            WxUserInfo userInfo = WxStore.getInstance().getUserInfo(rdSession);
            if (userInfo == null) {
                throw new LindianException(LindianException.ERROR_SESSION_NOT_FOUND, "用户未登录");
            } else {
                return userInfo;
            }
        } else {
            return null;
        }
    }

}

我司登录和获取用户信息是使用redis,登录完成返回给前端一个 token,并以此为redis的键值,前端在之后的http请求时在header中带上这个token表明他的身份。
WxUserInfo是我们定义的一个用户身份model,里面有用户id等信息,当请求过来的时候,查到请求的header并无token,或者该token在redis中并无对应的用户信息,则直接抛出 LindianException(LindianException.ERROR_SESSION_NOT_FOUND, "用户未登录"),在上文的 GlobalExceptionHandler 中则可以直接将其捕获到,并返回相应带有错误code的response返回给前端。
其余的Controller都继承自这个 BaseController,则在任何一个地方,只要方便的使用 getLoginUser() ,就可以获得用户信息,可以极大的方便代码的书写。

你可能感兴趣的:(SpringBoot 统一异常处理及延伸)