springcloud+swagger微服務環境下實現文檔管理

springcloud+swagger微服務環境下實現文檔管理

需求

springcloud是多個模塊的,怎麼用Swagger管理接口呢?

比如我的微服務有以下模塊

  • eureka

  • gateway(zuul)

  • user-service

  • order-service

其中user和order模塊需要暴露swagger文檔,那

  • 方案一:通過網關聚合成一個文檔,通過分組來切換不同模塊的文檔

  • 方案二:要訪問哪個模塊的文檔就去對應的模塊訪問

    • user模塊的文檔:http://127.0.0.1:8020/swagger-ui.html 或 http://127.0.0.1:8080/user/swagger-ui.html(通過網關)

    • order模塊的文檔:http://127.0.0.1:8010/swagger-ui.html 或 http://127.0.0.1:8080/order/swagger-ui.html(通過網關)

1. 方案一

1.1 步驟

  1. 在user和order的pom.xml裏,都要這麼操作:引入springfox-swagger2,無需引入ui,ui交給網。有包管理的請自行將版本提取到parent模塊裏

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.9.2</version>
      <exclusions>
        <exclusion>
          <groupId>io.swagger</groupId>
          <artifactId>swagger-models</artifactId>
        </exclusion>
        <exclusion>
          <groupId>io.swagger</groupId>
          <artifactId>swagger-annotations</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-models</artifactId>
      <version>1.6.0</version>
    </dependency>
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-annotations</artifactId>
      <version>1.6.0</version>
    </dependency>
    
  2. 在user和order模塊裏,建Swagger2Config配置類,內容如下(請根據具體的需求看下要不要修改basePackage,這是指定要掃描什麼路徑)

    package com.wyf.test.user.config;
    
    import io.swagger.models.Swagger;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @ConditionalOnClass(value = {Swagger.class})
    @Configuration
    @EnableSwagger2
    public class Swagger2Config {
        @Value("${spring.application.name}")
        private String applicationName;
    
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.wyf.test"))
                    .paths(PathSelectors.any())
                    .build();
        }
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title(applicationName + "接口文檔")
                    .description(applicationName + "接口文檔")
                    .version("1.0")
                    .build();
        }
    }
    
  3. 在Gateway的模塊的pom.xml裏新增swagger2和ui,兩個都要。有包管理的請自行將版本提取到parent模塊裏

    <!-- Swagger2 BEGIN -->
    <!--
            swagger2包中swagger-models版本有bug,example爲空串會爆出NumberFormatException,需要排除並引入高版本
            排除時將 swagger-models 和 swagger-annotations 整一對排除
            -->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.9.2</version>
      <exclusions>
        <exclusion>
          <groupId>io.swagger</groupId>
          <artifactId>swagger-models</artifactId>
        </exclusion>
        <exclusion>
          <groupId>io.swagger</groupId>
          <artifactId>swagger-annotations</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-models</artifactId>
      <version>1.6.0</version>
    </dependency>
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-annotations</artifactId>
      <version>1.6.0</version>
    </dependency>
    
    <!-- 不滿意可以註釋掉換其他UI,可以同時開啓多個UI -->
    <!-- 官方UI,請求路徑 http://{host}:{port}/swagger-ui.html -->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.9.2</version>
    </dependency>
    
    
    <!-- bootstrap-ui,請求路徑:http://{host}:{port}/doc.html,覺得是最好的UI -->
    <!-- <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>swagger-bootstrap-ui</artifactId>
                <version>1.9.6</version>
            </dependency> -->
    <!-- Swagger2 END -->
    
  4. 在Gateway模塊裏新增Swagger2Config的配置

    @Configuration
    @EnableSwagger2
    public class Swagger2Config {
        @Bean
        public Docket docket() {
            // basePackage 需要掃描註解生成文檔的路徑
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.wyf.test.gateway"))
                    .paths(PathSelectors.any())
                    .build();
        }
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("Swagger Entrance provided by GATEWAY")
                    .description("這是展示通過網關聚合所有子模塊的swagger文檔的例子")
                    .version("1.0").build();
        }
    }
    
  5. 在Gateway模塊,要增加 “發現Swagger服務的各個微服務” 的邏輯。這部分的作用就是取得微服務模塊,然後拼出這些微服務模塊的Swagger文檔的路徑。而且這個類定義爲@Primary,會替代默認的那個

    package com.wyf.test.gateway.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.netflix.zuul.filters.Route;
    import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
    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.ArrayList;
    import java.util.List;
    
    @Component
    @Primary
    public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider {
        @Autowired
        private RouteLocator routeLocator;
    
        @Override
        public List<SwaggerResource> get() {
            List<SwaggerResource> resources = new ArrayList<>();
            List<Route> routes = routeLocator.getRoutes();
    
            // 剛啓動Gateway的時候,Route只有2個,過個30秒,就註冊上來了,然後又4個,發現這4個有點重複
    
            // 2個
            // [{"id":"api-a","fullPath":"/user/**","path":"/**","location":"user-service","prefix":"/user","retryable":false,"sensitiveHeaders":[],"customSensitiveHeaders":false}
            // ,{"id":"api-b","fullPath":"/order/**","path":"/**","location":"order-service","prefix":"/order","retryable":false,"sensitiveHeaders":[],"customSensitiveHeaders":false}]
    
            // 4個(在排序上並不是按照id的字典升序,而是先會出現Gateway配置文件裏的)
            // [{"id":"api-a","fullPath":"/user/**","path":"/**","location":"user-service","prefix":"/user","retryable":false,"sensitiveHeaders":[],"customSensitiveHeaders":false}
            // ,{"id":"api-b","fullPath":"/order/**","path":"/**","location":"order-service","prefix":"/order","retryable":false,"sensitiveHeaders":[],"customSensitiveHeaders":false}
            // ,{"id":"order-service","fullPath":"/order-service/**","path":"/**","location":"order-service","prefix":"/order-service","retryable":false,"sensitiveHeaders":[],"customSensitiveHeaders":false}
            // ,{"id":"user-service","fullPath":"/user-service/**","path":"/**","location":"user-service","prefix":"/user-service","retryable":false,"sensitiveHeaders":[],"customSensitiveHeaders":false}]
    
    
            // 每個route會生成一個分組,因爲route有重複,我們需要去掉重複的,這樣界面好看點
            // 如果location一樣就認爲重複了,好像route的排序會優先api-a和api-b,並不是因爲api
            List<String> alreadyAdded = new ArrayList<>();
    
            for (Route route : routes) {
                if (!alreadyAdded.contains(route.getLocation())) {
                    // 構造文檔的路徑(因爲routes的順序是先出現Gateway配置文件裏的,它的prefix是 /user、/order 而不是 /user-service、/order-server 這些,所以使用前者是比較好的,不會逼死強迫症
                    // (如果使用後者,請求網關的接口會變成http://xxx/user-service/user/get,而顯然http://xxx/user/user/get纔是 "正統"
                    String docPath = route.getPrefix() + "/v2/api-docs";
    
                    // 用於顯示的分組名的字段,可自行決定
                    String groupName = route.getLocation();
                    resources.add(getSwaggerResource(groupName, docPath, "1.0"));
                }
                alreadyAdded.add(route.getLocation());
            }
            return resources;
        }
    
    
        private SwaggerResource getSwaggerResource(String name, String location, String version) {
            SwaggerResource swaggerResource = new SwaggerResource();
            swaggerResource.setName(name);
            swaggerResource.setLocation(location);
            swaggerResource.setSwaggerVersion(version);
            return swaggerResource;
        }
    }
    
  6. 啓動整個微服務,訪問文檔

    訪問網關的地址,加上/swagger-ui.html,如果你用了別的ui,請用他們的xxx.html。有時候會報錯,需要等待一會等微服務註冊好了就恢復了。效果頁面如圖,切換分組處可以看到各模塊的文檔,可以正常調試接口。

    在這裏插入圖片描述

