java Spring Cloud Gateway如何通过Swagger UI公开其(动态)路由?

cl25kdpy  于 11个月前  发布在  Java
关注(0)|答案(1)|浏览(149)

Spring Cloud Gateway如何通过Swagger UI暴露其路由?这里是一个极简的静态Gateway:

package com.example.gatewaydemo.routing;

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.http.HttpMethod;

@Configuration
public class MyRoutingConfig {
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) {
        return routeLocatorBuilder.routes()
                .route(predicateSpec -> predicateSpec
                        .path("/uuid")
                        .and()
                        .method(HttpMethod.GET)
                        .uri("https://httpbin.org")
                ).build();
    }
}

字符串
在常规的Springweb应用程序中,添加Swagger依赖项就足够了

<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>3.0.0</version>
        </dependency>


但Gateway的情况并非如此
My Gateway使用Sping Boot 3和Java 17,因此我包含了此依赖项

<dependency>
   <groupId>org.springdoc</groupId>
   <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
   <version>2.0.2</version>
</dependency>


但是,很明显,我必须提供 * 一些 * 配置,因为如果我只是包含springdoc依赖项并包含@OpenApiDefinition注解,

package by.afinny.apigateway;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@OpenAPIDefinition(info = @Info(title = "API Gateway", version = "1.0", description = "Documentation API Gateway v1.0"))
public class ApiGatewayV2Application {
    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayV2Application.class, args);
    }
}


我在向/swagger-ui.html发出请求时得到了这个响应(它被重定向到/webjars/swagger-ui/index.html)。
无法加载远程配置


的数据
请注意,我动态地检索服务及其文档,因此static solutions类似于

springdoc:
  enable-native-support: true
  api-docs:
    enabled: true
  swagger-ui:
    enabled: true
    path: /swagger-ui.html
    config-url: /v3/api-docs/swagger-config
    urls:
      - url: /v3/api-docs
        name: API Gateway Service
        primaryName: API Gateway Service
      - url: /product-service/v3/api-docs
        name: Product Service
        primaryName: Product Service
      - url: /price-service/v3/api-docs
        name: Price Service
        primaryName: Price Service


也就是说,在启动时,我的网关不知道是否有product-serviceprice-service运行
从某种意义上说,我想,我的问题可以归结为:如何在Spring Cloud Gateway应用程序中为Swagger UI编写Java配置?

UPD

这是我尝试的

springdoc:
  api-docs:
    enabled: false
  swagger-ui:
    enabled: true
    path: /swagger-ui.html
    config-url: /swagger-ui-config
package by.afinny.apigateway.controller;

import by.afinny.apigateway.model.uiConfig.SwaggerUiConfig;
import by.afinny.apigateway.service.SwaggerUiConfigProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequiredArgsConstructor
public class SwaggerUiConfigController {
    private final SwaggerUiConfigProvider configProvider;
    @GetMapping("/swagger-ui-config")
    public Mono<SwaggerUiConfig> getConfig() {
        return configProvider.getSwaggerUiConfig();
    }
}
package by.afinny.apigateway.model.uiConfig;

import by.afinny.apigateway.model.documentedApplication.SwaggerApplication;
import by.afinny.apigateway.service.SwaggerApplicationSerializer;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.Collection;

@NoArgsConstructor
@Getter
public class SwaggerUiConfig {
    @JsonProperty("urls")
    @JsonSerialize(contentUsing = SwaggerApplicationSerializer.class) // because SwaggerApplication contains a ton of other properties
    private Collection<SwaggerApplication> swaggerApplications;

    public SwaggerUiConfig(Collection<SwaggerApplication> swaggerApplications) {
        this.swaggerApplications = swaggerApplications;
    }

    public static SwaggerUiConfig from(Collection<SwaggerApplication> swaggerApplications) {
        return new SwaggerUiConfig(swaggerApplications);
    }
}
package by.afinny.apigateway.service;

import by.afinny.apigateway.model.documentedApplication.SwaggerApplication;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;

import java.io.IOException;

public class SwaggerApplicationSerializer extends JsonSerializer<SwaggerApplication> {
    @Override
    @SneakyThrows
    public void serialize(SwaggerApplication swaggerApplication, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("url", swaggerApplication.getUrl());
        jsonGenerator.writeStringField("name", swaggerApplication.getName());
        jsonGenerator.writeEndObject();
    }
}

返回的JSON看起来正确

{
  "urls": [
    {
      "url": "/HELLOWORLD/v3/api-docs",
      "name": "HELLOWORLD"
    }
  ]
}



然而,我仍然得到相同的重定向和相同的“加载配置失败”消息,什么都没有改变。我的错误是什么?

44u64gxh

44u64gxh1#

您的网关也应该为文档提供服务

package com.example.dynamicgateway.controller;

import com.example.dynamicgateway.model.uiConfig.SwaggerUiConfig;
import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiSupport;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequiredArgsConstructor
public class SwaggerUiConfigController {
    private final SwaggerUiSupport uiSupport;

    @GetMapping("/swagger-ui-config")
    public Mono<SwaggerUiConfig> getConfig() {
        return uiSupport.getSwaggerUiConfig();
    }
}
package com.example.dynamicgateway.controller;

import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiSupport;
import io.swagger.v3.oas.models.OpenAPI;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequiredArgsConstructor
public class SwaggerDocController {
    private final SwaggerUiSupport uiSupport;

