1 说明
SpringCloud项目中,微服务模块和网关模块必不可少。按照以前SpringBoot的模式,单个服务拥有自己的Api文档(Swagger文档),引入微服务后,多文档管理成了一个问题。我们需要一个统一的入口方便前端同学查看。本篇文章就是把各个微服务的swagger-api文档,集成到网关服务下面。
关于swagger3介绍,可见文章: https://mp.csdn.net/mp_blog/creation/editor/127736281https://mp.csdn.net/mp_blog/creation/editor/127736281 关于SpringCloudGateway介绍,可见文章:
2 代码部分
2.1 微服务部分
假设我们已经有3个微服务了,对应3个api文档,分别是:
Swagger文档地址:
服务1:http://localhost:9001/java/swagger-ui/index.html
服务2:http://localhost:9100/files/swagger-ui/index.html
服务3:http://localhost:9200/pays/swagger-ui/index.html
对应的API接口如下:
服务1:http://localhost:9001/java/v3/api-docs?group=YX
服务2:http://localhost:9100/files/v3/api-docs?group=YX
服务3:http://localhost:9200/pays/v3/api-docs?group=YX
2.2 网关部分
pom.xml:引入网关和swagger3
<?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>
<artifactId>ssm-mult-module-demo</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>org.example</groupId>
<artifactId>module-cloud-gateway</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>module-cloud-gateway Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- sentinel 限流+控制台 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<!--特别注意:在 gateway 网关服务中不能引入 spring-boot-starter-web 的依赖,否则会报错-->
<!-- Spring cloud gateway 网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--swagger3 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger3.version}</version>
</dependency>
</dependencies>
<!--<build>
<finalName>module-cloud-gateway</finalName>
<pluginManagement><!– lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) –>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!– see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging –>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>-->
<build>
<finalName>module-cloud-gateway</finalName>
<plugins>
<plugin>
<!--该插件主要用途:构建可执行的JAR -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml:配置网关和转发路由
server:
port: 8080
spring:
application:
name: module-cloud-gateway-win
cloud:
gateway: #网关路由配置
httpclient:
pool:
max-idle-time: 10000
routes:
# 请求:http://localhost:8080/java/remote/user/test1 会转发到 http://localhost:9001/java/remote/user/test1
- id: java-service # 路由 id,没有固定规则,但唯一,建议与服务名对应
uri: http://localhost:9001 # 匹配后提供服务的路由地址
# 以下是断言条件,必选全部符合条件
predicates:
- Path=/java/** #断言,路径匹配 注意:Path 中 P 为大写
- id: files-service
uri: http://localhost:9100/
predicates:
- Path=/files/**
- id: pays-service
uri: http://localhost:9200/
predicates:
- Path=/pays/**
springfox:
documentation:
swagger-ui:
enabled: true
Swagger3Config.java:swagger配置
package com.module.nacos.file.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.web.*;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.*;
/***
* @author
* @date
* @apiNote 访问链接:http://localhost:8080/swagger-ui/index.html#/
*/
@EnableOpenApi
@Configuration
public class Swagger3Config {
@Value("${spring.application.name}")
private String PROJECT_NAME;
/**
* 配置基本信息
* @return
*/
@Bean
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(PROJECT_NAME)
.description("swagger test app restful api")
.termsOfServiceUrl("http://localhost")
.contact(new Contact("SSM", "http://localhost", "xxxx@gmail.com"))
.version("1.0")
.build();
}
/**
* 配置文档生成最佳实践
*
* @param apiInfo
* @return
*/
@Bean
public Docket createRestApi(ApiInfo apiInfo) {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo)
.groupName("YX")
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.paths(PathSelectors.any())
.build()
//添加token的参数
.securityContexts(securityContexts())
.securitySchemes(securitySchemes());
}
/* ↓↓↓↓ 解决swagger3.0 head传参失效的问题 ↓↓↓↓ */
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> securitySchemes = new ArrayList<>();
securitySchemes.add(new ApiKey("Authorization", "Authorization", "header"));
securitySchemes.add(new ApiKey("Accept-Language", "Accept-Language", "header"));
return securitySchemes;
}
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("^(?!auth).*$")).build());
return securityContexts;
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope1 = new AuthorizationScope("global", "token");
AuthorizationScope[] authorizationScopes1 = new AuthorizationScope[1];
authorizationScopes1[0] = authorizationScope1;
AuthorizationScope authorizationScope2 = new AuthorizationScope("global", "language");
AuthorizationScope[] authorizationScopes2 = new AuthorizationScope[1];
authorizationScopes2[0] = authorizationScope2;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes1));
securityReferences.add(new SecurityReference("Accept-Language", authorizationScopes2));
return securityReferences;
}
/* ↑↑↑↑ 解决swagger3.0 head传参失效的问题 ↑↑↑↑ */
@SafeVarargs
private final <T> Set<T> hashSet(T... ts) {
if (ts.length > 0) {
return new LinkedHashSet<>(Arrays.asList(ts));
}
return null;
}
/**
* 增加如下配置可解决Spring Boot 6.x 与Swagger 3.0.0 不兼容问题
**/
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
}
private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
}
SwaggerProvider.java:关键代码,注意API_URI参数
package module.cloud.gateway.config.swagger;
import lombok.AllArgsConstructor;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
/**
* @author ssm
* @version V1.0.4
* @description TODO
* @date 2023/2/27 17:55
*/
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
public static final String API_URI = "/v3/api-docs?group=YX";
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
/**
* 这个类是核心,这个类封装的是SwaggerResource,即在swagger-ui.html页面中顶部的选择框,选择服务的swagger页面内容。
* RouteLocator:获取spring cloud gateway中注册的路由
* RouteDefinitionLocator:获取spring cloud gateway路由的详细信息
* RestTemplate:获取各个配置有swagger的服务的swagger-resources
*/
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//取出gateway的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//结合配置的route-路径(Path),和route过滤,只获取有效的route节点
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("/**", API_URI)))));
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("3.0.0");
return swaggerResource;
}
}
3 验证
打开网关服务的Swagger链接:http://localhost:8080/swagger-ui/index.html
右上角可切换不同服务