spring項目中,我們通常規定了返回的格式(成功-失敗-異常),特別是異常怎么處理方便呢?
1.自定義狀態碼實體
package com.ruoyi.common.constant; /** * 返回狀態碼 * * @author ruoyi */ public class HttpStatus { /** * 操作成功 */ public static final int SUCCESS = 200; /** * 對象創建成功 */ public static final int CREATED = 201; /** * 請求已經被接受 */ public static final int ACCEPTED = 202; /** * 操作已經執行成功,但是沒有返回數據 */ public static final int NO_CONTENT = 204; /** * 資源已被移除 */ public static final int MOVED_PERM = 301; /** * 重定向 */ public static final int SEE_OTHER = 303; /** * 資源沒有被修改 */ public static final int NOT_MODIFIED = 304; /** * 參數列表錯誤(缺少,格式不匹配) */ public static final int BAD_REQUEST = 400; /** * 未授權 */ public static final int UNAUTHORIZED = 401; /** * 訪問受限,授權過期 */ public static final int FORBIDDEN = 403; /** * 資源,服務未找到 */ public static final int NOT_FOUND = 404; /** * 不允許的http方法 */ public static final int BAD_METHOD = 405; /** * 資源沖突,或者資源被鎖 */ public static final int CONFLICT = 409; /** * 不支持的數據,媒體類型 */ public static final int UNSUPPORTED_TYPE = 415; /** * 系統內部錯誤 */ public static final int ERROR = 500; /** * 接口未實現 */ public static final int NOT_IMPLEMENTED = 501; }
2.創建返回實體
按照規定的格式創建返回實體,這樣子就可以規范返回的格式-下面是一個自定義的返回實體
package com.ruoyi.common.core.domain; import java.util.HashMap; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.utils.StringUtils; /** * 操作消息提醒 * * @author ruoyi */ public class AjaxResult extends HashMap<String, Object> { private static final long serialVersionUID = 1L; /** 狀態碼 */ public static final String CODE_TAG = "code"; /** 返回內容 */ public static final String MSG_TAG = "msg"; /** 數據對象 */ public static final String DATA_TAG = "data"; /** * 初始化一個新創建的 AjaxResult 對象,使其表示一個空消息。 */ public AjaxResult() { } /** * 初始化一個新創建的 AjaxResult 對象 * * @param code 狀態碼 * @param msg 返回內容 */ public AjaxResult(int code, String msg) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); } /** * 初始化一個新創建的 AjaxResult 對象 * * @param code 狀態碼 * @param msg 返回內容 * @param data 數據對象 */ public AjaxResult(int code, String msg, Object data) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); if (StringUtils.isNotNull(data)) { super.put(DATA_TAG, data); } } /** * 返回成功消息 * * @return 成功消息 */ public static AjaxResult success() { return AjaxResult.success("操作成功"); } /** * 返回成功數據 * * @return 成功消息 */ public static AjaxResult success(Object data) { return AjaxResult.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回內容 * @return 成功消息 */ public static AjaxResult success(String msg) { return AjaxResult.success(msg, null); } /** * 返回成功消息 * * @param msg 返回內容 * @param data 數據對象 * @return 成功消息 */ public static AjaxResult success(String msg, Object data) { return new AjaxResult(HttpStatus.SUCCESS, msg, data); } /** * 返回錯誤消息 * * @return */ public static AjaxResult error() { return AjaxResult.error("操作失敗"); } /** * 返回錯誤消息 * * @param msg 返回內容 * @return 警告消息 */ public static AjaxResult error(String msg) { return AjaxResult.error(msg, null); } /** * 返回錯誤消息 * * @param msg 返回內容 * @param data 數據對象 * @return 警告消息 */ public static AjaxResult error(String msg, Object data) { return new AjaxResult(HttpStatus.ERROR, msg, data); } /** * 返回錯誤消息 * * @param code 狀態碼 * @param msg 返回內容 * @return 警告消息 */ public static AjaxResult error(int code, String msg) { return new AjaxResult(code, msg, null); } }
3.使用@RestControllerAdvice創建全局處理器
@RestControllerAdvice是@ControllerAdvice和@ResponseBody的合並。此注解標記的類就是全局處理類,在這個類中可以自定義一個個的方法,用 @ExceptionHandler(異常類型)注解,那么它就回去攔截對應的異常,在該方法中進行處理,且把處理結果返回給頁面。
3.1創建全局異常處理器
package com.ruoyi.framework.web.exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.core.userdetails.UsernameNotFoundException; 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.NoHandlerFoundException; import com.ruoyi.common.constant.HttpStatus; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.exception.BaseException; import com.ruoyi.common.exception.CustomException; import com.ruoyi.common.exception.DemoModeException; import com.ruoyi.common.utils.StringUtils; /** * 全局異常處理器 * * @author ruoyi */ @RestControllerAdvice //標識這是全局異常處理器 public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 基礎異常 */ @ExceptionHandler(BaseException.class) //表示對指定異常進行攔截並處理 public AjaxResult baseException(BaseException e) { return AjaxResult.error(e.getMessage()); }
/**
* 業務異常
*/
@ExceptionHandler(CustomException.class)
public AjaxResult businessException(CustomException e)
{
if (StringUtils.isNull(e.getCode()))
{
return AjaxResult.error(e.getMessage());
}
return AjaxResult.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class) public AjaxResult handleException(Exception e) { log.error(e.getMessage(), e); return AjaxResult.error(e.getMessage()); } }
注解1:@RestControllerAdvice //標識這是全局異常處理器
注解2:@ExceptionHandler(BaseException.class) //表示對指定異常進行攔截並處理。當我們拋出指定的異常(可以自定義異常)時,會被ExceptionHandler攔截,並進行處理
3.2上面使用了自定義的異常
package com.ruoyi.common.exception; /** * 自定義異常 * * @author ruoyi */ public class CustomException extends RuntimeException { private static final long serialVersionUID = 1L; private Integer code; private String message; public CustomException(String message) { this.message = message; } public CustomException(String message, Integer code) { this.message = message; this.code = code; } public CustomException(String message, Throwable e) { super(message, e); this.message = message; } @Override public String getMessage() { return message; } public Integer getCode() { return code; } }
3.3業務中拋出異常
public String login(String username, String password, String code, String uuid) { //1.校驗驗證碼 String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid; //緩存中存入驗證碼格式 {Constants.CAPTCHA_CODE_KEY + uuid:驗證碼} String captcha = redisCache.getCacheObject(verifyKey); redisCache.deleteObject(verifyKey); if (captcha == null) { //緩存中沒有驗證碼 發起一個異步任務-記錄此次錯誤登錄信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) //驗證碼錯誤 發起一個異步任務-記錄此次錯誤登錄信息 { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); } // 2.用戶驗證 Authentication authentication = null; try { // 該方法會去調用package com.ruoyi.framework.web.service.UserDetailsServiceImpl.loadUserByUsername 若是校驗失敗,會拋出異常 authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(username, password)); //loadUserByUsername方法會獲取用戶,匹配密碼是自動完成的 } catch (Exception e) { if (e instanceof BadCredentialsException) //用戶密碼不匹配 發起一個異步任務-記錄此次錯誤登錄信息 { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { //其它不匹配 比如沒有該用戶 該用戶被停用等等 發起一個異步任務-記錄此次錯誤登錄信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new CustomException(e.getMessage()); } } //發起一個異步任務-記錄此次成功登錄信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); // 生成token return tokenService.createToken(loginUser); }
throw new CustomException(e.getMessage());
這里我們在service層就直接拋出異常CustomException,會被上面的全局異常處理器攔截,和@ExceptionHandler(CustomException.class)匹配,匹配到了下面的方法
/** * 業務異常 */ @ExceptionHandler(CustomException.class) public AjaxResult businessException(CustomException e) { if (StringUtils.isNull(e.getCode())) { return AjaxResult.error(e.getMessage()); } return AjaxResult.error(e.getCode(), e.getMessage()); }
拋出CustomException,該方法執行,最后返回一個AjaxResult對象給頁面 ----return AjaxResult.error(e.getCode(), e.getMessage());
{
- code: 狀態碼
- msg: 返回內容
- data :數據對象
}
3.4說明
全局異常管理器實際上就是創建了攔截器對拋出的異常進行處理,並把處理結果返回給頁面
上面我們使用的是@RestControllerAdvice而不是@ControllerAdvice,它是@ControllerAdvice和@ResponseBody的結合,返回的都是json數據。如果使用@ControllerAdvice,方法上需要添加@ResponseBody才能返回json格式數據。
