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; } }