一、前言、背景
在如今前后端分離的時代,后端已經由傳統的返回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(),"");
}
調用 參考SpringBoot
的HttpStatus
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~~