1.2 這種方案的缺陷

  • 所有註冊到eureka裏的服務,都會被當做存在swagger文檔出現在Select a spec 列表裏,但是實際切換過去,當不存在的文檔時出錯

在這裏插入圖片描述

  • 微服務 “延遲” 的問題

    即微服務啓動後,有時候各服務並不是那麼及時註冊到註冊中心,存在延遲,所以swagger文檔可能會刷出錯誤,但是等一段時間(一般最多30秒左右)就能刷出來了。

  • 從模塊中調試接口,是實際請求服務,還是通過網關請求的?

    是通過網關的,看圖便知,另外默認不配置網關永不超時,會在斷點阻礙時間過長的時候斷開

    在這裏插入圖片描述

1.3 換UI的問題

同樣支持換UI,在網關處改swagger ui的GAV

1.4 對於多服務節點

假如eureka、user、order都佈置多節點,會不會有問題? 實測在這種情況下,Swagger文檔也是沒問題的。多個user節點,並不會產生多個Swagger文檔分組!

2. 方案二(不建議)

就是在user和order模塊裏,和單springboot集成swagger的方法沒區別。

以下對user和order同樣的操作

  1. 修改pom.xml(swagger2和ui都要引入)

    <!-- Swagger2 BEGIN -->
    <!--
    swagger2包中swagger-models版本有bug,example爲空串會爆出NumberFormatException,需要排除並引入高版本
    排除時將 swagger-models 和 swagger-annotations 整一對排除
    -->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.9.2</version>
      <exclusions>
        <exclusion>
          <groupId>io.swagger</groupId>
          <artifactId>swagger-models</artifactId>
        </exclusion>
        <exclusion>
          <groupId>io.swagger</groupId>
          <artifactId>swagger-annotations</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-models</artifactId>
      <version>1.6.0</version>
    </dependency>
    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-annotations</artifactId>
      <version>1.6.0</version>
    </dependency>
    
    <!-- 不滿意可以註釋掉換其他UI,可以同時開啓多個UI -->
    <!-- 官方UI,請求路徑 http://{host}:{port}/swagger-ui.html -->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.9.2</version>
    </dependency>
    
    
    <!-- bootstrap-ui,請求路徑:http://{host}:{port}/doc.html,覺得是最好的UI -->
    <!-- <dependency>
                    <groupId>com.github.xiaoymin</groupId>
                    <artifactId>swagger-bootstrap-ui</artifactId>
                    <version>1.9.6</version>
                </dependency> -->
    
    
    <!-- ui-layer,請求路徑:http://{host}:{port}/docs.html,覺得沒比原生的好 -->
    <!--<dependency>
                    <groupId>com.github.caspar-chen</groupId>
                    <artifactId>swagger-ui-layer</artifactId>
                    <version>1.1.3</version>
                </dependency>-->
    <!-- Swagger2 END -->
    
  2. 新增swagger配置(user和order模塊改下包掃描路徑,改下title)

    @Configuration
    @EnableSwagger2
    public class Swagger2Config {
        @Bean
        public Docket docket() {
            // basePackage 需要掃描註解生成文檔的路徑
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.wyf.test"))
                    .paths(PathSelectors.any())
                    .build();
        }
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("Order-service API")
                    .description("")
                    .version("1.0").build();
        }
    }
    

3. 如何共存多套UI

其實是可以共存多個UI的,實際測試可行。注意有時候會發現其中一個UI能刷出來另外一個不行,可以稍微等一等再試,或者調換下一下兩個UI的順序再試下。總之我遇到過 “其中一個UI可以另外一個不行” 的情況,但是最終發現 “兩個UI可以並存”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章