作为网关,有些时候可能报文的结构并不符合前端或者某些服务的需求,或者因为某些原因,其他服务修改报文结构特别麻烦、或者需要修改的地方特别多,这个时候就需要走网关单独转换一次。
话不多说,直接上代码。
首先,我们定义好配置:
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()方法,主要功能就是转换报文结构。
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字符串。
toJsonString()
接着我们继续来写一个实现类,主要功能就是实现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的解析、转换等工作。
AbstractMessageTransform
objectMapper
JsonNode
ObjectNode
接下来我们定义一个枚举类,方便我们去匹配对应的报文转换实现类。
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类。
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()两个方法,主要功能就是判断是否是需要转换报文结构的接口,如果是则转换,否则返回原值。
compareAndTransform()
接下来就是在我们的Filter调用即可。调用代码如下:
content = messageTransformFactory.compareAndTransform(path, responseBody, content, charset, newResponseBody);
Git地址:https://github.com/lifengdi/spring-cloud-gateway-demo
原文链接:https://www.cnblogs.com/lifengdi/p/13292366.html