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

cl25kdpy  于 2024-01-05  发布在  Java
关注(0)|答案(1)|浏览(198)

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

  1. package com.example.gatewaydemo.routing;
  2. import org.springframework.cloud.gateway.route.RouteLocator;
  3. import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.http.HttpMethod;
  7. @Configuration
  8. public class MyRoutingConfig {
  9. @Bean
  10. public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder) {
  11. return routeLocatorBuilder.routes()
  12. .route(predicateSpec -> predicateSpec
  13. .path("/uuid")
  14. .and()
  15. .method(HttpMethod.GET)
  16. .uri("https://httpbin.org")
  17. ).build();
  18. }
  19. }

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

  1. <dependency>
  2. <groupId>io.springfox</groupId>
  3. <artifactId>springfox-boot-starter</artifactId>
  4. <version>3.0.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>io.springfox</groupId>
  8. <artifactId>springfox-swagger-ui</artifactId>
  9. <version>3.0.0</version>
  10. </dependency>


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

  1. <dependency>
  2. <groupId>org.springdoc</groupId>
  3. <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
  4. <version>2.0.2</version>
  5. </dependency>


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

  1. package by.afinny.apigateway;
  2. import io.swagger.v3.oas.annotations.OpenAPIDefinition;
  3. import io.swagger.v3.oas.annotations.info.Info;
  4. import org.springframework.boot.SpringApplication;
  5. import org.springframework.boot.autoconfigure.SpringBootApplication;
  6. @SpringBootApplication
  7. @OpenAPIDefinition(info = @Info(title = "API Gateway", version = "1.0", description = "Documentation API Gateway v1.0"))
  8. public class ApiGatewayV2Application {
  9. public static void main(String[] args) {
  10. SpringApplication.run(ApiGatewayV2Application.class, args);
  11. }
  12. }


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


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

  1. springdoc:
  2. enable-native-support: true
  3. api-docs:
  4. enabled: true
  5. swagger-ui:
  6. enabled: true
  7. path: /swagger-ui.html
  8. config-url: /v3/api-docs/swagger-config
  9. urls:
  10. - url: /v3/api-docs
  11. name: API Gateway Service
  12. primaryName: API Gateway Service
  13. - url: /product-service/v3/api-docs
  14. name: Product Service
  15. primaryName: Product Service
  16. - url: /price-service/v3/api-docs
  17. name: Price Service
  18. primaryName: Price Service


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

UPD

这是我尝试的

  1. springdoc:
  2. api-docs:
  3. enabled: false
  4. swagger-ui:
  5. enabled: true
  6. path: /swagger-ui.html
  7. config-url: /swagger-ui-config
  1. package by.afinny.apigateway.controller;
  2. import by.afinny.apigateway.model.uiConfig.SwaggerUiConfig;
  3. import by.afinny.apigateway.service.SwaggerUiConfigProvider;
  4. import lombok.RequiredArgsConstructor;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import reactor.core.publisher.Mono;
  8. @RestController
  9. @RequiredArgsConstructor
  10. public class SwaggerUiConfigController {
  11. private final SwaggerUiConfigProvider configProvider;
  12. @GetMapping("/swagger-ui-config")
  13. public Mono<SwaggerUiConfig> getConfig() {
  14. return configProvider.getSwaggerUiConfig();
  15. }
  16. }
  1. package by.afinny.apigateway.model.uiConfig;
  2. import by.afinny.apigateway.model.documentedApplication.SwaggerApplication;
  3. import by.afinny.apigateway.service.SwaggerApplicationSerializer;
  4. import com.fasterxml.jackson.annotation.JsonProperty;
  5. import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  6. import lombok.Getter;
  7. import lombok.NoArgsConstructor;
  8. import java.util.Collection;
  9. @NoArgsConstructor
  10. @Getter
  11. public class SwaggerUiConfig {
  12. @JsonProperty("urls")
  13. @JsonSerialize(contentUsing = SwaggerApplicationSerializer.class) // because SwaggerApplication contains a ton of other properties
  14. private Collection<SwaggerApplication> swaggerApplications;
  15. public SwaggerUiConfig(Collection<SwaggerApplication> swaggerApplications) {
  16. this.swaggerApplications = swaggerApplications;
  17. }
  18. public static SwaggerUiConfig from(Collection<SwaggerApplication> swaggerApplications) {
  19. return new SwaggerUiConfig(swaggerApplications);
  20. }
  21. }
  1. package by.afinny.apigateway.service;
  2. import by.afinny.apigateway.model.documentedApplication.SwaggerApplication;
  3. import com.fasterxml.jackson.core.JsonGenerator;
  4. import com.fasterxml.jackson.databind.JsonSerializer;
  5. import com.fasterxml.jackson.databind.SerializerProvider;
  6. import lombok.SneakyThrows;
  7. import java.io.IOException;
  8. public class SwaggerApplicationSerializer extends JsonSerializer<SwaggerApplication> {
  9. @Override
  10. @SneakyThrows
  11. public void serialize(SwaggerApplication swaggerApplication, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
  12. jsonGenerator.writeStartObject();
  13. jsonGenerator.writeStringField("url", swaggerApplication.getUrl());
  14. jsonGenerator.writeStringField("name", swaggerApplication.getName());
  15. jsonGenerator.writeEndObject();
  16. }
  17. }

