gateway网关聚合knife4j文档,同时兼容swagger2与swagger3

基于前两篇文章,进行整合

springcloud-gateway 聚合swagger3请求接口丢失appliactionName解决

springcloud-gateway聚合knife4j接口文档

为何要兼容?微服务开发者有的使用了swagger2版本,有的使用了swagger3版本,但暴露外部给前端使用的,均统一走网关处

兼容核心点:swagger3 返回缺少bathPath

网关配置

image-20221201105700475

@Data
@ConfigurationProperties(prefix = MyGateWayProperties.PREFIX)
public class MyGateWayProperties {

    public static final String PREFIX = "gateway";

    /**
     * swagger3 服务列表
     */
    private List<String> swagger3Set;

    /**
     * 请求前缀
     */
    private String apiPrefix;
}

请求文档的SwaggerResourcesProvider 进行调整,带上自定义网关请求前缀与 swagger文档后缀

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

    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;

    @Autowired(required = false)
    private UiConfiguration uiConfiguration;

    private final SwaggerResourcesProvider provider;

    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider provider) {
        this.provider = provider;
    }

    @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<List<SwaggerResource>>> swaggerResources() {
        return Mono.just((new ResponseEntity<>(provider.get(), HttpStatus.OK)));
    }
}
/**
 * @author lei
 * @create 2022-05-25 10:01
 * @desc 网关处获取所有服务swagger文档
 **/
@Slf4j
@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {

    /**
     * swagger2默认的url后缀
     */
    public static final String V3_API_DOCS = "/v3/api-docs";
    public static final String V2_API_DOCS = "/v2/api-docs";


    /**
     * 路由加载器
     */
    private final RouteLocator routeLocator;
    private final MyGateWayProperties myGateWayProperties;

    /**
     * 网关应用名称
     */
    @Value("${spring.application.name}")
    private String gateway;

    @Autowired
    public SwaggerProvider(RouteLocator routeLocator, MyGateWayProperties myGateWayProperties) {
        this.routeLocator = routeLocator;
        this.myGateWayProperties = myGateWayProperties;
    }

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routeHosts = new ArrayList<>();
        Flux<Route> routes = routeLocator.getRoutes();
        // 获取所有可用的host,因为是注册表服务调用,所以实际是服务名
        routes.filter(route -> route.getUri().getHost() != null)
                .filter(route -> !gateway.equals(route.getUri().getHost()))
                .subscribe(route -> routeHosts.add(route.getUri().getHost()));

        // 去重下拉列表相同服务名文档,多个服务展示一个,当网关调用这个接口时,会自动通过负载均衡寻找服务host
        Set<String> docServerSet = new HashSet<>();
        routeHosts.forEach(instance -> {
            String url;
            if (myGateWayProperties.getSwagger3Set().contains(instance)) {
                // 拼接url. /serviceId/v3/api-docs
                url = myGateWayProperties.getApiPrefix() + "/" + instance.toLowerCase() + V3_API_DOCS;
            } else {
                // 拼接url. /serviceId/v2/api-docs
                url = myGateWayProperties.getApiPrefix() + "/" + instance.toLowerCase() + V2_API_DOCS;
            }
            if (!docServerSet.contains(url)) {
                docServerSet.add(url);
                SwaggerResource swaggerResource = new SwaggerResource();
                swaggerResource.setUrl(url);
                swaggerResource.setName(instance);
                resources.add(swaggerResource);
            }
        });
        return resources;
    }
}

网关添加全局过滤器,发现以访问swagger3后缀的url则重新返回体,携带上bathPath

/**
 * @author lei
 * @create 2022-05-26 14:36
 * @desc swagger过滤器
 * 解决swagger3 响应缺少bathPath,请求无法动态路由到服务的问题
 **/
@Log4j2
@Component
public class SwaggerGlobalFilter implements GlobalFilter, Ordered {

    public static final String BASE_PATH = "basePath";

    private final MyGateWayProperties myGateWayProperties;

