Gateway+nacos动态网关配置

news/2025/3/13 3:58:42/

1.添加依赖

<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2.2.3.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2.nacos配置内容

1.新建配置
2.Data Id:butool-cloud-gateway-router
3.Group: butool-cloud
4.配置格式: JSON
5.配置内容
//配置不需要加注释
//获取路由对象,被网关反序列化成List<RouteDefinition>
[{"id":"butool-cloud-test",   //路由配置主键唯一,自定义即可"order":0, //优先级越小代表的优先级越高"predicates":[ //路由匹配{"args":{"pattern":"/api/butool-cloud-test/**"  //先映射到网关api/butool-cloud-test 工程路径前缀  server.servlet.context-path},"name":"Path"  //路径匹配}],"uri":"lb://butool-test", 转发到哪个微服务serviceid  ’lb://’开头 spring.application.name"filters":[{"name":"HeaderToken" //header携带token验证过滤器,寻找HeaderTokenGatewayFilterFactor,GatewayFilterFactor前面的名称},{"name":"StripPrefix", //跳过前缀过滤器"args":{"parts":"1" //跳过1个前缀,往后的路径实现转发}     }]}
]

3.bootstrap.yal

server:port: 9001servlet:context-path: /api
spring:application:name: api-butool-gatewaycloud:nacos:# 服务注册发现discovery:enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850namespace: fdf5864c-f4da-47c3-9ef6-898a60cfff3b# 接入到springBootAdminmetadata:management:context-path: ${server.servlet.context-path}/actuator
# 这个地方独立配置, 是网关的数据, 代码 GatewayConfig.java 中读取被监听
nacos:gateway:route:config:data-id: butool-cloud-gateway-routergroup: butool-cloud
​
# 暴露端点
management:endpoints:web:exposure:include: '*'endpoint:health:show-details: always
​

4.配置类

package cn.butool.config;
​
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
​
/*** 配置类:读取Nacos相关的配置项,用于配置监听器*/
@SuppressWarnings("all")
@Configuration
public class GatewayConfig {/*** 读取配置的超时时间*/public static final long DEFAULT_TIMEOUT = 30000;/*** Nacos 服务器地址*/public static String NACOS_SERVER_ADDR;
​/*** Nacos 命名空间*/public static String NACOS_NAMESPACE;/*** nacos 配置列表中的dataid*/public static String NACOS_ROUTE_DATE_ID;/*** nacos分组id*/public static String NACOS_GROUP_ID;
​@Value("${spring.cloud.nacos.discovery.server-addr}")public void setNacosServerAddr(String nacosServerAddr) {NACOS_SERVER_ADDR = nacosServerAddr;}
​@Value("${spring.cloud.nacos.discovery.namespace}")public void setNacosNamespace(String nacosNamespace) {NACOS_NAMESPACE = nacosNamespace;}
​@Value("${nacos.gateway.route.config.data-id}")public void setNacosRouteDateId(String nacosRouteDateId) {NACOS_ROUTE_DATE_ID = nacosRouteDateId;}
​@Value("${nacos.gateway.route.config.group}")public void setNacosGroupId(String nacosGroupId) {NACOS_GROUP_ID = nacosGroupId;}
}
​

5.事件推送 Aware:

package cn.butool.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
​
import java.util.List;
​
/*** 事件推送 Aware: 动态更新路由网关 Service* */
@Slf4j
@Service
@SuppressWarnings("all")
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
​/** 写路由定义 */private final RouteDefinitionWriter routeDefinitionWriter;/** 获取路由定义 */private final RouteDefinitionLocator routeDefinitionLocator;
​/** 事件发布 */private ApplicationEventPublisher publisher;
​public DynamicRouteServiceImpl(RouteDefinitionWriter routeDefinitionWriter,RouteDefinitionLocator routeDefinitionLocator) {this.routeDefinitionWriter = routeDefinitionWriter;this.routeDefinitionLocator = routeDefinitionLocator;}
​@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {// 完成事件推送句柄的初始化this.publisher = applicationEventPublisher;}
​/*** 从nacos读取路由配hi,写道gateway中* <h2>增加路由定义</h2>* */public String addRouteDefinition(RouteDefinition definition) {
​log.info("gateway add route: [{}]", definition);
​// 保存路由配置并发布routeDefinitionWriter.save(Mono.just(definition)).subscribe();// 发布事件通知给 Gateway, 同步新增的路由定义this.publisher.publishEvent(new RefreshRoutesEvent(this));
​return "success";}
​/*** <h2>更新路由</h2>* */public String updateList(List<RouteDefinition> definitions) {
​log.info("更新网关路由: [{}]", definitions);
​// 先拿到当前 Gateway 中存储的路由定义List<RouteDefinition> routeDefinitionsExits =routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {// 清除掉之前所有的 "旧的" 路由定义routeDefinitionsExits.forEach(rd -> {deleteById(rd.getId());log.info("清除掉之前所有的 旧的 路由定义: [{}]", rd);});}
​// 把更新的路由定义同步到 gateway 中definitions.forEach(definition -> updateByRouteDefinition(definition));return "success";}
​/*** <h2>根据路由 id 删除路由配置</h2>* */private String deleteById(String id) {
​try {log.info("要删除的路由id: [{}]", id);this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();// 发布事件通知给 gateway 更新路由定义this.publisher.publishEvent(new RefreshRoutesEvent(this));return "删除成功";} catch (Exception ex) {log.error("删除网关路由失败: [{}]", ex.getMessage(), ex);return "删除失败";}}
​/*** <h2>更新路由</h2>* 更新的实现策略比较简单: 删除 + 新增 = 更新* */private String updateByRouteDefinition(RouteDefinition definition) {
​try {log.info("更新网关路由: [{}]", definition);this.routeDefinitionWriter.delete(Mono.just(definition.getId()));} catch (Exception ex) {return "更新失败,没有查到更新的网关路由id: " + definition.getId();}
​try {this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();this.publisher.publishEvent(new RefreshRoutesEvent(this));return "成功";} catch (Exception ex) {return "更新路由失败!";}}
}

6.通过Nacos下发的动态配置,监听nacos中路由配置

package cn.butool.config;
​
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
​
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
​
/*** 通过Nacos下发的动态配置,监听nacos中路由配置*/
@Slf4j
@Component
//再另外一个Bean初始化之后再去初始化当前类
@DependsOn({"gatewayConfig"})
@SuppressWarnings("all")
public class DynamicRouteServiceImplNacos {
​private ConfigService configService;
​private final DynamicRouteServiceImpl dynamicRouteService;
​public DynamicRouteServiceImplNacos(DynamicRouteServiceImpl dynamicRouteService) {this.dynamicRouteService = dynamicRouteService;}
​/*** 初始化ConfigService** @return*/private ConfigService initConfigService() {try {Properties properties = new Properties();properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE);return configService = NacosFactory.createConfigService(properties);} catch (Exception ex) {log.error("初始化配置服务:[{}]", ex.getMessage());return null;}}
​/*** bean在容器中构造完成之后会立即执行当前的init方法* 加载路由信息,注册监听器*/@PostConstructpublic void init(){log.info("网关路由初始化...");
​try {//初始化nacos配置客户端configService = initConfigService();if(configService==null){log.error("初始化配置服务异常,配置服务是null!");return;}//通过 nacos config 并指定路由配置路径去获取路由配置String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATE_ID,GatewayConfig.NACOS_GROUP_ID,GatewayConfig.DEFAULT_TIMEOUT);log.info("当前网关配置信息:[{}]:",configInfo);//反序列化List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);if(CollectionUtil.isNotEmpty(routeDefinitions)){//新增for (RouteDefinition routeDefinition : routeDefinitions) {log.info("初始化 路由定义对象 信息:[{}]:",routeDefinition);dynamicRouteService.addRouteDefinition(routeDefinition);}
​}} catch (Exception e) {log.error("网关路由初始化失败:[{}]",e.getMessage());}//设置监听器dynamicRouteServiceImplNacosByListener(GatewayConfig.NACOS_ROUTE_DATE_ID,GatewayConfig.NACOS_GROUP_ID);}
​/*** 监听 Nacos下发的动态路由配置** @param dataId* @param group*/private void dynamicRouteServiceImplNacosByListener(String dataId, String group) {try {//给Nacos config 客户端增加一个监听器configService.addListener(dataId, group, new Listener() {/*** 自己提供线程池执行操作* @return*/@Overridepublic Executor getExecutor() {return null;}
​/*** 监听器收到接收到配置变更信息* @param configInfo nacos 中最新配置信息*/@Overridepublic void receiveConfigInfo(String configInfo) {log.info("接收的配置信息:[{}]", configInfo);List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);dynamicRouteService.updateList(routeDefinitions);log.info("更新后的路由配置信息:[{}]", routeDefinitions.toString());}});} catch (Exception e) {log.error("监听 Nacos下发的动态路由配置异常:[{}]", e.getMessage());}}
}
​