返回的JSON看起来正确

  1. {
  2. "urls": [
  3. {
  4. "url": "/HELLOWORLD/v3/api-docs",
  5. "name": "HELLOWORLD"
  6. }
  7. ]
  8. }



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

44u64gxh

44u64gxh1#

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

  1. package com.example.dynamicgateway.controller;
  2. import com.example.dynamicgateway.model.uiConfig.SwaggerUiConfig;
  3. import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiSupport;
  4. import lombok.RequiredArgsConstructor;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import reactor.core.publisher.Mono;
  8. @RestController
  9. @RequiredArgsConstructor
  10. public class SwaggerUiConfigController {
  11. private final SwaggerUiSupport uiSupport;
  12. @GetMapping("/swagger-ui-config")
  13. public Mono<SwaggerUiConfig> getConfig() {
  14. return uiSupport.getSwaggerUiConfig();
  15. }
  16. }
  1. package com.example.dynamicgateway.controller;
  2. import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiSupport;
  3. import io.swagger.v3.oas.models.OpenAPI;
  4. import lombok.RequiredArgsConstructor;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import reactor.core.publisher.Mono;
  9. @RestController
  10. @RequiredArgsConstructor
  11. public class SwaggerDocController {
  12. private final SwaggerUiSupport uiSupport;
  13. @GetMapping("{application-name}/doc")
  14. public Mono<OpenAPI> getSwaggerAppDoc(@PathVariable("application-name") String applicationName) {
  15. // return cached OpenAPI object right away
  16. return uiSupport.getSwaggerAppDoc(applicationName);
  17. }
  18. }
  1. package com.example.dynamicgateway.model.uiConfig;
  2. import com.example.dynamicgateway.model.documentedApplication.SwaggerApplication;
  3. import com.example.dynamicgateway.service.swaggerUiSupport.SwaggerUiConfigSerializer;
  4. import com.fasterxml.jackson.annotation.JsonProperty;
  5. import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  6. import lombok.Getter;
  7. import lombok.NoArgsConstructor;
  8. import java.util.Collection;
  9. @NoArgsConstructor
  10. @Getter
  11. public class SwaggerUiConfig {
  12. @JsonProperty("urls")
  13. @JsonSerialize(contentUsing = SwaggerUiConfigSerializer.class)
  14. private Collection<SwaggerApplication> swaggerApplications;
  15. public SwaggerUiConfig(Collection<SwaggerApplication> swaggerApplications) {
  16. this.swaggerApplications = swaggerApplications;
  17. }
  18. public static SwaggerUiConfig from(Collection<SwaggerApplication> swaggerApplications) {
  19. return new SwaggerUiConfig(swaggerApplications);
  20. }
  21. }
  1. package com.example.dynamicgateway.service.swaggerUiSupport;
  2. import com.example.dynamicgateway.model.documentedApplication.SwaggerApplication;
  3. import com.fasterxml.jackson.core.JsonGenerator;
  4. import com.fasterxml.jackson.databind.JsonSerializer;
  5. import com.fasterxml.jackson.databind.SerializerProvider;
  6. import lombok.SneakyThrows;
  7. import java.text.MessageFormat;
  8. public class SwaggerUiConfigSerializer extends JsonSerializer<SwaggerApplication> {
  9. @Override
  10. @SneakyThrows
  11. public void serialize(SwaggerApplication swaggerApplication, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) {
  12. jsonGenerator.writeStartObject();
  13. jsonGenerator.writeStringField("url", MessageFormat.format("/{0}/doc", swaggerApplication.getName()));
  14. jsonGenerator.writeStringField("name", swaggerApplication.getName());
  15. jsonGenerator.writeEndObject();
  16. }
  17. }
  1. gateway:
  2. servers:
  3. - url: https://localhost:8080
  4. description: Api-Gateway-V2
  5. v1Prefix: /api/v1
  6. springdoc:
  7. swagger-ui:
  8. enabled: true
  9. path: /swagger-ui.html
  10. config-url: /swagger-ui-config

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

  1. package com.example.dynamicgateway.service.swaggerUiSupport;
  2. import com.example.dynamicgateway.model.documentedApplication.DocumentedApplication;
  3. import com.example.dynamicgateway.model.documentedApplication.SwaggerApplication;
  4. import com.example.dynamicgateway.model.documentedEndpoint.DocumentedEndpoint;
  5. import com.example.dynamicgateway.model.documentedEndpoint.SwaggerEndpoint;
  6. import com.example.dynamicgateway.model.gatewayMeta.GatewayMeta;
  7. import com.example.dynamicgateway.model.uiConfig.SwaggerUiConfig;
  8. import com.example.dynamicgateway.service.endpointCollector.EndpointCollector;
  9. import io.swagger.v3.oas.models.OpenAPI;
  10. import io.swagger.v3.oas.models.PathItem;
  11. import io.swagger.v3.oas.models.Paths;
  12. import io.swagger.v3.parser.core.models.SwaggerParseResult;
  13. import lombok.RequiredArgsConstructor;
  14. import lombok.SneakyThrows;
  15. import lombok.extern.slf4j.Slf4j;
  16. import org.springframework.stereotype.Component;
  17. import reactor.core.publisher.Mono;
  18. import java.text.MessageFormat;
  19. import java.util.Map;
  20. import java.util.NoSuchElementException;
  21. import java.util.Set;
  22. import java.util.stream.Collectors;
  23. @Component
  24. @Slf4j
  25. @RequiredArgsConstructor
  26. public class BasicSwaggerUiSupport implements SwaggerUiSupport {
  27. private final EndpointCollector<SwaggerEndpoint> endpointCollector;
  28. private final GatewayMeta gatewayMeta;
  29. @Override
  30. public Mono<SwaggerUiConfig> getSwaggerUiConfig() {
  31. Set<SwaggerApplication> swaggerApps = endpointCollector.getKnownEndpoints().stream()
  32. .map(DocumentedEndpoint::getDeclaringApp)
  33. .collect(Collectors.toSet());
  34. return Mono.just(SwaggerUiConfig.from(swaggerApps));
  35. }
  36. @Override
  37. @SneakyThrows
  38. public Mono<OpenAPI> getSwaggerAppDoc(String appName) {
  39. return Mono.just(
  40. endpointCollector.getKnownEndpoints().stream()
  41. .map(DocumentedEndpoint::getDeclaringApp)
  42. .filter(documentedApplication -> documentedApplication.getName().equals(appName))
  43. .map(DocumentedApplication::getNativeDoc)
  44. .map(SwaggerParseResult::getOpenAPI)
  45. .peek(this::setGatewayPrefixes)
  46. .peek(this::setGatewayServers)
  47. .findFirst()
  48. .orElseThrow(() -> new IllegalArgumentException(MessageFormat.format(
  49. "No service with name {0} is known to this Gateway", appName
  50. )))
  51. );
  52. }
  53. private void setGatewayPrefixes(OpenAPI openAPI) {
  54. Paths newPaths = new Paths();
  55. for (Map.Entry<String, PathItem> pathItemEntry : openAPI.getPaths().entrySet()) {
  56. String servicePath = pathItemEntry.getKey();
  57. PathItem pathItem = pathItemEntry.getValue();
  58. String prefixedPath = servicePath;
  59. if (servicePath != null && !servicePath.startsWith(gatewayMeta.v1Prefix())) {
  60. String nonprefixedPath = endpointCollector.getKnownEndpoints().stream()
  61. .filter(documentedEndpoint -> documentedEndpoint.getDetails().getPath().equals(servicePath))
  62. .map(documentedEndpoint -> documentedEndpoint.getDetails().getNonPrefixedPath())
  63. .findFirst()
  64. .orElseThrow(() -> new NoSuchElementException(MessageFormat.format(
  65. "No endpoint found. Requested path: {0}", servicePath
  66. )));
  67. prefixedPath = gatewayMeta.v1Prefix() + nonprefixedPath;
  68. }
  69. newPaths.put(prefixedPath, pathItem);
  70. }
  71. openAPI.setPaths(newPaths);
  72. }
  73. private void setGatewayServers(OpenAPI openAPI) {
  74. openAPI.setServers(gatewayMeta.servers());
  75. }
  76. }

展开查看全部

相关问题