springboot統一返回處理、統一異常處理

代碼倉庫

https://github.com/tangtongda/springboot-advice.git

參考依據:https://github.com/purgeteam/unified-dispose-springboot
由於使用他人提供的方法發生了一些問題,比如:

  1. 返回string時報Class cast exception
  2. 部分包下面的異常無法正常捕獲
  3. 異常處理不能滿足業務需要等等

如何使用

1.maven

本人提交的Sonatype的審覈還沒有通過,暫不支持該種方式,如果通過會傳到Sonatype maven倉庫中。
但是maven依賴的方式無法自定義,比如我將錯誤狀態碼的業務異常定義爲4000,如果你想改,那麼maven依賴的方式不可能並不適合你,請看第二種。

2.自定義(推薦)

  1. 將代碼拉到本地 git clone https://github.com/tangtongda/springboot-advice.git
  2. 根據需要進行修改,推到自己公司的maven私服倉庫(如果沒有私服倉庫,也可以maven install後直接依賴jar包,不過注意版本控制。)
maven依賴的方式

注意修改私服地址

<!-- maven 私服配置 -->
<distributionManagement>
    <repository>
        <id>nexus.releases</id>
        <url>你的私服域名/repository/releases/</url>
        <uniqueVersion>false</uniqueVersion>
    </repository>
    <snapshotRepository>
        <id>nexus.snapshots</id>
        <url>你的私服域名/repository/snapshots/</url>
        <uniqueVersion>false</uniqueVersion>
    </snapshotRepository>
</distributionManagement>

如果項目已經微服務化,將依賴添加到公共依賴中保證所有服務都可以用,普通springboot項目直接pom添加私服即可

<dependency>
    <groupId>com.tino</groupId>
    <artifactId>advice</artifactId>
    <version>1.0.0-RELEASE</version>
</dependency>
jar包方式

mvn install 打包,在需要依賴的depencies中添加該jar包

開始使用

啓動上添加@EnableGlobalDispose開啓統一處理

/**
 * 核心服務啓動類
 *
 * @author tino
 * @date 2019/7/31
 */
@EnableGlobalDispose
@SpringBootApplication
public class CoreApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(CoreApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(CoreApplication.class, args);
    }
}

添加或在已有的配置類上繼承WebConfiguration

/**
 * 啓動配置,目的是利用WebConfiguration 中的configureMessageConverters來處理String類型的異常
 *
 * @author tino
 * @date 2019/8/2
 */
@Configuration
public class CoreConfiguration extends WebConfiguration implements WebMvcConfigurer  {
}

異常處理:添加BusinessExceptionAdvice 用戶處理全局異常
注意:之所以需要建該類的原因是包路徑的問題,由於BaseExceptionAdvice 所在的位置是在我定義的com.tino.advice.starter.advice下,如果你想捕獲你的包下面的異常,那麼必須在你的包下面繼承BaseExceptionAdvice,否則實測無法正確捕獲異常。或者也可以把我的代碼拉到本地,修改包路徑和你一樣,那麼也可以正確捕獲

/**
 * 基礎全局異常處理
 *
 * @author tino
 * @date 2020/1/2
 */
@RestControllerAdvice
public class BusinessExceptionAdvice extends BaseExceptionAdvice {
}

(非必須)添加BusinessErrorCode異常處理code、message枚舉類
如果你需要擴展業務異常的code和message,那麼你必須做這一步

/**
 * 異常
 *
 * @author tino
 * @date 2019/7/31
 */
public enum BusinessErrorCode {
    
    SG_USER_LOGIN_ERR(4000, "登錄異常"),
    ;

    private Integer code;
    private String message;

    BusinessErrorCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public Integer getCode() {
        return code;
    }
}

如何拋出異常

// 拋出通用異常
BaseException.exception(BaseErrorCode.PARAM_ERROR);
// 拋出自定義message的業務異常
BaseException.businessException(BusinessErrorCode.SG_USER_LOGIN_ERR.getMessage());
// 拋出自定義code和message的異常
BaseException.exception(BusinessErrorCode.SG_NO_LOGIN_ERR.getCode(), BusinessErrorCode.SG_NO_LOGIN_ERR.getMessage());

功能說明

項目結構

│  .gitignore
│  pom.xml
│  README.md
│  
├─src
│  └─main
│      ├─java
│      │  └─com
│      │      └─tino
│      │          └─advice
│      │              │  AdviceApplication.java
│      │              │  
│      │              └─starter
│      │                  ├─advice
│      │                  │      BaseExceptionAdvice.java
│      │                  │      BaseResponseAdvice.java
│      │                  │      
│      │                  ├─annotation
│      │                  │      EnableGlobalDispose.java
│      │                  │      IgnoreResponseAdvice.java
│      │                  │      
│      │                  ├─config
│      │                  │      DefaultConfiguration.java
│      │                  │      DefaultProperties.java
│      │                  │      WebConfiguration.java
│      │                  │      
│      │                  ├─exception
│      │                  │      BaseErrorCode.java
│      │                  │      BaseException.java
│      │                  │      
│      │                  └─response
│      │                          BaseResponse.java
│      │                          
│      └─resources
│              application.yml
│              dispose.properties
│              
└─target

簡要說明

1.數據結構類說明

BaseResponse.java 爲統一返況:正常返回和失敗返回

成功

{
    "code":200,
    "message":null,
    "success":true,
    "data":null
}

失敗

{
    "code":500,// http異常和業務異常
    "message":null,// 自定義的message
    "success":false,
    "data":null // 失敗時數據不返回
}

這裏由於GET請求通常不會處理請求結果中的message,所以成功的message直接返回null,如果有需要應該由前端來定義用戶看到的message

