為什么需要統(tǒng)一異常處理
在日常開發(fā)中,前后端需要規(guī)約一個清晰的規(guī)則,比如返回什么code是正常返回,什么code表示用戶未登錄,什么code是后端報錯,這樣后端返回了相應的code,前端會對不同的code做一些不同的處理,比如返回的code表示用戶未登錄,則前端直接強制用戶去登錄??墒侨绻看螜z測到用戶未登錄,則手工設置code = 1XXX,然后返回給前端,這樣做很明顯不太好,說不準哪次就設置錯了,就算每次都設置是對的,也會導致大量重復無用的代碼,很不優(yōu)雅。
解決方案
利用Spring的統(tǒng)一異常處理,其實可以很優(yōu)雅的解決這個問題,這里只說一下我司處理的方式,雖然未必有多好,但起碼項目跑了大半年,也沒因為這個出啥問題。
- 首先定義一個通用返回的基類
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,再根據(jù)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狀態(tài)即為500,這肯定是我們不想看見的,這樣捕捉的話,我們就能控制返回給前端一個正確的json串,以便于前端做統(tǒng)一的處理。
- 定義基類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() ,就可以獲得用戶信息,可以極大的方便代碼的書寫。