天天看點

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

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狀态碼:

200 - 請求成功;

301 - 資源(網頁等)被永久轉移到其它URL;

404 - 請求的資源(網頁等)不存在;

500 - 内部伺服器錯誤。

message:錯誤資訊

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

根據code 給予對應的錯誤碼定位;

把錯誤描述記錄到message中,便于接口調用者更詳細的了解錯誤。

2.3 統一結果類

public class HttpResult 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 + '\'' +
            '}';
}           

說明:

構造器私有,外部不可以直接建立;

隻可以調用統一傳回類的靜态方法傳回對象;

success 是一個Boolean 值,通過這個值,可以直接觀察到該次請求是否成功;

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

"data": "風塵部落格",

"code": 404,

"message": "not found",

"success": false

四、全局異常處理

使用統一傳回結果時,還有一種情況,就是程式的報錯是由于運作時異常導緻的結果,有些異常是我們在業務中抛出的,有些是無法提前預知。

是以,我們需要定義一個統一的全局異常,在Controller捕獲所有異常,并且做适當處理,并作為一種結果傳回。

4.1 設計思路:

自定一個異常類(如:TokenVerificationException),捕獲針對項目或業務的異常;

使用@ExceptionHandler注解捕獲自定義異常和通用異常;

使用@ControllerAdvice內建@ExceptionHandler的方法到一個類中;

異常的對象資訊補充到統一結果枚舉中;

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;
    }
}           

說明

我使用的是@RestControllerAdvice ,等同于@ControllerAdvice + @ResponseBody

錯誤枚舉類這裡省略了,詳見Github代碼。

五、測試及總結

5.1 測試接口

@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 傳回結果

"code": 500,

"message": "For input string: "abc123"",

"code": 4000,

"message": "token 已經過期",

5.3 小結

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

六、總結

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

6.1 示例代碼

Github 示例代碼

6.2 技術交流

風塵部落格

風塵部落格-掘金

風塵部落格-部落格園

Github

原文位址

https://www.cnblogs.com/vandusty/p/12557551.html

繼續閱讀