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


    背景

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

    实现

    话不多说,直接上代码。

    首先,我们定义好配置:

    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

  • 相关阅读:
    并发基础(一) 线程介绍
    java基础(九) 可变参数列表介绍
    全球 43 亿 IPv4 地址已耗尽!IPv6,刻不容缓
    IPv6,无需操作就可升级?
    为什么 HTTPS 比 HTTP 安全
    从《国产凌凌漆》看到《头号玩家》,你就能全面了解5G
    再谈 APISIX 高性能实践
    API 网关的选型和持续集成
    尹吉峰:使用 OpenResty 搭建高性能 Web 应用
    鱼和熊掌可兼得?一文看懂又拍云 SCDN
  • 原文地址:https://www.cnblogs.com/lifengdi/p/13292366.html
Copyright © 2020-2023  润新知