集成SPRINGDOC OPENAPI 的微服务实践-spring cloud 入门教程
本文中的代码示例,我们将使用使用 Spring Cloud 构建的典型微服务架构。它由Nacos作为注册中心和配置中心 ,Spring Cloud Gateway为API 网关。我们还有三个微服务,外部客户端只能通过网关才能访问它们暴露 REST API 。
<dependency>
<groupId>org.springdocgroupId>
<artifactId>springdoc-openapi-webmvc-coreartifactId>
dependency>
<dependency>
<groupId>org.springdocgroupId>
<artifactId>springdoc-openapi-uiartifactId>
dependency>
<dependency>
<groupId>io.swagger.core.v3groupId>
<artifactId>swagger-annotationsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-commonsartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<scope>providedscope>
dependency>
只有在Application中添加如下注解才能启用文档
/**
* 开启 spring doc
*
* @author edevp
* @date 2022-03-26
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableConfigurationProperties(SwaggerProperties.class)
@Import({ SwaggerAutoConfiguration.class })
public @interface EnableSwaggerDoc {
}
/**
* swagger配置
*
*
* 禁用方法1:使用注解@Profile({"dev","test"})
*
* 表示在开发或测试环境开启,而在生产关闭。(推荐使用) 禁用方法2:使用注解@ConditionalOnProperty(name = "swagger.enable",
*
* havingValue = "true") 然后在测试配置或者开发配置中添加swagger.enable=true即可开启,生产环境不填则默认关闭Swagger.
*
*
* @author edevp
*/
@RequiredArgsConstructor
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
public class SwaggerAutoConfiguration {
private final SwaggerProperties swaggerProperties;
private final ServiceInstance serviceInstance;
@Bean
public OpenAPI springOpenAPI() {
OpenAPI openAPI = new OpenAPI().info(new Info().title(swaggerProperties.getTitle()));
// oauth2.0 password
openAPI.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION));
openAPI.schemaRequirement(HttpHeaders.AUTHORIZATION, this.securityScheme());
// servers 提供调用的接口地址前缀
List<Server> serverList = new ArrayList<>();
String path = swaggerProperties.getServices().get(serviceInstance.getServiceId());
serverList.add(new Server().url(swaggerProperties.getGateway() + "/" + path));
openAPI.servers(serverList);
return openAPI;
}
// 全局添加token
private SecurityScheme securityScheme() {
SecurityScheme securityScheme = new SecurityScheme();
//类型
securityScheme.setType(SecurityScheme.Type.APIKEY);
//请求头的name
securityScheme.setName(HttpHeaders.AUTHORIZATION);
//token所在未知
securityScheme.setIn(SecurityScheme.In.HEADER);
return securityScheme;
}
}
@Data
@ConfigurationProperties("swagger")
public class SwaggerProperties {
/**
* 是否开启swagger
*/
private Boolean enabled = true;
/**
* swagger会解析的包路径
**/
private String basePackage = "";
/**
* swagger会解析的url规则
**/
private List<String> basePath = new ArrayList<>();
/**
* 在basePath基础上需要排除的url规则
**/
private List<String> excludePath = new ArrayList<>();
/**
* 需要排除的服务
*/
private List<String> ignoreProviders = new ArrayList<>();
/**
* 标题
**/
private String title = "";
/**
* 网关
*/
private String gateway;
/**
* 获取token
*/
private String tokenUrl;
/**
* 作用域
*/
private String scope;
/**
* 服务转发配置
*/
private Map<String, String> services;
}
<dependency>
<groupId>com.edevpgroupId>
<artifactId>edevp-common-swaggerartifactId>
dependency>
application- s p r i n g . p r o f i l e s . a c t i v e . {spring.profiles.active}. spring.profiles.active.{spring.cloud.nacos.config.file-extension}
# swagger 配置
swagger:
enabled: true
title: Edevp Swagger API
gateway: http://${GATEWAY_HOST:edevp-gateway}:${GATEWAY-PORT:8899}
token-url: ${swagger.gateway}/auth/oauth/token
scope: server
services:
edevp-org: org
edevp-store: storage
edevp-auth: auth
构建在 Spring Cloud Gateway 之上的 API 网关使用 Netty 作为嵌入式服务器,并基于响应式 Spring WebFlux。它还提供 Swagger UI 以访问所有微服务公开的文档,因此它必须包含启用 UI 的库。必须包含以下两个库才能为基于 Spring WebFlux 的响应式应用程序启用 Springdoc 支持
<dependency>
<groupId>org.springdocgroupId>
<artifactId>springdoc-openapi-webflux-uiartifactId>
dependency>
<dependency>
<groupId>org.springdocgroupId>
<artifactId>springdoc-openapi-webflux-coreartifactId>
dependency>
一旦您启动每个微服务,它将公开端点/v3/api-docs。我们可以通过使用springdoc.api-docs.pathSpring 配置文件中的属性来自定义该上下文。由于不是必须的,我们可以继续在 Spring Cloud Gateway 上实现。Springdoc 没有提供与 SpringFox 类似的类SwaggerResource,它在上一篇文章中用于暴露来自不同微服务的多个 API。幸运的是,有一种分组机制允许将 OpenAPI 定义分成具有给定名称的不同组。要使用它,我们需要声明一个GroupOpenAPIbean列表。
这是网关服务中负责创建由网关处理的 OpenAPI 资源列表的代码片段。首先,我们使用RouteDefinitionLocator豆。然后我们获取每个路由的 id 并将其设置为组名。因此,我们在 path 下有多个 OpenAPI 资源/v3/api-docs/{SERVICE_NAME},例如/v3/api-docs/org。
读取gateway的路由配置,截取服务前缀
@Autowired
RouteDefinitionLocator locator;
@Bean
public List<GroupedOpenApi> apis() {
List<GroupedOpenApi> groups = new ArrayList<>();
List<RouteDefinition> definitions = locator.getRouteDefinitions().collectList().block();
definitions.stream().filter(routeDefinition -> routeDefinition.getId().matches(".*-service")).forEach(routeDefinition -> {
String name = routeDefinition.getId().replaceAll("-service", "");
GroupedOpenApi.builder().pathsToMatch("/" + name + "/**").setGroup(name).build();
});
return groups;
}
读取公共配置中application-dev.yml中暴露的服务列表
@Configuration(proxyBeanMethods = false)
public class SpringDocConfiguration {
@Autowired
RouteDefinitionLocator locator;
@Bean
@Lazy(false)
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", matchIfMissing = true)
public List<GroupedOpenApi> apis(SwaggerUiConfigParameters swaggerUiConfigParameters,
SwaggerDocProperties swaggerProperties) {
List<GroupedOpenApi> groups = new ArrayList<>();
List<RouteDefinition> definitions = locator.getRouteDefinitions().collectList().block();
// 将每个服务作为一个分组,比如edevp-org ,对应的value是org
for (String value : swaggerProperties.getServices().values()) {
swaggerUiConfigParameters.addGroup(value);
}
return groups;
}
@Data
@Component
@ConfigurationProperties("swagger")
public class SwaggerDocProperties {
private Map<String, String> services;
/**
* 认证参数
*/
private SwaggerBasic basic = new SwaggerBasic();
@Data
public class SwaggerBasic {
/**
* 是否开启 basic 认证
*/
private Boolean enabled;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
}
}
for (String value : swaggerProperties.getServices().values()) {
swaggerUiConfigParameters.addGroup(value);
}
这个类会把生成swagger中definition
,例如
由于我们启动某个微服务,比如edevp-auth暴露出的文档如下
由于 Springdoc 不允许自定义分组机制的默认行为来更改生成的路径,因此我们需要提供一些解决方法。我的提议只是在专用于 Open API 路径处理的网关配置中添加一个新的路由定义。它将路径重写/v3/api-docs/{SERVICE_NAME}为/{SERVICE_NAME}/v3/api-docs,由另一个负责与 nacos 发现交互的路由处理。
# 固定路由转发配置 无修改
- id: openapi
uri: lb://edevp-gateway
predicates:
- Path=/v3/api-docs/**
filters:
- RewritePath=/v3/api-docs/(?>.*), /$\{path}/v3/api-docs
也就是当通过网关访问
http://edevp-gateeay:8899/v3/api-docs/auth
时,会转发到http://edevp-gateeay:8899/auth/v3/api-docs,而根据定义的路由就会请求到edevp-auth服务,由于StripPrefix=1
其实最终是访问到了http://edevp-auth/v3/api-docs
,正好跟我们单独访问授权中心的http://127.0.0.1:8201/swagger-ui/index.html
对应上
完整配置如下:
server:
port: 8899
spring:
application:
name: @artifactId@
profiles:
# active: @profiles.active@
active: dev
cloud:
nacos:
discovery: #服务注册与发现
server-addr: ${NACOS_HOST:nacos}:${NACOS_PORT:8848} #nacos地址
username: dev
password: 123456
namespace: edevp-demo #指定命名空间 可以删掉namespace不写默认public
#配置文件组成 : 通俗点 服务名称-指定环境.后缀名称 name-active.file-extension
config: #动态配置
server-addr: ${spring.cloud.nacos.discovery.server-addr} #nacos地址
username: dev
password: 123456
file-extension: yml #配置文件类型 非常重要后缀一定要一致 xxx.yml
namespace: edevp-demo #指定命名空间 可以删掉namespace不写默认public
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
gateway:
discovery:
locator:
# 是否可以通过其他服务的serviceId来转发到具体的服务实例。默认为false
# 为true,自动创建路由,路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,其中微服务应用名默认大写访问
# 让gateway通过服务发现组件找到其他的微服务,如果是true则自动,false才可以配置routes属性生效
enabled: false
# 服务名默认必须大写,否则会抛404错误,如果服务名要用小写,可在属性配置文件中添加spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true配置解决
lower-case-service-id: true
#default-filters: #全局用于配置所有路由共享过滤器
# - StripPrefix=1 #去掉- Path=/auth 前缀
# - PreserveHostHeader #发送原主机头
routes: # 网关路由配置
- id: edevp-auth # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8202 # 路由的目标地址,http就是固定地址
uri: lb://edevp-auth # 路由的目的地址,lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的调价
- Path=/auth/** # 这个是按照规则匹配,只要以/user/开头就符合要求
filters:
- StripPrefix=1 #去掉请求path中的第一层目录/org
- AddRequestHeader=X-Request-From, edevp-gateway
- id: edevp-org # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8202 # 路由的目标地址,http就是固定地址
uri: lb://edevp-org # 路由的目的地址,lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的调价
- Path=/org/** # 这个是按照规则匹配,只要以/client/开头就符合要求
filters:
- StripPrefix=1 #去掉请求path中的第一层目录/org
- id: edevp-user # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8202 # 路由的目标地址,http就是固定地址
uri: lb://edevp-user # 路由的目的地址,lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的调价
- Path=/users/** # 这个是按照规则匹配,只要以/user/开头就符合要求
filters:
- AddRequestHeader=X-Request-red, blue
- id: edevp-order
uri: lb://edevp-order
predicates: # 路由断言,也就是判断请求是否符合路由规则的调价
- Path=/orders/** # 这个是按照规则匹配,只要以/user/开头就符合要求
- id: edevp-store
uri: lb://edevp-store
predicates: # 路由断言,也就是判断请求是否符合路由规则的调价
- Path=/storage/** # 这个是按照规则匹配,只要以/user/开头就符合要求
# 固定路由转发配置 无修改
- id: openapi
uri: lb://edevp-gateway
predicates:
- Path=/v3/api-docs/**
filters:
- RewritePath=/v3/api-docs/(?>.*), /$\{path}/v3/api-docs
default-filters:
- AddRequestHeader=X-Request-red, green
- AddRequestHeader=origin, gateway # 添加名为origin的请求头,值为gateway
security:
oauth:
ignore:
urls:
- /auth/oauth/token
- /auth/oauth/login
访问在网关上公开的 Swagger UI 后,您可能会看到我们可以在发现中注册的所有三个微服务之间进行选择。这正是我们想要实现的。
http://edevp-gateway:8899/webjars/swagger-ui/index.html
Springdoc OpenAPI 兼容 OpenAPI 3,并支持 Spring WebFlux,而 SpringFox 不支持。
springdoc:
api-docs:
enabled: false