本文目标
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 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 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配置。
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信息,但是在测试接口时还是会出现问题,请求会直接发送至微服务,并不会经过网关代理,如下所示
所以说需要修改各微服务配置,指定当前服务访问的url,在SpringDoc配置中添加servers
属性,并设置值为被网关代理的路径,如下所示
在引用的微服务中设置自定义配置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
其它微服务都是一些测试接口,没必要贴了,大家用自己的就好,或者去代码仓库拉取代码看看。
附录
- SpringDoc枚举字段处理与SpringBoot接收枚举参数处理
- SpringDoc基础配置和集成OAuth2登录认证教程
- 代码仓库:Gitee、Github