cloud-alibaba-gateway 网关搭建
前言
统一请求路径, 统一入参校验, 统一在线文档入口. 避免众多微服务后产生的生成现场混乱, 避免前端调用接口时不同地址的管理, 统一增加负载均衡.
版本选择
spring cloud alibaba-项目搭建-版本选择
首先按照以上项目搭建父工程后 :
- 在父工程下创建子工程: alibaba-cloud-gateway
- 更新maven的pom文件
- 配置路由文件 (存放到nacos配置中心中)
- 搭建nacos注册中心server
- gateway拦截器重写
- gateway全局异常捕获
- 补充 : 按需可接入sentinel流控组件 (此处不实现… 后续补充)
- 补充: 按需可接入sleuth+ zipkin (此处不实现… 后续补充)
创建Maven项目
POM 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>alibaba-cloud-demo</artifactId>
<groupId>com.zly</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>alibaba-cloud-gateway</artifactId>
<dependencies>
<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>
<!-- nacos 配置发现中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
bootstrap.yml
server:
port: 10010
spring:
profiles:
active: ${ACTIVE:dev}
application:
name: alibaba-cloud-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yaml
extension-configs:
- data-id: gateway-${spring.profiles.active}.yaml
logging:
level:
com.alibaba.nacos.client.config.impl: WARN
重写gateway拦截器:
package alibaba.cloud.gateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author zly
*/
@Configuration
public class HttpRequestFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String path = serverHttpRequest.getURI().getPath();
String url = null;
try {
url = path.split("/")[3];
} catch (Exception e) {
// nothing
}
/* 参数校验 */
this.checkParams(serverHttpRequest.getQueryParams());
/* 校验请求token是否过期 */
if (this.isCheckToken(url)) {
this.checkToken(serverHttpRequest);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
/**
* 参数校验
*
* @param params 参数Map
*/
private void checkParams(MultiValueMap<String, String> params) {
/* 非法参数校验 */
}
private boolean isCheckToken(String path) {
/* 判断当前请求路径是否需要校验token */
return false;
}
private void checkToken(ServerHttpRequest serverHttpRequest) {
/* 校验token是否正确 (token存放在请求头中,取出对应请求头即可) */
}
}
重写gateway全局异常:
package alibaba.cloud.gateway.global.exception;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.Collections;
import java.util.List;
/**
* 覆盖默认的异常处理
*
* @author zly
*/
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorHandlerConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
errorAttributes,
this.resourceProperties,
this.serverProperties.getError(),
this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
package alibaba.cloud.gateway.global.exception;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;
import java.util.HashMap;
import java.util.Map;
/**
* 自定义异常处理
*
* <p>异常时用JSON代替HTML异常信息<p>
*
* @author zly
*/
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* 获取异常属性
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
int code = HttpStatus.INTERNAL_SERVER_ERROR.value();
Throwable error = super.getError(request);
/* 处理全局自定义异常类拦截 */
if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
code = HttpStatus.NOT_FOUND.value();
}
if (error instanceof org.springframework.web.server.ResponseStatusException) {
code = HttpStatus.NOT_FOUND.value();
}
return response(code, code, this.buildMessage(request, error));
}
/**
* 指定响应处理方法为JSON处理的方法
*
* @param errorAttributes 错误属性
*/
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
/**
* 根据code获取对应的HttpStatus
*
* @param errorAttributes 错误属性
* @return httpStatus
*/
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("http-code");
HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
return httpStatus.value();
}
/**
* 构建异常信息
*
* @param request 请求域
* @param ex 错误类
* @return String
*/
private String buildMessage(ServerRequest request, Throwable ex) {
StringBuilder message = new StringBuilder("Failed to handle request [");
message.append(request.methodName());
message.append(" ");
message.append(request.uri());
message.append("]");
if (ex != null) {
message.append(": ");
message.append(ex.getMessage());
}
return message.toString();
}
/**
* 构建返回的JSON数据格式
*
* @param status 状态码
* @param errorMessage 异常信息
* @return Map
*/
public static Map<String, Object> response(int httpCode, int status, String errorMessage) {
Map<String, Object> map = new HashMap<>(3);
map.put("http-code", httpCode);
map.put("code", status);
map.put("message", errorMessage);
map.put("data", null);
return map;
}
}
gateway 路由配置 (此处配置可以存放到nacos配置中心…)
spring:
cloud:
gateway:
discovery:
locator:
enabled: true #开启gateway发现注册服务
routes: # 路由数组
- id: base_route #当前路由的标识 要求唯一
uri: lb://wisdom-service-base #请求要被转发的地址
order: 1 #路由的优先级(数字越小,优先级越高)
predicates:
- Path=/server/demo-service/** #当请求路径满足Path指定的规则时,进行路由转发
filters:
- StripPrefix=2 #在请求转发之前去掉2层路径