李锋镝的博客

  • 首页
  • 时间轴
  • 评论区显眼包🔥
  • 左邻右舍
  • 博友圈
  • 关于我
    • 关于我
    • 另一个网站
    • 我的导航站
    • 网站地图
    • 赞助
  • 留言
  • 🚇开往
Destiny
自是人生长恨水长东
  1. 首页
  2. 原创
  3. 正文

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

2020年7月13日 228点热度 1人点赞 0条评论

背景

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

实现

话不多说,直接上代码。

首先,我们定义好配置:

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

最后

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

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/article/2006

相关文章

  • 从零搭建Spring Cloud Gateway网关(二)—— 打印请求响应日志
  • 从零搭建Spring Cloud Gateway网关(一)
  • SpringBoot常用注解
  • CompletableFuture使用详解
  • SpringBoot 中内置的 49 个常用工具类
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: JAVA Spring Cloud Spring Cloud Gateway SpringBoot
最后更新:2020年7月13日

李锋镝

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

打赏 点赞
< 上一篇
下一篇 >

文章评论

1 2 3 4 5 6 7 8 9 11 12 13 14 15 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 46 47 48 49 50 51 52 53 54 55 57 58 60 61 62 63 64 65 66 67 69 72 74 76 77 78 79 80 81 82 85 86 87 90 92 93 94 95 96 97 98 99
取消回复

愿将腰下剑,直为斩楼兰。

那年今日(04月20日)

  • 1971年:中国著名法学家周鲠生逝世
  • 1901年:著名建筑学家梁思成出生于日本东京,祖籍广东新会
  • 1889年:德国纳粹党元首希特勒出生于奥地利布劳瑙
  • 1808年:法兰西第二帝国皇帝拿破仑出生
  • 429年:中国古代数学家祖冲之出生
  • 更多历史事件
最新 热点 随机
最新 热点 随机
Everything Claude Code 详细使用文档 配置Jackson使用字段而不是getter/setter来序列化和反序列化 这个域名注册整整十年了,十年时间,真快啊 Claude Code全维度实战指南:从入门到精通,解锁AI编程新范式 Apollo配置中心中的protalDB的作用是什么 org.apache.ibatis.plugin.Interceptor类详细介绍及使用
AI时代,个人技术博客的出路在哪里?使用WireGuard在Ubuntu 24.04系统搭建VPN这个域名注册整整十年了,十年时间,真快啊WordPress实现用户评论等级排行榜插件WordPress网站换了个字体,差点儿把样式换崩了做了一个WordPress文章热力图插件
使用WireGuard在Ubuntu 24.04系统搭建VPN 为什么 Apache Doris 是比 Elasticsearch 更好的实时分析替代方案? 妹妹的画【2019.07.03】 jmap命令(jdk1.8) 我要狠狠的反驳“公司禁止使用 Lombok ”的观点! Java之五种遍历Map集合的方式
标签聚合
SpringBoot 多线程 分布式 AI docker 数据库 AI编程 ElasticSearch Redis Spring JVM 设计模式 WordPress IDEA SQL JAVA 架构 日常 MySQL K8s
友情链接
  • Blogs·CN
  • Honesty
  • Mr.Sun的博客
  • 临窗旋墨
  • 哥斯拉
  • 彬红茶日记
  • 志文工作室
  • 懋和道人
  • 拾趣博客导航
  • 搬砖日记
  • 旧时繁华
  • 林羽凡
  • 瓦匠个人小站
  • 皮皮社
  • 知向前端
  • 蜗牛工作室
  • 韩小韩博客
  • 风渡言

COPYRIGHT © 2026 lifengdi.com. ALL RIGHTS RESERVED.

域名年龄

Theme Kratos Made By Dylan

津ICP备2024022503号-3

京公网安备11011502039375号