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的依赖管理
<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信息,但是在测试接口时还是会出现问题,请求会直接发送至微服务,并不会经过网关代理,如下所示
所以说需要修改各微服务配置,指定当前服务访问的url,在SpringDoc配置中添加servers
属性,并设置值为被网关代理的路径,如下所示
在引用的微服务中设置自定义配置custom.info.gateway-url
,相信看到这里就明白为什么上方网关的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
其它微服务都是一些测试接口,没必要贴了,大家用自己的就好,或者去代码仓库拉取代码看看。