Spring?Cloud?Gateway?整合?knife4j?聚合接口文檔功能
當(dāng)系統(tǒng)中微服務(wù)數(shù)量越來(lái)越多時(shí),如果任由這些服務(wù)散落在各處,那么最終管理每個(gè)項(xiàng)目的接口文檔將是一件十分麻煩的事情,單是記住所有微服務(wù)的接口文檔訪問(wèn)地址就是一件苦差事了。當(dāng)如果能夠?qū)⑺形⒎?wù)項(xiàng)目的接口文檔都統(tǒng)一匯總在同一個(gè)可視化頁(yè)面,那么將大大減少我們的接口文檔管理維護(hù)工作,為此,我們可以基于 Spring Cloud Gateway 網(wǎng)關(guān) + nacos + knife4j 對(duì)所有微服務(wù)項(xiàng)目的接口文檔進(jìn)行聚合,從而實(shí)現(xiàn)我們想要的文檔管理功能
注:本案例需要 springboot 提前整合 nacos 作為注冊(cè)中心,springcloud 整合 nacos 注冊(cè)中心部分內(nèi)容歡迎閱讀這篇文章:Nacos注冊(cè)中心的部署與用法詳細(xì)介紹
1、Spring Cloud Gateway 網(wǎng)關(guān)整合 Knife4j:
(1)開(kāi)啟gateway自動(dòng)路由功能:
隨著我們的系統(tǒng)架構(gòu)不斷地發(fā)展,系統(tǒng)中微服務(wù)的數(shù)量肯定會(huì)越來(lái)越多,我們不可能每添加一個(gè)服務(wù),就在網(wǎng)關(guān)配置一個(gè)新的路由規(guī)則,這樣的維護(hù)成本很大;特別在很多種情況,我們?cè)谡?qǐng)求路徑中會(huì)攜帶一個(gè)路由標(biāo)識(shí)方便進(jìn)行轉(zhuǎn)發(fā),而這個(gè)路由標(biāo)識(shí)一般都是服務(wù)在注冊(cè)中心中的服務(wù)名,因此這是我們就可以開(kāi)啟 spring cloud gateway 的自動(dòng)路由功能,網(wǎng)關(guān)自動(dòng)根據(jù)注冊(cè)中心的服務(wù)名為每個(gè)服務(wù)創(chuàng)建一個(gè)router,將以服務(wù)名開(kāi)頭的請(qǐng)求路徑轉(zhuǎn)發(fā)到對(duì)應(yīng)的服務(wù),配置如下:
# enabled:默認(rèn)為false,設(shè)置為true表明spring cloud gateway開(kāi)啟服務(wù)發(fā)現(xiàn)和路由的功能,網(wǎng)關(guān)自動(dòng)根據(jù)注冊(cè)中心的服務(wù)名為每個(gè)服務(wù)創(chuàng)建一個(gè)router,將以服務(wù)名開(kāi)頭的請(qǐng)求路徑轉(zhuǎn)發(fā)到對(duì)應(yīng)的服務(wù) spring.cloud.gateway.discovery.locator.enabled = true # lowerCaseServiceId:?jiǎn)?dòng) locator.enabled=true 自動(dòng)路由時(shí),路由的路徑默認(rèn)會(huì)使用大寫(xiě)ID,若想要使用小寫(xiě)ID,可將lowerCaseServiceId設(shè)置為true spring.cloud.gateway.discovery.locator.lower-case-service-id = true
這里需要注意的是,如果我們的配置了 server.servlet.context-path 屬性,這會(huì)導(dǎo)致自動(dòng)路由失敗的問(wèn)題,因此我們需要做如下兩個(gè)修改:
# 重寫(xiě)過(guò)濾鏈,解決項(xiàng)目設(shè)置了 server.servlet.context-path 導(dǎo)致 locator.enabled=true 默認(rèn)路由策略404的問(wèn)題 spring.cloud.gateway.discovery.locator.filters[0] = PreserveHostHeader
@Configuration public class GatewayConfig { @Value ("${server.servlet.context-path}") private String prefix; /** * 過(guò)濾 server.servlet.context-path 屬性配置的項(xiàng)目路徑,防止對(duì)后續(xù)路由策略產(chǎn)生影響,因?yàn)?gateway 網(wǎng)關(guān)不支持 servlet */ @Bean @Order (-1) public WebFilter apiPrefixFilter() { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getRawPath(); path = path.startsWith(prefix) ? path.replaceFirst(prefix, "") : path; ServerHttpRequest newRequest = request.mutate().path(path).build(); return chain.filter(exchange.mutate().request(newRequest).build()); }; } }
至此,網(wǎng)關(guān)將自動(dòng)根據(jù)注冊(cè)中心的服務(wù)名為每個(gè)服務(wù)創(chuàng)建一個(gè)router,將以服務(wù)名開(kāi)頭的請(qǐng)求路徑轉(zhuǎn)發(fā)到對(duì)應(yīng)的服務(wù)。
(2)pom.xml 文件引入 knife4j 依賴(lài):
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.4</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency>
(3)配置 SwaggerHeaderFilter:
在集成 Spring Cloud Gateway 網(wǎng)關(guān)的時(shí)候,會(huì)出現(xiàn)沒(méi)有 basePath 的情況,例如定義的 /user、/order 等微服務(wù)前綴,因此我們需要在 Gateway 網(wǎng)關(guān)添加一個(gè) Filter 過(guò)濾器
import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.context.annotation.Configuration; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.server.ServerWebExchange; @Configuration public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory { private static final String HEADER_NAME = "X-Forwarded-Prefix"; private static final String URI = "/v2/api-docs"; @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); if(StringUtils.endsWithIgnoreCase(path, URI)) { String basePath = path.substring(0, path.lastIndexOf(URI)); ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build(); ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); return chain.filter(newExchange); } else return chain.filter(exchange); }; } }
(4)重寫(xiě) swagger-resources:
在使用 SpringBoot 等單體架構(gòu)集成 swagger 時(shí),我們是基于包路徑進(jìn)行業(yè)務(wù)分組,然后在前端進(jìn)行不同模塊的展示,而在微服務(wù)架構(gòu)下,一個(gè)服務(wù)就類(lèi)似于原來(lái)我們寫(xiě)的一個(gè)業(yè)務(wù)組。springfox-swagger 提供的分組接口是 swagger-resource,返回的是分組接口名稱(chēng)、地址等信息,而在Spring Cloud微服務(wù)架構(gòu)下,我們需要重寫(xiě)該接口,改由通過(guò)網(wǎng)關(guān)的注冊(cè)中心動(dòng)態(tài)發(fā)現(xiàn)所有的微服務(wù)文檔,代碼如下:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import springfox.documentation.swagger.web.SwaggerResource; import springfox.documentation.swagger.web.SwaggerResourcesProvider; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 使用Spring Boot單體架構(gòu)集成swagger時(shí),是通過(guò)包路徑進(jìn)行業(yè)務(wù)分組,然后在前端進(jìn)行不同模塊的展示,而在微服務(wù)架構(gòu)下,單個(gè)服務(wù)類(lèi)似于原來(lái)業(yè)務(wù)組; * springfox-swagger提供的分組接口是swagger-resource,返回的是分組接口名稱(chēng)、地址等信息; * 在Spring Cloud微服務(wù)架構(gòu)下,需要swagger-resource重寫(xiě)接口,由網(wǎng)關(guān)的注冊(cè)中心動(dòng)態(tài)發(fā)現(xiàn)所有的微服務(wù)文檔 */ @Primary @Configuration public class SwaggerResourceConfig implements SwaggerResourcesProvider { @Autowired private RouteLocator routeLocator; // 網(wǎng)關(guān)應(yīng)用名稱(chēng) @Value ("${spring.application.name}") private String applicationName; //接口地址 private static final String API_URI = "/v2/api-docs"; @Override public List<SwaggerResource> get() { //接口資源列表 List<SwaggerResource> resources = new ArrayList<>(); //服務(wù)名稱(chēng)列表 List<String> routeHosts = new ArrayList<>(); // 獲取所有可用的應(yīng)用名稱(chēng) routeLocator.getRoutes() .filter(route -> route.getUri().getHost() != null) .filter(route -> !applicationName.equals(route.getUri().getHost())) .subscribe(route -> routeHosts.add(route.getUri().getHost())); // 去重,多負(fù)載服務(wù)只添加一次 Set<String> existsServer = new HashSet<>(); routeHosts.forEach(host -> { // 拼接url String url = "/" + host + API_URI; //不存在則添加 if (!existsServer.contains(url)) { existsServer.add(url); SwaggerResource swaggerResource = new SwaggerResource(); swaggerResource.setUrl(url); swaggerResource.setName(host); resources.add(swaggerResource); } }); return resources; } }
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import springfox.documentation.swagger.web.*; import java.util.Optional; /** * 獲取api接口信息 */ @RestController @RequestMapping ("/swagger-resources") public class SwaggerHandler { @Autowired(required = false) private SecurityConfiguration securityConfiguration; private UiConfiguration uiConfiguration; private final SwaggerResourcesProvider swaggerResources; @Autowired public SwaggerHandler(SwaggerResourcesProvider swaggerResources) { this.swaggerResources = swaggerResources; } @GetMapping("/configuration/security") public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() { return Mono.just(new ResponseEntity<>(Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK)); @GetMapping ("/configuration/ui") public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() return Mono.just(new ResponseEntity<>(Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK)); @GetMapping("") public Mono<ResponseEntity> swaggerResources() return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK))); }
2、微服務(wù)架構(gòu)中其他項(xiàng)目接入knife4j:
<!-- knife4j文檔,微服務(wù)架構(gòu) --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-micro-spring-boot-starter</artifactId> <version>2.0.4</version> </dependency>
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.async.DeferredResult; import springfox.documentation.builders.*; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.paths.RelativePathProvider; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import javax.servlet.ServletContext; /** * @description: swagger配置文件 **/ @Configuration @EnableSwagger2 @EnableKnife4j public class Swagger2Config { @Value("${spring.profiles.active}") private String env; @Value("${spring.application.name}") private String serviceName; @Value("${gateway.service.name}") private String serviceNameForGateway; @Bean public Docket createDocket(ServletContext servletContext) { Docket docket = new Docket(DocumentationType.SWAGGER_2) .genericModelSubstitutes(DeferredResult.class) .forCodeGeneration(true) .pathMapping("/") .select() .build() .apiInfo(new ApiInfoBuilder() .title(serviceName + "接口文檔") .version("1.0") .contact(new Contact("xxx","","")) .license("XXX有限公司") .build()) // 如果為生產(chǎn)環(huán)境,則不創(chuàng)建swagger .enable(!"real".equals(env)); // 在knife4j前端頁(yè)面的地址路徑中添加gateway網(wǎng)關(guān)的項(xiàng)目名,解決在調(diào)試接口、發(fā)送請(qǐng)求出現(xiàn)404的問(wèn)題 docket.pathProvider(new RelativePathProvider(servletContext) { @Override public String getApplicationBasePath() { return "/" + serviceNameForGateway + super.getApplicationBasePath(); } }); return docket; } }
3、最終效果:
文章的最后,再介紹 knife4j 官方提供的另一種接口文檔聚合的方式:Aggregation微服務(wù)聚合組件,官方地址:https://doc.xiaominfo.com/knife4j/resources/,感興趣的讀者可以去官方看下如何使用
到此這篇關(guān)于Spring Cloud Gateway 整合 knife4j 聚合接口文檔的文章就介紹到這了,更多相關(guān)Spring Cloud Gateway 整合 knife4j內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
重新啟動(dòng)IDEA時(shí)maven項(xiàng)目SSM框架文件變色所有@注解失效
這篇文章主要介紹了重新啟動(dòng)IDEA時(shí)maven項(xiàng)目SSM框架文件變色所有@注解失效,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03spring MVC中傳遞對(duì)象參數(shù)示例詳解
這篇文章主要給大家介紹了在spring MVC中傳遞對(duì)象參數(shù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看吧。2017-06-06SpringBoot中JPA更新時(shí)部分字段無(wú)效
本文主要介紹了SpringBoot中JPA更新時(shí)部分字段無(wú)效,在通過(guò)注解自動(dòng)更新時(shí),部分字段在調(diào)試時(shí)可以找到,卻沒(méi)有被自動(dòng)更新到數(shù)據(jù)庫(kù)中,下面就介紹一下解決方法2023-04-04淺談spring使用策略模式實(shí)現(xiàn)多種場(chǎng)景登錄方式
本文主要介紹了spring使用策略模式實(shí)現(xiàn)多種場(chǎng)景登錄方式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12