SpringBoot使用Gateway聚合Springdoc,Knife4j

2,432 阅读4分钟

SpringBoot使用Gateway聚合Springdoc,Knife4j

前言

同时支持springboot:3.0,springboot:2.0,使用gateway聚合springdoc,ui使用knife4j,解决由于nginx配置代理前缀导致的文档无法访问,不强依赖注册中心(nacos,zk,Eureka)

有帮助的话记得点个赞哟!!!

基础环境

将所有依赖集成好作为一个本地包供其他项目使用

  • jdk17
  • maven3.6+
  • springboot3.0+|springboot2.0+
  • springcloud:2022.0.1
  • springcloud-alibaba:1.8.1-2022.0.0-RC2
  • springdoc:2.0.2|springdoc:1.6.0+
  • knife4j:4.0.0

当使用springboot:3.0,请使用 springdoc:2.0.0+ 当使用springboot:2.0,请使用 springdoc:1.6.0+(部分springdoc的包名替换即可)

工具包(framework-document)

依赖

<dependencies>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webflux-api</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

代码

文档信息properties
import com.github.mpcloud.framework.core.consts.Constant;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * The type Swagger properties.
 *
 * @author : Milo
 */
 @Data
@ConfigurationProperties("springdoc.info")
public class DocumentInfoProperties {
    private static final String DEFAULT_OPEN_API_ROUTE_NAME = "open-api-route";
    private static final String DEFAULT_GATEWAY_API_ROUTE_NAME = "open-api-gateway-route";
    /**
     * 文档标题
     */
    private String title;
    /**
     * 文档描述
     */
    private String description;
    /**
     * 项目version
     */
    private String projectVersion;
    /**
     * 许可证
     */
    private String license;
    /**
     * 许可证URL
     */
    private String licenseUrl;
    /**
     * 项目负责人信息
     */
    private final Contact contact = new Contact();
    /**
     * gateway
     */
    private final Gateway gateway = new Gateway();

    @Data
    public static class Contact {
        /**
         * 联系人
         **/
        private String name;
        /**
         * 联系人url
         **/
        private String url;
        /**
         * 联系人email
         **/
        private String email;
    }

    @Data
    public static class Gateway {
        /**
         * api 前缀
         */
        private String apiPathPrefix = Constant.URL_PREFIX;
        /**
         * 接口文档路由名称
         */
        private String openApiRouteName = DEFAULT_OPEN_API_ROUTE_NAME;
        /**
         * 网关接口文档路由名称
         */
        private String openApiGatewayApiRouteName = DEFAULT_GATEWAY_API_ROUTE_NAME;
    }
}
文档信息Configuration
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author milo
 */
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(DocumentInfoProperties.class)
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true")
@ConditionalOnWebApplication
public class DocumentConfiguration {
    @Bean
    public OpenAPI openApi(final DocumentInfoProperties docInfo) {
        return new OpenAPI().info(new Info()
            .title(docInfo.getTitle())
            .description(docInfo.getDescription())
            .version(docInfo.getProjectVersion())
            .contact(new Contact().name(docInfo.getContact().getName()).email(docInfo.getContact().getEmail()).url(docInfo.getContact().getUrl()))
            .license(new License().name(docInfo.getLicense()).url(docInfo.getLicenseUrl()))
        );
    }
}
解决Nginx代理导致的接口前缀问题

解决代理导致的接口文档请求不了的问题 如果nginx配置的以下内容,则需要如下类解决这个问题.

		location /api{
			proxy_pass http://localhost:8080;
			rewrite "^/api/(.*)$" /$1 break;
			proxy_set_header Host $host:$server_port;
		}

复写原有地址前缀

import com.github.mpcloud.framework.core.utils.PathUtils;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.webflux.core.providers.SpringWebFluxProvider;

/**
 * @author : Milo
 */
public class CustomizeSpringWebFluxProvider extends SpringWebFluxProvider {
    @Override
    public String findPathPrefix(SpringDocConfigProperties springDocConfigProperties) {
        DocumentInfoProperties documentInfoProperties = applicationContext.getBean(DocumentInfoProperties.class);
        return PathUtils.append(documentInfoProperties.getGateway().getApiPathPrefix() + super.findPathPrefix(springDocConfigProperties));
    }
}
网关与SpringDoc整合的配置类

import com.github.mpcloud.framework.core.consts.Constant;
import com.github.mpcloud.framework.core.utils.PathUtils;
import com.github.mpcloud.framework.core.utils.spring.SpringContextHolder;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties.SwaggerUrl;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigParameters;
import org.springdoc.core.providers.SpringWebProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;

import java.util.List;

import static org.springdoc.core.utils.Constants.SPRINGDOC_ENABLED;
import static org.springdoc.core.utils.Constants.SPRINGDOC_SWAGGER_UI_ENABLED;
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;

/**
 * @author : Milo
 */
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnProperty(name = { SPRINGDOC_ENABLED, SPRINGDOC_SWAGGER_UI_ENABLED }, matchIfMissing = true, havingValue = "true")
public class GatewayRouterGroupProviderConfiguration {
    private final DiscoveryClient discoveryClient;
    private final SpringDocConfigProperties springDocConfig;
    private final DocumentInfoProperties documentInfo;
    private final SwaggerUiConfigParameters swaggerUiConfig;

    public GatewayRouterGroupProviderConfiguration(DiscoveryClient discoveryClient, SpringDocConfigProperties springDocConfig, DocumentInfoProperties documentInfo, SwaggerUiConfigParameters swaggerUiConfig) {
        this.discoveryClient = discoveryClient;
        this.springDocConfig = springDocConfig;
        this.documentInfo = documentInfo;
        this.swaggerUiConfig = swaggerUiConfig;
    }

