SpringCloud集成SpringDoc和Swagger3

一、参考

集成SPRINGDOC OPENAPI 的微服务实践-spring cloud 入门教程

二、架构

本文中的代码示例,我们将使用使用 Spring Cloud 构建的典型微服务架构。它由Nacos作为注册中心和配置中心 ,Spring Cloud Gateway为API 网关。我们还有三个微服务,外部客户端只能通过网关才能访问它们暴露 REST API 。

  • 授权中心(edevp-auth) /auth/**
  • 组织架构(edevp-org)/org/**
  • 库存中心(edevp-storage)/storage/**
  • 网关(edevp-gateway)

三、构建公共edevp-common-swagger

1、引入依赖

<!--接口文档 v3-->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-webmvc-core</artifactId>
</dependency>
<!-- 引入swagger页面 -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
</dependency>
<dependency>
   <groupId>io.swagger.core.v3</groupId>
    <artifactId>swagger-annotations</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-commons</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <scope>provided</scope>
</dependency>

2、新建注解类EnableSwaggerDoc

只有在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 {

}

3、SwaggerAutoConfiguration

/**
 * swagger配置
 *
 * <p>
 * 禁用方法1:使用注解@Profile({"dev","test"})
 *
 * 表示在开发或测试环境开启,而在生产关闭。(推荐使用) 禁用方法2:使用注解@ConditionalOnProperty(name = "swagger.enable",
 *
 * havingValue = "true") 然后在测试配置或者开发配置中添加swagger.enable=true即可开启,生产环境不填则默认关闭Swagger.
 * </p>
 *
 * @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;
	}

}

services维护的就是如下:
在这里插入图片描述

3、配置属性类SwaggerProperties

@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.edevp</groupId>
    <artifactId>edevp-common-swagger</artifactId>
</dependency>

五、共享配置application-dev.yml

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

六、网关

1、引入包

构建在 Spring Cloud Gateway 之上的 API 网关使用 Netty 作为嵌入式服务器,并基于响应式 Spring WebFlux。它还提供 Swagger UI 以访问所有微服务公开的文档,因此它必须包含启用 UI 的库。必须包含以下两个库才能为基于 Spring WebFlux 的响应式应用程序启用 Springdoc 支持

<!--接口文档-->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-webflux-ui</artifactId>
</dependency>
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-webflux-core</artifactId>
</dependency>

2、配置分组,也就是各个微服务

一旦您启动每个微服务,它将公开端点/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;
}

方法二SpringDocConfiguration

读取公共配置中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,例如
在这里插入图片描述

3、配置yml

由于我们启动某个微服务,比如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>.*), /$\{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>.*), /$\{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 不支持。

八、关闭swagger

springdoc:
  api-docs:
    enabled: false
  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值