李锋镝的博客 - LiFengdi.Com

  • 首页
  • 时间轴
  • 留言
  • 左邻右舍
  • 我的日常
  • 关于我
青衿之志 履践致远
霁月光风 不萦于怀
  1. 首页
  2. 原创
  3. 正文

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

2020年7月13日 14156点热度 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

最后

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

除非注明,否则均为李锋镝的博客 - LiFengdi.Com原创文章,转载必须以链接形式标明本文链接
本文链接:https://www.lifengdi.com/archives/article/2006
本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: JAVA Spring Cloud Spring Cloud Gateway SpringBoot
最后更新:2020年7月13日

李锋镝

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

打赏 点赞
< 上一篇
下一篇 >
guest
您的姓名(必填)
您的邮箱(必填)
您的站点
guest
您的姓名(必填)
您的邮箱(必填)
您的站点
0 评论
Inline Feedbacks
查看所有评论
支付宝红包

三十功名尘与土,八千里路云和月。

最新 热点 随机
最新 热点 随机
回忆是一条没有尽头的路 这样的日子什么时候才是个头 MySQL 中的 distinct 和 group by 哪个效率更高? 开工啦~ 今晚,回家过年! 图数据库选型:Neo4j、Janus、HugeGraph
看病难~取药难~~阳了...开工啦~RocketMQ的push消费方式实现详解国庆节过的也很累~~MybatisCodeHelperPro激活
Java中的字符和字符串 张爱玲——《红玫瑰与白玫瑰》 彻底搞懂mysql日志系统binlog,redolog,undolog 简单易做的早餐(持续更新ing) 如何高效的学习技术? SVN服务端和客户端的安装以及在MyEclipse中的配置
最近评论
李锋镝 发布于 2 周前(03月10日) 已添加~欢迎回访喔
博客录(boke.lu) 发布于 2 周前(03月10日) 已添加贵站0.0 名称:博客录(boke.lu) 简介:boke.lu · 博客收录展示平台~ 链接...
李锋镝 发布于 3 周前(03月05日) 系统版本是win11吗?
HJQ 发布于 4 周前(02月28日) 同问,注册表都没有楼主说的值。
林羽凡 发布于 1 个月前(02月16日) 开工大吉。
有情链接
  • 志文工作室
  • 临窗旋墨
  • 旧时繁华
  • 城南旧事
  • 强仔博客
  • 林三随笔
  • 徐艺扬的博客
  • 云辰博客
  • 韩小韩博客
  • 知向前端
  • 阿誉的博客
  • 林羽凡
  • 情侣头像
  • 哥斯拉
  • 博客录

COPYRIGHT © 2022 lifengdi.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

豫ICP备16004681号-2