之前使用SpringBoot一般会使用SpringFox Swagger进行API接口管理,但是如果基于Spring Cloud开发,SpringFox 库最重要的问题是缺乏对WebFlux 构建的反应式 API的支持,并且也没有API聚合功能。通过搜索,我们发现了SpringDoc,支持WebMvc支持,WebFlux支持,Javadoc 支持等,支持非常广,可以参考SpringDoc官网。下面就来测试下
微服务整合
只需要配置gradle依赖springdoc-openapi-ui
即可.
1
| implementation 'org.springdoc:springdoc-openapi-ui'
|
然后我们可以在Nacos中定义common.yaml,将springdoc配置一下,如下
1 2 3 4 5 6 7 8 9
| springdoc: api-docs: enabled: true swagger-ui: enabled: true packages-to-scan: com.mayahx.stcm cache: disabled: false pre-loading-enabled: true
|
Swagger UI 页面将http://server:port/context-path/swagger-ui.html在以下 url 提供,OpenAPI 描述将在以下 json 格式的 URL 中提供:http://server:port/context-path/v3/api-docs。
我们打开下页面看看

可以看到文档已经生成,已经实现单体Swagger一样的功能。但是微服务下会有多个模块,我们可以将不同模块的API给前端使用,但是这种方式有点麻烦,所以我们希望给一个聚合页面,让前端可以在一个主页面中就将接口调试完成。
Spring Cloud Gateway集成
因为Spring Cloud Gateway使用的响应式编程,所以我们需要集成webflux,如下所示
1 2
| implementation 'org.springdoc:springdoc-openapi-webflux-ui' implementation 'org.springdoc:springdoc-openapi-webmvc-core'
|
Springdoc支持使用GroupedOpenApi分组机制实现将OpenAPI 定义分成具有给定名称的不同组,
我们需要声明一个GroupOpenAPI Bean列表。这是网关服务中负责创建由网关处理的 OpenAPI 资源列表的代码片段。首先,我们使用RouteDefinitionLocator。然后我们获取每个路由的 id 并将其设置为组名。因此,我们在 path 下有多个 OpenAPI 资源/v3/api-docs/{SERVICE_NAME},例如/v3/api-docs/order。配置如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Configuration @Slf4j @Profile("!prod") public class ApiConfig { @Bean @Lazy(false) public List<GroupedOpenApi> apis(SwaggerUiConfigParameters swaggerUiConfigParameters, RouteDefinitionLocator locator) { List<GroupedOpenApi> groups = new ArrayList<>(); List<RouteDefinition> definitions = locator.getRouteDefinitions().collectList().block(); if (definitions!=null&&definitions.size()>0) { for (RouteDefinition definition : definitions) { if (!Objects.equals(definition.getId(), "openapi")) { swaggerUiConfigParameters.addGroup(definition.getId()); } } } return groups; } }
|
通过上面的代码实现分组聚合后,地址确是/v3/api-docs/{SERVICE_NAME}结尾,比如订单服务的API就变成了http://192.168.0.107:8000/v3/api-docs/order,这样是无法访问到我们子服务的API DOC的,所以需要在网关配置路由重写。如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| spring: cloud: gateway: globalcors: cors-configurations: '[/**]': allowedOrigins: "*" allowedMethods: - GET - POST - PUT - DELETE - OPTIONS discovery: locator: lower-case-service-id: true routes: - id: order uri: lb://order predicates: - Path=/order/** - id: pay uri: lb://pay predicates: - Path=/pay/** - id: stock uri: lb://stock predicates: - Path=/stock/** - id: openapi uri: http://localhost:${server.port} predicates: - Path=/v3/api-docs/** filters: - RewritePath=/v3/api-docs/(?<path>.*), /$\{path}/v3/api-docs
|
globalcors是网关的跨域配置,openapi路由代表路由规则的重写。下面我们就可以看看集成后的样子

这样Springdoc就可以在右上角看到我们在网关中配置的端点,并且可以选择端点进行测试了。
跨域配置
API聚合完成后,我们进行接口调试会发现有两个问题
- servers的API地址是内网地址,远程调内网地址肯定是不通的。
- 接口调试会产生跨域访问cors问题。
Server内网地址解决
发生这个问题时,我尝试修改/ets/hosts域名映射的方式,但是没有生效,所以可以选择另外一种方式,在服务中定义server,将配置放在公共模块中。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @Profile("!prod") @Configuration public class OpenApiConfig { public static final String LOCAL_HOST = "127.0.0.1"; public static final String DEV_HOST = "150.158.188.244"; public static final String DEV = "dev"; public static final String DEBUG = "debug"; public static final String TEST = "test"; public static final String URL_SEPARATOR = "/"; public static final String URL_HTTP = "http://"; public static final String URL_PORT = ":";
@Value("${server.port}") public int port;
@Value("${spring.application.name}") public String path;
@Value("${spring.profiles.active}") public String active;
@Bean public OpenAPI openAPI(){ Server server = new Server(); StringBuilder builder = new StringBuilder(); builder.append(URL_HTTP); switch (active){ case DEV: builder.append(LOCAL_HOST); break; case DEBUG: case TEST: builder.append(DEV_HOST); break; } builder.append(URL_PORT); builder.append(port); builder.append(URL_SEPARATOR); builder.append(path); server.setUrl(builder.toString()); return new OpenAPI().addServersItem(server) .addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION)) .schemaRequirement(HttpHeaders.AUTHORIZATION,new SecurityScheme().type(SecurityScheme.Type.HTTP) .scheme("bearer").bearerFormat("JWT").in(SecurityScheme.In.HEADER).name(HttpHeaders.AUTHORIZATION)); } }
|
代码中将dev环境的server配置为localhost,然后将debug调试版本,test测试版本的server设置为公网IP。这样在Gateway聚合时,针对不同的版本就可以展示不同的Server。
跨域解决
针对本地通过网关访问子服务的跨域,有四种方式解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Configuration public class CorsConfig { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.setMaxAge(3600L); corsConfiguration.setAllowCredentials(true); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); return new CorsFilter(source); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Configuration public class WebMvcConfg implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:9527", "http://127.0.0.1:9527", "http://127.0.0.1:8082", "http://127.0.0.1:8083") .allowCredentials(true) .allowedMethods("*") .maxAge(3600); } }
|
1 2 3 4 5 6
| @Controller @RequestMapping("/admin/sysLog") @CrossOrigin public class SysLogController { }
|
- 手工设置响应头(HttpServletResponse )
1 2 3 4 5 6
| @RequestMapping("/test") @ResponseBody public String test(){ response.addHeader("Access-Control-Allow-Origin", "http://localhost:8080"); return "success"; }
|
这里我使用的是@CrossOrigin
注解,因为项目中定义了抽象类BaseController,对HttpServletRequest,HttpServletResponse做了一些操作。
SpringDoc注解介绍

如果觉得使用注解的方式侵入性较强,我们还可以使用注释的方式。后面有时间再来搞一下。