RestFul API 統一格式返回 + 全局異常處理

一、背景

在分佈式、微服務盛行的今天,絕大部分項目都採用的微服務框架,前後端分離方式。前端和後端進行交互,前端按照約定請求URL路徑,並傳入相關參數,後端服務器接收請求,進行業務處理,返回數據給前端。

所以統一接口的返回值,保證接口返回值的冪等性很重要,本文主要介紹博主當前使用的結果集。

二、統一格式設計

2.1 統一結果的一般形式

  • 示例:
{
	# 是否響應成功
	success: true,
	# 響應狀態碼
	code: 200,		
	# 響應數據
	data: Object
	# 返回錯誤信息
	message: "",
}
複製代碼

2.2 結果類枚舉

public enum ResultCodeEnum {
    /*** 通用部分 100 - 599***/
    // 成功請求
    SUCCESS(200, "successful"),
    // 重定向
    REDIRECT(301, "redirect"),
    // 資源未找到
    NOT_FOUND(404, "not found"),
    // 服務器錯誤
    SERVER_ERROR(500,"server error"),

    /*** 這裏可以根據不同模塊用不同的區級分開錯誤碼,例如:  ***/

    // 1000~1999 區間表示用戶模塊錯誤
    // 2000~2999 區間表示訂單模塊錯誤
    // 3000~3999 區間表示商品模塊錯誤
    // 。。。

    ;
    /**
     * 響應狀態碼
     */
    private Integer code;
    /**
     * 響應信息
     */
    private String message;

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

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}
複製代碼
  • code:響應狀態碼

一般小夥伴們是在開發的時候需要什麼,就添加什麼。但是,爲了規範,我們應當參考HTTP請求返回的狀態碼。

  code區間 類型 含義
1** 100-199 信息 服務器接收到請求,需要請求者繼續執行操作
2** 200-299 成功 請求被成功接收並處理
3** 300-399 重定向 需要進一步的操作以完成請求
4** 400-499 客戶端錯誤 請求包含語法錯誤或無法完成請求
5** 500-599 服務器錯誤 服務器在處理的時候發生錯誤

常見的HTTP狀態碼:

  1. 200 - 請求成功;
  2. 301 - 資源(網頁等)被永久轉移到其它URL
  3. 404 - 請求的資源(網頁等)不存在;
  4. 500 - 內部服務器錯誤。
  • message:錯誤信息

在發生錯誤時,如何友好的進行提示?

  1. 根據code 給予對應的錯誤碼定位;
  2. 把錯誤描述記錄到message中,便於接口調用者更詳細的瞭解錯誤。

2.3 統一結果類

public class HttpResult <T> implements Serializable {

    /**
     * 是否響應成功
     */
    private Boolean success;
    /**
     * 響應狀態碼
     */
    private Integer code;
    /**
     * 響應數據
     */
    private T data;
    /**
     * 錯誤信息
     */
    private String message;

    // 構造器開始
    /**
     * 無參構造器(構造器私有,外部不可以直接創建)
     */
    private HttpResult() {
        this.code = 200;
        this.success = true;
    }
    /**
     * 有參構造器
     * @param obj
     */
    private HttpResult(T obj) {
        this.code = 200;
        this.data = obj;
        this.success = true;
    }

    /**
     * 有參構造器
     * @param resultCode
     */
    private HttpResult(ResultCodeEnum resultCode) {
        this.success = false;
        this.code = resultCode.getCode();
        this.message = resultCode.getMessage();
    }
    // 構造器結束

    /**
     * 通用返回成功(沒有返回結果)
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> success(){
        return new HttpResult();
    }

    /**
     * 返回成功(有返回結果)
     * @param data
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> success(T data){
        return new HttpResult<T>(data);
    }

    /**
     * 通用返回失敗
     * @param resultCode
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> failure(ResultCodeEnum resultCode){
        return  new HttpResult<T>(resultCode);
    }

    public Boolean getSuccess() {
        return success;
    }

    public void setSuccess(Boolean success) {
        this.success = success;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "HttpResult{" +
                "success=" + success +
                ", code=" + code +
                ", data=" + data +
                ", message='" + message + '\'' +
                '}';
    }
}
複製代碼

說明:

  1. 構造器私有,外部不可以直接創建;
  2. 只可以調用統一返回類的靜態方法返回對象;
  3. success 是一個Boolean 值,通過這個值,可以直接觀察到該次請求是否成功;
  4. data 表示響應數據,用於請求成功後,返回客戶端需要的數據。

三、測試及總結

3.1 簡單的接口測試

@RestController
@RequestMapping("/httpRest")
@Api(tags = "統一結果測試")
public class HttpRestController {

    @ApiOperation(value = "通用返回成功(沒有返回結果)", httpMethod = "GET")
    @GetMapping("/success")
    public HttpResult success(){
        return HttpResult.success();
    }

    @ApiOperation(value = "返回成功(有返回結果)", httpMethod = "GET")
    @GetMapping("/successWithData")
    public HttpResult successWithData(){
        return HttpResult.success("風塵博客");
    }

    @ApiOperation(value = "通用返回失敗", httpMethod = "GET")
    @GetMapping("/failure")
    public HttpResult failure(){
        return HttpResult.failure(ResultCodeEnum.NOT_FOUND);
    }

}
複製代碼

這裏 Swagger以及SpringMVC的配置就沒貼出來了,詳見Github 示例代碼。

3.2 返回結果

http://localhost:8080/swagger-ui.html#/

{
  "code": 200,
  "success": true
}
複製代碼
{
  "code": 200,
  "data": "風塵博客",
  "success": true
}
複製代碼
{
  "code": 404,
  "message": "not found",
  "success": false
}
複製代碼

四、全局異常處理

使用統一返回結果時,還有一種情況,就是程序的報錯是由於運行時異常導致的結果,有些異常是我們在業務中拋出的,有些是無法提前預知。

因此,我們需要定義一個統一的全局異常,在Controller捕獲所有異常,並且做適當處理,並作爲一種結果返回。

4.1 設計思路:

  1. 自定一個異常類(如:TokenVerificationException),捕獲針對項目或業務的異常;
  2. 使用@ExceptionHandler註解捕獲自定義異常和通用異常;
  3. 使用@ControllerAdvice集成@ExceptionHandler的方法到一個類中;
  4. 異常的對象信息補充到統一結果枚舉中;

4.2 自定義異常

public class TokenVerificationException extends RuntimeException {

    /**
     * 錯誤碼
     */
    protected Integer code;