BaseErrorCodeEnum.java 爲自定義的異常code、message基類,可以根據需要擴展
BaseException.java 爲RuntimeException的子類,用來提供自定義拋出異常的方法

1.統一返回結果包裝

原理是通過@RestControllerAdvice註解和ResponseBodyAdvice中的beforeBodyWrite方法來攔截controller層的返回結果幷包裝成自己想要的樣子。

/**
 * 統一返回包裝器
 *
 * @author tino
 * @date 2020/1/2
 */
@RestControllerAdvice
public class BaseResponseAdvice implements ResponseBodyAdvice<Object> {

    private DefaultProperties defaultProperties;

    public BaseResponseAdvice(DefaultProperties defaultProperties) {
        this.defaultProperties = defaultProperties;
    }

    @Override
    public boolean supports(MethodParameter methodParameter,
                            Class<? extends HttpMessageConverter<?>> aClass) {
        return filter(methodParameter);
    }

    @Nullable
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
                                  Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse) {
        // 如果手動進行了返回封裝,判斷類型防止二次封裝
        if (!(o instanceof BaseResponse)) {
            return BaseResponse.succeed(o);
        }
        return o;
    }

    /**
     * 用來過濾不需要包裝返回參數的接口
     *
     * @param methodParameter
     * @return
     */
    private Boolean filter(MethodParameter methodParameter) {
        Class<?> declaringClass = methodParameter.getDeclaringClass();
        // 檢查過濾包路徑
        long count = defaultProperties.getAdviceFilterPackage().stream()
                .filter(l -> declaringClass.getName().contains(l)).count();
        if (count > 0) {
            return false;
        }
        // 檢查<類>過濾列表
        if (defaultProperties.getAdviceFilterClass().contains(declaringClass.getName())) {
            return false;
        }
        // 檢查註解是否存在
        if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnoreResponseAdvice.class)) {
            return false;
        }
        return !methodParameter.getMethod().isAnnotationPresent(IgnoreResponseAdvice.class);
    }
}

2.異常統一處理

同樣的原理,通過@RestControllerAdvice註解攔截所有controller,然後通過@ExceptionHandler來處理異常並返回指定結果,異常類型可以根據自身需要添加,因爲考慮到返回體的包裝結果大部分都是前端開發去看,所以我只處理了HTTP的常見異常,至於RPC類型的異常,我把它交給500異常統一捕獲(Exception.class)。

/**
 * 基礎全局異常處理
 *
 * @author tino
 * @date 2020/1/2
 */
@RestControllerAdvice
@ResponseBody
public class BaseExceptionAdvice {

    private static Logger logger = LoggerFactory.getLogger(BaseExceptionAdvice.class);

    /**
     * 處理其他所以未知的異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({Exception.class})
    public BaseResponse globalExceptionHandler(Exception e) {
        logger.error(e.getMessage(), e);
        return BaseResponse.fail(BaseErrorCode.EXCEPTION);
    }

    /**
     * 業務異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({BaseException.class})
    public BaseResponse businessExceptionHandler(BaseException e) {
        logger.error(e.getMessage(), e);
        return BaseResponse.fail(e.getCode(), e.getMessage());
    }

    /**
     * 404 異常處理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public BaseResponse handlerNoHandlerFoundException(NoHandlerFoundException e) {
        logger.error(e.getMessage(), e);
        return BaseResponse.fail(BaseErrorCode.NOT_FOUND);
    }

    /**
     * 405 異常處理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public BaseResponse handlerHttpRequestMethodNotSupportedException(
            HttpRequestMethodNotSupportedException e) {
        logger.error(e.getMessage(), e);
        return BaseResponse.fail(BaseErrorCode.METHOD_NOT_ALLOWED);
    }

    /**
     * 415 異常處理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public BaseResponse handlerHttpMediaTypeNotSupportedException(
            HttpMediaTypeNotSupportedException e) {
        logger.error(e.getMessage(), e);
        return BaseResponse.fail(BaseErrorCode.UNSUPPORTED_MEDIA_TYPE);
    }
}

3.處理String類型轉換錯誤異常

當返回String時,會發生BaseResponse cannot cast to String

/**
 * 重寫configureMessageConverters,處理字符串返回cannot be cast to java.lang.String錯誤
 *
 * @author tino
 * @date 2020/1/2
 */
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0, new MappingJackson2HttpMessageConverter());
    }
}

4.自定義註解的作用

EnableGlobalDispose 的作用是用來開啓全局統一返回處理,需要加在springboot啓動類上
IgnoreResponseAdvice 的作用是忽略不需要封裝返回結果的接口,例如【微信公衆號開發】,【微信支付回調】,【支付寶支付回調】等等需要獲取我方接口返回結果的三方接口。
切記!!否則可能發生調試不通或請求阻塞的情況

    /**
     * GET請求校驗開發者
     *
     * @param request
     * @return
     * @throws Exception
     */
    @GetMapping("/callback")
    @IgnoreResponseAdvice
    public Long checkToken(HttpServletRequest request) {
        // 微信加密簽名
        String signature = request.getParameter("signature");
        // 時間戳
        String timestamp = request.getParameter("timestamp");
        // 隨機數
        String nonce = request.getParameter("nonce");
        // 隨機字符串
        String echostr = request.getParameter("echostr");
        // 通過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,表示接入成功,否則接入失敗
        if (WXPublicSignUtils.checkSignature(signature, timestamp, nonce, token)) {
            return Long.parseLong(echostr);
        }
        return 0L;
    }
發佈了26 篇原創文章 · 獲贊 29 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章