7.局部过滤器

7.1过滤器实现

package cn.butool.filter;
​
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
/*** header携带token验证过滤器*/
public class HeaderTokenGateWayFilter implements GatewayFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//从HttpHeader中寻找key为token,value为butool的键值对String token = exchange.getRequest().getHeaders().getFirst("token");if("butool".equals(token)){return chain.filter(exchange);}// 标记此次请求没有权限,并且结束这次请求exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//请求结束return exchange.getResponse().setComplete();}
​/*** 优先级* @return*/@Overridepublic int getOrder() {return HIGHEST_PRECEDENCE+2;}
}

7.2工厂实现类

package cn.butool.filter.factory;
​
import cn.butool.filter.HeaderTokenGateWayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
​
@Component
public class HeaderTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Overridepublic GatewayFilter apply(Object config) {return new HeaderTokenGateWayFilter();}
}

7.3配置文件中进行配置

//注意命名规则 
"filters":[{"name":"HeaderToken" //header携带token验证过滤器,寻找HeaderTokenGatewayFilterFactor,GatewayFilterFactor前面的名称},{"name":"StripPrefix", //跳过前缀过滤器"args":{"parts":"1" //跳过1个前缀,往后的路径实现转发}     }]

7.4局部过滤器步骤

1.新建过滤器实现
2.新建工厂类实现
3.配置文件中进行配置

8.缓存请求body的全局过滤器

package cn.butool.constan;
​
/*** 网关常量定于*/
@SuppressWarnings("all")
public class GatewayConstant {/**登录的url*/public static final String LOGIN_URL = "/butool/login";
​/** 注册的url*/public static final String REGISTER_URL = "/butool/register";
​/** 去授权中心拿到登录 token 的 uri 格式化接口 */public static final String AUTHORITY_CENTER_TOKEN_URL_FORMAT ="http://%s:%s/butool-cloud-Authority-center/authority/token";
​/** 去授权中心注册并拿到 token 的 uri 格式化接口 */public static final String AUTHORITY_CENTER_REGISTER_URL_FORMAT ="http://%s:%s/butool-cloud-Authority-center/authority/register";
​
​
}
​

package cn.butool.filter;
​
import cn.butool.constan.GatewayConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
​
/*** <h1>全局缓存请求Body过滤器</h1>* 将请求数据缓存方便后面的过滤器拿取* spring WebFlux*/
@Slf4j
@Component
public class GlobalCacheRequestBodyFilter implements GlobalFilter, Ordered {
​@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String path = exchange.getRequest().getURI().getPath();boolean isLoginOrRegister =path.contains(GatewayConstant.LOGIN_URL) || path.contains(GatewayConstant.REGISTER_URL);// 如果没有请求头或者不是登录注册接口,不用去缓存了,往下走下面的过滤器if (null == exchange.getRequest().getHeaders().getContentType() || !isLoginOrRegister) {return chain.filter(exchange);}return DataBufferUtils.join(exchange.getRequest().getBody()) //获取请求数据.flatMap(dataBuffer -> {// 确保数据缓冲区不被释放, 必须要 DataBufferUtils.retainDataBufferUtils.retain(dataBuffer);// defer、just 都是去创建数据源, 得到当前数据的副本Flux<DataBuffer> cachedFlux = Flux.defer(() ->Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));// 重新包装 ServerHttpRequest, 重写 getBody 方法, 能够返回请求数据ServerHttpRequest mutatedRequest =new ServerHttpRequestDecorator(exchange.getRequest()) {@Overridepublic Flux<DataBuffer> getBody() {return cachedFlux;}};// 将包装之后的 ServerHttpRequest 向下继续传递return chain.filter(exchange.mutate().request(mutatedRequest).build());});}
​/*** 下面过滤器要拿到当前过滤器缓存内容优先级要比当前返回数值要大** @return*/@Overridepublic int getOrder() {return HIGHEST_PRECEDENCE + 1;}
}
​

9.全局登录、鉴权过滤器

9.1注入RestTemplate对象

package cn.butool.conf;
​
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
​
/*** 网关需要注入到容器中的bean*/
@Configuration
public class GatewayBeanConf {@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
}
​

9.2鉴权授权登陆注册接口常量

package cn.butool.constan;
​
/*** 网关常量定于*/
@SuppressWarnings("all")
public class GatewayConstant {/**登录的url*/public static final String LOGIN_URL = "/butool/login";public static final String LOGINL = "/authority/login";
​/** 注册的url*/public static final String REGISTER_URL = "/butool/register";public static final String REGISTER = "/authority/register";
​/** 去授权中心拿到登录 token 的 uri 格式化接口 */public static final String AUTHORITY_CENTER_TOKEN_URL_FORMAT ="http://%s:%s/butool-cloud-Authority-center/authority/token";
​/** 去授权中心注册并拿到 token 的 uri 格式化接口 */public static final String AUTHORITY_CENTER_REGISTER_URL_FORMAT ="http://%s:%s/butool-cloud-Authority-center/authority/register";
​
​
}
​

9.3登录鉴权过滤器

package cn.butool.filter;
​
import cn.butool.constan.GatewayConstant;
import cn.butool.constant.CommonConstant;
import cn.butool.util.TokenParseUtil;
import cn.butool.vo.JwtToken;
import cn.butool.vo.LoginUserInfo;
import cn.butool.vo.UserRegisterInfo;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
​
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
​
/*** <h1>全局登录、鉴权过滤器</h1>* spring WebFlux*/
@Slf4j
@Component
public class GlobalLoginOrRegisterFilter implements GlobalFilter, Ordered {/*** 注册中心客户端, 可以从注册中心中获取服务实例信息*/private final LoadBalancerClient loadBalancerClient;/*** 发送http请求*/private final RestTemplate restTemplate;
​public GlobalLoginOrRegisterFilter(LoadBalancerClient loadBalancerClient,RestTemplate restTemplate) {this.loadBalancerClient = loadBalancerClient;this.restTemplate = restTemplate;}
​/*** 登录注册鉴权* 1.如果时登录注册,则去授权中心拿到token 并返回给客户端* 2.如果是访问其他服务,则鉴权,没权限返回 401* @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();//1.如果是登录if(request.getURI().getPath().contains(GatewayConstant.LOGIN_URL)){//去授权中心拿tokenString token = getTokenFromAuthorityCenter(request, GatewayConstant.AUTHORITY_CENTER_TOKEN_URL_FORMAT);// header   中不能设置 nullresponse.getHeaders().add(CommonConstant.JWT_USER_INFO_KEY,null==token?"null":token);response.setStatusCode(HttpStatus.OK);return response.setComplete();}else if(request.getURI().getPath().contains(GatewayConstant.REGISTER_URL)){//去授权中心拿tokenString token = getTokenFromAuthorityCenter(request, GatewayConstant.AUTHORITY_CENTER_REGISTER_URL_FORMAT);// header   中不能设置 nullresponse.getHeaders().add(CommonConstant.JWT_USER_INFO_KEY,null==token?"null":token);response.setStatusCode(HttpStatus.OK);return response.setComplete();}// 3.访问其他服务,则鉴权,校验是否能从token中解析出用户信息HttpHeaders headers = request.getHeaders();String token = headers.getFirst(CommonConstant.JWT_USER_INFO_KEY);LoginUserInfo loginUserInfo = null;try {loginUserInfo = TokenParseUtil.parseUserInfoFromToken(token);} catch (Exception ex) {log.error("parse user info from token error: [{}]", ex.getMessage(), ex);}if(null == loginUserInfo){response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}//解析通过则放行return chain.filter(exchange);}
​/*** <h2>从授权中心获取 Token</h2>*/private String getTokenFromAuthorityCenter(ServerHttpRequest request, String uriFormat) {
​// service id 就是服务名字, 负载均衡ServiceInstance serviceInstance = loadBalancerClient.choose(CommonConstant.AUTHORITY_CENTER_SERVICE_ID);log.info("Nacos Client Info: [{}], [{}], [{}]",serviceInstance.getServiceId(), serviceInstance.getInstanceId(),JSON.toJSONString(serviceInstance.getMetadata()));
​String requestUrl = String.format(uriFormat, serviceInstance.getHost(), serviceInstance.getPort());JwtToken token = null;if(uriFormat.contains(GatewayConstant.LOGINL)){LoginUserInfo requestBody = JSON.parseObject(parseBodyFromRequest(request), LoginUserInfo.class);log.info("login request url and body: [{}], [{}]", requestUrl,JSON.toJSONString(requestBody));
​HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);token = restTemplate.postForObject(requestUrl,new HttpEntity<>(JSON.toJSONString(requestBody), headers),JwtToken.class);}else if(uriFormat.contains(GatewayConstant.REGISTER)){UserRegisterInfo requestBody = JSON.parseObject(parseBodyFromRequest(request), UserRegisterInfo.class);log.info("注册请求url地址和请求body: [{}], [{}]", requestUrl,JSON.toJSONString(requestBody));HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);token = restTemplate.postForObject(requestUrl,new HttpEntity<>(JSON.toJSONString(requestBody), headers),JwtToken.class);}if (null != token) {return token.getToken();}return null;}
​/*** 从post请求中获取请数据** @param request* @return*/private String parseBodyFromRequest(ServerHttpRequest request) {// 获取请求体Flux<DataBuffer> body = request.getBody();AtomicReference<String> bodyRef = new AtomicReference<>();
​// 订阅缓冲区去消费请求体中的数据body.subscribe(buffer -> {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());// 一定要使用 DataBufferUtils.release 释放掉, 否则, 会出现内存泄露DataBufferUtils.release(buffer);bodyRef.set(charBuffer.toString());});
​// 获取 request bodyreturn bodyRef.get();}
​/*** 下面过滤器要拿到当前过滤器缓存内容优先级要比当前返回数值要大** @return*/@Overridepublic int getOrder() {return HIGHEST_PRECEDENCE + 2;}
}

10.全局接口耗时过滤器

package cn.butool.filter;
​
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
import java.util.concurrent.TimeUnit;
​
/*** <h1>全局接口耗时日志过滤器</h1>* */
@Slf4j
@Component
public class GlobalElapsedLogFilter implements GlobalFilter, Ordered {
​@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
​// 前置逻辑StopWatch sw = StopWatch.createStarted();String uri = exchange.getRequest().getURI().getPath();
​return chain.filter(exchange).then(// 后置逻辑Mono.fromRunnable(() ->log.info("[{}] elapsed: [{}ms]",uri, sw.getTime(TimeUnit.MILLISECONDS))));}@Overridepublic int getOrder() {return HIGHEST_PRECEDENCE;}
}


http://www.ppmy.cn/news/13018.html

相关文章

[Leetcode] 股票的价格跨度(单调栈)

题目链接&#xff1a;496 下一个更大元素 I901 股票价格跨度先看一道单调栈相关的题目下一个更大元素nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置右侧的 第一个 比 x 大的元素。两个没有重复元素的数组 nums1 和 nums2 &#xff0c;下标从 0 开始计数&#x…

linux基本功系列之chage命令实战

文章目录前言一. chage命令的介绍二. 常用案例示范1. 查看用户密码的有效期2. 设置密码的过期时间3. 设置账号的失效时间总结前言 前言&#x1f680;&#x1f680;&#x1f680; 想要学好Linux&#xff0c;命令是基本功&#xff0c;企业中常用的命令大约200多个&#xff0c;不管…

2022年海南省职业院校技能大赛“网络安全”比赛任务书

2022年海南省职业院校技能大赛“网络安全” 比赛任务书 一、竞赛时间 总计&#xff1a;360分钟 二、竞赛任务书内容 &#xff08;一&#xff09;拓扑图 &#xff08;二&#xff09;A模块基础设施设置/安全加固&#xff08;350分&#xff09; 一、项目和任务描述&#xff…

集合框架及背后的数据结构

集合框架及背后的数据结构1. 介绍2. 学习的意义2.1 Java 集合框架的优点及作用2.2 笔试及面试题3. 接口 interfaces3.1 基本关系说明3.2 Collection 接口说明3.3 Collection 常用方法说明3.4 Collection 示例3.5 Map 接口说明Map3.6 Map 常用方法说明3.7 Map 示例4. 实现 class…

【自学Docker 】Docker top命令

Docker top命令 大纲 docker top教程 使用 docker top 命令可以用来查看 Docker 中运行的进程信息。docker top 命令后面的 CONTAINER 可以是容器 ID&#xff0c;或者是容器名。 docker top语法 haicoder(www.haicoder.net)# docker top [OPTIONS] CONTAINER [ps OPTIONS]案…

10款最佳在线地图软件介绍

有人说&#xff1a;一个人从1岁活到80岁很平凡&#xff0c;但如果从80岁倒着活&#xff0c;那么一半以上的人都可能不凡。 生活没有捷径&#xff0c;我们踩过的坑都成为了生活的经验&#xff0c;这些经验越早知道&#xff0c;你要走的弯路就会越少。 在线地图有无数的用途&…

<Python>使用python来控制windows系统音量

使用python可以对windows系统的音量进行读取或者设置。 平台&#xff1a;visual studio code 语言&#xff1a;python 需要的python模块&#xff1a; 1、pyqt5 2、ctypes&#xff1a; ctypes 是 Python 的外部函数库。它提供了与 C 兼容的数据类型&#xff0c;并允许调用 DLL …

【SpringCloud13】SpringCloud Config分布式配置中心

1.概述 1.1 分布式系统面临的配置问题 微服务意味着要将单体应用中的业务拆分成一个个子服务&#xff0c;每个服务的粒度相对较小&#xff0c;因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行&#xff0c;所以一套集中式的、动态的配置管理设施是必不…