给公司做的微服务架构中,我们用了gateway作为所有微服务的前端入口,对后端微服务进行路由。gateway使用了webflux的纯异步IO方式开发。实现了更高效的网关服务。
使用很简单,这里简单说一下,我们使用的是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配置已经基本可以满足了。
关于其他的路径谓词和过滤器后续有空补上吧。这里直接介绍我们使用的这一套配置。通过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
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;
}
}
我们后端很多微服务,现在基本都使用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中发布时查看镜像版本号。