SpringCloudGateway与swagger集成不

通过自定义### 问题描述
网关集成swagger 通过网关访问swagger在线接口文档,配置不生效

问题出现的环境背景及自己尝试过哪些方法

1、上网查询过swagger集成资料
2、借鉴网上开源项目集成swagger
3、业务模块实体类、属性、Controller层入口均已加上swagger相关注解

相关代码

// 请把代码文本粘贴到下方(请勿用图片代替代码)
自定义starter Swagger 将通用配置提取到starter中、避免每个模块都重写一遍swagger配置代码如下:

Swagger 配置类


package com.bus.cloud.swagger.config;



import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author seata
 * swagger配置 作为公用starter
 */
@Configuration
@EnableSwagger2
@EnableConfigurationProperties(SwaggerProperties.class)
@ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true)
public class SwaggerAutoConfiguration {
    /**
     * 默认的排除路径,排除Spring Boot默认的错误处理路径和端点
     */
    private static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");


    private static final String BASE_PATH = "/**";

    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties() {
        return new SwaggerProperties();
    }

    @Bean
    public Docket api(SwaggerProperties swaggerProperties) {
        // base-path处理
        if (swaggerProperties.getBasePath().isEmpty()) {
            swaggerProperties.getBasePath().add(BASE_PATH);
        }
        //noinspection unchecked
        List<Predicate<String>> basePath = new ArrayList<>();
        swaggerProperties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));

        // exclude-path处理
        if (swaggerProperties.getExcludePath().isEmpty()) {
            swaggerProperties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
        }
        List<Predicate<String>> excludePath = new ArrayList<>();
        swaggerProperties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        // 版本请求头处理

        //noinspection Guava
        return new Docket(DocumentationType.SWAGGER_2)
                .host(swaggerProperties.getHost())
                .apiInfo(apiInfo(swaggerProperties))

                .select()
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getBasePackage()))
                .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath)))
                .build()

                .pathMapping("/");
    }

    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
                .contact(new Contact(swaggerProperties.getContact().getName(),
                        swaggerProperties.getContact().getUrl(), swaggerProperties.getContact().getEmail()))
                .version(swaggerProperties.getVersion())
                .build();
    }
}


==================SwaggerProperties 属性=======================

package com.bus.cloud.swagger.config;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.ArrayList;
import java.util.List;

/**
 * SwaggerProperties
 *
 * @Author:     seata
 * @CreateDate: 2021/9/3 10:57
 * @UpdateUser: seata
 * @UpdateDate: 2021/9/3 10:57
 * @UpdateRemark: 修改内容
 * @Version: 1.0
 */
@Data
@ConfigurationProperties("swagger")
public class SwaggerProperties {
    /**
     * 是否开启swagger
     */
    private Boolean enabled;
    /**
     * swagger会解析的包路径
     **/
    private String basePackage = "";
    /**
     * swagger会解析的url规则
     **/
    private List<String> basePath = new ArrayList<>();
    /**
     * 在basePath基础上需要排除的url规则
     **/
    private List<String> excludePath = new ArrayList<>();
    /**
     * 标题
     **/
    private String title = "";
    /**
     * 描述
     **/
    private String description = "";
    /**
     * 版本
     **/
    private String version = "";
    /**
     * 许可证
     **/
    private String license = "";
    /**
     * 许可证URL
     **/
    private String licenseUrl = "";
    /**
     * 服务条款URL
     **/
    private String termsOfServiceUrl = "";

    /**
     * host信息
     **/
    private String host = "";

    /**
     * 联系人信息
     */
    private Contact contact = new Contact();
    /**
     * 全局统一鉴权配置
     **/
    private Authorization authorization = new Authorization();

    @Data
    @NoArgsConstructor
    public static class Contact {

        /**
         * 联系人
         **/
        private String name = "";
        /**
         * 联系人url
         **/
        private String url = "";
        /**
         * 联系人email
         **/
        private String email = "";

    }

    @Data
    @NoArgsConstructor
    public static class Authorization {

        /**
         * 鉴权策略ID,需要和SecurityReferences ID保持一致
         */
        private String name = "";

        /**
         * 需要开启鉴权URL的正则
         */
        private String authRegex = "^.*$";

        /**
         * 鉴权作用域列表
         */
        private List<AuthorizationScope> authorizationScopeList = new ArrayList<>();

        private List<String> tokenUrlList = new ArrayList<>();
    }

    @Data
    @NoArgsConstructor
    public static class AuthorizationScope {

        /**
         * 作用域名称
         */
        private String scope = "";

        /**
         * 作用域描述
         */
        private String description = "";

    }
}



