gateway网关聚合knife4j文档,同时兼容swagger2与swagger3

news2024/12/25 22:54:24

基于前两篇文章,进行整合

springcloud-gateway 聚合swagger3请求接口丢失appliactionName解决

springcloud-gateway聚合knife4j接口文档

为何要兼容?微服务开发者有的使用了swagger2版本,有的使用了swagger3版本,但暴露外部给前端使用的,均统一走网关处

兼容核心点:swagger3 返回缺少bathPath

网关配置

image-20221201105700475

@Data
@ConfigurationProperties(prefix = MyGateWayProperties.PREFIX)
public class MyGateWayProperties {

    public static final String PREFIX = "gateway";

    /**
     * swagger3 服务列表
     */
    private List<String> swagger3Set;

    /**
     * 请求前缀
     */
    private String apiPrefix;
}

请求文档的SwaggerResourcesProvider 进行调整,带上自定义网关请求前缀与 swagger文档后缀

@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {

    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;

    @Autowired(required = false)
    private UiConfiguration uiConfiguration;

    private final SwaggerResourcesProvider provider;

    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider provider) {
        this.provider = provider;
    }

    @GetMapping("/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()),
                HttpStatus.OK));
    }

    @GetMapping("/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping
    public Mono<ResponseEntity<List<SwaggerResource>>> swaggerResources() {
        return Mono.just((new ResponseEntity<>(provider.get(), HttpStatus.OK)));
    }
}
/**
 * @author lei
 * @create 2022-05-25 10:01
 * @desc 网关处获取所有服务swagger文档
 **/
@Slf4j
@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {

    /**
     * swagger2默认的url后缀
     */
    public static final String V3_API_DOCS = "/v3/api-docs";
    public static final String V2_API_DOCS = "/v2/api-docs";


    /**
     * 路由加载器
     */
    private final RouteLocator routeLocator;
    private final MyGateWayProperties myGateWayProperties;

    /**
     * 网关应用名称
     */
    @Value("${spring.application.name}")
    private String gateway;

    @Autowired
    public SwaggerProvider(RouteLocator routeLocator, MyGateWayProperties myGateWayProperties) {
        this.routeLocator = routeLocator;
        this.myGateWayProperties = myGateWayProperties;
    }

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routeHosts = new ArrayList<>();
        Flux<Route> routes = routeLocator.getRoutes();
        // 获取所有可用的host,因为是注册表服务调用,所以实际是服务名
        routes.filter(route -> route.getUri().getHost() != null)
                .filter(route -> !gateway.equals(route.getUri().getHost()))
                .subscribe(route -> routeHosts.add(route.getUri().getHost()));

        // 去重下拉列表相同服务名文档,多个服务展示一个,当网关调用这个接口时,会自动通过负载均衡寻找服务host
        Set<String> docServerSet = new HashSet<>();
        routeHosts.forEach(instance -> {
            String url;
            if (myGateWayProperties.getSwagger3Set().contains(instance)) {
                // 拼接url. /serviceId/v3/api-docs
                url = myGateWayProperties.getApiPrefix() + "/" + instance.toLowerCase() + V3_API_DOCS;
            } else {
                // 拼接url. /serviceId/v2/api-docs
                url = myGateWayProperties.getApiPrefix() + "/" + instance.toLowerCase() + V2_API_DOCS;
            }
            if (!docServerSet.contains(url)) {
                docServerSet.add(url);
                SwaggerResource swaggerResource = new SwaggerResource();
                swaggerResource.setUrl(url);
                swaggerResource.setName(instance);
                resources.add(swaggerResource);
            }
        });
        return resources;
    }
}

网关添加全局过滤器,发现以访问swagger3后缀的url则重新返回体,携带上bathPath

/**
 * @author lei
 * @create 2022-05-26 14:36
 * @desc swagger过滤器
 * 解决swagger3 响应缺少bathPath,请求无法动态路由到服务的问题
 **/
@Log4j2
@Component
public class SwaggerGlobalFilter implements GlobalFilter, Ordered {

    public static final String BASE_PATH = "basePath";

    private final MyGateWayProperties myGateWayProperties;

