java統一返回標準型別

前度吖 發表於 2021-10-05
Java

一、前言、背景

在如今前後端分離的時代,後端已經由傳統的返回view檢視轉變為返回json資料,此json資料可能包括返回狀態、資料、資訊等......因為程式猿的習慣不同所以返回json資料的格式也各有千秋;此時迫切需要一個需求----將後臺返回的資料再封裝起來統一返回一個標準的型別供前端程式猿呼叫。

SpringBoot框架中,它已經給我們封裝了一個標準的型別ResponseEntity,但是框架考慮的很多,很是冗餘,因此我們需要自己動手編寫適合自己使用的標準 我把他稱之為R型別。循序漸進

二、返回的統一格式

在開發中 我常用 code 表示狀態碼、message表示返回的提示資訊、data表示返回的具體資料

{
 "code": 數字, //響應碼
 "message": 字串, //返回訊息
 "data": object //返回資料
}

三、建立統一返回類

1. 建立標準返回類

// 統一結果返回類
@Data
@AllArgsConstructor
public class R {
    //標識返回的狀態碼
    private Integer code;
    //標識返回的資訊
    private String message;
    //標識返回的資料
    private Object data;

    //私有化,防止new
    private R() {  }
  
    //成功
    public static R ok(Object data, String message) {
        return new R(200, message, data);  //code 也可以使用字典管理 下面會談到
    }

    //成功返回 過載 message沒有特別要求
    public static R ok(Object data) {
        return R.ok(data, "success"); //message 也可以使用字典管理 下面會談到
    }

    // 失敗
    public static R error( Integer code, String message) {
        return new R(code, message, "");
    }
}

牛刀小試返回結果

{
    "code": 200,
    "message": "成功",
    "data": {
        "id": 1,
        "account": "qd666",
        "passWord": "123456"
    }
}

2. 統一字典管理

在上述成功的案例中,code 的值為200,而在失敗中就不止這一種情況 所以需要改進

新建RConstants介面 統一維護

//返回常量
public interface RConstants {
    Integer FAIL_CODE=201;
    String FAIL_MESSAGE="查詢失敗";
    //......
}

呼叫

 return R.error(RConstants.FAIL_CODE, RConstants.FAIL_MESSAGE); //201,查詢失敗

結果

{
    "code": 201,
    "message": "查詢失敗",
    "data": ""
}

3. 列舉型別匹配

在上述介面中,我們發現,如果一個code 如果要對應message的話,需要建立大量的自定義常量,非常麻煩,這時我們可以改變其為列舉型別使我們的程式碼更加簡介 但是如果只有一個屬性的話,使用列舉就多此一舉了

建立RHttpStatusEnum 列舉

public enum RHttpStatusEnum {

    //舉個例子 查詢失敗與登入失敗
    QUERY_USER_FAIL(201, "查詢失敗"),
    LOGIN_USER_FAIL(202, "登入失敗");
  
	  final int code;
    final String message;
    RHttpStatusEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }
 //生成get方法  已經賦值了所以不需要set方法
}  

呼叫

return R.error(RHttpStatusEnum.QUERY_USER_FAIL.getCode(),
                RHttpStatusEnum.QUERY_USER_FAIL.getMessage());

當我們使用了列舉之後,發現在呼叫R類時比較複雜 我們可以改變R類的error方法


4. 簡化R類方法

修改error方法

    // 失敗
    public static R error(RHttpStatusEnum httpStatusEnum) {
        return new R(httpStatusEnum.getCode(),httpStatusEnum.getMessage(),"");
    }

呼叫 參考SpringBootHttpStatus

return R.error(RHttpStatusEnum.QUERY_USER_FAIL);

ok !寫道這裡就發現我們定義的這個標準格式已經非常完美了 滿足日常開發的使用了 但同時也會有絲絲缺點,因為這個R類把後端返回的資料限制住了,開發人員必須在實際程式碼中都要返回這個R類,降低了擴充套件性 。

解決方案: 在開發之中,程式猿想要隨心所欲的返回任何的資料,我們只需使用R類對其封裝一層即可! 利用Spring的後置機制可以實現該要求。 個人覺得不在包裝也是很好的,依據自己的情況而定!

四、封裝R類

可以使用spring提供的結果攔截增強處理機制來解決這個問題,如下:

採用springboot提供的 ResponseBodyAdvicel處理即可。

ResponseBodyAdvice的作用是:攔截Controller方法的返回值,統一處理返回值到響應體中,一般來做response的統一格式,加密,簽名等。

  • 實現ResponseBodyAdvice介面,並掃描包名
@ControllerAdvice(basePackages = "com.qd.springboot") //掃描包
public class ResultResponseHandler implements ResponseBodyAdvice<Object> {

    /**
     * 是否支援advice功能,true是支援、false是不支援
     *
     * @param methodParameter
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    //o = controller方法的返回值
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //如果返回的是string會預設呼叫string的處理器直接返回
        if (o instanceof String) {
           return JSON.toJSONString(R.ok(o));
        }else if(o instanceof  R){
            return o;
        }
       return  R.ok(o);
    }
}
  • 匯入需要的依賴
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.54</version>
        </dependency>
  • R類 如果沒有太多的對應關係,直接用介面實現即可
@Data
@AllArgsConstructor
public class R {
    //標識返回的狀態碼
    private Integer code;
    //標識返回的資訊
    private String message;
    //標識返回的資料
    private Object data;

    //私有化,防止new
    private R() {
    }
  
      //成功
    public static R ok(Object data, String message) {
        return new R(200, message, data);
    }
  
    //成功
    public static R ok(Object data) {
        return R.ok(data, "success");
    }

    // 失敗
    public static R error(Integer code, String message) {
        return new R(code, message, "");
    }
}
  • 字典管理
public interface RConstants {
    Integer FAIL_CODE=201;
    String FAIL_MESSAGE="查詢失敗";
    //......
}

這樣設定後,需要捕獲程式執行中的異常,對於不正確的請求,我們主動丟擲異常,然後統一去捕獲,最後呼叫R類error方法統一返回錯誤資訊

  • GlobalExceptionHandler 全域性異常
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(value = RuntimeException.class)
    @ResponseBody
    public Object exceptionHandler1(RuntimeException e) {
        log.error("異常型別:{}--異常資訊:{}", e.getClass(), e.getMessage());
        //code狀態碼可在字典中定義
        return R.error(201, e.getMessage());
    }
  
  
  //資料校驗
      @ExceptionHandler(value = ConstraintViolationException.class)
    public Object ViolationExceptions(ConstraintViolationException e) {
        log.error("異常型別:{}--異常資訊:{}", e.getClass(), e.getMessage());
        String ex = e.getMessage();
        String message = null;
        //判斷是否多引數錯誤 是:一個個返回;否:返回
        if (ex.contains(",")) {
            message = ex.substring(ex.indexOf(":") + 1, ex.indexOf(","));
        } else {
            message = ex.split(":")[1].toString();
        }
        return R.error(201, message);
    }
	
  //其他的異常....
  

}
  • controller或者service 判斷標誌 主動丟擲異常
    @GetMapping("user/id/{id}")
    public User queryUserByID(@PathVariable("id") Integer id) {
        if ("2".equals(id)) {
            throw new RuntimeException(RConstants.FAIL_MESSAGE);
        }
        return userService.queryUserById(id);
    }

到此,就學習的差不多了


The End~~