SpringCloudGateway整合swagger3文档

本文介绍了如何在SpringCloud项目中将多个微服务的Swagger3API文档集成到网关服务下,提供一个统一的入口供前端查看。通过示例展示了微服务和网关的配置,包括POM.xml、application.yml和关键代码如Swagger3Config和SwaggerProvider。最后,验证了网关服务的Swagger链接可以成功切换不同服务的文档。
摘要由CSDN通过智能技术生成

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介绍,可见文章:

Spring Cloud Gateway 服务网关的部署与使用详细介绍_张维鹏的博客-CSDN博客网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务,所以,使用网关的好处在于:(1)简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;(2)降低函数间的耦合度。 一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性(3)解放开发人员把精力专注于业务逻辑的实现。由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能https://blog.csdn.net/a745233700/article/details/122917167

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>&lt;!&ndash; lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) &ndash;&gt;
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        &lt;!&ndash; see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging &ndash;&gt;
        <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

        右上角可切换不同服务

4 参考链接

(1) Spring Cloud Gateway 服务网关的部署与使用详细介绍_张维鹏的博客-CSDN博客

(2) SpringCloudAlibaba篇(八)SpringCloudGateWay聚合swagger3、SpringBoot2.6.X整合swagger3+knife4j_springboot 2.6整合springcloud gateway_fate急速出击的博客-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡卡木樨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值