    public SwaggerGlobalFilter(MyGateWayProperties myGateWayProperties) {
        this.myGateWayProperties = myGateWayProperties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().toString();
        if (!path.endsWith(SwaggerProvider.V3_API_DOCS)) {
            return chain.filter(exchange);
        }
        path = path.replace(myGateWayProperties.getApiPrefix(), "");
        String[] pathArray = path.split("/");
        String basePath = pathArray[1];
        ServerHttpResponse originalResponse = exchange.getResponse();
        ServerHttpResponseDecorator newResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (!Objects.equals(getStatusCode(), HttpStatus.OK)) {
                    return super.writeWith(body);
                }
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        List<String> list = new ArrayList<>();
                        dataBuffers.forEach(dataBuffer -> {
                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            DataBufferUtils.release(dataBuffer);
                            list.add(new String(content, StandardCharsets.UTF_8));
                        });
                        String jsonStr = listToString(list);
                        JSONObject jsonObject = JSON.parseObject(jsonStr);
                        jsonObject.put(BASE_PATH, myGateWayProperties.getApiPrefix() + "/" + basePath);
                        jsonStr = jsonObject.toString();
                        return bufferFactory().wrap(jsonStr.getBytes());
                    }));
                }

                return super.writeWith(body);
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                //由于修改了请求体的body,导致content-length长度不确定,因此使用分块编码
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                return httpHeaders;
            }

            private String listToString(List<String> list) {
                StringBuilder stringBuilder = new StringBuilder();
                for (String s : list) {
                    stringBuilder.append(s);
                }
                return stringBuilder.toString();
            }
        };
        return chain.filter(exchange.mutate().response(newResponse).build());
    }


    @Override
    public int getOrder() {
        return -2;
    }
}

网关获取到文档访问列表

image-20221201110418429

访问具体服务的文档,触发全局过滤器

image-20221201110720238

改写响应数据,访问swagger3文档,带上了bathPath

image-20221201110923634

image-20221201111821666

访问swagger2文档时,不受影响

image-20221201111130320

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Gateway网关聚合knife4j 4.x是一个基于Spring Cloud Gateway的API网关聚合工具,可以方便地将多个微服务的API文档聚合在一起,并提供统一的API文档页面。它使用了Knife4j 4.x作为API文档展示工具,支持多种文档格式展示,如Swagger、OpenAPI等。 使用Gateway网关聚合knife4j 4.x,可以大大简化API文档的维护工作,减少对多个微服务进行单独维护的工作量。同时,还可以提供一致的API文档展示风格,使得API文档更加易读易用。 要使用Gateway网关聚合knife4j 4.x,需要进行如下步骤: 1. 引入依赖:在Spring Cloud Gateway的pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>${knife4j.version}</version> </dependency> ``` 2. 配置Swagger文档:在每个微服务的配置文件中,配置Swagger文档的相关信息,如下所示: ```yaml spring: application: name: user-service swagger: enabled: true title: User Service API description: API for User Service version: 1.0 base-package: com.example.user.api ``` 3. 配置Gateway网关:在Gateway网关的配置文件中,配置路由规则,并将Swagger文档的请求进行聚合,如下所示: ```yaml spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/user/** filters: - RewritePath=/user/(?<path>.*), /$\{path} - StripPrefix=1 - id: api-docs uri: lb://user-service predicates: - Path=/v2/api-docs/** filters: - RewritePath=/v2/api-docs/(?<path>.*), /swagger-resources/configuration/ui - id: swagger-resources uri: lb://user-service predicates: - Path=/swagger-resources/** filters: - RewritePath=/swagger-resources/(?<path>.*), /swagger-resources/$\{path} - id: swagger-ui uri: lb://user-service predicates: - Path=/swagger-ui/** filters: - RewritePath=/swagger-ui/(?<path>.*), /swagger-ui/$\{path} ``` 4. 启动应用:启动应用后,访问Gateway网关Swagger文档页面即可看到聚合后的API文档。 以上就是使用Gateway网关聚合knife4j 4.x的基本步骤,希望对你有帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值