spring-cloud-gateway
一、引入依赖
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>二、运行即可启动
三、网关功能
1、断言配置:只有通过过了断言才继续往下走
2、路由,断言后指定走哪个路由
3、路由过程中经过网关过滤器:网关内置了很多过滤器,可以按需选择(例如:路径重写过滤器)
四、网关路由配置
spring: cloud: gateway: routes: - id: coupon uri: lb://gulimall-coupon predicates: - Path=/api/coupon/** filters: - RewritePath=/api/coupon(?<segment>/?.*), /$\{segment} - id: product uri: lb://gulimall-product predicates: - Path=/api/product/** filters: - RewritePath=/api/(?<segment>/?.*), /$\{segment} - id: fast uri: lb://renren-fast predicates: - Path=/api/** filters: - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}id <随便取的名字>随便取的名字>
uri: 要路由到的地址(这里写的是服务名),lb<代表>代表> 负载均衡
predicates: 断言规则,符合条件的进入该路由
filters: 过滤器,配置到路由服务前,或则后的过滤规则(这里用到的是路径重写)
示例- RewritePath=/api/(?<segment>/?.*), /$\{segment}分析api/cms/user/add 路径重写后 user/add
五、网关异常处理
Spring Cloud Gateway中的全局异常处理不能直接用@ControllerAdvice来处理,通过跟踪异常信息的抛出,找到对应的源码,自定义一些处理逻辑来符合业务的需求。
网关都是给接口做代理转发的,后端对应的都是REST API,返回数据格式都是JSON。如果不做处理,当发生异常时,Gateway默认给出的错误信息是页面,不方便前端进行异常处理。
需要对异常信息进行处理,返回JSON格式的数据给客户端。下面先看实现的代码,后面再跟大家讲下需要注意的地方。 自定义异常处理逻辑:
GlobalGatewayExceptionHandler
异常处理器
package com.easypark.gateway.exception;
import com.easypark.common.exception.EpException;import com.easypark.common.utils.response.ServiceCodeEnum;import lombok.Getter;import lombok.Setter;import lombok.extern.slf4j.Slf4j;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.cloud.gateway.support.NotFoundException;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;
/** * @author zy * @version 1.0 * @Description 网关异常全局处理 * @date 2021/4/29 9:16 */@Slf4j@Getter@Setterpublic class GlobalGatewayExceptionHandler extends DefaultErrorWebExceptionHandler {
public GlobalGatewayExceptionHandler(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 = 500; Throwable error = super.getError(request); String message = this.buildMessage(request, error); if(error instanceof EpException){ EpException epException = (EpException)error; code = epException.getErrorCode(); } if(error instanceof NotFoundException){ log.error("【服务器异常】url={}",request.uri().getPath()); code = 503; message = ServiceCodeEnum.SERVICE_UNAVAILABLE.getMessage(); }
return response(code,message); }
/** * 指定响应处理方法为JSON处理的方法 * @param errorAttributes errorAttributes */ @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); }
/** * 根据code获取对应的HttpStatus * @param errorAttributes errorAttributes */ @Override protected int getHttpStatus(Map<String, Object> errorAttributes) { int statusCode = (int) errorAttributes.get("code"); return HttpStatus.valueOf(statusCode).value(); }
/** * 构建异常信息 * @param request request * @param ex 异常 * @return */ 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(); return ex.getMessage().toString(); }
/** * 构建返回的JSON数据格式 * @param status 状态码 * @param errorMessage 异常信息 * @return */ public static Map<String, Object> response(int status, String errorMessage) { Map<String, Object> map = new HashMap<>(); map.put("code", status); map.put("msg", errorMessage); map.put("data", null); return map; }}GlobalGatewayExceptionConfig
配置:异常处理器
package com.easypark.gateway.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.context.annotation.Primary;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 zy * @version 1.0 * @Description 网关异常配置,覆盖默认的异常处理 * @date 2021/4/29 9:16 */@Configuration@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})public class GlobalGatewayExceptionConfig {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GlobalGatewayExceptionConfig(ServerProperties serverProperties, ApplicationContext applicationContext, ResourceProperties resourceProperties, ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.serverProperties = serverProperties; this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; }
@Primary @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) { GlobalGatewayExceptionHandler exceptionHandler = new GlobalGatewayExceptionHandler( errorAttributes, this.resourceProperties, this.serverProperties.getError(), this.applicationContext); exceptionHandler.setViewResolvers(this.viewResolvers); exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters()); exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders()); return exceptionHandler; }}注意点
异常时如何返回JSON而不是HTML
在org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler中的getRoutingFunction()方法就是控制返回格式的,原代码如下:
@Overrideprotected RouterFunction<ServerResponse> getRoutingFunction( ErrorAttributes errorAttributes) { return RouterFunctions.route(acceptsTextHtml(), this::renderErrorView) .andRoute(RequestPredicates.all(), this::renderErrorResponse);}这边优先是用HTML来显示的,想用JSON的改下就可以了,如下:
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);}如果我们定义的格式中没有status字段的话,这么就会报错,找不到对应的响应码,要么返回数据格式中增加status子段,要么重写,我这边返回的是code,所以要重写,代码如下:
/** * 根据code获取对应的HttpStatus * @param errorAttributes errorAttributes */ @Override protected int getHttpStatus(Map<String, Object> errorAttributes) { int statusCode = (int) errorAttributes.get("code"); return HttpStatus.valueOf(statusCode).value(); }六、网关限流
从1.6.0版本开始,Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流:
route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeId
自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组
1 导入依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId></dependency>2 编写配置类
基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的
SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可。
路由限流:
package com.easypark.gateway.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;import com.easypark.common.utils.response.ServiceCodeEnum;import org.springframework.beans.factory.ObjectProvider;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.context.annotation.Bean;import org.springframework.core.Ordered;import org.springframework.core.annotation.Order;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.codec.ServerCodecConfigurer;import org.springframework.web.reactive.function.BodyInserters;import org.springframework.web.reactive.function.server.ServerResponse;import org.springframework.web.reactive.result.view.ViewResolver;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;import java.util.*;
/** * @author zy * @version 1.0 * @Description 网关配置 * @date 2021/4/29 14:03 *///@Configurationpublic class GatewayConfiguration {
private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; }
/** * 初始化一个限流的过滤器 * * @return GlobalFilter */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); }
/** * 配置初始化的限流参数 */ @PostConstruct public void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); //资源名称,对应路由id rules.add(new GatewayFlowRule("cms") // 限流阈值 .setCount(100) // 统计时间窗口,单位是秒,默认是 1 秒 .setIntervalSec(1)); GatewayRuleManager.loadRules(rules); }
/** * 配置限流的异常处理器 * * @return SentinelGatewayBlockExceptionHandler */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); }
/** * 自定义限流异常页面 */ @PostConstruct public void initBlockHandlers() { BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { Map map = new HashMap<>(); map.put("code", ServiceCodeEnum.FAIL.getCode()); map.put("msg", "接口被限流了"); return ServerResponse.status(HttpStatus.OK). contentType(MediaType.APPLICATION_JSON_UTF8). body(BodyInserters.fromObject(map)); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); }
}路由id加url限流
package com.easypark.gateway.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;import org.springframework.beans.factory.ObjectProvider;import org.springframework.cloud.gateway.filter.GlobalFilter;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 javax.annotation.PostConstruct;import java.util.Collections;import java.util.HashSet;import java.util.List;import java.util.Set;
/** * @author zy * @version 1.0 * @Description 网关配置 * @date 2021/4/29 14:03 */@Configurationpublic class GatewayConfigurationV2 {
private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfigurationV2(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) { this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; }
/** * 初始化一个限流的过滤器 * * @return GlobalFilter */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public GlobalFilter sentinelGatewayFilter() { return new SentinelGatewayFilter(); }
/*** 配置初始化的限流参数 */ @PostConstruct public void initGatewayRules() { Set<GatewayFlowRule> rules = new HashSet<>(); rules.add(new GatewayFlowRule("cms").setCount(1).setIntervalSec(1)); rules.add(new GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1)); GatewayRuleManager.loadRules(rules); }
/** * 配置限流的异常处理器 * * @return SentinelGatewayBlockExceptionHandler */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() { return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); }
//自定义API分组 @PostConstruct private void initCustomizedApis() { Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition api1 = new ApiDefinition("cms") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // 以/api/cms/sys/adminUser/** 开头的请求 add(new ApiPathPredicateItem() .setPattern("/api/cms/sys/adminUser/**") .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); ApiDefinition api2 = new ApiDefinition("product_api2") .setPredicateItems(new HashSet<ApiPredicateItem>() {{ // 以/product-serv/product/api2/demo1 完成的url路径匹配 add(new ApiPathPredicateItem().setPattern("/product-serv/product/api2/demo1")); }}); definitions.add(api1); definitions.add(api2); GatewayApiDefinitionManager.loadApiDefinitions(definitions); }
}如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时