星星博客 »  > 

微服务gateway聚合swagger文档解决

gateway网关聚合swagger2文档,由于gateway网关需要排除spring-web依赖,使用的是webFlux,故需要对swagger做一些配置

 

如下在对应的地方排除web依赖:

<exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>

增加swagger依赖:这个依赖包括了ui和swagger

 <!--knife4j版本的swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>

微服务通用模块中增加自定义注册swagger模块文档: 只有当配置了swagger扫描的服务会自动生成文档

package com.wm.blog_config.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.Properties;


/**
 * 文档配置
 */
@Configuration
@ConditionalOnProperty(name = "swagger.basepackage")
@EnableSwagger2
public class Knife4jConfig implements EnvironmentAware, WebMvcConfigurer {

    private String basePackage;
    private String createName;
    private String serviceName;
    private String description;


    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        //.title("swagger-bootstrap-ui-demo RESTful APIs")
                        .description(this.description)
                        .termsOfServiceUrl("http://www.xx.com/")
                        .contact(this.createName)
                        .version("2.0")
                        .build())
                //分组名称
                .groupName(this.serviceName)
                .select()
                 //这里指定Controller扫描包路径,从配置文件中读取
                .apis(RequestHandlerSelectors.basePackage(this.basePackage))
                .paths(PathSelectors.any())
                .build();
    }

    @Override
    public void setEnvironment(Environment environment) {
        Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
        Binder binder = new Binder(sources);
        BindResult<Properties> bindResult = binder.bind("swagger", Properties.class);
        Properties properties= bindResult.get();
        this.basePackage = properties.getProperty("basepackage");
        this.createName = properties.getProperty("service.developer");
        this.serviceName = properties.getProperty("service.name");
        this.description = properties.getProperty("service.description");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars*").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

在网关服务中增加转发处理逻辑:

package com.wm.blog_gateway.config.swagger;

import com.alibaba.fastjson.JSONObject;
import com.wm.blog_common.entity.TRoute;
import com.wm.blog_common.util.StringUtil;
import com.wm.blog_gateway.dao.TRouteDAO;
import com.wm.blog_gateway.vo.RoutePredicateVo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.*;

/**
 *
 */
@Slf4j
@Component
@Primary
@AllArgsConstructor
public class SwaggerResourceConfig implements SwaggerResourcesProvider {

    @Autowired
    private ApiDocConfig apiDocConfig;

    @Autowired
    private  RouteLocator routeLocator;

    @Autowired
    private TRouteDAO tRouteDAO;

    /**
     * 当请求文档服务时会调用这个接口,这里扩展我们转发逻辑和定义的文档名称
     * @return
     */
    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        // 取出gateway的route
        routeLocator.getRoutes().subscribe(route -> {
            // 根据文档提供者过滤
            if (apiDocConfig.getProviders().contains(route.getId()))
                routes.add(route.getId());
        });
        // 结合配置的route-路径(Path),和route过滤,只获取有效的route节点
        //查询数据库中所有的路由信息
        List<TRoute> routeList = Optional
                .ofNullable(tRouteDAO.list())
                .orElse(Collections.emptyList());
        if (CollectionUtils.isNotEmpty(routeList)) {
            for (TRoute route : routeList) {
                if (routes.contains(route.getRouteId())) {
                    if(StringUtil.isNotEmpty(route.getPredicates())){
                        List<RoutePredicateVo> routePredicateVoList = JSONObject.parseArray(route.getPredicates(), RoutePredicateVo.class);
                        try {
                            if (CollectionUtils.isNotEmpty(routePredicateVoList)) {
                                String genKey;
                                RoutePredicateVo routePredicateVo = routePredicateVoList.stream().filter(routePredicate -> routePredicate.getArgs().containsKey(NameUtils.GENERATED_NAME_PREFIX + "0")).findFirst().orElse(null);
                                if (routePredicateVo != null) {
                                    genKey = routePredicateVo.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", "/v2/api-docs?group=blog_admin");
                                    resources.add(swaggerResource(route.getRouteId(), genKey,"2.0"));
                                }
                            }
                        } catch (Exception e) {
                            log.error(e.getMessage(), e);
                        }
                    }
                }
            }
        }
        return resources;
    }

    private SwaggerResource swaggerResource(String name, String location, String version) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion(version);
        return swaggerResource;
    }

}

由于再gateway中不能使用@EnableSwagger等注解(因为它默认是spring mvc),故需要我们自定义给定访问controller,如下

package com.wm.blog_gateway.controller;

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.*;

import java.util.Optional;

/**
 * 因为Gateway里没有配置SwaggerConfig,而运行Swagger-ui又需要依赖一些接口 ,同时使用gateway后(用到了webFlux排除掉了web),不能引入@EnableSwagger等注解,故自定义swagger的接口
 * 因为Swagger暂不支持webflux项目,所以Gateway里不能配置SwaggerConfig
 */
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {

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

    @Autowired
    public SwaggerHandler(SwaggerResourcesProvider 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)));
    }
}

 

 

我的动态网关是配置在数据库中的:

在每个文档服务中配置,请求的路径:这是自定义swagger拉取接口的路径,默认是/v2/api-docs

 

springfox:
  documentation:
    swagger:
      v2:
        path: /blog/v2/api-docs

原因是swagger会调用如下的接口(swagger源码):

可以看到,是会优先读取你自定义的配置路径的,如果没有则默认它自己的路径:

我们测试下文档路径

ui页面如下

这篇博客不是网站的解决方案,只是指出关键点,具体可以查看我的项目地址,分支是dev-nacos

 

https://gitee.com/banjuanliunian/mock_blog

有问题欢迎指出

 

 

 

 

相关文章