    public SwaggerGlobalFilter(MyGateWayProperties myGateWayProperties) {
        this.myGateWayProperties = myGateWayProperties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().toString();
        if (!path.endsWith(SwaggerProvider.V3_API_DOCS)) {
            return chain.filter(exchange);
        }
        path = path.replace(myGateWayProperties.getApiPrefix(), "");
        String[] pathArray = path.split("/");
        String basePath = pathArray[1];
        ServerHttpResponse originalResponse = exchange.getResponse();
        ServerHttpResponseDecorator newResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (!Objects.equals(getStatusCode(), HttpStatus.OK)) {
                    return super.writeWith(body);
                }
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        List<String> list = new ArrayList<>();
                        dataBuffers.forEach(dataBuffer -> {
                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            DataBufferUtils.release(dataBuffer);
                            list.add(new String(content, StandardCharsets.UTF_8));
                        });
                        String jsonStr = listToString(list);
                        JSONObject jsonObject = JSON.parseObject(jsonStr);
                        jsonObject.put(BASE_PATH, myGateWayProperties.getApiPrefix() + "/" + basePath);
                        jsonStr = jsonObject.toString();
                        return bufferFactory().wrap(jsonStr.getBytes());
                    }));
                }

                return super.writeWith(body);
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                //由于修改了请求体的body,导致content-length长度不确定,因此使用分块编码
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                return httpHeaders;
            }

            private String listToString(List<String> list) {
                StringBuilder stringBuilder = new StringBuilder();
                for (String s : list) {
                    stringBuilder.append(s);
                }
                return stringBuilder.toString();
            }
        };
        return chain.filter(exchange.mutate().response(newResponse).build());
    }


    @Override
    public int getOrder() {
        return -2;
    }
}

网关获取到文档访问列表

image-20221201110418429

访问具体服务的文档,触发全局过滤器

image-20221201110720238

改写响应数据,访问swagger3文档,带上了bathPath

image-20221201110923634

image-20221201111821666

访问swagger2文档时,不受影响

image-20221201111130320

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/51130.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊一聊我的第一个开源项目

项目地址&#xff1a;https://github.com/kpretty/hdd 我在21年的国庆写过一篇文章&#xff1a;《Docker 实战&#xff1a;部署hadoop集群》&#xff0c;当时也是刚接触docker&#xff0c;作为docker第一个练手项目对很多概念理解的不是很到位&#xff0c;因此那篇文章所使用的…

基于PHP+MySQL菜品食谱美食网站的设计与实现

美食是人类永恒的追求,现在有很多的美食爱好者,他们希望通过自己的各种方式来学习更多的美食制作方式,以及分享自己制作美食的一些过程,说让更多的人。享受到更加美味可口的饭菜。本系统也是基于这样的目的来进行开发的。 本系统是通过PHP&#xff1a;MySQL来进行开发,主要实现…

存储器扩展,画图题

目录 存储器与CPU的接口 地址线的连接 数据线的连接 控制线的连接&#xff08;读写和片选&#xff09; 考题 引出 第一题 第二题 第三题 计算地址范围&#xff08;这里用的38译码器&#xff09; 第四题 填空题 第五题 第六题&#xff08;2017&#xff09; 要求&…

【微信小程序】CSS模块化、使用缓存在本地模拟服务器数据库

&#x1f3c6;今日学习目标&#xff1a;第十五期——CSS模块化、使用缓存在本地模拟服务器数据库 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;25分钟 &#x1f389;专栏系列&#xff1a;我的第一个微信小程序 文…

【这款神器可以有】3DMAX一键墙体门洞窗洞插件使用教程

3DMAX一键墙体门洞窗洞插件&#xff0c;只需导入户型图&#xff0c;单/双面墙体一键生成。 【主要功能】 --一键生成墙体 --一键门洞 --一键窗洞 --支持单/双面墙体生成 【安装方法】 无需安装&#xff0c;直接拖动插件脚本到3dmax窗口即可打开插件。 【快速开始】 将3dm…

11.我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭,异常关闭,半关闭场景

我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭&#xff0c;异常关闭&#xff0c;半关闭场景 本系列Netty源码解析文章基于 4.1.56.Final版本 写在前面..... 本文是笔者肉眼盯 Bug 系列的第三弹&#xff0c;前两弹分别是: 抓到Netty一个Bug&#xff0c;顺带来…

【Spring(七)】带你手写一个Spring容器

有关Spring的所有文章都收录于我的专栏&#xff1a;&#x1f449;Spring&#x1f448; 目录 前置准备 第一步、创建我们自定的注解 第二步、创建我们自己的容器类 测试 总结 相关文章 【Spring&#xff08;一&#xff09;】如何获取对象&#xff08;Bean&#xff09;【Spring&a…

CSS伪类使用详解

基本描述 CSS伪类是很常用的功能&#xff0c;主要应用于选择器的关键字&#xff0c;用来改变被选择元素的特殊状态下的样式。 伪类类似于普通CSS类的用法&#xff0c;是对CSS选择器的一种扩展&#xff0c;增强选择器的功能。 目前可用的伪类有大概40多个&#xff0c;少部分有兼…

