背景

作为网关,有些时候可能报文的结构并不符合前端或者某些服务的需求,或者因为某些原因,其他服务修改报文结构特别麻烦、或者需要修改的地方特别多,这个时候就需要走网关单独转换一次。

实现

话不多说,直接上代码。

首先,我们定义好配置:

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()方法,使用objectMapperJsonNodeObjectNode来实现对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

最后

上面的只是简单的示例,很多情况都没有考虑进去,大家借鉴即可。

原文地址:https://www.lifengdi.com/archives/article/2006

从零搭建Spring Cloud Gateway网关(三)——报文结构转换的更多相关文章

  1. 从零搭建Spring Cloud Gateway网关(一)

    新建Spring Boot项目 怎么新建Spring Boot项目这里不再具体赘述,不会的可以翻看下之前的博客或者直接百度.这里直接贴出对应的pom文件. pom依赖如下: <?xml vers ...

  2. 从零搭建Spring Cloud Gateway网关(二)—— 打印请求响应日志

    作为网关,日志记录是必不可少的功能,可以在网关出增加requestId来查询整个请求链的调用执行情况等等. 打印请求日志 打印请求日志最重要的就是打印请求参数这些东西,不过RequestBody通常情 ...

  3. Spring Cloud Gateway(三):网关处理器

    1.Spring Cloud Gateway 源码解析概述 API网关作为后端服务的统一入口,可提供请求路由.协议转换.安全认证.服务鉴权.流量控制.日志监控等服务.那么当请求到达网关时,网关都做了哪 ...

  4. Spring Cloud gateway 网关服务二 断言、过滤器

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  5. Spring Cloud gateway 网关四 动态路由

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  6. Spring Cloud实战 | 第十一篇:Spring Cloud Gateway 网关实现对RESTful接口权限控制和按钮权限控制

    一. 前言 hi,大家好,这应该是农历年前的关于开源项目 的最后一篇文章了. 有来商城 是基于 Spring Cloud OAuth2 + Spring Cloud Gateway + JWT实现的统 ...

  7. .net core下,Ocelot网关与Spring Cloud Gateway网关的对比测试

    有感于 myzony 发布的 针对 Ocelot 网关的性能测试 ,并且公司下一步也需要对.net和java的应用做一定的整合,于是对Ocelot网关.Spring Cloud Gateway网关做个 ...

  8. 网关服务Spring Cloud Gateway(三)

    上篇文章介绍了 Gataway 和注册中心的使用,以及 Gataway 中 Filter 的基本使用,这篇文章我们将继续介绍 Filter 的一些常用功能. 修改请求路径的过滤器 StripPrefi ...

  9. 微服务架构spring cloud - gateway网关限流

    1.算法 在高并发的应用中,限流是一个绕不开的话题.限流可以保障我们的 API 服务对所有用户的可用性,也可以防止网络攻击. 一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池.线程池). ...

随机推荐

  1. java特性 JDK JRE JVM

    1简单性 2可移植性性(跨平台) 3面向对象 4高性能 5分布式 6动态性 7多线程 8安全性JDK:java开发工具 . JRE:JDK:java运行环境 . JVM:JDK:java虚拟机

  2. Adobe Photoshop CC 2019 下载+安装教程

    1. 安装包 链接: https://pan.baidu.com/s/1_w1SjGVjWNJ9nuTqEcaykg 提取码: xatq 2. 打开安装包 运行Set-up,选择语言,位置 ,选择继续 ...

  3. 《Java并发编程的艺术》第10章 Executor框架

    Java的线程既是工作单元,也是执行机制.从JDK5开始,把工作单元与执行机制分离开来.工作单元包括Runnable和Callable,执行机制由Executor框架提供. 10.1 Executor ...

  4. SSH网上商城四

    第29课:10-SSH网上商城:购物模块的实体的封装 1.现在我们要实现购物车的模块,当用户在点击 加入购物车按钮的时候需要跳转到 上面我们需要对购物车的对象进行封装 上面一个商品就对应一个记录项,购 ...

  5. 前后端分层架构MVC&MVVM

    早期 特点 页面由 JSP.PHP 等工程师在服务端生成 JSP 里揉杂大量业务代码 浏览器负责展现,服务端给什么就展现什么,展现的控制在 Web Server 层 优点 简单明快,本地起一个 Tom ...

  6. Spring Boot是什么?

    背景 最近因公司需要,开始研究java相关的开发,之前一直从事.net相关开发,所以写的或者理解的不对的地方呢,希望大家批评指正. 首先开发框架吧,就像.net很早之前有asp.net webForm ...

  7. max depth exceeded when dereferencing c0-param0问题的解决

    在做项目的时候,用到了dwr,有一次居然报错,错误是 max depth exceeded when dereferencing c0-param0 上网查了一下,我居然传参数的时候传的是object ...

  8. for循环里的break,continue和return有什么差别

    break: 此语句导致程序终止包含它的循环,并进行程序的下一阶段(整个循环后面的语句),即,不是跳到下一个循环周期而是退出循环.如果break语句包含在嵌套循环里,它只跳出最里面的循环. 如下代码 ...

  9. 洛谷 P1347 【排序】

    这篇题解没有用拓补排序 (嗐 菜就直说) 个人感觉这道题拓补排序没有变种\(Floyd\)好写吧,思维难度也低一点(亲眼目睹机房dalao这道题拓补排序调了很久). 吐槽结束,开始正题~ 对于这道题为 ...

  10. (私人收藏)商务工作学习万能简约大气PPT模板

    商务工作学习万能简约大气PPT模板 https://pan.baidu.com/s/1aPnPZ285N5VSSErro1cPngehoa