==============自定义swagger注解=========
``` java

package com.bus.cloud.swagger.annotation;


import com.bus.cloud.swagger.config.SwaggerAutoConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * 开启 swagger
 * @Author:         cong zhi
 * @CreateDate:     2021/9/3 10:54
 * @UpdateUser:     cong zhi
 * @UpdateDate:     2021/9/3 10:54
 * @UpdateRemark:   修改内容
 * @Version:        1.0
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({SwaggerAutoConfiguration.class})
public @interface EnableSwagger {
}

复制代码

============META-INF文件夹,spring.factories文件配置============

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.bus.cloud.swagger.config.SwaggerAutoConfiguration



### bus-starter-swagger工程 pom 文件依赖
```pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.bus.cloud</groupId>
        <artifactId>bus-core</artifactId>
        <version>0.0.3-SNAPSHOT</version>
    </parent>
    <groupId>com.bus.cloud</groupId>
    <artifactId>bus-starter-swagger</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>${project.artifactId}</description>


    <dependencies>


        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-micro-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

   <build>
       <plugins>

           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>3.8.1</version>
               <configuration>
                   <source>${maven.compiler.source}</source>
                   <target>${maven.compiler.target}</target>
                   <showDeprecation>true</showDeprecation>
                   <compilerArgument>-Xlint:unchecked</compilerArgument>
                   <skip>true</skip>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>


复制代码

GateWay 网关聚合Swagger

===== GateWay pom文件依赖=========

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.bus.cloud</groupId>
        <artifactId>bus-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <groupId>com.bus.cloud</groupId>
    <artifactId>bus-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <description>station-gateway</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>com.bus.cloud</groupId>
            <artifactId>bus-starter-core</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-webflux</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>bus-starter-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.bus.cloud</groupId>
            <artifactId>bus-starter-swagger</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- SpringCloud gateway 网关治理 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>


    </dependencies>


    <build>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.bus.cloud.code.BusGeneratorCodeApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>


复制代码

聚合接口文档注册,注入路由到SwaggerResource,在网关服务中增加转发处理逻辑

package com.bus.cloud.gateway.config;

import com.google.common.collect.Lists;
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.List;
/**
 * GatewaySwaggerResourcesProvider
 * @Author:         seata
 * @CreateDate:     2021/9/4 9:11
 * @UpdateUser:     seata
 * @UpdateDate:     2021/9/4 9:11
 * @UpdateRemark:   修改内容
 * @Version:        1.0
 */
@Component
@Primary
public class SwaggerResourceProvider implements SwaggerResourcesProvider {

    public static final String API_URI = "/v2/api-docs";
    private final RouteLocator routeLocator;
    private final GatewayProperties gatewayProperties;

    public SwaggerResourceProvider(RouteLocator routeLocator,GatewayProperties gatewayProperties) {
        this.routeLocator = routeLocator;
        this.gatewayProperties=gatewayProperties;
    }
    /**
     * 当请求文档服务时会调用这个接口,转发逻辑和定义的文档名称
     * @return
     */	
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = Lists.newArrayList();
        List<String> routes = Lists.newArrayList();
        //取出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;
    }

    /**
     * 获取到配置路由信息,重写swaggerResource
     * @param name
     * @param location
     * @return
     */
    private SwaggerResource swaggerResource(String name, String location) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion("2.0");
        return swaggerResource;
    }
}


复制代码

网关上请求头过滤器

package com.bus.cloud.gateway.handler;

import com.bus.cloud.gateway.config.SwaggerResourceProvider;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;


/**
 *  网关上请求头过滤
 * @Author:         seata
 * @CreateDate:     2021/9/4 17:54
 * @UpdateUser:     seata
 * @UpdateDate:     2021/9/4 17:54
 * @UpdateRemark:   修改内容
 * @Version:        1.0
 */
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {

    /**
     * swagger 在拼装URL 数据时候,会增加X-Forwarder-Prefix 请求头里面的信息为前缀
     */
    private static final String HEADER_NAME = "X-Forwarded-Prefix";
    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            if (!StringUtils.endsWithIgnoreCase(path, SwaggerResourceProvider.API_URI)) {
                return chain.filter(exchange);
            }

            String basePath = path.substring(0, path.lastIndexOf(SwaggerResourceProvider.API_URI));


            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(newExchange);
        };
    }
}


复制代码

资源的提供handler

package com.bus.cloud.gateway.handler;

import com.bus.cloud.gateway.config.SwaggerResourceProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger.web.UiConfiguration;
import springfox.documentation.swagger.web.UiConfigurationBuilder;

