Spring Cloud 项目中使用 Swagger

  • 关于方案的选择
    在 Spring Cloud 项目中使用 Swagger 有以下 4 种方式:

  • 方式一 :在网关处引入 Swagger ,去聚合各个微服务的 Swagger。未来是访问网关的 Swagger 原生界面。

  • 方式二 :在网关处引入 knife4j,去聚合各个微服务的 Swagger。未来是访问网关的 knife4j 的美化版界面。

  • 方式三 :使用 knife4j 去创建一个独立的聚合项目,去聚合各个微服务的 Swagger。未来是访问这个聚合项目的 knife4j 的美化版界面。

  • 方式四 :使用独立的、另外的第三方工具(例如,Apifox),去聚合各个微服务的 Swagger。未来是访问这个第三方工具。

在上述 4 种方案中,我们选择的是方案四,原因在于:

  • 对于方案一方案二而言,本质上是一样的,主要无非就是页面美不美观的问题。knife4j 的页面要强过 swagger 的原生界面的,但和第三方工具比起来,还是远不如第三方插件好看(和功能丰富)。
  • knife4j 对原生 Swagger 有所拓展,提升了便捷性,但是提升有效。如果抛开“页面美观”这个主要优点,其它有关“拓展”层面的优点并没有那么多的“非用不可”。
  • 方案三的 knife4j 的聚合项目虽然能够很方便的集合各个微服务,使用上要方便于网关聚合,但是,你是用它就意味着你的测试请求都绕过了网关,如果你在网管处有代码逻辑,那么这部分代码就“跳”过去了,不利于测试。

所以,从美观、完善各方面综合考虑,我们采用上面的方案四

使用方案四意味着:

  • 我们的项目中只需要引入 swagger 的包,不需要引入 knife4j 的包。
  • 如果我们不需要在网关处看到原生的 swagger 页面,那么网关项目不需要有任何改动。原生的 swagger 页面,那么网关项目不需要有任何改动。

各个微服务的改动

改动一:引包和配置文件
<dependency>  
    <groupId>com.github.xiaoymin</groupId>  
    <artifactId>knife4j-spring-boot-starter</artifactId>  
    <version>3.0.3</version>  
    <!--<version>2.0.9</version>-->  
</dependency>
# knife4j公共配置  
knife4j.enable=true
改动二:新增配置类
@Configuration  
@EnableOpenApi  
@RequiredArgsConstructor  
public class SwaggerConfiguration {  
  
    private final OpenApiExtensionResolver openApiExtensionResolver;  
  
    @Value("${spring.application.name}")  
    private String applicationName;  
  
    @Bean  
    @Order(value = 1)  
    public Docket docDocket() {  
        return new Docket(DocumentationType.OAS_30)  
                .pathMapping("/" + applicationName) // ==> /department-service  
                .enable(true)  
                .apiInfo(groupApiInfo())  
                .select()  
                .apis(RequestHandlerSelectors.withMethodAnnotation(Operation.class))  
                .paths(PathSelectors.any())  
                .build()  
                .extensions(openApiExtensionResolver.buildExtensions("部门微服务"))  
                ;  
    }  
  
    private ApiInfo groupApiInfo() {  
        return new ApiInfoBuilder()  
                .title("Knife4j接口文档")  
                .description("Knife4j接口文档")  
                .termsOfServiceUrl("https://doc.xiaominfo.com/")  
                .version("1.0.0")  
                .build();  
    }
}
关键项说明

上述内容,无论是 pom 引包,还是加配置类,绝大部分内容都是复制粘贴,无需改动的。

但是在配置类中,有一个信息必须注意,它必须和你的 spring-cloud 的配置相关:

.pathMapping("/" + applicationName) 

.pathMapping() 方法的值表示的是:Swagger 对外暴露的测试功能中,在原本的(@RestController)的 URI 之外,额外加上一段什么样的前缀。

为什么会有这样的要求?

未来,无论是在 Apifox 这样的第三方工具中测试,还是在网关处的原生的 Swagger 页面上进行测试,我们的测试请求都是应该发送给网关的,再由网关将请求路由给微服务。

所以,在 Apifox 中,或者是网管处的原生的 Swagger 中,我们发送请求的“前一段”的 IP 和 Port 的组合是网关的 IP 和 Port 。而网关在默认情况下则是根据它所收到的“请求的 URI 中的前一段和微服务的服务名的匹配情况”作为依据来路由请求。

如果,各个微服务的 Swagger 的配置中没有主动的多“加上”一段能路由到自己的 URI 前缀,那么 Swagger 所暴露出来的请求测试功能所产生的拼接出来的 URI 就成了:网关的 IP 和 Port 拼上微服务的 URI,而没有微服务标识那一段。例如:

## 127.0.0.1:10000 是网关地址
http://127.0.0.1:10000/department/delete

但是,从上帝视角看,本应该是:

## 127.0.0.1:10000 是网关地址
http://127.0.0.1:10000/department-service/department/delete

只有这样,Swagger 所暴露出来的测试功能才能正常使用。

所以,这就需要 department-service 自己在它的 Swagger 配置中说明:在自己的 Swagger 暴露的测试功能中,需要在正常的 URI 前面“多”加上 /department-service 前缀,这样才能让 Swagger 暴露的 URI 经过网关后正常路由到自己这里来。

网关处的改动

注意
如果你不指望在网关处看到、访问原生的 Swagger 界面,那么,这一步操作就不是必须的,不用做。

引入 swagger 包
<dependency>  
    <groupId>io.springfox</groupId>  
    <artifactId>springfox-boot-starter</artifactId>  
    <version>3.0.0</version>  
</dependency>
新增配置类
@Primary  
@Configuration  
@EnableOpenApi  
@RequiredArgsConstructor  
public class SwaggerConfig implements SwaggerResourcesProvider {  
  
    private static final String OAS_30_URL = "/v3/api-docs";  
  
    private final RouteLocator routeLocator;  
  
    private final GatewayProperties gatewayProperties;  
  
    /**  
     * 网关应用名称     
     */
    @Value("${spring.application.name}")  
    private String self;  
  
    @Override  
    public List<SwaggerResource> get() {  
        List<SwaggerResource> resources = new ArrayList<>();  
        List<String> routeHosts = new ArrayList<>();  
        routeLocator.getRoutes()  
                .filter(route -> route.getUri().getHost() != null)  
                .filter(route -> Objects.equals(route.getUri().getScheme(), "lb"))  
                // 过滤掉网关自身的服务 uri 中的 host 就是服务 id 
                .filter(route -> !self.equalsIgnoreCase(route.getUri().getHost()))  
                .subscribe(route -> routeHosts.add(route.getUri().getHost()));  
  
        // 记录已经添加过的server,存在同一个应用注册了多个服务在注册中心上  
        Set<String> dealed = new HashSet<>();  
        routeHosts.forEach(instance -> {  
            // 拼接 url ,目标 swagger 的 url  
            String url = "/" + instance.toLowerCase() + OAS_30_URL;  
            System.out.println("url: " + url);  
            if (!dealed.contains(url)) {  
                dealed.add(url);  
                SwaggerResource swaggerResource = new SwaggerResource();  
                swaggerResource.setUrl(url);  
                swaggerResource.setName(instance);  
                //swaggerResource.setSwaggerVersion("3.0.3");  
                resources.add(swaggerResource);  
            }  
        });  
        return resources;  
    }  
  
}

Apifox 引入

image.png

image.png

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