vlambda博客
学习文章列表

分布式系统 - 网关篇之SpringCloud Gateway

一、整合SpringCloud Gateway

加入pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
Gateway不需要配置其他注解,直接配置yml即可
spring:
application:
name: sunway-gateway
gateway:
# 路由转发规则
routes:
- id: ES
uri: lb://es-service
predicates:
- Path=/es/**
filters:
- StripPrefix=1

二、整合Swagger文档

微服务项目中,通常把Swagger2单独放到公共模块中,然后需要的项目引一下依赖,最终通过网关的文档地址去 转发访问 各个微服务的接口文档。
swagger 公共模块 pom依赖
    <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- bootstrap版本的页面 地址 /doc.html -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- 原始swagger页面 地址 /swagger-ui.html -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
swagger配置类
package com.sunway.swagger.config;

import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.Collections;
import java.util.List;

@Configuration
@EnableSwagger2
@EnableSwaggerBootstrapUI
public class Swagger2Config implements WebMvcConfigurer {

@Value("${spring.application.name}")
private String applicationName;

@Bean
public Docket esDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getInfo(applicationName + "服务接口"))
.select()
.apis(RequestHandlerSelectors.basePackage("com.sunway"))
.paths(PathSelectors.any())
.build()
.globalOperationParameters(getParameterList());
}

/**
* 添加全局请求头
*
* @return java.util.List<springfox.documentation.service.Parameter>
* @date 2021/1/27 12:52
*/

private List<Parameter> getParameterList() {
//创建全局参数用于测试(每个请求都带一个header参数"X-token")
return Collections.singletonList(new ParameterBuilder().required(false)
.modelRef(new ModelRef("string"))
.parameterType("header")
.name("X-token")
.description("认证信息")
.build()
);
}

/**
* 整体说明信息
*
* @return ApiInfo
*/

private ApiInfo getInfo(String description) {
//创建文档的整体说明对象(作者、描述、标题、版本)
return new ApiInfoBuilder().contact(new Contact("标题", "", ""))
.title("标题")
.description(description)
.version("1.0")
.build();
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/META-INF/resources/")
.addResourceLocations("classpath:/resources/")
.addResourceLocations("classpath:/static/")
.addResourceLocations("classpath:/public/");
}
}

这里有个问题就是 swagger依赖是基于spring webmvc的,而springCloud gateway是基于 webflux的,这两者会冲突导致启动失败,所以我们可以在gateway中单独引入swagger的依赖包,整合到webflux上面:
        <dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
如何整合?如下配置:
此配置会拉取 已经注册路由的ID,作为分组名,然后取路由的Path作为转发路径,/v2/api-docs接口返回的 是接口的JSON信息,转发地址即是 /{路由Path}/v2/api-docs,就能拿到各个服务的api文档了。
@Component
@Primary
@AllArgsConstructor
public class CloudSwagger2Config implements SwaggerResourcesProvider {

public static final String API_URI = "/v2/api-docs";
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;

@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
//取出gatewayroute
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(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("/**", 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;
}
}
接着自定义一套swagger文档的配置接口
package com.sunway.gateway.swagger;

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;

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

三、自定义拦截器

需要实现GlobalFilter,Ordered接口,order越大,优先级越低。

@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

private static final String AUTHORIZE_TOKEN = "Authorize";

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// String token = exchange.getRequest().getHeaders().getFirst(AUTHORIZE_TOKEN);
// // token 鉴权
// if (StringUtils.isBlank(token)) {
// exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// return exchange.getResponse().setComplete();
// }

return chain.filter(exchange);
}

@Override
public int getOrder() {
return 0;
}
}