    @GetMapping("{application-name}/doc")
    public Mono<OpenAPI> getSwaggerAppDoc(@PathVariable("application-name") String applicationName) {
        // return cached OpenAPI object right away
        return uiSupport.getSwaggerAppDoc(applicationName);
    }
}
package com.example.dynamicgateway.model.uiConfig;

import com.example.dynamicgateway.model.documentedApplication.SwaggerApplication;
import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiConfigSerializer;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.Collection;

@NoArgsConstructor
@Getter
public class SwaggerUiConfig {
    @JsonProperty("urls")
    @JsonSerialize(contentUsing = SwaggerUiConfigSerializer.class)
    private Collection<SwaggerApplication> swaggerApplications;

    public SwaggerUiConfig(Collection<SwaggerApplication> swaggerApplications) {
        this.swaggerApplications = swaggerApplications;
    }

    public static SwaggerUiConfig from(Collection<SwaggerApplication> swaggerApplications) {
        return new SwaggerUiConfig(swaggerApplications);
    }
}
package com.example.dynamicgateway.service.swaggerUiSupport;

import com.example.dynamicgateway.model.documentedApplication.SwaggerApplication;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.SneakyThrows;

import java.text.MessageFormat;

public class SwaggerUiConfigSerializer extends JsonSerializer<SwaggerApplication> {
    @Override
    @SneakyThrows
    public void serialize(SwaggerApplication swaggerApplication, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) {
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("url", MessageFormat.format("/{0}/doc", swaggerApplication.getName()));
        jsonGenerator.writeStringField("name", swaggerApplication.getName());
        jsonGenerator.writeEndObject();
    }
}
gateway:
  servers:
    - url: https://localhost:8080
      description: Api-Gateway-V2
  v1Prefix: /api/v1

springdoc:
  swagger-ui:
    enabled: true
    path: /swagger-ui.html
    config-url: /swagger-ui-config

我不会包括整个代码,但这里是我的SwaggerUiSupportEndpointCollector是一个表示端点缓存的接口(从Eureka 应用程序收集的所有端点,假设它们具有Swagger/OpenApi/Springdoc依赖项)

package com.example.dynamicgateway.service.swaggerUiSupport;

import com.example.dynamicgateway.model.documentedApplication.DocumentedApplication;
import com.example.dynamicgateway.model.documentedApplication.SwaggerApplication;
import com.example.dynamicgateway.model.documentedEndpoint.DocumentedEndpoint;
import com.example.dynamicgateway.model.documentedEndpoint.SwaggerEndpoint;
import com.example.dynamicgateway.model.gatewayMeta.GatewayMeta;
import com.example.dynamicgateway.model.uiConfig.SwaggerUiConfig;
import com.example.dynamicgateway.service.endpointCollector.EndpointCollector;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.text.MessageFormat;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.Collectors;

@Component
@Slf4j
@RequiredArgsConstructor
public class BasicSwaggerUiSupport implements SwaggerUiSupport {
    private final EndpointCollector<SwaggerEndpoint> endpointCollector;
    private final GatewayMeta gatewayMeta;

    @Override
    public Mono<SwaggerUiConfig> getSwaggerUiConfig() {
        Set<SwaggerApplication> swaggerApps = endpointCollector.getKnownEndpoints().stream()
                .map(DocumentedEndpoint::getDeclaringApp)
                .collect(Collectors.toSet());
        return Mono.just(SwaggerUiConfig.from(swaggerApps));
    }

    @Override
    @SneakyThrows
    public Mono<OpenAPI> getSwaggerAppDoc(String appName) {
        return Mono.just(
                endpointCollector.getKnownEndpoints().stream()
                        .map(DocumentedEndpoint::getDeclaringApp)
                        .filter(documentedApplication -> documentedApplication.getName().equals(appName))
                        .map(DocumentedApplication::getNativeDoc)
                        .map(SwaggerParseResult::getOpenAPI)
                        .peek(this::setGatewayPrefixes)
                        .peek(this::setGatewayServers)
                        .findFirst()
                        .orElseThrow(() -> new IllegalArgumentException(MessageFormat.format(
                                "No service with name {0} is known to this Gateway", appName
                        )))
        );
    }

    private void setGatewayPrefixes(OpenAPI openAPI) {
        Paths newPaths = new Paths();
        for (Map.Entry<String, PathItem> pathItemEntry : openAPI.getPaths().entrySet()) {
            String servicePath = pathItemEntry.getKey();
            PathItem pathItem = pathItemEntry.getValue();

            String prefixedPath = servicePath;
            if (servicePath != null && !servicePath.startsWith(gatewayMeta.v1Prefix())) {
                String nonprefixedPath = endpointCollector.getKnownEndpoints().stream()
                        .filter(documentedEndpoint -> documentedEndpoint.getDetails().getPath().equals(servicePath))
                        .map(documentedEndpoint -> documentedEndpoint.getDetails().getNonPrefixedPath())
                        .findFirst()
                        .orElseThrow(() -> new NoSuchElementException(MessageFormat.format(
                                "No endpoint found. Requested path: {0}", servicePath
                        )));

                prefixedPath = gatewayMeta.v1Prefix() + nonprefixedPath;
            }
            newPaths.put(prefixedPath, pathItem);
        }
        openAPI.setPaths(newPaths);
    }

    private void setGatewayServers(OpenAPI openAPI) {
        openAPI.setServers(gatewayMeta.servers());
    }
}

相关问题