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

开始集成

项目模块

Spring Cloud Gateway集成SpringDoc,集中管理微服务API_第1张图片

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

引入依赖,配置依赖管理

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


<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.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>3.1.5version>
        <relativePath/> 
    parent>
    <groupId>com.examplegroupId>
    <artifactId>spring-doc-spring-cloudartifactId>
    <version>0.0.1version>
    <packaging>pompackaging>
    <name>spring-doc-spring-cloudname>
    <description>spring-doc-spring-clouddescription>
    <modules>
        <module>spring-doc-cloud-commonmodule>
        <module>spring-doc-cloud-gatewaymodule>
        <module>spring-doc-cloud-webfluxmodule>
        <module>spring-doc-cloud-webmvcmodule>
    modules>
    <properties>
        
        <java.version>17java.version>
        
        <common.version>0.0.1common.version>
        
        <snakeyaml.version>2.0snakeyaml.version>
        
        <spring-doc.version>2.2.0spring-doc.version>
        
        <spring-cloud.version>2022.0.4spring-cloud.version>
        
        <maven-surefire-plugin.version>3.2.2maven-surefire-plugin.version>
        
        <spring-cloud-alibaba.version>2022.0.0.0spring-cloud-alibaba.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
    dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>${spring-cloud.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>

            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>${spring-cloud-alibaba.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>

            
            <dependency>
                <groupId>org.springdocgroupId>
                <artifactId>springdoc-openapi-starter-webmvc-uiartifactId>
                <version>${spring-doc.version}version>
            dependency>

            
            <dependency>
                <groupId>org.springdocgroupId>
                <artifactId>springdoc-openapi-starter-webflux-uiartifactId>
                <version>${spring-doc.version}version>
            dependency>
        dependencies>
    dependencyManagement>

project>

spring-doc-cloud-gateway模块说明

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


<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.0modelVersion>
    <parent>
        <groupId>com.examplegroupId>
        <artifactId>spring-doc-spring-cloudartifactId>
        <version>0.0.1version>
    parent>

    <artifactId>spring-doc-cloud-gatewayartifactId>

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

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webfluxartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-loadbalancerartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springdocgroupId>
            <artifactId>springdoc-openapi-starter-webflux-uiartifactId>
        dependency>

        <dependency>
            <groupId>io.projectreactorgroupId>
            <artifactId>reactor-testartifactId>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>com.examplegroupId>
            <artifactId>spring-doc-cloud-commonartifactId>
            <version>${common.version}version>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.graalvm.buildtoolsgroupId>
                <artifactId>native-maven-pluginartifactId>
            plugin>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-tiny:latestbuilder>
                    image>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombokgroupId>
                            <artifactId>lombokartifactId>
                        exclude>
                    excludes>
                configuration>
            plugin>
        plugins>
    build>

project>

修改application.yml

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

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中的组。

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信息,但是在测试接口时还是会出现问题,请求会直接发送至微服务,并不会经过网关代理,如下所示

Spring Cloud Gateway集成SpringDoc,集中管理微服务API_第2张图片

Spring Cloud Gateway集成SpringDoc,集中管理微服务API_第3张图片

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

Spring Cloud Gateway集成SpringDoc,集中管理微服务API_第4张图片

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

修改微服务yml

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

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

Spring Cloud Gateway集成SpringDoc,集中管理微服务API_第5张图片

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

附录

  1. SpringDoc枚举字段处理与SpringBoot接收枚举参数处理
  2. SpringDoc基础配置和集成OAuth2登录认证教程
  3. 代码仓库:Gitee、Github

你可能感兴趣的:(SpringDoc,微服务,java,架构)