背景
作為閘道器,有些時候可能報文的結構並不符合前端或者某些服務的需求,或者因為某些原因,其他服務修改報文結構特別麻煩、或者需要修改的地方特別多,這個時候就需要走閘道器單獨轉換一次。
實現
話不多說,直接上程式碼。
首先,我們定義好配置:
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;
/**
* <p>格式</p>
* <p>新欄位:老欄位</p>
* <p>若新老欄位一致,可以只配置新欄位</p>
*/
private List<String> fields;
/**
* <p>返回體型別,預設為json </p>
* <p>可配置的型別參見{@link com.lifengdi.gateway.enums.TransformContentTypeEnum}</p>
* <p>如需自定義配置,可以繼承{@link com.lifengdi.gateway.transform.AbstractMessageTransform}類,
* 或者實現{@link com.lifengdi.gateway.transform.IMessageTransform}介面類,重寫transform方法</p>
*/
private String contentType;
private Set<String> pathList;
public Set<String> getPathList() {
if (CollectionUtils.isEmpty(pathList) && Objects.nonNull(paths)) {
setPathList(new HashSet<>(Arrays.asList(paths)));
}
return pathList;
}
}
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<MessageTransformUrl> urlList;
}
在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()
方法,主要功能就是轉換報文結構。
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);
}
然後我們再增加一個抽象類,這個類主要提供一個解耦的作用,也是為了方便後續進行擴充套件。
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型別的報文的結構轉換,如果需要其他型別的報文的同學,可以自定義開發。
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<String> 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的解析、轉換等工作。
接下來我們定義一個列舉類,方便我們去匹配對應的報文轉換實現類。
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獲取對應列舉
* <p>
* 如果contentType為空則返回預設列舉
* </p>
*
* @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呼叫。
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<String, AbstractMessageTransform> 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<MessageTransformUrl> 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<String> newResponseBody) {
if (StringUtils.isBlank(originalContent)) {
return null;
}
List<MessageTransformUrl> 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呼叫即可。呼叫程式碼如下:
content = messageTransformFactory.compareAndTransform(path, responseBody, content, charset, newResponseBody);
Git地址:https://github.com/lifengdi/spring-cloud-gateway-demo
最後
上面的只是簡單的示例,很多情況都沒有考慮進去,大家借鑑即可。