    /**
     * openapi 路由(重写服务名所在位置)
     *
     * @param builder 路由构造器
     *
     * @return 路由
     */
    @Bean
    public RouteLocator openApiRouteLocator(final RouteLocatorBuilder builder) {
        return builder.routes()
            .route(this.documentInfo.getGateway().getOpenApiRouteName(), route -> route
                .path(this.springDocConfig.getApiDocs().getPath() + "/**")
                .filters(filter -> filter.rewritePath(this.springDocConfig.getApiDocs().getPath() + "/(?<segment>.*)", "/$\{segment}" + this.springDocConfig.getApiDocs().getPath()))
                .uri(Constant.HTTP_PREFIX + "localhost:" + SpringContextHolder.getServerPort())
            )
            .build();
    }

    /**
     * 网关当前路由 (去掉服务名)
     *
     * @param builder 路由构造器
     *
     * @return 路由
     */
    @Bean
    public RouteLocator registrationRouteLocator(Registration registration, final RouteLocatorBuilder builder) {
        return builder.routes()
            .route(this.documentInfo.getGateway().getOpenApiRouteName(), route -> route
                .path(DEFAULT_PATH_SEPARATOR + registration.getServiceId() + "/**")
                .filters(filter -> filter.rewritePath(DEFAULT_PATH_SEPARATOR + registration.getServiceId() + "/(?<segment>.*)", "/$\{segment}"))
                .uri(Constant.HTTP_PREFIX + "localhost:" + SpringContextHolder.getServerPort())
            )
            .build();
    }

    /**
     * Spring web provider spring web provider.
     *
     * @return the spring web provider
     */
    @Bean
    SpringWebProvider springWebProvider() {
        return new SpringGatewayProvider();
    }

    @EventListener(classes = { ApplicationReadyEvent.class, HeartbeatEvent.class, RefreshRoutesEvent.class })
    public synchronized void discover() {
        List<SwaggerUrl> swaggerUrls = this.discoveryClient.getServices()
            .stream()
            .map(instance -> {
                final SwaggerUrl swaggerUrl = new SwaggerUrl();
                swaggerUrl.setName(instance);
                swaggerUrl.setDisplayName(instance);
                swaggerUrl.setUrl(PathUtils.append(this.documentInfo.getGateway().getApiPathPrefix(), this.springDocConfig.getApiDocs().getPath(), instance));
                return swaggerUrl;
            })
            .toList();
        swaggerUiConfig.getUrls().clear();
        swaggerUiConfig.getUrls().addAll(swaggerUrls);
    }
}

使用工具包

gateway中使用

依赖
<dependencies>
    <!--上面的步骤所构建的包(这个是我本地的,请替换为自己写的)-->
    <dependency>
        <groupId>com.github.mpcloud</groupId>
        <artifactId>framework-document</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-openapi3-ui</artifactId>
    </dependency>
</dependencies>
配置
server:
  port: 8080
spring:
  application:
    name: mpcloud-gateway
  cloud:
    nacos:
      username: nacos
      password: nacos
      discovery:
        server-addr: localhost:8848
        namespace: mpcloud
        cluster-name: shanghai
        heart-beat-timeout: 40000
        heart-beat-interval: 20000
        ip-delete-timeout: 80000
      config:
        server-addr: localhost:8848
        namespace: mpcloud
        timeout: 3000
        file-extension: yml
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      globalcors:
        cors-configurations:
          '[/**]':
            allowedHeaders: '*'
            allowedMethods: '*'
            allowedOrigins: '*'
      httpclient:
        connect-timeout: 2000
        response-timeout: 60s
  main:
    banner-mode: off
  webflux:
    multipart:
      file-storage-directory: /tmp
      max-in-memory-size: 12MB
    format:
      date-time: yyyy-MM-dd HH:mm:ss
      date: yyyy-MM-dd
      time: HH:mm:ss
springdoc:
  api-docs:
    enabled: true
    groups:
      enabled: true
    path: /v3/api-docs
  swagger-ui:
    enabled: true
  info:
    title: 网关Api文档
    description: 网关Api文档
    project-version: 0.0.1
    license: https://milo-xiaomeng.github.io
    license-url: https://milo-xiaomeng.github.io
    contact:
      name: milo
      url: http://localhost:8080
      email: milo.xiaomeng@gmail.com
    gateway:
      # 如果Nginx设置了代理路径前缀,则此处位置必须为对应前缀 如果没有前缀,可以不设置或者为 /
      # 如Nginx如下配置
      # location /api{
      #   proxy_pass http://localhost:8080;
      #   rewrite "^/api/(.*)$" /$1 break;
      #   proxy_set_header Host $host:$server_port;
      # }
      api-path-prefix: /api
  show-actuator: true

web服务中使用

依赖
dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <!--上面的步骤所构建的包(这个是我本地的,请替换为自己写的)-->
    <dependency>
        <groupId>com.github.mpcloud</groupId>
        <artifactId>framework-document</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
    </dependency>
</dependencies>
配置
server:
  port: 8083
spring:
  application:
    name: mpcloud-monitoring
  profiles:
    active: dev
  cloud:
    nacos:
      username: nacos
      password: nacos
      discovery:
        server-addr: localhost:8848
        namespace: mpcloud
        cluster-name: shanghai
      config:
        server-addr: localhost:8848
        namespace: mpcloud
        timeout: 3000
        file-extension: yml
springdoc:
  api-docs:
    enabled: true
    groups:
      enabled: true
    path: /v3/api-docs
  info:
    title: 监控Api文档
    description: 监控Api文档
    project-version: 0.0.1
    license: mpcloud.github.com
    license-url: mpcloud.github.com
    contact:
      name: milo
      url: http://localhost:8080
      email: milo.xiaomeng@gmail.com
  show-actuator: true

效果

image.png