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可以并存”

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值