如何動態的處理介面的返回資料

語言: CN / TW / HK

theme: qklhk-chocolate

趁熱記錄下,給未來的自己

需求說明

業務場景:服務A對接了服務B,服務C等服務的一些介面,然後由服務A統一暴露介面給到外部使用者使用。

需求是: 1. 服務A可以動態的接入服務B/C的介面,對外暴露並無需重啟(不在本文的討論); 1. 對接的服務B/C的介面部分欄位需要過濾掉,不透出給外部使用者(如資料庫的自增ID等敏感資訊)。

image.png

思路方案

基本思路:在服務A裡對各個服務介面返回的資料進行攔截並二次加工後再返回給前端。

  1. 攔截:比較簡單,可以在服務A對其他服務介面請求的返回之後進行業務操作,也可以統一放到切面裡用 @After 註解進行操作。從 demo 的快速演示考慮,這裡選擇直接在請求的返回體直接進行業務操作。

  2. 二次加工:服務A對返回body的部分欄位過濾掉,不返回給前端。二次加工的方法有很多種,比如:

    a. 用一個 map 去接收 body,然後對這個 body map 進行遍歷,和服務A裡的 map 進行比較, 將服務A map 裡需要的 key-value,從 body map 裡遍歷取出,put 到一個新的 map,最後返回這個新的 map 給前端。

    b. 用 string 去接收 body,接收到的body是一個 json 字串,然後將 json 字串轉成特定的物件(這個物件是返回給前端的),這樣物件裡沒有定義的欄位在 json 字串轉物件的過程中就會被捨棄。

方案a有幾個缺陷:

  1. 首先,要求其他服務介面的返回必須是一個 json 型別(可用 map 接收),如果是一個 json陣列([{},{}])的話, 就無法用map接收,這樣會導致對接入服務的介面資料結構有限制,不ok;
  2. 其次,map 資料型別可能會很複雜,由於不確定 map 裡的 value的資料結構是 string,list 還是 map 等,就需要用 instanceof 對所有的資料結構進行遍歷判斷再比較賦值,很複雜,計算效率也不高。
  3. 沒有可利用的輪子,類似將物件A賦值給物件B的屬性拷貝(BeanUtils.copyProperties()),可以將mapA的 key-value 賦值給mapB

```

mapA

{ "a": "a", "b": "b", "c": "c" }

mapB

{ "a": null, "b": null, } ```

相反,方案b有一個很大的優勢:可以利用現成的序列化和反序列化工具(如Gson)來實現我們的需求。先放一個反序列化的工具,後面會用到:

``` /* * Json字串轉為指定的物件 * @param ret json字串 * @param clazz 指定物件的類 * @return T 指定的物件 / public class JsonUtil {

public static  <T> T jsonStr2Obj(String ret, Class<T> clazz) {
    Gson gson = new Gson();
    return gson.fromJson(ret, (Type) clazz);
}

} ```

但是說到這裡,解決的只是對介面返回body的修改,沒有體現出標題的“動態”二字。那麼如何可以動態的對返回的body資料進行過濾處理呢?用 groovy 動態載入類

具體實施

  1. 獲取介面的返回(以string型別):

ResponseEntity<String> exchange = restTemplate.getForEntity($url, String.class); String body = exchange.getBody();

  1. 通過groovy獲取動態編譯類

String clazzInString = getFromRedis($key) // 從redis獲取字串型別的java class Object obj = DynamicClassCompilerUtil.run(clazzInString)

public class DynamicClassCompilerUtil { public static Object run(String cls) { Class<?> clazz = new GroovyClassLoader().parseClass(cls); try { return clazz.newInstance(); } catch (Exception e) { log.error("parse groovy class failed: {}", e); return null; } } }

  1. 將 body 反序列化

Object ret = JsonUtil.jsonStr2Obj(body, o.getClass())

該 ret 物件即為過濾後的物件,可以加工後返回給前端。

至此,“對接的服務B/C的介面部分欄位需要過濾掉,不透出給外部使用者(如資料庫的自增ID等敏感資訊)” 需求實現了。

至於 “服務A可以動態的接入服務B/C的介面,對外暴露並無需重啟” 需求,有時間的話,將會另起一篇來講。

以上。