    protected String msg;

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    /**
     * 有參構造器,返回碼在枚舉類中,這裏可以指定錯誤信息
     * @param msg
     */
    public TokenVerificationException(String msg) {
        super(msg);
    }
}
複製代碼

4.3 統一異常處理器

@ControllerAdvice註解是一種作用於控制層的切面通知(Advice),能夠將通用的@ExceptionHandler@InitBinder@ModelAttributes方法收集到一個類型,並應用到所有控制器上。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 異常捕獲
     * @param e 捕獲的異常
     * @return 封裝的返回對象
     **/
    @ExceptionHandler(Exception.class)
    public HttpResult handlerException(Exception e) {
        ResultCodeEnum resultCodeEnum;
        // 自定義異常
        if (e instanceof TokenVerificationException) {
            resultCodeEnum = ResultCodeEnum.TOKEN_VERIFICATION_ERROR;
            resultCodeEnum.setMessage(getConstraintViolationErrMsg(e));
            log.error("tokenVerificationException:{}", resultCodeEnum.getMessage());
        }else {
            // 其他異常,當我們定義了多個異常時,這裏可以增加判斷和記錄
            resultCodeEnum = ResultCodeEnum.SERVER_ERROR;
            resultCodeEnum.setMessage(e.getMessage());
            log.error("common exception:{}", JSON.toJSONString(e));
        }
        return HttpResult.failure(resultCodeEnum);
    }

    /**
     * 獲取錯誤信息
     * @param ex
     * @return
     */
    private String getConstraintViolationErrMsg(Exception ex) {
        // validTest1.id: id必須爲正數
        // validTest1.id: id必須爲正數, validTest1.name: 長度必須在有效範圍內
        String message = ex.getMessage();
        try {
            int startIdx = message.indexOf(": ");
            if (startIdx < 0) {
                startIdx = 0;
            }
            int endIdx = message.indexOf(", ");
            if (endIdx < 0) {
                endIdx = message.length();
            }
            message = message.substring(startIdx, endIdx);
            return message;
        } catch (Throwable throwable) {
            log.info("ex caught", throwable);
            return message;
        }
    }
}
複製代碼
  • 說明
  1. 我使用的是@RestControllerAdvice ,等同於@ControllerAdvice + @ResponseBody
  2. 錯誤枚舉類這裏省略了,詳見Github代碼

五、測試及總結

5.1 測試接口

@RestController
@RequestMapping("/exception")
@Api(tags = "異常測試接口")
public class ExceptionRestController {

    @ApiOperation(value = "業務異常(token 異常)", httpMethod = "GET")
    @GetMapping("/token")
    public HttpResult token() {
        // 模擬業務層拋出 token 異常
        throw new TokenVerificationException("token 已經過期");
    }


    @ApiOperation(value = "其他異常", httpMethod = "GET")
    @GetMapping("/errorException")
    public HttpResult errorException() {
        //這裏故意造成一個其他異常,並且不進行處理
        Integer.parseInt("abc123");
        return HttpResult.success();
    }
}
複製代碼

5.2 返回結果

http://localhost:8080/swagger-ui.html#/

{
  "code": 500,
  "message": "For input string: \"abc123\"",
  "success": false
}
複製代碼
{
  "code": 4000,
  "message": "token 已經過期",
  "success": false
}
複製代碼

5.3 小結

@RestControllerAdvice@ExceptionHandler會捕獲所有Rest接口的異常並封裝成我們定義的HttpResult的結果集返回,但是:處理不了攔截器裏的異常

六、總結

沒有哪一種方案是適用於各種情況的,如:分頁情況,還可以增加返回分頁結果的靜態方案,具體實現,這裏就不展示了。所以,適合自己的,具有一定可讀性都是很好的,歡迎持不同意見的大佬給出意見建議。

6.1 示例代碼

Github 示例代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章