SpringBoot3使用Gateway聚合Springdoc+Knife4j
SpringBoot3使用Gateway聚合Springdoc+Knife4j
前言
基础环境
将所有依赖集成好作为一个本地包供其他项目使用
- jdk17
- maven3.6+
- springboot3.0
- springcloud:2022.0.1
- springcloud-alibaba:1.8.1-2022.0.0-R
##具包(framework-document)
依赖
<dependencies>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
代码
文档信息properties
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* The type Swagger properties.
*
* @author : Milo
*/
@ConfigurationProperties("springdoc.info")
public class DocumentInfoProperties {
/**
* 文档标题
*/
private String title;
/**
* 文档描述
*/
private String description;
/**
* 项目version
*/
private String projectVersion;
/**
* 许可证
*/
private String license;
/**
* 许可证URL
*/
private String licenseUrl;
/**
* api 前缀
*/
private String apiPrefix;
/**
* 项目负责人信息
*/
private Contact contact = new Contact();
/*省略get,set*/
public static class Contact {
/**
* 联系人
**/
private String name;
/**
* 联系人url
**/
private String url;
/**
* 联系人email
**/
private String email;
/*省略get,set*/
}
}
文档信息Configuration
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author milo
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(DocumentInfoProperties.class)
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true")
@ConditionalOnWebApplication
public class DocumentConfiguration {
@Bean
public OpenAPI openApi(final DocumentInfoProperties docInfo) {
return new OpenAPI().info(new Info()
.title(docInfo.getTitle())
.description(docInfo.getDescription())
.version(docInfo.getProjectVersion())
.contact(new Contact().name(docInfo.getContact().getName()).email(docInfo.getContact().getEmail()).url(docInfo.getContact().getUrl()))
.license(new License().name(docInfo.getLicense()).url(docInfo.getLicenseUrl()))
);
}
}
监听服务,通过服务ID创建Group
服务节点变动事件
import org.springframework.context.ApplicationEvent;
/**
* @author : Milo
* 服务节点变动事件
*/
public class ServiceChangeEvent extends ApplicationEvent {
public ServiceChangeEvent(Object source) {
super(source);
}
}
Nacos监听服务上下线
如果使用的其他的注册中心,主动去监听服务上下线,如果监听到了,直接发布ServiceChangeEvent
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.github.mpcloud.document.event.ServiceChangeEvent;
import com.github.mpcloud.framework.core.utils.spring.SpringContextHolder;
import org.springframework.beans.factory.InitializingBean;
/**
* @author : Milo
*/
public class NacosServiceListener extends Subscriber<InstancesChangeEvent> implements InitializingBean {
@Override
public void onEvent(InstancesChangeEvent ignored) {
// 这里可以用org.springframework.context.ApplicationContext.publishEvent替代
SpringContextHolder.publishEvent(new ServiceChangeEvent(this));
}
@Override
public Class<? extends Event> subscribeType() {
return InstancesChangeEvent.class;
}
@Override
public void afterPropertiesSet() {
NotifyCenter.registerSubscriber(this);
}
}
网关与SpringDoc整合的配置类
import com.alibaba.nacos.client.naming.event.InstancesChangeEvent;
import com.github.mpcloud.document.event.ServiceChangeEvent;
import com.github.mpcloud.document.listener.NacosServiceListener;
import com.github.mpcloud.framework.core.consts.Constant;
import com.github.mpcloud.framework.core.utils.spring.SpringContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigParameters;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import java.util.Set;
import static org.springdoc.core.utils.Constants.SPRINGDOC_ENABLED;
import static org.springdoc.core.utils.Constants.SPRINGDOC_SWAGGER_UI_ENABLED;
/**
* @author : Milo
*/
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnProperty(name = { SPRINGDOC_ENABLED, SPRINGDOC_SWAGGER_UI_ENABLED }, matchIfMissing = true, havingValue = "true")
public class GatewayRouterGroupProviderConfiguration {
public static final String PEN_API_ROUTE_NAME = "open-api-route";
private static final Logger log = LoggerFactory.getLogger(GatewayRouterGroupProviderConfiguration.class);
private final ReactiveDiscoveryClient discoveryClient;
private final Registration registration;
private final SpringDocConfigProperties springDocConfigProperties;
private final SwaggerUiConfigParameters swaggerUiConfigParameters;
public GatewayRouterGroupProviderConfiguration(ReactiveDiscoveryClient discoveryClient, final Registration registration, SpringDocConfigProperties springDocConfigProperties, SwaggerUiConfigParameters swaggerUiConfigParameters) {
this.discoveryClient = discoveryClient;
this.registration = registration;
this.springDocConfigProperties = springDocConfigProperties;
this.swaggerUiConfigParameters = swaggerUiConfigParameters;
}
/**
* openapi 路由(重写服务名所在位置)
*
* @param builder 路由构造器
*
* @return 路由
*/
@Bean
public RouteLocator openApiRouteLocator(final RouteLocatorBuilder builder) {
return builder.routes()
.route(PEN_API_ROUTE_NAME, route -> route
.path(this.springDocConfigProperties.getApiDocs().getPath() + "/**")
.filters(filter -> filter.rewritePath(this.springDocConfigProperties.getApiDocs().getPath() + "/(?<segment>.*)", "/$\{segment}" + this.springDocConfigProperties.getApiDocs().getPath()))
.uri(Constant.HTTP_PREFIX + "localhost:" + SpringContextHolder.getServerPort()))
.build();
}
/**
* 网关当前路由 (去掉服务名)
*
* @param builder 路由构造器
*
* @return 路由
*/
@Bean
public RouteLocator registrationRouteLocator(final RouteLocatorBuilder builder) {
return builder.routes()
.route(this.registration.getServiceId(), route ->
route.path("/" + this.registration.getServiceId() + "/**")
.filters(filter -> filter.rewritePath("/" + this.registration.getServiceId() + "/(?<segment>.*)", "/$\{segment}"))
.uri(Constant.HTTP_PREFIX + "localhost:" + SpringContextHolder.getServerPort()))
.build();
}
/**
* nacos服务节点变动监听器
*
* @return NacosServiceListener
*/
@Bean
@ConditionalOnClass(InstancesChangeEvent.class)
public NacosServiceListener nacosServiceListener() {
return new NacosServiceListener();
}
/**
* 目前有以下三种方式。比较推荐1和3,这两种方式 当前采用的是方式1
* 1. 通过注册中心获取到组名(暂时没有从 spring cloud 里面得到监听服务上下线的办法)
* 优点: 获取到准确的服务名
* 缺点: 强依赖注册中心,需要对每个注册中心分别实现监听器
* 2. 通过监听RefreshRoutesEvent获取到所有路由后获取组名
* 优点: 不强依赖注册中心
* 缺点: 获取服务名没有一种很友好的方式
* 3. 通过定时任务每隔段时间去调用当前方法
* 优点:获取到准确的服务名,不强依赖注册中心
* 缺点:需要开辟一个线程池
*/
@EventListener(classes = { ApplicationReadyEvent.class, ServiceChangeEvent.class })
public synchronized void discover() {
discoveryClient.getServices()
.flatMap(discoveryClient::getInstances)
.map(instance -> {
final AbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl();
swaggerUrl.setName(instance.getServiceId());
swaggerUrl.setDisplayName(instance.getServiceId());
swaggerUrl.setUrl( "/" + instance.getServiceId() + this.springDocConfigProperties.getApiDocs().getPath());
return swaggerUrl;
}).subscribe(swaggerUrl -> {
Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> originalSwaggerUrls = this.swaggerUiConfigParameters.getUrls();
originalSwaggerUrls.removeIf(originalSwaggerUrl -> originalSwaggerUrl.getName().equals(swaggerUrl.getName()));
originalSwaggerUrls.add(swaggerUrl);
}, ex -> log.error("Listener swagger service event error.", ex));
}
}
使用工具包
gateway
依赖
<dependencies>
<!--上面的步骤所构建的包(这个是我本地的,请替换为自己写的)-->
<dependency>
<groupId>com.github.mpcloud</groupId>
<artifactId>framework-document</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-ui</artifactId>
</dependency>
</dependencies>
配置
server:
port: 8080
spring:
application:
name: mpcloud-gateway
cloud:
nacos:
username: nacos
password: nacos
discovery:
server-addr: localhost:8848
namespace: mpcloud
cluster-name: shanghai
heart-beat-timeout: 40000
heart-beat-interval: 20000
ip-delete-timeout: 80000
config:
server-addr: localhost:8848
namespace: mpcloud
timeout: 3000
file-extension: yml
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
globalcors:
cors-configurations:
'[/**]':
allowedHeaders: '*'
allowedMethods: '*'
allowedOrigins: '*'
httpclient:
connect-timeout: 2000
response-timeout: 60s
main:
banner-mode: off
webflux:
multipart:
file-storage-directory: /tmp
max-in-memory-size: 12MB
format:
date-time: yyyy-MM-dd HH:mm:ss
date: yyyy-MM-dd
time: HH:mm:ss
logging:
file:
path: ./logs
springdoc:
api-docs:
enabled: true
groups:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
info:
title: 网关Api文档
description: 网关Api文档
project-version: 0.0.1
license: https://milo-xiaomeng.github.io
license-url: https://milo-xiaomeng.github.io
contact:
name: milo
url: http://localhost:8080
email: milo.xiaomeng@gmail.com
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
web服务中使用
依赖
dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--上面的步骤所构建的包(这个是我本地的,请替换为自己写的)-->
<dependency>
<groupId>com.github.mpcloud</groupId>
<artifactId>framework-document</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
</dependency>
</dependencies>
配置
server:
port: 8083
spring:
application:
name: mpcloud-monitoring
profiles:
active: dev
cloud:
nacos:
username: nacos
password: nacos
discovery:
server-addr: localhost:8848
namespace: mpcloud
cluster-name: shanghai
config:
server-addr: localhost:8848
namespace: mpcloud
timeout: 3000
file-extension: yml
springdoc:
api-docs:
enabled: true
groups:
enabled: true
path: /v3/api-docs
info:
title: 监控Api文档
description: 监控Api文档
project-version: 0.0.1
license: mpcloud.github.com
license-url: mpcloud.github.com
contact:
name: milo
url: http://localhost:8080
email: milo.xiaomeng@gmail.com
group-configs:
- pathsToMatch: /**
group: ${spring.application.name}
show-actuator: true