springboot之全局處理統一返回

springboot之全局處理統一返回

簡介

在REST風格的開發中,避免一般會告知前臺返回是否成功以及狀態碼等信息。這裏咱們一般返回的時候作一次util的包裝處理工做,如:Result相似的類,裏面包含succcodemsgdata等字段。前端

接口調用返回相似以下:java

{
  "succ": false,        // 是否成功
  "ts": 1566467628851,  // 時間戳
  "data": null,         // 數據
  "code": "CLOUD800",   // 錯誤類型
  "msg": "業務異常",    // 錯誤描述
  "fail": true
}

固然在每一個接口裏返回要經過Result的工具類將這些信息給封裝一下,這樣致使業務和技術類的代碼耦合在一塊兒。git

接口調用處理相似以下:github

@GetMapping("hello")
  public Result list(){
    return Result.ofSuccess("hello");
  }

結果:spring

{
  "succ": ture,         // 是否成功
  "ts": 1566467628851,  // 時間戳
  "data": "hello",      // 數據
  "code": null,         // 錯誤類型
  "msg": null,          // 錯誤描述
  "fail": true
}

咱們將這些操抽出一個公共starter包,各個服務依賴便可,作一層統一攔截處理的工做,進行技術解耦。json

配置

unified-dispose-springboot-starterspringboot

這個模塊裏包含異常處理以及全局返回封裝等功能,下面。服務器

完整目錄結構以下:數據結構

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── purgetiem
│   │   │           └── starter
│   │   │               └── dispose
│   │   │                   ├── GlobalDefaultConfiguration.java
│   │   │                   ├── GlobalDefaultProperties.java
│   │   │                   ├── Interceptors.java
│   │   │                   ├── Result.java
│   │   │                   ├── advice
│   │   │                   │   └── CommonResponseDataAdvice.java
│   │   │                   ├── annotation
│   │   │                   │   ├── EnableGlobalDispose.java
│   │   │                   │   └── IgnorReponseAdvice.java
│   │   │                   └── exception
│   │   │                       ├── GlobalDefaultExceptionHandler.java
│   │   │                       ├── category
│   │   │                       │   └── BusinessException.java
│   │   │                       └── error
│   │   │                           ├── CommonErrorCode.java
│   │   │                           └── details
│   │   │                               └── BusinessErrorCode.java
│   │   └── resources
│   │       ├── META-INF
│   │       │   └── spring.factories
│   │       └── dispose.properties
│   └── test
│       └── java

統一返回處理app

按照通常的模式,咱們都須要建立一個能夠進行處理包裝的工具類以及一個返回對象。

Result(返回類):

建立Result<T> Tdata的數據類型,這個類包含了前端經常使用的字段,還有一些經常使用的靜態初始化Result對象的方法。

/**
 * 返回統一數據結構
 *
 * @author purgeyao
 * @since 1.0
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {

  /**
   * 是否成功
   */
  private Boolean succ;

  /**
   * 服務器當前時間戳
   */
  private Long ts = System.currentTimeMillis();

  /**
   * 成功數據
   */
  private T data;

  /**
   * 錯誤碼
   */
  private String code;

  /**
   * 錯誤描述
   */
  private String msg;

  public static Result ofSuccess() {
    Result result = new Result();
    result.succ = true;
    return result;
  }

  public static Result ofSuccess(Object data) {
    Result result = new Result();
    result.succ = true;
    result.setData(data);
    return result;
  }

  public static Result ofFail(String code, String msg) {
    Result result = new Result();
    result.succ = false;
    result.code = code;
    result.msg = msg;
    return result;
  }

  public static Result ofFail(String code, String msg, Object data) {
    Result result = new Result();
    result.succ = false;
    result.code = code;
    result.msg = msg;
    result.setData(data);
    return result;
  }

  public static Result ofFail(CommonErrorCode resultEnum) {
    Result result = new Result();
    result.succ = false;
    result.code = resultEnum.getCode();
    result.msg = resultEnum.getMessage();
    return result;
  }

  /**
   * 獲取 json
   */
  public String buildResultJson(){
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("succ", this.succ);
    jsonObject.put("code", this.code);
    jsonObject.put("ts", this.ts);
    jsonObject.put("msg", this.msg);
    jsonObject.put("data", this.data);
    return JSON.toJSONString(jsonObject, SerializerFeature.DisableCircularReferenceDetect);
  }
}

這樣已經知足通常返回處理的需求了,在接口能夠這樣使用:

@GetMapping("hello")
  public Result list(){
    return Result.ofSuccess("hello");
  }

固然這樣是耦合的使用,每次都須要調用Result裏的包裝方法。


ResponseBodyAdvice 返回統一攔截處理

ResponseBodyAdvice在 spring 4.1 新加入的一個接口,在消息體被HttpMessageConverter寫入以前容許Controller@ResponseBody修飾的方法或ResponseEntity調整響應中的內容,好比作一些返回處理。

ResponseBodyAdvice接口裏一共包含了兩個方法

  • supports:該組件是否支持給定的控制器方法返回類型和選擇的{@code HttpMessageConverter}類型

  • beforeBodyWrite:在選擇{@code HttpMessageConverter}以後調用,在調用其寫方法以前調用。

那麼咱們就能夠在這兩個方法作一些手腳。

  • supports用於判斷是否須要作處理。

  • beforeBodyWrite用於作返回處理。

CommonResponseDataAdvice類實現ResponseBodyAdvice兩個方法。

