在开发过程中, 多个微服务, 多个接口文档时,发现每次访问都很麻烦;所以根据网上的一些资料, 做了gateway聚合swagger2;spring cloud 版本: 2.1.3, 服务发现使用的是nacos;spring cloud项目新建就先略过;
目录如下:
spring-cloud-nooyoo
    |- nooyoo-auth (服务1 端口:8080)
    |- nooyoo-user (服务2 端口:8081)
    |- nooyoo-gateway (网关 端口:7000)

项目pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nooyoo</groupId>
    <artifactId>spring-cloud-nooyoo</artifactId>
    <packaging>pom</packaging>
    <version>1.0</version>
    <name>spring-cloud-nooyoo</name>

    <modules>
        <module>nooyoo-auth</module>
        <module>nooyoo-user</module>
        <module>nooyoo-gateway</module>
    </modules>

    <properties>
        <spring-boot.version>2.1.3.RELEASE</spring-boot.version>
        <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.3</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>0.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <!--阿里云主仓库,代理了maven central和jcenter仓库-->
        <repository>
            <id>aliyun</id>
            <name>aliyun</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <!--阿里云代理Spring 官方仓库-->
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://maven.aliyun.com/repository/spring</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <!--阿里云代理Spring 插件仓库-->
        <pluginRepository>
            <id>spring-plugin</id>
            <name>spring-plugin</name>
            <url>https://maven.aliyun.com/repository/spring-plugin</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

</project>

首先是nooyoo-auth模块, 新增swagger2配置:

/**
 * Created by NeeYoo.
 * Created on 2019/7/29 下午9:50.
 * Description: swagger文档配置
 */
@Configuration
@EnableSwagger2
public class Swagger2Config {

    @Bean(name = "nooyoo-auth")
    public Docket createRestApi() {
        //=====添加head参数start============================
        ParameterBuilder parameter1 = new ParameterBuilder();
        ParameterBuilder parameter2 = new ParameterBuilder();
        ParameterBuilder parameter3 = new ParameterBuilder();
        List<Parameter> pars = new ArrayList<Parameter>();
        parameter1.name("Authorization").description("Authorization令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
        parameter2.name("userId").description("用户Id").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
        parameter3.name("userPhone").description("用户电话").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
        pars.add(parameter1.build());
        pars.add(parameter2.build());
        pars.add(parameter3.build());
        // =========添加head参数end===================
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.nooyoo"))
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                .globalOperationParameters(pars).groupName("nooyoo-auth"); // 分组

    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("auth-接口文档")
                .description("接口文档-鉴权服务")
                .termsOfServiceUrl("http://localhost:8080/auth/doc.html")
                .contact(new Contact("nooyoo", "", ""))
                .version("v1.0")
                .build();
    }

}

nooyoo-auth模块的pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-nooyoo</artifactId>
        <groupId>com.nooyoo</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>nooyoo.auth</groupId>
    <artifactId>nooyoo-auth</artifactId>
    <version>1.0</version>

    <dependencies>

        <!--Nacos-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--springboot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <fork>true</fork>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <compilerArguments>
                        <verbose />
                        <bootclasspath>${java.home}\lib\rt.jar;${java.home}\lib\jce.jar</bootclasspath>
                    </compilerArguments>
                    <compilerArgs>
                        <arg>-extdirs</arg>
                        <arg>${project.basedir}/src/lib/*.jar</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

写一个controller, 后面测试用 

/**
 * Created by NooYoo.
 * Created on 2019/7/29 下午9:31.
 * Description:
 */
@Slf4j
@RestController
@Api(tags = "auth-接口文档")
public class TestController {

    @ApiOperation(value = "auth-测试接口", notes = "测试接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "a", paramType = "path", value = "数字a", required = true, dataType = "path"),
            @ApiImplicitParam(name = "b", paramType = "path", value = "数字b", required = true, dataType = "path")
    })
    @GetMapping("/{a}/{b}")
    public Integer get(@PathVariable Integer a, @PathVariable Integer b) {
        log.info("auth文档访问");
        return a + b;
    }
}

同理操作neeyoo-user模块, 你也可以随便写2个微服务, 主要是通过网关能直接做不同微服务间的测试;
然后启动微服务, 先单独访问各自的接口文档(文档接口用了swgger的bootstrapUI, 访问地址是:localhost:8080/user/doc.html),记得把neeyoo-user模块中swagger配置改成这个groupName("nooyoo-user")

下面就是关键点, neeyoo-gateway模块中, 目录结构如下:

关键代码, 直接上代码:

# 网关配置
spring:
  profiles: local
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        file-extension: yml
    gateway:
      locator:
        enabled: true
      routes:
      - id: nooyoo-auth
        uri: lb://nooyoo-auth
        predicates:
        - Path=/auth/**
        filter:
        - SwaggerHeaderFilter
        - StripPrefix = 1
      - id: nooyoo-user
        uri: lb://nooyoo-user
        predicates:
        - Path=/user/**
        filter:
        - SwaggerHeaderFilter
        - StripPrefix = 1
/**
 * Created by NooYoo.
 * Created on 2019/7/29 下午10:39.
 * Description: Swagger-ui需要依赖的一些接口
 */
@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)));
    }
}
/**
 * Created by NooYoo.
 * Created on 2019/7/30 下午10:09.
 * Description: 拦截器
 */
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {

    private static final String HEADER_NAME = "X-Forwarded-Prefix";

    private static final String HOST_NAME = "X-Forwarded-Host";

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
                return chain.filter(exchange);
            }
            String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(newExchange);

        };

    }
}
/**
 * Created by NooYoo.
 * Created on 2019/7/29 下午10:33.
 * Description: 因为Swagger暂不支持webflux项目,所以Gateway里不能配置SwaggerConfig,也就是说Gateway无法提供自身API。
 * 配置SwaggerProvider,获取Api-doc,即SwaggerResources
 */
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {

    public static final String API_URI = "/v2/api-docs";
    public static final String NEW_API_URI = "/v2/api-docs?group="; // 这里访问设置的分组文档
    private final RouteLocator routeLocator;
    private final GatewayProperties gatewayProperties;

    @Override
    public List<SwaggerResource> get() {
        List<SwaggerResource> resources = new ArrayList<>();
        List<String> routes = new ArrayList<>();
        //取出gateway的route
        routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
        //结合配置的route-路径(Path),和route过滤,只获取有效的route节点
        gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
                .forEach(routeDefinition -> routeDefinition.getPredicates().stream()
                        .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
                        .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
                                predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
                                        .replace("/**", NEW_API_URI + routeDefinition.getId())))));
        return resources;
    }

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

我发现当我通过gateway访问文档时, 接口报404错误, 于是我看是什么接口导致的访问异常,后来看到这样一个接口:localhost:7000/auth/v2/api-docs?group=nooyoo-auth(直接访问该接口是有json数据返回的), 于是打断点调试, 在SwaggerProvider.java中返回的路径并没被替换掉,原来的写法是这样

gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
                .forEach(routeDefinition -> routeDefinition.getPredicates().stream()
                        .filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
                        .forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
                                predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
                                        .replace("/**", API_URI)))));

于是我就将API_URI重新替换为: NEW_API_URI + routeDefinition.getId()

最后重新调试, 搞定, 在替换url那里, 取来一个巧, 文档的groupName跟gateway中routeid一样, 最终也达到来想要的效果;

最后上一个效果图:

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