<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在建立專案的初期,我們需要規範後端返回的資料結構,以便更好地與前端開發人員合作。
比如後端返回的資料為:
{ "msg": "請跳轉登陸頁面", }
此時前端無法確定後端服務的處理結果是成功的還是失敗的。在前端展示頁面,成功與失敗的展示是要作區分的,甚至不同的成功或失敗結果要做出不同的展現效果,這也就是我們為什麼要對返回結果做出統一規範的原因。
public class ResultWrap<T, M> { // 方便前端判斷當前請求處理結果是否正常 private int code; // 業務處理結果 private T data; // 產生錯誤的情況下,提示使用者資訊 private String message; // 產生錯誤情況下的異常堆疊,提示開發人員 private String error; // 發生錯誤的時候,返回的附加資訊 private M metaInfo; }
code
欄位,前端可以根據這個欄位來判斷我們的處理結果到底是成功的還是失敗的;比如code=200
的時候,我們的處理結果一定是成功的,其他的code值全都是失敗的,並且我們會有多種code值,以便前端頁面可以根據不同的code值做出不同的互動動作。data
裡面,只有業務邏輯處理成功的情況下,data
欄位裡面才可能有資料。伺服器內部錯誤
的時候,我們需要讓開發人員能第一時間定位是啥原因引起的,我們會把錯誤的堆疊資訊給到error
欄位。定義好返回結果後,我們和前端的互動資料結果就統一好了。
之所以定義一個統一的異常類,是為了把所有的異常全部彙總成一個異常,最終我們只需要在例外處理的時候單獨處理這一個異常即可。
@Data public class AwesomeException extends Throwable { // 錯誤碼 private int code; // 提示訊息 private String msg; public AwesomeException(int code, String msg, Exception e) { super(e); this.code = code; this.msg = msg; } public AwesomeException(int code, String msg) { this.code = code; this.msg = msg; } }
code
欄位,這樣的話,我們在例外處理的時候,才能把當前異常轉換成最終的ResultWrap結果。這樣的話,我們的後端開發人員遇到異常的情況,只需要通過建立AwesomeException
異常物件丟擲即可,不需要再為建立什麼異常而煩惱了。
我們下面需要針對所有丟擲的異常進行統一的處理:
import com.example.awesomespring.exception.AwesomeException; import com.example.awesomespring.vo.ResultWrap; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.AuthorizationException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @className ExceptionHandler * @description: */ @Slf4j @RestControllerAdvice public class AwesomeExceptionHandler { /** * 捕獲沒有使用者許可權的異常 * * @return */ @ExceptionHandler(AuthorizationException.class) public ResultWrap handleException(AuthorizationException e) { return ResultWrap.failure(401, "您暫時沒有存取許可權!", e); } /** * 處理AwesomeException * * @param e * @param request * @param response * @return */ @ExceptionHandler(AwesomeException.class) public ResultWrap handleAwesomeException(AwesomeException e, HttpServletRequest request, HttpServletResponse response) { return ResultWrap.failure(e); } /** * 專門針對執行時異常 * * @param e * @return */ @ExceptionHandler(RuntimeException.class) public ResultWrap handleRuntimeException(RuntimeException e) { return ResultWrap.failure(e); } }
在專案中,我們整合了shiro許可權管理框架,因為它丟擲的異常沒有被我們的
AwesomeException
包裝,所以這個AuthorizationException
異常需要我們單獨處理。
AwesomeException
是我們大多數業務邏輯丟擲來的異常,我們根據AwesomeException
裡面的code、msg和它包裝的cause,封裝成一個最終的響應資料ResultWrap。另一個
RuntimeException
是必須要額外處理的,任何開發人員都無法保證自己的程式碼是完全沒有bug的,任何的空指標異常都會影響使用者體驗,這種編碼性的錯誤我們需要通過統一的錯誤處理讓它變得更柔和一點。
我們需要針對所有的返回結果進行檢查,如果不是ResultWrap
型別的返回資料,我們需要包裝一下,以便保證我們和前端開發人員達成的共識。
import com.example.awesomespring.vo.ResultWrap; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.util.Objects; /** * @className AwesomeResponseAdvice * @description: */ @RestControllerAdvice public class AwesomeResponseAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { // 如果返回String,那麼就不包裝了。 if (StringHttpMessageConverter.class.isAssignableFrom(converterType)) { return false; } // 有一些情況是不需要包裝的,比如呼叫第三方API返回的資料,所以我們做了一個自定義註解來避免所以的結果都被包裝成ResultWrap。 boolean ignore = false; IgnoreResponseAdvice ignoreResponseAdvice = returnType.getMethodAnnotation(IgnoreResponseAdvice.class); // 如果我們在方法上新增了IgnoreResponseAdvice註解,那麼就不要攔截包裝了 if (Objects.nonNull(ignoreResponseAdvice)) { ignore = ignoreResponseAdvice.value(); return !ignore; } // 如果我們在類上面新增了IgnoreResponseAdvice註解,也在方法上面新增了IgnoreResponseAdvice註解,那麼以方法上的註解為準。 Class<?> clazz = returnType.getDeclaringClass(); ignoreResponseAdvice = clazz.getDeclaredAnnotation(IgnoreResponseAdvice.class); RestController restController = clazz.getDeclaredAnnotation(RestController.class); if (Objects.nonNull(ignoreResponseAdvice)) { ignore = ignoreResponseAdvice.value(); } else if (Objects.isNull(restController)) { ignore = true; } return !ignore; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 如果返回結果為null,那麼我們直接返回ResultWrap.success() if (Objects.isNull(body)) { return ResultWrap.success(); } // // 如果返回結果已經是ResultWrap,直接返回 if (body instanceof ResultWrap) { return body; } // 否則我們把返回結果包裝成ResultWrap return ResultWrap.success(body); } } import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @className IgnoreResponseAdvice * @description: */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface IgnoreResponseAdvice { // 是否忽略ResponseAdvice;決定了是否要包裝返回資料 boolean value() default true; }
至此,我們把整個例外處理與返回結果的統一處理全部關聯起來了,我們後端的開發人員無論是返回異常還是返回ResultWrap或者其他資料結果,都能很好地保證與前端開發人員的正常共同作業,不必為資料結構的變化過多地溝通。
ResultWrap.java
import com.example.awesomespring.exception.AwesomeException; import com.example.awesomespring.util.JsonUtil; import lombok.AllArgsConstructor; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Objects; /** * @className ResultWrap * @description: */ @Data @AllArgsConstructor public class ResultWrap<T, M> { // 方便前端判斷當前請求處理結果是否正常 private int code; // 業務處理結果 private T data; // 產生錯誤的情況下,提示使用者資訊 private String message; // 產生錯誤情況下的異常堆疊,提示開發人員 private String error; // 發生錯誤的時候,返回的附加資訊 private M metaInfo; /** * 成功帶處理結果 * * @param data * @param <T> * @return */ public static <T> ResultWrap success(T data) { return new ResultWrap(HttpStatus.OK.value(), data, StringUtils.EMPTY, StringUtils.EMPTY, null); } /** * 成功不帶處理結果 * * @return */ public static ResultWrap success() { return success(HttpStatus.OK.name()); } /** * 失敗 * * @param code * @param message * @param error * @return */ public static <M> ResultWrap failure(int code, String message, String error, M metaInfo) { return new ResultWrap(code, null, message, error, metaInfo); } /** * 失敗 * * @param code * @param message * @param error * @param metaInfo * @param <M> * @return */ public static <M> ResultWrap failure(int code, String message, Throwable error, M metaInfo) { String errorMessage = StringUtils.EMPTY; if (Objects.nonNull(error)) { errorMessage = toStackTrace(error); } return failure(code, message, errorMessage, metaInfo); } /** * 失敗 * * @param code * @param message * @param error * @return */ public static ResultWrap failure(int code, String message, Throwable error) { return failure(code, message, error, null); } /** * 失敗 * * @param code * @param message * @param metaInfo * @param <M> * @return */ public static <M> ResultWrap failure(int code, String message, M metaInfo) { return failure(code, message, StringUtils.EMPTY, metaInfo); } /** * 失敗 * * @param e * @return */ public static ResultWrap failure(AwesomeException e) { return failure(e.getCode(), e.getMsg(), e.getCause()); } /** * 失敗 * * @param e * @return */ public static ResultWrap failure(RuntimeException e) { return failure(500, "服務異常,請稍後存取!", e.getCause()); } private static final String APPLICATION_JSON_VALUE = "application/json;charset=UTF-8"; /** * 把結果寫入響應中 * * @param response */ public void writeToResponse(HttpServletResponse response) { int code = this.getCode(); if (Objects.isNull(HttpStatus.resolve(code))) { response.setStatus(HttpStatus.OK.value()); } else { response.setStatus(code); } response.setContentType(APPLICATION_JSON_VALUE); try (PrintWriter writer = response.getWriter()) { writer.write(JsonUtil.obj2String(this)); writer.flush(); } catch (IOException e) { e.printStackTrace(); } } /** * 獲取異常堆疊資訊 * * @param e * @return */ private static String toStackTrace(Throwable e) { if (Objects.isNull(e)) { return StringUtils.EMPTY; } StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); try { e.printStackTrace(pw); return sw.toString(); } catch (Exception e1) { return StringUtils.EMPTY; } } }
AwesomeException.java
import lombok.Data; /** * @className AwesomeException * @description: */ @Data public class AwesomeException extends Throwable { private int code; private String msg; public AwesomeException(int code, String msg, Exception e) { super(e); this.code = code; this.msg = msg; } public AwesomeException(int code, String msg) { this.code = code; this.msg = msg; } }
AwesomeExceptionHandler.java
import com.example.awesomespring.exception.AwesomeException; import com.example.awesomespring.vo.ResultWrap; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.AuthorizationException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @className ExceptionHandler * @description: */ @Slf4j @RestControllerAdvice public class AwesomeExceptionHandler { /** * 捕獲沒有使用者許可權的異常 * * @return */ @ExceptionHandler(AuthorizationException.class) public ResultWrap handleException(AuthorizationException e) { return ResultWrap.failure(401, "您暫時沒有存取許可權!", e); } /** * 處理AwesomeException * * @param e * @param request * @param response * @return */ @ExceptionHandler(AwesomeException.class) public ResultWrap handleAwesomeException(AwesomeException e, HttpServletRequest request, HttpServletResponse response) { return ResultWrap.failure(e); } /** * 專門針對執行時異常 * * @param e * @return */ @ExceptionHandler(RuntimeException.class) public ResultWrap handleRuntimeException(RuntimeException e) { return ResultWrap.failure(e); } }
IgnoreResponseAdvice.java
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @className IgnoreResponseAdvice * @description: */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface IgnoreResponseAdvice { boolean value() default true; }
AwesomeResponseAdvice.java
import com.example.awesomespring.vo.ResultWrap; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import java.util.Objects; /** * @className AwesomeResponseAdvice * @description: */ @RestControllerAdvice public class AwesomeResponseAdvice implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter returnType, Class converterType) { if (StringHttpMessageConverter.class.isAssignableFrom(converterType)) { return false; } boolean ignore = false; IgnoreResponseAdvice ignoreResponseAdvice = returnType.getMethodAnnotation(IgnoreResponseAdvice.class); if (Objects.nonNull(ignoreResponseAdvice)) { ignore = ignoreResponseAdvice.value(); return !ignore; } Class<?> clazz = returnType.getDeclaringClass(); ignoreResponseAdvice = clazz.getDeclaredAnnotation(IgnoreResponseAdvice.class); RestController restController = clazz.getDeclaredAnnotation(RestController.class); if (Objects.nonNull(ignoreResponseAdvice)) { ignore = ignoreResponseAdvice.value(); } else if (Objects.isNull(restController)) { ignore = true; } return !ignore; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (Objects.isNull(body)) { return ResultWrap.success(); } if (body instanceof ResultWrap) { return body; } return ResultWrap.success(body); } }
// 這裡只要返回AwesomeException,就會被ExceptionHandler處理掉,包裝成ResultWrap @PostMapping("/image/upload") String upload(@RequestPart("userImage") MultipartFile userImage) throws AwesomeException { fileService.putObject("video", userImage); return "success"; } // 加上IgnoreResponseAdvice註解,該返回結果就不會被包裝 @IgnoreResponseAdvice @GetMapping("/read") Boolean read() { return true; }
到此這篇關於Springboot專案例外處理及返回結果統一的文章就介紹到這了,更多相關Springboot例外處理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45