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 步骤
-
在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>
-
在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(); } }
-
在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 -->
-
在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(); } }
-
在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; } }
-
启动整个微服务,访问文档
访问网关的地址,加上/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同样的操作
-
修改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 -->
-
新增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可以并存”
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135275.html