Spring Cloud Gateway 微服务网关

给公司做的微服务架构中,我们用了gateway作为所有微服务的前端入口,对后端微服务进行路由。gateway使用了webflux的纯异步IO方式开发。实现了更高效的网关服务。

 

gateway的使用

 

使用很简单,这里简单说一下,我们使用的是gradle作为构建工具

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-webflux'
    compile 'org.springframework.cloud:spring-cloud-starter-gateway'
    compile "org.springframework.boot:spring-boot-starter-actuator"
    compile "org.springframework.boot:spring-boot-starter-data-redis-reactive"

    compile "io.springfox:springfox-swagger2:" + swaggerVersion
    compile "io.springfox:springfox-swagger-ui:" + swaggerVersion

    compile 'org.springframework.cloud:spring-cloud-starter-alibaba-nacos-config'
    compile 'org.springframework.cloud:spring-cloud-starter-alibaba-nacos-discovery'
}

然后启动类配置

@EnableDiscoveryClient

启动gateway。这时候gateway已经可以使用了。

 

在gateway中有两种配置方式,一种是通过注入@bean注入路由;一种是通过配置文件yml实现。一般情况下我们不需要自定义路由时,其实通过yml配置已经基本可以满足了。

 

gateway的predicates配合StripPrefix

 

关于其他的路径谓词和过滤器后续有空补上吧。这里直接介绍我们使用的这一套配置。通过predicates的path,和route的StripPrefix过滤器进行微服务转发。

gateway中有一个discovery配置,可以配置gateway使用和zuul类似的注册发现机制的路由规则。

不过我们有特殊需求不能使用类默认路由规则。

这里我们每个微服务又都是没配置context path的,所有访问没没有前缀。所以他们的请求拦截应该都是/**,

这时候转发就出现问题了,网关会将所有请求转发给配置中的第一个微服务。

这时候解决方式是配置路由过滤器,StripPrefix,他会将请求path按照/进行分割。去除你指定的前几个路径。比如/user/info/123,配置StripPrefix=1他会转发到/info/123

这就可以实现不使用默认注册发现机制的路由规则、微服务不配置context path的前提下,将请求通过路由转发后端多个微服务。

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: false # 根据注册中心自动默认规则转发,不建议使用
          lower-case-service-id: false # 开启服务注册小写支持
      routes:
      - id: order
        uri: lb://order
        predicates:
        - Path=/order/**
        filters:
        - StripPrefix=1

 

配置gateway的跨域配置

 

package org.codingfly.gateway.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 跨域配置
 *
 * @author: wangmeng
 * @date: 2019/10/12
 **/
@Configuration
@Slf4j
public class RouteConfig {

    /**
     * 这里为支持的请求头,如果有自定义的header字段请自己添加(不知道为什么不能使用*)
     */
    private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,tenant,username,client";
    private static final String ALLOWED_METHODS = "*";
    private static final String ALLOWED_EXPOSE = "*";
    private static final String MAX_AGE = "18000L";


    @Bean
    public WebFilter corsWebFilter() {

        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            String allowHeadersStr = getAllowHeadersStr(request);
            if (CorsUtils.isCorsRequest(request)) {
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                List objOrigin = request.getHeaders().get("Origin");
                if (objOrigin == null || objOrigin.size() == 0) {
                    headers.add("Access-Control-Allow-Origin", "null");
                } else {
                    headers.add("Access-Control-Allow-Origin", objOrigin.get(0));
                }
                headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
                headers.add("Access-Control-Max-Age", MAX_AGE);
                headers.add("Access-Control-Allow-Headers", allowHeadersStr);
                headers.add("Access-Control-Expose-Headers", ALLOWED_EXPOSE);
                headers.add("Access-Control-Allow-Credentials", "true");
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }

    /**
     * 动态获取header
     *
     * @param request 请求
     * @return 返回值
     */
    private String getAllowHeadersStr(ServerHttpRequest request) {
        List accessControlRequestHeaders = request.getHeaders().getAccessControlRequestHeaders();
        if (accessControlRequestHeaders.size() > 0) {
            StringBuilder stringBuilder = new StringBuilder();
            accessControlRequestHeaders.forEach(f -> stringBuilder.append(f).append(","));
            String allowHeadersStr = stringBuilder.substring(0, stringBuilder.length() - 1);
            log.info("allowHeadersStr:{}", allowHeadersStr);
            return allowHeadersStr;
        }
        return ALLOWED_HEADERS;
    }

}

 

配置gateway统一管理swagger接口

 

我们后端很多微服务,现在基本都使用swagger进行api管理。

既然gateway可以代理后端所有微服务,那么其实也可以通过gateway统一后端微服务swagger入口。

 

package org.codingfly.gateway.config;

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;

/**
 * swagger2-ui运行需要的接口配置
 *
 * @author: 王锰
 * @date: 2019/9/2
 */
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {

    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;
    @Autowired(required = false)
    private UiConfiguration uiConfiguration;
    @Autowired(required = false)
    private SwaggerResourcesProvider swaggerResources;

    @GetMapping("/configuration/security")
    public Mono> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("/configuration/ui")
    public Mono> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }

    @GetMapping("")
    public Mono swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }
}

 

package org.codingfly.gateway.config;

import lombok.AllArgsConstructor;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

/**
 * 配置SwaggerProvider,获取Api-doc
 *
 * @author: 王锰
 * @date: 2019/9/2
 */
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {

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


    @Override
    public List get() {
        List resources = new ArrayList<>();
        List 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(),
                                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;
    }
}

这时候就可以在直接访问gateway的swagger了。在右上角的下拉框,会显示配置的路由。

 

这里在推荐一个各个微服务的swagger配置

package org.codingfly.common.config;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * swagger2动态配置
 *
 * @author: wangmeng
 * @date: 2019/9/28
 **/
@Configuration
@EnableSwagger2
@ConditionalOnProperty(prefix = "codingfly", name = "swagger.title")
public class Swagger2Configuration {

    @Value("${codingfly.swagger.basePackage}")
    private String basePackage;

    @Value("${codingfly.swagger.title}")
    private String title;

    @Value("${codingfly.swagger.desc}")
    private String desc;

    @Value("${codingfly.swagger.serviceUrl}")
    private String serviceUrl;

    @Value("${codingfly.swagger.version:1.0}")
    private String version;

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                //包下的类,才生成接口文档
                .apis(RequestHandlerSelectors.basePackage(basePackage))
                .paths(PathSelectors.any())
                .build();
        //.securitySchemes(security());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(title)
                .description(desc)
                .termsOfServiceUrl(serviceUrl)
                .contact(new Contact("wd", "", ""))
                .version(version)
                .build();
    }

}

这里通过yml直接配置各个应用扫描接口的包,swagger的标题,描述,访问地址,以及系统当前swagger的版本号,

 

这方便后续集成到k8s中发布时查看镜像版本号。

 

 

你可能感兴趣的:(spring,cloud,spring,分布式,gateway,Spring,cloud,微服务,swagger,分布式)