filter(MethodParameter methodParameter) 私有方法裏進行判斷是否要進行攔截統一返回處理。

如:

  • 添加自定義註解@IgnorReponseAdvice忽略攔截。
  • 判斷某些類不進行攔截.
  • 判斷某些包下全部類不進行攔截。如swaggerspringfox.documentation包下的接口忽略攔截等。

filter方法:
判斷爲false就不須要進行攔截處理。

private Boolean filter(MethodParameter methodParameter) {
    Class<?> declaringClass = methodParameter.getDeclaringClass();
    // 檢查過濾包路徑
    long count = globalDefaultProperties.getAdviceFilterPackage().stream()
        .filter(l -> declaringClass.getName().contains(l)).count();
    if (count > 0) {
      return false;
    }
    // 檢查<類>過濾列表
    if (globalDefaultProperties.getAdviceFilterClass().contains(declaringClass.getName())) {
      return false;
    }
    // 檢查註解是否存在
    if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnorReponseAdvice.class)) {
      return false;
    }
    if (methodParameter.getMethod().isAnnotationPresent(IgnorReponseAdvice.class)) {
      return false;
    }
    return true;
  }

CommonResponseDataAdvice類:

最核心的就在beforeBodyWrite方法處理裏。

  1. 判斷Object o是否爲null,爲null構建Result對象進行返回。
  2. 判斷Object o是不是Result子類或其自己,該狀況下,多是接口返回時建立了Result,爲了不再次封裝一次,判斷是Result子類或其自己就返回Object o自己。
  3. 判斷Object o是不是爲String,在測試的過程當中發現String的特殊狀況,在這裏作了一次判斷操做,若是爲String就進行JSON.toJSON(Result.ofSuccess(o)).toString()序列號操做。
  4. 其餘狀況默認返回Result.ofSuccess(o)進行包裝處理。
/**
 * {@link IgnorReponseAdvice} 處理解析 {@link ResponseBodyAdvice} 統一返回包裝器
 *
 * @author purgeyao
 * @since 1.0
 */
@RestControllerAdvice
public class CommonResponseDataAdvice implements ResponseBodyAdvice<Object> {

  private GlobalDefaultProperties globalDefaultProperties;

  public CommonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties) {
    this.globalDefaultProperties = globalDefaultProperties;
  }

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

  @Nullable
  @Override
  @SuppressWarnings("all")
  public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
      Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
      ServerHttpResponse serverHttpResponse) {

    // o is null -> return response
    if (o == null) {
      return Result.ofSuccess();
    }
    // o is instanceof ConmmonResponse -> return o
    if (o instanceof Result) {
      return (Result<Object>) o;
    }
    // string 特殊處理
    if (o instanceof String) {
      return JSON.toJSON(Result.ofSuccess(o)).toString();
    }
    return Result.ofSuccess(o);
  }

  private Boolean filter(MethodParameter methodParameter) {
    ···略
  }

}

這樣基本完成了核心的處理工做。固然還少了上文提到的@IgnorReponseAdvice註解。

@IgnorReponseAdvice:
比較簡單點,只做爲一個標識的做用。

/**
 * 統一返回包裝標識註解
 *
 * @author purgeyao
 * @since 1.0
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnorReponseAdvice {

}

加入spring容器

最後將GlobalDefaultExceptionHandlerbean的方式注入spring容器。

@Configuration
@EnableConfigurationProperties(GlobalDefaultProperties.class)
@PropertySource(value = "classpath:dispose.properties", encoding = "UTF-8")
public class GlobalDefaultConfiguration {

  ···略
  
  @Bean
  public CommonResponseDataAdvice commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties){
    return new CommonResponseDataAdvice(globalDefaultProperties);
  }

}

GlobalDefaultConfigurationresources/META-INF/spring.factories文件下加載。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.purgetime.starter.dispose.GlobalDefaultConfiguration

不過咱們此次使用註解方式開啓。其餘項目依賴包後,須要添加@EnableGlobalDispose才能夠將全局攔截的特性開啓。

將剛剛建立的spring.factories註釋掉,建立EnableGlobalDispose註解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(GlobalDefaultConfiguration.class)
public @interface EnableGlobalDispose {

}

使用@ImportGlobalDefaultConfiguration導入便可。

使用

添加依賴

<dependency>
  <groupId>com.purgeteam</groupId>
  <artifactId>unified-dispose-deepblueai-starter</artifactId>
  <version>0.1.1.RELEASE</version>
</dependency>

啓動類開啓@EnableGlobalDispose註解便可。

  1. 業務使用

接口:

@GetMapping("test")
public String test(){
  return "test";
}

返回

{
  "succ": true,             // 是否成功
  "ts": 1566386951005,      // 時間戳
  "data": "test",           // 數據
  "code": null,             // 錯誤類型
  "msg": null,              // 錯誤描述
  "fail": false             
}
  1. 忽略封裝註解:@IgnorReponseAdvice

@IgnorReponseAdvice容許範圍爲:類 + 方法,標識在類上這個類下的說有方法的返回都將忽略返回封裝。

接口:

@IgnorReponseAdvice // 忽略數據包裝 可添加到類、方法上
@GetMapping("test")
public String test(){
  return "test";
}

返回 test

總結

項目裏不少重複的code,咱們能夠經過必定的方式去簡化,以達到必定目的減小開發量。

示例代碼地址:unified-dispose-springboot

做者GitHub:
Purgeyao 歡迎關注

相關文章
相關標籤/搜索