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