严格统一流量入口,不让Swagger前端绕过网关直接访问后端微服务的接口;(改代码麻烦)
直接改nginx转发了
不严格统一流量入口,让Swagger前端绕过网关直接访问后端微服务的接口;
原文在网关实现合并多个微服务Swagger接口文档的详细步骤 - 云+社区 - 腾讯云
在网关实现合并多个微服务Swagger接口文档的详细步骤
2021-05-11阅读 6130
由于微服务的划分,使用Swagger生成的接口文档也随之拆散,前端同事不得不把每个微服务的接口文档保存为浏览器标签,方便快速切换。在引入网关之后我们想改善这个问题,统一多个微服务接口文档的入口,最好不需要将每个微服务暴露到外网,能够统一配置是否开启接口文档功能,也不需要为接口文档配置路由规则。
WebFlux整合Swagger
基于Spring Cloud Gateway开发微服务网关的前提是我们已经了解了响应式编程,并且会使用Project Reactor、WebFlux提供的API。而在网关项目中整合Swagger实际就是在WebFlux项目中整合Swagger。
首先是在项目中添加Swagger相关依赖,注意选择版本号。
由于我们项目使用的Spring Boot版本号是2.3.0.RELEASE,因此我们选择的Swagger版本号为2.10.x。
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-spring-webflux</artifactId> <version>2.10.5</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.10.5</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.10.5</version> </dependency>
接着需要为WebFlux添加静态资源文件访问路径映射,即添加ResourceWebHandler。
@EnableSwagger2WebFlux @Configuration public class WebfluxConfiguration implements WebFluxConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/swagger-ui.html**") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } }
到此为止,我们只是为一个WebFlux项目配置了Swagger功能,现在打开浏览器访问/swagger-ui.html#/
看到的只是Swagger为网关项目生成的接口文档。
合并多个微服务Swagger接口文档
方案一(笔者从一些博客看到的)
在网关项目中自定义SwaggerResourcesProvider替换Swagger提供的。
自定义SwaggerResourcesProvider实现SwaggerResourcesProvider接口的get方法,方法可返回多个SwaggerResource,每个SwaggerResource对应每个微服务,我们可以过滤掉网关自身的,代码如下。
@Primary @Profile({"dev", "test"}) // 仅本地测试、测试环境开启 @Component public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider{ @Override public List<SwaggerResource> get() { List<SwaggerResource> swaggerResources = new ArrayList<>(); routeProperties.getRoutes().forEach(routeDefinition -> { String routeId = routeDefinition.getId(); String baseUrl; if (isLocalDebug) { baseUrl = "http://127.0.0.1:" + routeDefinition.getUri().getPort(); } else { baseUrl = ${网关域名} + "/" + ${路由前缀}; } swaggerResources.add(getSwaggerResources(routeId, baseUrl)); }); return swaggerResources; } private SwaggerResource getSwaggerResources(String name, String baseUrl) { SwaggerResource resource = new SwaggerResource(); resource.setName(name); resource.setLocation(baseUrl + "/v2/api-docs"); resource.setSwaggerVersion("2.0"); return resource; } }
这些SwaggerResource就是我们在Swagger ui看到的"select a definition"的一个个选项,如下图所示。
关于SwaggerResource:
- name:swagger资源名称,微服务名称,也是我们在Swagger ui看到的选项的名称;
- location:对应微服务接口文档的
/v2/api-docs
API的url(即https://{网关host}/{路由到该应用的路由规则}/v2/api-docs
)。
GatewaySwaggerResourcesProvider
中的下面这段代码只是拼接接口文档的baseUrl,满足“baseUrl+/v2/api-docs”能够直接在浏览器访问。
String baseUrl; if (isLocalDebug) { baseUrl = "http://127.0.0.1:" + routeDefinition.getUri().getPort(); } else { baseUrl = ${网关域名} + "/" + ${路由到该应用的路由规则}; }
由于SwaggerResource配置的location是由前端直接发起请求的,而不是由网关发起请求获取再响应给前端,因此需要在网关为每个微服务配置“/v2/api-docs
”接口的路由规则。
最后还需要为其它微服务配置支持跨域请求,否则Swagger前端无法调用SwaggerResource配置的location向后端服务发起“/v2/api-docs”
接口请求。
在其它微服务中添加跨域请求配置如下(注意:不是在网关添加!)。
@Profile({"dev", "test"}) // 仅测试环境 @ConditionalOnClass(WebMvcConfigurer.class) @Configuration public class CorsAutoConfiguration { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); return new CorsFilter(source); } }
方案二
虽然方案一可行,但弊端是要为每个微服务添加Swagger的“/v2/api-docs
”接口路由配置,笔者并未采用这种方式,而是使用自己想的一种方案:通过在网关代理后端每个微服务的“/v2/api-docs
”接口请求实现。
简单说,就是每个SwaggerResource的location配置的都是网关代理的“/v2/api-docs
”接口,并且给location拼接一个参数,如指向用户中心的SwaggerResource配置的location为“https://网关host/proxySwagger/v2/api-docs?routeId=usercenter
”。然后由网关提供/proxySwagger/v2/api-docs
接口,实现根据参数routeId
向后端微服务发起“/v2/api-docs
”接口请求,并将响应结果直接响应给前端。
GatewaySwaggerResourcesProvider
代码实现如下:
@Primary @Profile({"dev", "test"}) // 仅本地测试、测试环境开启 @Component public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider { @Resource private RouteProperties routeProperties; @Value("${server.domain:http://127.0.0.1:8600}") private String gatewayDomain; @Override public List<SwaggerResource> get() { List<SwaggerResource> swaggerResources = new ArrayList<>(); // 遍历路由定义 routeProperties.getRoutes().forEach(routeDefinition -> { String routeId = routeDefinition.getId(); // 参数1:路由的id // 参数2:网关的域名 swaggerResources.add(getSwaggerResources(routeId, gatewayDomain)); }); return swaggerResources; } private SwaggerResource getSwaggerResources(String routeId, String baseUrl) { SwaggerResource resource = new SwaggerResource(); resource.setName(routeId); resource.setLocation(baseUrl + "/proxySwagger/v2/api-docs?routeId=" + routeId); resource.setSwaggerVersion("2.0"); return resource; } }
由网关提供/v2/api-docs
的代理接口/proxySwagger/v2/api-docs?routeId=${routeId}
,代码如下:
@Profile({"dev", "test"}) @RestController @RequestMapping("/proxySwagger") public class GatewayApiDocsController { @Resource private RouteProperties routeProperties; private WebClient webClient; private boolean isLocalDebug; @PostConstruct public void init() { webClient = WebClient.create(); isLocalDebug = ....;// dev: true, test: false } @GetMapping("/v2/api-docs") public Mono<String> proxyApiDocs(@RequestParam("routeId") String routeId) { // 根据路由id获取路由配置 RouteDefinition routeDefinition = routeProperties.getRoutes().stream() .filter(rd -> rd.getId().equals(routeId)) .findFirst().get(); String baseUrl; URI routeUri = routeDefinition.getUri(); if (isLocalDebug) { // 本地debug baseUrl = "http://127.0.0.1"; } else { baseUrl = "http://" + routeUri.getHost(); } if (routeUri.getPort() > 0) { baseUrl += (":" + routeUri.getPort()); } // 转发给后端微服务 return webClient.get() .uri(baseUrl + "/v2/api-docs") .retrieve() .bodyToMono(String.class); } }
- 注意:由于我们项目是部署在k8s上的,我们去掉了服务注册/发现,所有路由规则配置的uri就已经是k8s集群内部pod可以直接访问的。
这种方案相比第一种方案优点在于:
- 1、严格统一流量入口,不让Swagger前端绕过网关直接访问后端微服务的接口;
- 2、不必要求每个微服务都配置支持跨域请求;
- 3、不必为Swagger的api配置路由规则。