Spring Cloud Gateway集成SpringDoc,集中管理微服务API

本文目标

Spring Cloud微服务集成SpringDoc,在Spring Cloud Gateway中统一管理微服务的API,微服务上下线时自动刷新SwaggerUi中的group组。

依赖版本

框架 版本
Spring Boot 3.1.5
Spring Cloud 2022.0.4
Spring Cloud Alibaba 2022.0.0.0
Spring Doc 2.2.0
Nacos Server 2.2.3

开始集成

项目模块

公共模块里的配置是之前文章中提到的内容,加了一个webmvc和webflux的适配,我会将文章和代码仓库的链接放在最下边,有需要的可以去看看。

引入依赖,配置依赖管理

在父模块中添加lombok、测试包和服务发现与注册的包,管理Spring Cloud、Spring Cloud Alibaba依赖版本,如下
不要忘了SpringDoc的依赖管理

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-doc-spring-cloud</artifactId>
    <version>0.0.1</version>
    <packaging>pom</packaging>
    <name>spring-doc-spring-cloud</name>
    <description>spring-doc-spring-cloud</description>
    <modules>
        <module>spring-doc-cloud-common</module>
        <module>spring-doc-cloud-gateway</module>
        <module>spring-doc-cloud-webflux</module>
        <module>spring-doc-cloud-webmvc</module>
    </modules>
    <properties>
        <!-- 指定Java版本为Java17 -->
        <java.version>17</java.version>
        <!-- 公共模块版本 -->
        <common.version>0.0.1</common.version>
        <!-- 修复SpringBoot自带snakeyaml依赖版本的漏洞 -->
        <snakeyaml.version>2.0</snakeyaml.version>
        <!-- SpringDoc-OpenApi版本号 -->
        <spring-doc.version>2.2.0</spring-doc.version>
        <!-- SpringCloud版本 -->
        <spring-cloud.version>2022.0.4</spring-cloud.version>
        <!-- 指定打包插件版本 -->
        <maven-surefire-plugin.version>3.2.2</maven-surefire-plugin.version>
        <!-- Spring Cloud Alibaba版本号 -->
        <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- 服务注册与发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 适用于webmvc的SpringDoc依赖 -->
            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
                <version>${spring-doc.version}</version>
            </dependency>

            <!-- 适用于webflux的SpringDoc依赖 -->
            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
                <version>${spring-doc.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

spring-doc-cloud-gateway模块说明

引入webflux、gateway、loadbalancer负载均衡和springdoc依赖,同时引入公共模块

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>spring-doc-spring-cloud</artifactId>
        <version>0.0.1</version>
    </parent>

    <artifactId>spring-doc-cloud-gateway</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- webflux依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <!-- 网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- 负载均衡 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <!-- SpringDoc -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
        </dependency>

        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- 公共包,这里是对于swagger的自定义配置,可以参考之前的文章或直接查看代码仓库的实现 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>spring-doc-cloud-common</artifactId>
            <version>${common.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-tiny:latest</builder>
                    </image>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

修改application.yml

开启gateway自动扫描,根据注册中心的服务自动生成路由,路由名转小写,添加自定义的swagger配置。

yml 复制代码
spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          # 根据注册中心的服务自动生成路由
          enabled: true
          # 路由名转小写
          lower-case-service-id: true

# ------------以下内容可改为公共配置------------
# SpringDoc自定义配置
custom:
  info:
    title: ${spring.application.name}-api
    version: 0.0.1
    description: 这是一个使用SpringDoc生成的在线文档.
    terms-of-service: http://127.0.0.1:8000/test01
    gateway-url: http://127.0.0.1:8080
  license:
    name: Apache 2.0
  security:
    name: Authenticate
    token-url: http://kwqqr48rgo.cdhttp.cn/oauth2/token
    authorization-url: http://kwqqr48rgo.cdhttp.cn/oauth2/authorize

添加InstancesChangeEventListener

监听微服务启、停状态,微服务状态改变后刷新Swagger UI中的组。

java 复制代码
package com.example.config;

import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.Subscriber;
import com.alibaba.nacos.common.utils.JacksonUtils;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ObjectUtils;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springdoc.core.utils.Constants.DEFAULT_API_DOCS_URL;
import static org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier.SERVICE_INSTANCE_CACHE_NAME;

/**
 * 监听注册中心实例注册状态改变事件,微服务实例状态改变后刷新swagger ui的组(一个组等于一个微服务)
 * 
 * @author vains
 */
@Slf4j
@Configuration(proxyBeanMethods = false)
public class InstancesChangeEventListener extends Subscriber<InstancesChangeEvent> {

    private final String LB_SCHEME = "lb";

    private final RouteDefinitionLocator locator;

    @Resource
    private CacheManager defaultLoadBalancerCacheManager;

    private final SwaggerUiConfigProperties swaggerUiConfigProperties;

    /**
     * 获取配置文件中默认配置的swagger组
     */
    private final Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> defaultUrls;


    public InstancesChangeEventListener(RouteDefinitionLocator locator,
                                        SwaggerUiConfigProperties swaggerUiConfigProperties) {
        this.locator = locator;
        this.swaggerUiConfigProperties = swaggerUiConfigProperties;
        // 构造器中初始化配置文件中的swagger组
        this.defaultUrls = swaggerUiConfigProperties.getUrls();
    }

    @Override
    public void onEvent(InstancesChangeEvent event) {
        if (log.isDebugEnabled()) {
            log.info("Spring Gateway 接收实例刷新事件:{}, 开始刷新缓存", JacksonUtils.toJson(event));
        }
        Cache cache = defaultLoadBalancerCacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
        if (cache != null) {
            cache.evict(event.getServiceName());
        }
        // 刷新group
        this.refreshGroup();

        if (log.isDebugEnabled()) {
            log.info("Spring Gateway 实例刷新完成");
        }
    }

    /**
     * 刷新swagger的group
     */
    public void refreshGroup() {
        // 获取网关路由
        List<RouteDefinition> definitions = locator.getRouteDefinitions().collectList().block();
        if (ObjectUtils.isEmpty(definitions)) {
            return;
        }

        // 根据路由规则生成 swagger组 配置
        Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> swaggerUrls = definitions.stream()
                // 只处理在注册中心注册过的(lb://service)
                .filter(definition -> definition.getUri().getScheme().equals(LB_SCHEME))
                .map(definition -> {
                    // 生成 swagger组 配置,以微服务在注册中心中的名字当做组名、请求路径(我这里使用的是自动扫描生成的,所以直接用了这个,其它自定义的按需修改)
                    String authority = definition.getUri().getAuthority();
                    return new AbstractSwaggerUiConfigProperties.SwaggerUrl(authority, authority + DEFAULT_API_DOCS_URL, authority);
                })
                .collect(Collectors.toSet());

        // 如果在配置文件中有添加其它 swagger组 配置则将两者合并
        if (!ObjectUtils.isEmpty(defaultUrls)) {
            swaggerUrls.addAll(defaultUrls);
        }

        // 重置配置文件
        swaggerUiConfigProperties.setUrls(swaggerUrls);

        if (log.isDebugEnabled()) {
            String groups = swaggerUrls.stream()
                    .map(AbstractSwaggerUiConfigProperties.SwaggerUrl::getName)
                    .collect(Collectors.joining(","));
            log.debug("刷新Spring Gateway Doc Group成功,获取到组:{}.", groups);
        }
    }

    @PostConstruct
    public void registerToNotifyCenter() {
        // 注册监听事件
        NotifyCenter.registerSubscriber((this));
    }

    @Override
    public Class<? extends Event> subscribeType() {
        return InstancesChangeEvent.class;
    }
}

网关启动时、微服务停止、微服务启动时网关会从注册中心获取最新的服务列表,然后根据服务列表生成路由配置,路由的代理路径就是微服务的名字,使用http://网关ip:网关端口/微服务名/**访问对应的微服务。

在注册中心(Nacos)的服务列表更新时会有一个SpringEvent事件通知,也就是上边类中的监听实现,每次收到通知时就会根据网关的路由生成SwaggerUrl列表,其中name是微服务的名字(application.name),路径是/{application.name}/v3/api-docs,这样实际上就是通过网关将请求代理至各微服务了,获取到的api信息实际上也是各微服务的,如果某个微服务禁用swagger,在网关中也获取不到对应的api信息。以上内容就是之前提到的微服务状态改变后刷新Swagger UI中的组。

当然,虽然可以通过网关代理获取到微服务的api信息,但是在测试接口时还是会出现问题,请求会直接发送至微服务,并不会经过网关代理,如下所示

所以说需要修改各微服务配置,指定当前服务访问的url,在SpringDoc配置中添加servers属性,并设置值为被网关代理的路径,如下所示

在引用的微服务中设置自定义配置custom.info.gateway-url,相信看到这里就明白为什么上方网关的yml中会有这么一个配置了。

修改微服务yml

添加类似如下配置,设置spring.application.name,设置SpringDoc自定义配置,设置custom.info.gateway-url

yml 复制代码
spring:
  application:
    name: webmvc

# ------------以下内容可改为公共配置------------
# SpringDoc自定义配置
custom:
  info:
    title: ${spring.application.name}-api
    version: 0.0.1
    description: 这是一个使用SpringDoc生成的在线文档.
    terms-of-service: http://127.0.0.1:8200/test01
    # 设置当前服务在网关中的代理路径
    gateway-url: http://127.0.0.1:8080/${spring.application.name}
  license:
    name: Apache 2.0
  security:
    name: Authenticate
    token-url: http://kwqqr48rgo.cdhttp.cn/oauth2/token
    authorization-url: http://kwqqr48rgo.cdhttp.cn/oauth2/authorize
server:
  port: 8200

查看效果

webflux访问地址,默认会有一个webjars前缀

http://127.0.0.1:8080/webjars/swagger-ui/index.html

其它微服务都是一些测试接口,没必要贴了,大家用自己的就好,或者去代码仓库拉取代码看看。

附录

  1. SpringDoc枚举字段处理与SpringBoot接收枚举参数处理
  2. SpringDoc基础配置和集成OAuth2登录认证教程
  3. 代码仓库:GiteeGithub
相关推荐
Neteen19 分钟前
七大经典基于比较排序算法【Java实现】
java·算法·排序算法
海无极41 分钟前
EDUCODER头哥 SpringBoot 异常处理
java·spring boot·spring
.生产的驴1 小时前
SpringBootCloud 服务注册中心Nacos对服务进行管理
java·spring boot·spring·spring cloud·tomcat·rabbitmq·java-rabbitmq
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS课程答疑系统(JAVA毕业设计)
java·jvm·vue.js·spring boot·spring cloud·kafka·开源
乌啼霜满天2491 小时前
Java Web项目的webapp目录
java·开发语言·web app
古人诚不我欺1 小时前
jmeter常用配置元件介绍总结之线程组
java·开发语言·jmeter
一个松1 小时前
IDEA报包不存在,但实际存在
java·ide·intellij-idea
一直学习永不止步1 小时前
LeetCode题练习与总结:至少有 K 个重复字符的最长子串--395
java·算法·leetcode·字符串·滑动窗口·哈希表·分治
一叶飘零_sweeeet2 小时前
JVM 中的完整 GC 流程
java·开发语言·jvm
猫爪笔记2 小时前
JavaSE:运算符 (学习笔记)
java·开发语言·笔记·学习