为什么需要统一异常处理
在日常开发中,前后端需要规约一个清晰的规则,比如返回什么code是正常返回,什么code表示用户未登录,什么code是后端报错,这样后端返回了相应的code,前端会对不同的code做一些不同的处理,比如返回的code表示用户未登录,则前端直接强制用户去登录。可是如果每次检测到用户未登录,则手工设置code = 1XXX,然后返回给前端,这样做很明显不太好,说不准哪次就设置错了,就算每次都设置是对的,也会导致大量重复无用的代码,很不优雅。
解决方案
利用Spring的统一异常处理,其实可以很优雅的解决这个问题,这里只说一下我司处理的方式,虽然未必有多好,但起码项目跑了大半年,也没因为这个出啥问题。
- 首先定义一个通用返回的基类
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<T> 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的值做下一步操作。
- 定义一个自定义的异常
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;
}
}
这里定义一个自定义异常,做个简单的例子,只定义一个未登录异常。
- 接下来就是定义全局异常
@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串,以便于前端做统一的处理。
- 定义基类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() ,就可以获得用户信息,可以极大的方便代码的书写。