SpringCloud Gateway整合Swagger2聚合微服務系統API文檔
- 需求
- 解決
-
- 配置swagger2
-
- pom
- swagger配置類
- order-service測試使用
- 配置gateway網關
-
- pom
- yml
- GatewayApplication啟動類
- SwaggerProvider
- SwaggerHandler
- SwaggerHeaderFilter
- AuthorizeFilter全局過濾器
- 測試
需求
1、有兩個微服務user-service和order-service都需要使用swagger2生成接口文檔,後期還有其他微服務。如果每一個都去通路
http://ip:port/swagger-ui.html
會比較麻煩。
2、後端接口通路都是需要攜帶token令牌才能進行請求通路的,如果每一個微服務都需要做請求攔截,代碼備援繁瑣,後期不便于維護。
想達到的效果: 統一通路gateway網關
http://localhost:10010/swagger-ui.html
位址,在網關中統一管理所有微服務的swagger文檔,通路使用者管理的單點登入擷取token,然後攜帶token去通路微服務的其他接口。
端口如下:
頁面效果:
user-service使用者微服務的接口文檔
切換order-service訂單微服務的接口文檔
解決
配置swagger2
pom
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
swagger配置類
這裡和springboot使用swagger一樣
/**
* Swagger2配置資訊
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
// @Value("${swagger.enable}")
// private boolean swaggerEnable;
//是否允許顯示swagger。此值可在application.yml中設定。
//作為開關,可在生産環境和開發環境打開或關閉,簡便易行。
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(webApiInfo())
//.enable(swaggerEnable)
.select()
// 針對方法注解ApiOperation生成接口文檔api
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("網站-API文檔")
.description("本文檔描述了網站微服務接口定義")
.version("1.0")
.contact(new Contact("封于修", "http://zysheep.cn", "[email protected]"))
.build();
}
}
這裡我把swagger2抽成了一個公共的元件,微服務需要就引入坐标依賴,并在啟動類上使用
@ComponentScan
注解掃描元件到ioc容器中即可使用swagger2
order-service測試使用
這裡使用
@ComponentScan(basePackages = {"cn.zysheep"})
掃描swagger注冊為容器中的Bean
配置gateway網關
我的環境
springboot | springcloud | spring-cloud-alibaba |
---|---|---|
2.3.9.RELEASE | Hoxton.SR10 | 2.2.6.RELEASE |
pom
<!--網關-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服務發現依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--swagger依賴 Spring Cloud Gateway整合Swagger聚合微服務系統API文檔-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.9.1.RELEASE</version>
</dependency>
<dependency>
<groupId>cn.zysheep</groupId>
<artifactId>commons-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
yml
server:
port: 10010 # 網關端口
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
discovery:
namespace: 4f64927e-a2a7-4e2f-b657-3da7c57eb008
gateway:
routes: # 網關路由配置
- id: user-service # 路由id,自定義,隻要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标位址 http就是固定位址
uri: lb://user-service # 路由的目标位址 lb就是負載均衡,後面跟服務名稱
predicates: # 路由斷言,也就是判斷請求是否符合路由規則的條件
- Path=/user/** # 這個是按照路徑比對,隻要以/user/開頭就符合要求
filters:
- StripPrefix=1
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- StripPrefix=1
logging:
level:
cn.zysheep.gateway: debug
GatewayApplication啟動類
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
SwaggerProvider
配置SwaggerProvider,擷取
Api-doc
,即SwaggerResources。
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
public static final String API_URI = CommonConstants.SWAGGER_URL;
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//取出gateway的route
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
//結合配置的route-路徑(Path),和route過濾,隻擷取有效的route節點
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId()))
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
// 1、公共路徑使用服務名的http://localhost:10010/user/v2/api-docs
// predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
// .replace("/**", API_URI)))));
// 2、公共路徑使用服務名的http://localhost:10010/user-service/v2/api-docs
"/"+routeDefinition.getId()+API_URI ))));
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
SwaggerHandler
因為Gateway裡沒有配置SwaggerConfig,而運作Swagger-ui又需要依賴一些接口,是以我的想法是自己建立相應的swagger-resource端點。
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
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)));
}
}
SwaggerHeaderFilter
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
AuthorizeFilter全局過濾器
@Order(-1)
@Component
@Slf4j
public class AuthorizeFilter implements GlobalFilter {
/**
* 處理目前請求,有必要的話通過{@link GatewayFilterChain}将請求交給下一個過濾器處理
*
* @param exchange 請求上下文,裡面可以擷取Request、Response等資訊
* @param chain 用來把請求委托給下一個過濾器
* @return {@code Mono<Void>} 傳回标示目前過濾器業務結束
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.debug("請求: {}", exchange.getRequest().getURI().getPath());
log.info("---------enter gateway interceptor--------");
String url = exchange.getRequest().getURI().getPath();
log.info("url---------------{}", url);
//登入直接放行
if (StringUtils.contains(url, CommonConstants.LOGIN_URL)) {
log.info(" login .............. ");
return chain.filter(exchange);
}else if(StringUtils.contains(url, CommonConstants.SWAGGER_URL)){//swagger直接放行
log.info(" swagger2 .............. ");
return chain.filter(exchange);
} else {
//擷取token
ServerHttpRequest request = exchange.getRequest();
String token = request.getHeaders().getFirst("token");
//驗證token
Map<String, Object> tokenMap = JWTUtil.parseToken(token);
log.info("-----{}", tokenMap);
if (ObjectUtils.isEmpty(tokenMap.get("ERR_CODE"))) {
request = exchange.getRequest().mutate().headers(httpHeaders -> {
httpHeaders.add("tokenMap", JSON.toJSONString(tokenMap));
}).build();
ServerWebExchange build = exchange.mutate().request(request).build();
return chain.filter(build);
} else {
return errorInfo(exchange, tokenMap.get("ERR_MSG").toString(), 500);
}
}
}
/**
* 傳回response
*
* @param exchange
* @param message 異常資訊
* @param status data中的status
* @return
*/
public static Mono<Void> errorInfo(ServerWebExchange exchange, String message, Integer status) {
// 自定義傳回格式
Map<String, Object> resultMap = new HashMap<>(8);
resultMap.put("code", status);
resultMap.put("msg", StringUtils.isBlank(message) ? "服務異常!" : message);
resultMap.put("data", null);
return Mono.defer(() -> {
byte[] bytes;
try {
bytes = new ObjectMapper().writeValueAsBytes(resultMap);
} catch (JsonProcessingException e) {
log.error("網關響應異常:", e);
throw new RuntimeException("資訊序列化異常");
} catch (Exception e) {
log.error("網關響應異常:", e);
throw new RuntimeException("寫入響應異常");
}
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_UTF8.toString());
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
});
}
}
測試
測試流程:
- 通路gateway網關
http://localhost:10010/swagger-ui.html
- 通路使用者微服務的單點登入擷取token值
- 複制token,通路訂單微服務的訂單管理查詢訂單
1、通路gateway網關
http://localhost:10010/swagger-ui.html
2、通路使用者微服務的單點登入擷取token值
3、複制token,通路訂單微服務的訂單管理查詢訂單
成功響應結果