李锋镝的博客

  • 首页
  • 时间轴
  • 说说
  • 每日心情
  • Now
  • 系列文章
  • AI工具集
  • 论坛
  • 左邻右舍
    • 左邻右舍
    • 朋友圈
    • 游客中心
  • 留言
    • 留言
    • 走心评论
  • 关于
    • 关于本站
    • 网站地图
    • 网站统计
    • 另一个网站
    • 赞助
  • 星球穿梭
  • 🚇开往
白雪成红
惟坚韧者始能遂其志
  1. 首页
  2. 原创
  3. 正文

从零搭建Spring Cloud Gateway网关(三)——报文结构转换

2020年7月13日 约 1,757 字6 分钟 194 1 0
本文最后更新于 2020年7月13日,距今已 2276 天,其中的信息可能已经发生变化,请注意甄别。

背景

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

实现

话不多说,直接上代码。

首先,我们定义好配置:

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

最后

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

参考链接

1
  1. 1https://github.com/lifengdi/spring-cloud-gateway-demogithub.com
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
分享到

从零搭建Spring Cloud Gateway网关(三)——报文结构转换

也可使用浏览器菜单中的「分享」功能

微信扫一扫分享

标签: JAVA Spring Cloud Spring Cloud Gateway SpringBoot
最后更新:2020年7月13日
相关文章
  • SpringBoot 实现 RSA+AES 自动接口解密2025年5月26日
  • 解决Intellij IDEA运行报Command line is too long的问题2021年2月18日
  • SpringBoot优雅关闭应用2025年3月2日
  • 关闭apache httpclient4.5 DEBUG日志2019年7月9日
  • 记一次spring-cloud-netflix-core引发的内存溢出分析2021年12月7日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >
1234567891112131415161718192021222324252627282930313233343536373839404142434446474849505152535455575859606162636465666769727476777879808182858687909293949596979899
取消回复
…

文章评论

还没有评论,快来抢沙发吧~

尘世难逢开口笑,菊花须插满头归。

听点儿音乐吧 朋友~
文章目录
最新 热点 随机
最新 热点 随机
Kratos-plus v1.1.26版本更新说明 我觉得是时候公布我的导航网站了 代码是 AI 写的,生产事故谁背锅? 给主题增加了游客中心功能 祝大家中秋安康 游本昌去世
给主题增加了游客中心功能游本昌去世关于主题加载速度优化的一点儿小演进我觉得是时候公布我的导航网站了给主题增加了Now、每日心情、年度回顾、岁月同一天、随机漫步等功能WordPress缓存插件WP Fastest Cache、WP Rocket 、FlyingPress对比
不慌不忙的坚强(林徽因39段最美文字!) 我的第一个WordPress插件:Dylan Custom Plugin上线了 MySQL 的自增 ID 用完了,怎么办? 因在公司上不正经网站,我没过试用期… AI 编程高效提示词模板库(2025 实战版) MySQL数据库详解——执行SQL更新时,其底层经历了哪些操作?
最近评论
热衷于 发布于 10 小时前(10月06日) AI 喜欢讨好人是一大弊病, 盲目地信任 AI 才是罪魁祸首 我问 AI "你确定吗?&...
响石潭 发布于 10 小时前(10月06日) 哈哈哈,不错,游客中心,再加上导游功能,就可以按照导游,到博客里面畅游一番
fairy 发布于 5 天前(10月02日) 很明显,只能处罚人,当然是人背锅。你能罚AI钱吗?能把AI辞退吗?很明显不能。
彬红茶 发布于 5 天前(10月02日) 很全面哦~国庆快乐
彬红茶 发布于 5 天前(10月01日) 确实可以加一下这个功能,我最近就改了,评论区加了个等级,游客中心实属无能为力 :78:
标签聚合
Redis Claude 分布式 SpringBoot MySQL 日常 多线程 JAVA Theme Spring SQL K8s AI JVM 数据库 ElasticSearch WordPress 架构 AI编程 IDEA
友情链接
  • 瓦匠个人小站
  • 懋和道人
  • Serendipity
  • 若梦博客
  • lijie blog
  • 皮皮社
  • Honesty
  • sssr7844的博客
  • 蜗牛工作室
  • 知向前端
  • 老张博客
  • 临窗旋墨
  • 韩小韩博客
  • 九仞之行
  • Mr.Sun的博客
  • 彬红茶日记
  • 韩情脉脉
  • 搬砖日记
  • 志文工作室
  • 哥斯拉

COPYRIGHT © 2016-2026 lifengdi.com. ALL RIGHTS RESERVED.

lifengdi.com

Domain age badge for lifengdi.com

Theme Kratos-plus By Dylan Li

津ICP备2024022503号-3

京公网安备11011502039375号