從零搭建Spring Cloud Gateway閘道器(三)——報文結構轉換
阿新 • • 發佈:2020-07-13
## 背景
作為閘道器,有些時候可能報文的結構並不符合前端或者某些服務的需求,或者因為某些原因,其他服務修改報文結構特別麻煩、或者需要修改的地方特別多,這個時候就需要走閘道器單獨轉換一次。
## 實現
話不多說,直接上程式碼。
首先,我們定義好配置:
```java
package com.lifengdi.gateway.properties.entity;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.util.*;
/**
* 需要轉換報文結構的URL地址配置類
*
* @author: Li Fengdi
* @date: 2020-7-11 16:57:07
*/
@Data
public class MessageTransformUrl {
// 介面地址,多個地址使用英文逗號分隔
private String[] paths;
/**
* fields;
/**
*
*/
private String contentType;
private Set pathList;
public Set getPathList() {
if (CollectionUtils.isEmpty(pathList) && Objects.nonNull(paths)) {
setPathList(new HashSet<>(Arrays.asList(paths)));
}
return pathList;
}
}
```
```java
package com.lifengdi.gateway.properties;
import com.lifengdi.gateway.properties.entity.MessageTransformUrl;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 報文結構轉換引數配置
* @author: Li Fengdi
* @date: 2020-7-11 16:55:53
*/
@Component
@ConfigurationProperties(prefix = "trans")
@Data
public class MessageTransformProperties {
private List urlList;
}
```
在yaml檔案中的配置如下:
```yaml
# 報文轉換配置
trans:
url-list:
- paths: /jar/api/cockpit
content-type: application/json
fields:
# 新欄位:老欄位,若新老欄位一致,可以只配置新欄位
- code:rs
- msg:rsdesp
- data:resultMessage
- paths: /war/api/delivertool
fields:
- code:rs
- msg:rsdesp
- data:resultMessage
```
這裡呢,大家也可以根據需要,放入資料庫或者其他可以動態修改的地方,這裡只是圖方便,所以直接放在yaml檔案中。
其次我們定義一個報文轉換介面類,方便後續的擴充套件。這個介面很簡單,只有一個`transform()`方法,主要功能就是轉換報文結構。
```java
package com.lifengdi.gateway.transform;
import com.lifengdi.gateway.properties.entity.MessageTransformUrl;
/**
* 報文結構轉換介面
*
* @author: Li Fengdi
* @date: 2020-7-11 16:57:07
*/
public interface IMessageTransform {
/**
* 轉換報文結構
*
* @param originalContent 需要轉換的原始內容
* @param transformUrl MessageTransformUrl
* @return 轉換後的結構
*/
String transform(String originalContent, MessageTransformUrl transformUrl);
}
```
然後我們再增加一個抽象類,這個類主要提供一個解耦的作用,也是為了方便後續進行擴充套件。
```java
package com.lifengdi.gateway.transform;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.annotation.Resource;
/**
* 報文轉換抽象類
*
* @author: Li Fengdi
* @date: 2020-7-11 16:57:07
*/
public abstract class AbstractMessageTransform implements IMessageTransform {
@Resource
protected ObjectMapper objectMapper;
/**
* ResponseResult轉JSON
*
* @param object 需要轉換為json的物件
* @return JSON字串
*/
public String toJsonString(Object object) throws JsonProcessingException {
return objectMapper.writeValueAsString(object);
}
}
```
這個類非常簡單,只有一個`toJsonString()`方法,主要作用就是將物件轉換成json字串。
接著我們繼續來寫一個實現類,主要功能就是實現JSON型別的報文的結構轉換,如果需要其他型別的報文的同學,可以自定義開發。
```java
package com.lifengdi.gateway.transform.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.lifengdi.gateway.properties.entity.MessageTransformUrl;
import com.lifengdi.gateway.transform.AbstractMessageTransform;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* application/json型別轉換實現類
* @author: Li Fengdi
* @date: 2020-7-11 16:57:07
*/
@Service
@Slf4j
public class JsonMessageTransformImpl extends AbstractMessageTransform {
@Override
public String transform(String originalContent, MessageTransformUrl transformUrl) {
if (StringUtils.isBlank(originalContent)) {
return originalContent;
}
try {
// 原始報文轉換為JsonNode
JsonNode jsonNode = objectMapper.readTree(originalContent);
List fields = transformUrl.getFields();
// 建立新的JSON物件
ObjectNode rootNode = objectMapper.createObjectNode();
fields.forEach(field -> {
String[] fieldArray = field.split(":");
String newFiled = fieldArray[0];
String oldField = fieldArray.length > 1 ? fieldArray[1] : newFiled;
if (jsonNode.has(oldField)) {
rootNode.set(newFiled, jsonNode.get(oldField));
}
});
return toJsonString(rootNode);
} catch (JsonProcessingException e) {
log.error("application/json型別轉換異常,originalContent:{},transformUrl:{}", originalContent, transformUrl);
return originalContent;
}
}
}
```
這個類繼承了`AbstractMessageTransform`這個類,重寫了`transform()`方法,使用`objectMapper`、`JsonNode`、`ObjectNode`來實現對JSON的解析、轉換等工作。
接下來我們定義一個列舉類,方便我們去匹配對應的報文轉換實現類。
```java
package com.lifengdi.gateway.enums;
import lombok.Getter;
import org.apache.commons.lang.StringUtils;
import org.springframework.lang.Nullable;
/**
* 報文結構轉換轉換型別列舉類
*
* @author: Li Fengdi
* @date: 2020-7-11 16:57:07
*/
@Getter
public enum TransformContentTypeEnum {
DEFAULT(null, "jsonMessageTransformImpl")
, APPLICATION_JSON("application/json", "jsonMessageTransformImpl")
;
/**
* 內容型別
*/
private String contentType;
/**
* 報文轉換結構實現類
*/
private String transImpl;
TransformContentTypeEnum(String contentType, String transImpl) {
this.contentType = contentType;
this.transImpl = transImpl;
}
/**
* 根據contentType獲取對應列舉
*
*
* @param contentType contentType
* @return TransformContentTypeEnum
*/
public static TransformContentTypeEnum getWithDefault(@Nullable String contentType) {
if (StringUtils.isNotBlank(contentType)) {
for (TransformContentTypeEnum transformContentTypeEnum : values()) {
if (contentType.equals(transformContentTypeEnum.contentType)) {
return transformContentTypeEnum;
}
}
}
return DEFAULT;
}
}
```
這個類也很簡單,定義列舉,然後一個靜態方法,靜態方法的作用是根據響應頭中的`contentType`來獲取對應的報文結構轉換實現類,如果獲取不到,則會返回一個預設的實現類,我這裡定義的預設的實現類就是我們上邊寫的`JsonMessageTransformImpl`類。
最後呢,我們定義一個工廠類,供我們的Filter呼叫。
```java
package com.lifengdi.gateway.transform;
import com.lifengdi.gateway.enums.TransformContentTypeEnum;
import com.lifengdi.gateway.properties.MessageTransformProperties;
import com.lifengdi.gateway.properties.entity.MessageTransformUrl;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* 報文結構轉換工廠類
*
* @author: Li Fengdi
* @date: 2020-7-11 16:57:07
*/
@Component
public class MessageTransformFactory {
@Resource
private Map messageTransformMap;
@Resource
private MessageTransformProperties messageTransformProperties;
/**
* 根據contentType獲取對應的內容轉換實現類
*
* @param contentType 內容型別
* @return 內容轉換實現類
*/
private AbstractMessageTransform getMessageTransform(String contentType) {
return messageTransformMap.get(TransformContentTypeEnum.getWithDefault(contentType).getTransImpl());
}
/**
* 報文轉換
*
* @param originalContent 原始內容
* @param transformUrl url
* @return 轉換後的訊息
*/
private String messageTransform(String originalContent, MessageTransformUrl transformUrl) {
String contentType = transformUrl.getContentType();
AbstractMessageTransform messageTransform = getMessageTransform(contentType);
return messageTransform.transform(originalContent, transformUrl);
}
/**
* 判斷是否是需要轉換報文結構的介面,如果是則轉換,否則返回原值
*
* @param path 介面路徑
* @param originalContent 原始內容
* @return 轉換後的內容
*/
public String compareAndTransform(String path, String originalContent) {
if (StringUtils.isBlank(originalContent)) {
return null;
}
List urlList = messageTransformProperties.getUrlList();
if (CollectionUtils.isEmpty(urlList)) {
return originalContent;
}
return urlList .stream()
.filter(transformUrl -> transformUrl.getPathList().contains(path))
.findFirst()
.map(url -> messageTransform(originalContent, url))
.orElse(originalContent);
}
/**
* 判斷是否是需要轉換報文結構的介面,如果是則轉換,否則返回原值
*
* @param path 介面路徑
* @param originalContent 原始內容
* @param originalByteArray 二進位制原始內容
* @param charset charset
* @param newResponseBody 新報文內容
* @return 響應體陣列陣列
*/
public byte[] compareAndTransform(String path, String originalContent, byte[] originalByteArray, Charset charset,
AtomicReference newResponseBody) {
if (StringUtils.isBlank(originalContent)) {
return null;
}
List urlList = messageTransformProperties.getUrlList();
if (CollectionUtils.isEmpty(urlList)) {
return originalByteArray;
}
return urlList.stream()
.filter(transformUrl -> transformUrl.getPathList().contains(path))
.findFirst()
.map(url -> {
String messageTransform = messageTransform(originalContent, url);
if (originalContent.equals(messageTransform)) {
return originalByteArray;
}
newResponseBody.set(messageTransform);
return messageTransform.getBytes(charset);
})
.orElse(originalByteArray);
}
}
```
這個工廠對外提供的方法只有`compareAndTransform()`兩個方法,主要功能就是判斷是否是需要轉換報文結構的介面,如果是則轉換,否則返回原值。
接下來就是在我們的Filter呼叫即可。呼叫程式碼如下:
```java
content = messageTransformFactory.compareAndTransform(path, responseBody, content, charset, newResponseBody);
```
Git地址:[https://github.com/lifengdi/spring-cloud-gateway-demo](https://github.com/lifengdi/spring-cloud-gateway-demo)
## 最後
上面的只是簡單的示例,很多情況都沒有考慮進去,大家借鑑即可。
原文地址:[https://www.lifengdi.com/archives/article/2006](https://www.lifengdi.com/archives/artic
格式
*新欄位:老欄位
*若新老欄位一致,可以只配置新欄位
*/ private List返回體型別,預設為json
*可配置的型別參見{@link com.lifengdi.gateway.enums.TransformContentTypeEnum}
*如需自定義配置,可以繼承{@link com.lifengdi.gateway.transform.AbstractMessageTransform}類, * 或者實現{@link com.lifengdi.gateway.transform.IMessageTransform}介面類,重寫transform方法
* 如果contentType為空則返回預設列舉 *