Spring Bean的生命周期理解

一、Spring Bean的生命周期大的概括起来有四个阶段&#xff1a; 1、实例化 2、属性填充注入 3、初始化使用 4、Bean的销毁 二、如流程图所示 三、步骤说明 1、实例化 实例化一个Bean&#xff0c;即new 2、IOC依赖注入 按照Spring上下文对实例化的Bean进行属性填充注入 3、setB…

昆船智能上市:预计年营收19亿到22.5亿 市值48亿

雷递网 雷建平 11月30日昆船智能技术股份有限公司&#xff08;简称&#xff1a;“昆船智能”&#xff0c;证券代码&#xff1a;301311&#xff09;今日在深交所创业板上市。昆船智能本次发行股票6000万股&#xff0c;发行价为13.88元&#xff0c;募资8.33亿元。昆船智能开盘价为…

2022CTF培训(七)逆向专项练习

附件下载链接 babyre 首先是一个迷宫&#xff0c;由于答案不唯一&#xff0c;因此到 dfs 求出所有路径。 #include <bits/stdc.h>constexpr char s[] "**************.****.**s..*..******.****.***********..***..**..#*..***..***.********************.**..*…

springMVC01,springMVC的执行流程【第一个springMVC例子(XML配置版本):HelloWorld】

springMVC01,springMVC的执行流程【第一个springMVC项目&#xff1a;HelloWorld】springMVC的简介springMVC的执行流程第一个springMVC项目&#xff08;XML配置版本&#xff09;1.创建项目1.1 新建maven项目&#xff1a;1.2 添加web支持1.3 在pom.xml中导入依赖1.4 配置tomcat2…

【云享·人物】华为云AI高级专家白小龙:AI如何释放应用生产力,向AI工程化前行?

摘要&#xff1a;AI技术发展&#xff0c;正由应用落地阶段向效率化生产阶段演进&#xff0c;AI工程化能力将会不断深入业务&#xff0c;释放企业生产力。本文分享自华为云社区《【云享人物】华为云AI高级专家白小龙&#xff1a;AI如何释放应用生产力&#xff0c;向AI工程化前行…

[附源码]Python计算机毕业设计Django飞越青少儿兴趣培训机构管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

旅游景区地图导览系统,传统导览智慧新升级

地图在景区导览中一直扮演着重要角色。 从传统导览的纸质地图&#xff0c;再到智慧导览的电子地图&#xff0c;游客都可以从景区地图上了解到景点名称、游玩路线、服务设施等内容&#xff0c;帮助游客更好地游览景区。 相比传统的纸质地图导览&#xff0c;电子地图导览系统有哪…

计算机组成原理习题课第四章-4(唐朔飞)

计算机组成原理习题课第四章-4&#xff08;唐朔飞&#xff09; ✨欢迎关注&#x1f5b1;点赞&#x1f380;收藏⭐留言✒ &#x1f52e;本文由京与旧铺原创&#xff0c;csdn首发&#xff01; &#x1f618;系列专栏&#xff1a;java学习 &#x1f4bb;首发时间&#xff1a;&…

TIA博途中通用函数库指令FIFO先入先出的具体使用方法

TIA博途中通用函数库指令FIFO先入先出的具体使用方法 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 如下图所示,在TIA博途中添加通用函数库指令,然后在库指令中找到FIFO,鼠标直接拖拽到程序段中,系统会自动生成一…

【毕业设计】10-基于单片机的车站安检门_磁性霍尔传感器系统设计(原理图+源码+仿真工程+答辩论文)

【毕业设计】10-基于单片机的车站安检门/磁性霍尔传感器系统设计&#xff08;原理图源码仿真工程答辩论文&#xff09; 文章目录【毕业设计】10-基于单片机的车站安检门/磁性霍尔传感器系统设计&#xff08;原理图源码仿真工程答辩论文&#xff09;任务书设计说明书摘要设计框架…

https加密解密过程二、名词解析及文件生成

https加密解密过程二、名词解析及文件生成 密钥仓库keystore文件 Keytool是一个Java数据证书的管理工具 &#xff0c;Keytool将密钥(key)和证书(certificates)存在一个称为keystore的文件中 keystore文件的内容其实就是把私钥、公钥以及公钥对应的地址等信息输出为json格式的…

git的基础操作

git的基础操作 一、Git理论 &#xff08;一&#xff09;工作区域 基本概念&#xff1a; 工作区&#xff1a;平时存放项目代码的地方。 暂存区(Stage/Index)&#xff1a;暂存区&#xff0c;用于临时存放你的改动&#xff0c;事实上它只是一个文件&#xff0c;保存即将提交到…