使用 Zuul 来聚合多个微服务的 Knife4j 文档,将其整合为一个统一的文档界面。Knife4j 是基于 Swagger 增强的工具,提供更友好的界面和更丰富的功能。
1. 在每个微服务中配置 Knife4j
1.1 3.0版本的微服务
目标:确保每个微服务都集成并启用了 Knife4j,以暴露其 API 文档。
操作:
在每个微服务的项目中添加Knife4j 的依赖。例如,对于 Spring Boot 项目,可以在 pom.xml 中添加:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
配置一个 Docket bean 来定义 API 文档。例如:
//这里声明了Swagger的版本为OpenAPI 3.0
@Bean
public Docket api3() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("控制器包路径"))
.paths(PathSelectors.any())
.build()
.apiInfo(new ApiInfoBuilder()
.title("微服务 API 文档")
.version("1.0")
.build());
}
完成后,每个微服务将通过类似 http://microsoft:9076/v3/api-docs 端点暴露其 OpenAPI 文档。
1.2 2.0版本的微服务
对于使用Swagger 2.0
的微服务,不同点在于在 Docket
配置中设置DocumentationType.SWAGGER_2
,如下:
//这里声明了Swagger的版本为Swagger 2.0
@Bean
public Docket api2() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("控制器包路径"))
.paths(PathSelectors.any())
.build()
.apiInfo(new ApiInfoBuilder()
.title("微服务 API 文档")
.version("1.0")
.build());
}
完成后,每个微服务将通过类似 http://microsoft:9077/v2/api-docs 端点暴露其 OpenAPI 文档。
2. 配置Zuul API网关
2.1 添加Knife4j依赖
目标:创建一个 Zuul 网关服务,用于将请求路由到各个微服务。
操作:创建一个新的 Spring Boot 项目作为Zuul网关,添加Knife4j 的依赖:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
在 application.yml 中配置路由规则,例如:
zuul:
routes:
service1-id:
path: /service1/**
serviceId: service1
service2-id:
path: /service2/**
serviceId: service2
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
2.2 实现动态收集Knife4j 文档资源
目标:通过 Zuul 动态收集各个微服务的 Knife4j 文档资源。
操作:在Zuul网关服务中实现一个 SwaggerResourcesProvider,以便根据 Zuul 的路由动态生成 Swagger 资源。要求兼容Swagger 2.0和3.0,例如:
@Data
@Component
//这里添加Primary目的是标记首选SwaggerResourcesProvider
//因为默认系统会生成inMemorySwaggerResourcesProvider和此处CustomSwaggerResourcesProvider产生冲突
//为了可以保留框架完整功能的同时解决冲突,故标记为Primary
@Primary
@ConfigurationProperties(prefix = "swagger")
public class CustomSwaggerResourcesProvider implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
public CustomSwaggerResourcesProvider(RouteLocator routeLocator) {
this.routeLocator = routeLocator;
}
private List<Map<String, String>> services;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
Map<String, String> serviceVersionMap =
services.stream().collect(Collectors.toMap(m -> m.get("name"), m -> m.get("version")));
routeLocator.getRoutes().stream()
.filter(n -> serviceVersionMap.containsKey(n.getId()))
.forEach(
route -> {
String name = route.getId();
String version = serviceVersionMap.getOrDefault(name, "3.0");
String apiDocsPath = version.startsWith("2") ? "v2/api-docs" : "v3/api-docs";
String location = route.getFullPath().replace("**", apiDocsPath);
resources.add(swaggerResource(view, location, version));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location, String version) {
SwaggerResource resource = new SwaggerResource();
resource.setName(name);
resource.setUrl(location);
resource.setSwaggerVersion(version);
return resource;
}
}
为了兼容Swagger 2.0和3.0,我们在application.yml中定义了swagger.services属性,如下:
swagger:
services:
- { name: service1-id,version: 2.0 }
- { name: service2-id,version: 3.0 }
显式的定义每个微服务使用swagger版本,在CustomSwaggerResourcesProvider中,通过services属性在zuul启动后,对不同的微服务赋予不同版本的文档url。
2.3 通过Zuul服务访问Knife4j
您可以在 Zuul 网关中通过一个统一的 Knife4j 界面访问所有微服务的 API 文档。访问 Zuul 的 Knife4j UI(通常是 /doc.html),即可看到聚合后的文档。比如zuul的服务http://zuul:8802/doc.html
您将看到一个包含 service1、service2 等微服务文档的下拉菜单或分组视图。如下图:
通过这种方法,您可以有效管理微服务架构中的 API 文档,为开发人员提供一个集中式的访问入口。
3 问题
问题:访问http://service1:9901/v2/api-docs?group=xxx正常返回json串,而访问http://service1:9901/v2/api-docs返回404
分析:究其原因微服务配置了多组 Swagger 文档(配置Docket分组),没有给出默认的分组,并且 /v2/api-docs 的行为与分组配置有关。
Knife4j的doc.html自动加载 xxx 分组的文档,因此界面正常。但直接访问 /v2/api-docs 失败,这是由于Springfox 要求多组文档时通过 group 参数指定分组,否则返回 404。如果不加group参数,默认是加载名为default的分组文档,如果分组时没有指定,则会返回404。
解决:最直接的解决办法就是配置Docket分组,直接指定一个名为default的分组。如下图所示:
希望上述文档对你有所帮助。
评论区