import java.util.Optional;
/**
*

  • 因为Gateway里没有配置SwaggerConfig,而运行Swagger-ui又需要依赖一些接口 ,同时使用gateway后(用到了webFlux排除掉了web),不能引入@EnableSwagger等注解,故自定义swagger的接口
  • 因为Swagger暂不支持webflux项目,所以Gateway里不能配置SwaggerConfig
  • SwaggerHandler
  • @Author: seata
  • @CreateDate: 2021/9/4 9:30
  • @UpdateUser: seata
  • @UpdateDate: 2021/9/4 9:30
  • @UpdateRemark: 修改内容
  • @Version: 1.0

*/
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {

@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;


private final SwaggerResourceProvider swaggerResources;

@Autowired
public SwaggerHandler(SwaggerResourceProvider swaggerResources) {
    this.swaggerResources = swaggerResources;
}


@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
    return Mono.just(new ResponseEntity<>(
            Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}

@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
    return Mono.just(new ResponseEntity<>(
            Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));

}

@GetMapping("/")
public Mono<ResponseEntity> swaggerResources() {

    return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));

}
复制代码

}

GateWay 网关application.yml配置

# 应用名称
#spring.application.name=station-gateway

###服务启动端口号
server:
  port: 8040
  #  ssl:
  #    key-password: sdadffff
  #    key-store: classpath:wx.sss.com.jks
  #    key-store-type: JKS
# 服务名称
spring:
  application:
    name: @artifactId@
    instance_id: @artifactId@
  # 开启 Gateway 服务注册中心服务发现
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lowerCaseServiceId: true
      routes:
        # 路由的ID
        - id: bus-org
          uri: lb://bus-org  # uri 代表路由的目标地址。注意:uri地址后面不要加 " / "
          predicates:
            - Path=/bus-org/**
        - id: station-auth
          uri: lb://bus-auth
          predicates:
            - Path=/bus-auth/**
        - id: bus-estate-service
          uri: lb://bus-estate-service
          predicates:
            - Path=/bus-estate-service/**
        - id: bus-web
          uri: lb://bus-web
          predicates:
            - Path=/bus-web/**
          filters:
            - StripPrefix=1
            - SwaggerHeaderFilter  #swagger过滤器
  mvc:
    static-path-pattern: /PC/**
  resources:
    static-locations:
      - file:${web.view}
  main:
    allow-bean-definition-overriding: true
  security:
    user:
      name: admin
      password: admin
  # redis 相关配置
  redis:
    database: 1
    port: 6379
    host: 192.168.110.105 # Redis服务器地址
#    password: huayue&123
    timeout: 5000ms     #连接超时时间(毫秒)
    lettuce:
      pool:
        max-active: 8   #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms  #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8     #连接池中的最大空闲连接
        min-idle: 2     #连接池中最小连接数
# Eureka Server 配置
eureka:
  instance:
    non-secure-port: ${server.port:1111}
    prefer-ip-address: true   # 指定ip 地址,默认为false
    instanceId: ${spring.application.name}:${spring.application.instance_id:${server.port}}  # 指定应用名称和应用端口
  client:
    service-url:
      defaultZone: http://localhost:${eureka.port:8761}/eureka/
# 配置Gateway日志等级,输出转发细节信息
logging:
  level:
    org.springframework.cloud.gateway: warn

secure:
  ignored:
    urls: #配置白名单路径
      - "/actuator/**"
      - "/bus-auth/auth/login"

#auth:
#  url: http://api-auth/SysUser/tokenToId?token=
web:
  view: E:/Source/Branch/web_project/

复制代码

业务模块启动类

package com.bus.cloud;

import com.bus.cloud.swagger.annotation.EnableSwagger;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;

/**
 * 巴士物业
 * @Author:         seata
 * @CreateDate:     2021/8/14 22:56
 * @UpdateUser:     seata
 * @UpdateDate:     2021/8/14 22:56
 * @UpdateRemark:   修改内容
 * @Version:        1.0
 */
@EnableSwagger
//@EnableFeignClients
@SpringCloudApplication
@MapperScan("com.bus.*.mapper")
public class BusEstateServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(BusEstateServiceApplication.class, args);
    }

}


复制代码

业务模块 application.yml 配置文件

# 切换配置文件
spring:
  profiles:
    active: test

swagger:
  enabled: true
  base-package: com.bus.cloud.controller
  version: 1.0.0
  title: 物业管理
  description: API文档自动生成


复制代码

20210904185510.png

你期待的结果是什么?实际看到的错误信息又是什么?

启动成功期望正常访问swagger在线接口文档,实际无任何输出,没有任何报错