网关路由SpringCloudGateway、nacos配置管理(热更新、动态路由)

文章目录

  • 前言
  • 一、网关路由
  • 二、SpringCloudGateway
    • 1. 路由过滤
    • 2. 网关登录校验
      • 2.1 鉴权
      • 2.2 网关过滤器
      • 2.3 登录校验
        • 2.3.1 JWT
        • 2.3.2 登录校验过滤器
    • 3. 微服务从网关获取用户
    • 4. 微服务之间用户信息传递
  • 三、nacos配置管理
    • 问题引入
    • 3.1 配置共享
      • 3.1.1 在Nacos中添加共享配置
      • 3.1.2 拉取共享配置
    • 3.2 配置热更新
      • 问题引入
      • 实现步骤
    • 3.3 动态路由
      • 问题引入
      • 3.3.1 监听nacos配置变更,更新路由表
      • 3.3.2 实现动态路由


前言

前端请求不能直接访问微服务,而是要请求网关(SpringCloudGateway)
网关干什么?路由过滤,登录校验。
nacos既是注册中心也可用作配置管理,用来解决各个微服务块配置文件中相同的配置冗余,配置热更新属性,动态路由等。

一、网关路由

数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。
在这里插入图片描述
微服务中的网关作用:前端请求不能直接访问微服务,而是要请求网关。

  • 网关可以做安全控制,也就是登录身份校验,校验通过才放行
  • 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去

二、SpringCloudGateway

网关本身也是一个独立的微服务,所以也需要单独建立一个模块。实现步骤:

  • 创建网关微服务模块
  • 引入SpringCloudGateway、NacosDiscovery依赖
  • 编写启动类
  • 配置网关路由

1. 路由过滤

spring:cloud:gateway:routes:- id: itemuri: lb://item-servicepredicates:- Path=/items/**,/search/**   #请求路径必须符合指定规则

RouteDefinition就是具体的路由规则定义。

  • id:路由的唯一标示
  • predicates:路由断言,其实就是匹配条件
  • filters:路由过滤条件
  • uri:路由目标地址,lb://代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。

2. 网关登录校验

2.1 鉴权

在网关中保存密钥开发登录校验功能
在这里插入图片描述
问题
1、怎么在转发之前做登录校验
2、校验JWT之后,怎么把用户信息传递给微服务
3、微服务之间怎么传递用户信息

2.2 网关过滤器

在这里插入图片描述
实现原理

  1. 客户端请求进入网关后由HandlerMapping对请求做判断,找到与当前请求匹配的路由规则(Route),然后将请求交给WebHandler去处理。
  2. WebHandler则会加载当前路由下需要执行的过滤器链(Filter chain),然后按照顺序逐一执行过滤器(后面称为Filter)。
  3. 图中Filter被虚线分为左右两部分,是因为Filter内部的逻辑分为pre和post两部分,分别会在请求路由到微服务之前和之后被执行。
  4. 只有所有Filter的pre逻辑都依次顺序执行通过后,请求才会被路由到微服务。
  5. 微服务返回结果后,再倒序执行Filter的post逻辑。
  6. 最终把响应结果返回。

定义一个过滤器,在其中实现登录校验逻辑,并且将过滤器执行顺序定义到NettyRoutingFilter之前?

网关过滤器

  • GatewayFilter:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route.
  • GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。FilteringWebHandler在处理请求时,会将GlobalFilter装饰为GatewayFilter,然后放到同一个过滤器链中,排序以后依次执行。
  • AddRequestHeaderGatewayFilterFacotry: 就是添加请求头的过滤器,可以给请求添加一个请求头并传递到下游微服务。直接在yaml文件中配置

1、自定义GatewayFilter
定义一个类,这个类的名称一定要以GatewayFilterFactory为后缀,这个类继承了AbstractGatewayFilterFactory
2、自定义GlobalFilter
定义一个类,直接实现GlobalFilter接口。

2.3 登录校验

2.3.1 JWT
  • AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问
  • JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置
  • SecurityConfig:工具的自动装配
  • JwtTool:JWT工具,其中包含了校验和解析token的功能
  • hmall.jks:秘钥文件
2.3.2 登录校验过滤器

定义一个登录校验的过滤器,实现GlobalFilter接口。

@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final JwtTool jwtTool;private final AuthProperties authProperties;private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取RequestServerHttpRequest request = exchange.getRequest();// 2.判断是否不需要拦截if(isExclude(request.getPath().toString())){// 无需拦截,直接放行return chain.filter(exchange);}// 3.获取请求头中的tokenString token = null;List<String> headers = request.getHeaders().get("authorization");if (!CollUtils.isEmpty(headers)) {token = headers.get(0);}// 4.校验并解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果无效,拦截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete();}// TODO 5.如果有效,传递用户信息//保存用户请求头String userInfo = userId.toString();ServerWebExchange ex = exchange.mutate().request(b -> b.header("user-info",userInfo)).build();// 6.放行return chain.filter(exchange);}private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}

3. 微服务从网关获取用户

将用户信息以请求头的方式传递到下游微服务,然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。
在这里插入图片描述
实现步骤:

  • 改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务
  • 编写微服务拦截器,拦截请求获取用户信息,保存到ThreadLocal后放行(因为每个微服务都执行获取用户信息的逻辑,因此统一拦截所有请求,进行处理(AOP的思想))

4. 微服务之间用户信息传递

由于微服务获取用户信息是通过拦截器在请求头中读取,因此要想实现微服务之间的用户信息传递,就必须在微服务发起调用时把用户信息存入请求头。
微服务之间调用是基于OpenFeign来实现的,并不是我们自己发送的请求。我们如何才能让每一个由OpenFeign发起的请求自动携带登录用户信息呢?
Feign中提供的一个拦截器接口:feign.RequestInterceptor
实现方式
定义一个类,实现RequestInterceptor接口,实现接口中的apply方法,用RequestTemplate类来添加请求头,将用户信息保存到请求头中。这样以来,每次OpenFeign发起请求的时候都会调用该方法,传递用户信息。

@Bean
public RequestInterceptor userInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 获取登录用户Long userId = UserContext.getUser();if(userId == null) {// 如果为空则直接跳过return;}// 如果不为空则放入请求头中,传递给下游微服务template.header("user-info", userId.toString());}};
}

在这里插入图片描述

三、nacos配置管理

问题引入

  • 网关路由在配置文件中写死了,如果变更必须重启微服务
  • 某些业务配置在配置文件中写死了,每次修改都要重启服务
  • 每个微服务都有很多重复的配置,维护成本高

nacos: 无需重启即可生效,实现配置热更新。实现动态路由功能,无需重启网关即可修改路由配置。
在这里插入图片描述

3.1 配置共享

3.1.1 在Nacos中添加共享配置

抽取配置文件yaml中重复的模块,如jdbc相关配置(datasource、mybatis-plus)、日志配置(logging)、swagger(knife4j)以及OpenFeign的配置(feign)
在这里插入图片描述
在这里插入图片描述

3.1.2 拉取共享配置

如何去加载nacos中的配置文件?
在这里插入图片描述
实现步骤
1、在模块中引入依赖

  <!--nacos配置管理--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--读取bootstrap文件--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency>

2、新建bootstrap.yaml,配置nacos地址

spring:cloud:nacos:server-addr: 192.168.150.101 # nacos地址config:file-extension: yaml # 文件后缀名shared-configs: # 共享配置- dataId: shared-jdbc.yaml # 共享mybatis配置- dataId: shared-log.yaml # 共享日志配置- dataId: shared-swagger.yaml # 共享日志配置

3.2 配置热更新

问题引入

业务相关参数,将来可能会根据实际情况临时调整。例如购物车业务,购物车数量有一个上限,默认是10。

实现步骤

1、在nacos中添加配置属性
2、新建一个属性读取类,在需要使用该属性的业务类中注入。

3.3 动态路由

问题引入

网关的路由配置全部是在项目启动的时候加载,并且一经加载就会缓存到内存中的路由表内(一个Map),不会改变,也不会监听路由变更。

  • 如何监听Nacos配置变更?
  • 如何把路由信息更新到路由表?

3.3.1 监听nacos配置变更,更新路由表

使用 Nacos 动态监听配置接口来实现。

public void addListener(String dataId, String group, Listener listener)

核心点:

  • 创建ConfigService,连接到Nacos(spring-cloud-starter-alibaba-nacos-config自动装配,拿到NacosConfigManager就等于拿到了ConfigService)
  • 添加配置监听器,编写配置变更的通知处理逻辑
String serverAddr = "{serverAddr}";
String dataId = "{dataId}";
String group = "{group}";
// 1.创建ConfigService,连接Nacos
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
ConfigService configService = NacosFactory.createConfigService(properties);
// 2.读取配置
String content = configService.getConfig(dataId, group, 5000);
// 3.添加配置监听器
configService.addListener(dataId, group, new Listener() {@Overridepublic void receiveConfigInfo(String configInfo) {// 配置变更的通知处理System.out.println("recieve1:" + configInfo);}@Overridepublic Executor getExecutor() {return null;}
});

更新路由表

package org.springframework.cloud.gateway.route;
import reactor.core.publisher.Mono;
/*** @author Spencer Gibb*/
public interface RouteDefinitionWriter {/*** 更新路由到路由表,如果路由id重复,则会覆盖旧的路由*/Mono<Void> save(Mono<RouteDefinition> route);/*** 根据路由id删除某个路由*/Mono<Void> delete(Mono<String> routeId);
}

3.3.2 实现动态路由

1、网关gateway引入依赖

<!--统一配置管理-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--加载bootstrap-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

2、在网关gateway的resources目录创建bootstrap.yaml文件

spring:application:name: gatewaycloud:nacos:server-addr: 192.168.150.101config:file-extension: yamlshared-configs:- dataId: shared-log.yaml # 共享日志配置

3、在gateway中定义配置监听器

package com.hmall.gateway.route;import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.hmall.common.utils.CollUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {private final RouteDefinitionWriter writer;private final NacosConfigManager nacosConfigManager;// 路由配置文件的id和分组private final String dataId = "gateway-routes.json";private final String group = "DEFAULT_GROUP";// 保存更新过的路由idprivate final Set<String> routeIds = new HashSet<>();@PostConstructpublic void initRouteConfigListener() throws NacosException {// 1.注册监听器并首次拉取配置String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic void receiveConfigInfo(String configInfo) {updateConfigInfo(configInfo);}});// 2.首次启动时,更新一次配置updateConfigInfo(configInfo);}private void updateConfigInfo(String configInfo) {log.debug("监听到路由配置变更,{}", configInfo);// 1.反序列化List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);// 2.更新前先清空旧路由// 2.1.清除旧路由for (String routeId : routeIds) {writer.delete(Mono.just(routeId)).subscribe();}routeIds.clear();// 2.2.判断是否有新的路由要更新if (CollUtils.isEmpty(routeDefinitions)) {// 无新路由配置,直接结束return;}// 3.更新路由routeDefinitions.forEach(routeDefinition -> {// 3.1.更新路由writer.save(Mono.just(routeDefinition)).subscribe();// 3.2.记录路由id,方便将来删除routeIds.add(routeDefinition.getId());});}
}

4、在nacos中配置路由信息,路由文件名为gateway-routes.json,类型为json

[{"id": "item","predicates": [{"name": "Path","args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}}],"filters": [],"uri": "lb://item-service"},....

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/329067.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue.js状态管理和服务端渲染

状态管理 vuejs状态管理的几种方式 组件内管理状态&#xff1a;通过data&#xff0c;computed等属性管理组件内部状态 父子组件通信&#xff1a;通过props和自定义事件实现父子组件状态的通信和传递 事件总线eventBus&#xff1a;通过new Vue()实例&#xff0c;实现跨组件通…

yolov8训练自己数据集时出现loss值为nan。

具体原因目前暂未寻找到。 解决办法 将参数amp改成False即可。 相关资料&#xff1a; https://zhuanlan.zhihu.com/p/165152789 https://github.com/ultralytics/ultralytics/issues/1148

人类听觉处理和语言中枢

人类听觉概述 人类听觉是指通过耳朵接收声音并将其转化为神经信号&#xff0c;从而使我们能够感知和理解声音信息的能力。听觉是人类五种感觉之一&#xff0c;对我们的日常生活和交流至关重要。 听觉是人类交流和沟通的重要工具。通过听觉&#xff0c;我们能够听到他人的语言…

新媒体运营十大能力,让品牌闻达天下!

" 现在新媒体蓬勃发展&#xff0c;很多品牌都有新媒体运营这个岗位。新媒体运营好的话&#xff0c;可以提高公司品牌曝光、影响力。那新媒体运营具备什么能力&#xff0c;才能让品牌知名度如虎添翼呢&#xff1f;" 信息收集能力 在移动互联网时代&#xff0c;信息的…

中国医学健康管理数字化发展风向标——专家共话未来趋势

随着科技的飞速发展&#xff0c;数字化已经成为中国医学健康管理领域的重要发展方向。 2024年5月20日由中国管理科学研究院智联网研究所、中国民族医药协会医养教育委员会、国家卫健委基层健康服务站、中国老龄事业发展基金会、中国智联网健康管理系统平台、中国医学健康管理数…

使用大模型结合Mermaid实现业务流程图快速生成

一、需求描述 在日常系统研发过程中&#xff0c;经常面临前期要写投标技术文档&#xff0c;中期要写系统概要设计、详细设计等各类文档&#xff0c;最耗时间的便是画一些业务流程图。随着大模型的不断普及&#xff0c;大模型对文字的处理越来越强&#xff0c;现可以找一个能简化…

Linux——进程信号(一)

1.信号入门 1.1生活中的信号 什么是信号? 结合实际红绿灯、闹钟、游戏中的"&#xff01;"等等这些都是信号。 以红绿灯为例子&#xff1a; 一看到红绿灯我们就知道&#xff1a;红灯停、绿灯行&#xff1b;我们不仅知道它是一个红绿灯而且知道当其出现不同的状况…

半导体测试基础 - AC 参数测试

AC 测试确保 DUT 的时特性序满足其规格需求。 基本 AC 参数 建立时间(Setup Time) 建立时间指的是在参考信号(图中为 WE)发生变化(取中间值 1.5V)前,为了确保能被正确读取,数据(图中为 DATA IN)必须提前保持稳定不变的最短时间。在最小建立时间之前,数据可以随意变…

C语言游戏实战(12):植物大战僵尸(坤版)

植物大战僵尸 前言&#xff1a; 本游戏使用C语言和easyx图形库编写&#xff0c;通过这个项目我们可以深度的掌握C语言的各种语言特性和高级开发技巧&#xff0c;以及锻炼我们独立的项目开发能力&#xff0c; 在开始编写代码之前&#xff0c;我们需要先了解一下游戏的基本规则…

OSPF路由聚合

原理概述 与RIP不同&#xff0c;OSPF不支持自动路由聚合&#xff0c;仅支持手动路由聚合。OSPF的路由聚合有两种机制&#xff1a;区域间路由聚合和外部路由聚合。区域间路由聚合必须配置在ABR路由器上&#xff0c;指的是ABR在把与自己直接相连区域&#xff08;Area&#xff09…

Dijkstra算法在《庆余年》中的应用:范闲的皇宫之旅

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

【探索数据结构】线性表之双链表

&#x1f389;&#x1f389;&#x1f389;欢迎莅临我的博客空间&#xff0c;我是池央&#xff0c;一个对C和数据结构怀有无限热忱的探索者。&#x1f64c; &#x1f338;&#x1f338;&#x1f338;这里是我分享C/C编程、数据结构应用的乐园✨ &#x1f388;&#x1f388;&…

MT3041 多项式变换求值

注意点&#xff1a; 1.使用单调栈 2.用ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);避免超时 3.此题除了ans最好不要用long long&#xff0c;如果a[]和b[]都是long long 类型&#xff0c;可能会超内存 4.ans (ans % p p) % p;防止负数 5.使用秦九韶算法计算指数…

【Linux】语言级文件接口与系统级文件接口

目录 前言 一、回顾C语言中的文件操作 二、认识文件缓冲区 三、Linux系统提供的文件接口 四、文件描述符fd简介 Linux下C语言文件接口简单模拟实现 前言 每个编程语言都有自己的文件操作方法&#xff0c;在不同的操作系统下相同的语言有相同的文件操作方法。这是如何实现…

声音转文本(免费工具)

声音转文本&#xff1a;解锁语音技术的无限可能 在当今这个数字化时代&#xff0c;信息的传递方式正以前所未有的速度进化。从手动输入到触控操作&#xff0c;再到如今的语音交互&#xff0c;技术的发展让沟通变得更加自然与高效。声音转文本&#xff08;Speech-to-Text, STT&…

git拉取项目前需要操作哪些?

1.输入 $ ssh-keygen -t rsa -C "秘钥说明" 按enter键 2.出现 ssh/id_rsa&#xff1a;(输入也可以不输入也可以) 然后按enter键 3.出现empty for no passphrase&#xff1a;(输入也可以不输入也可以) 然后按enter键 4.出现same passphrase again: (输入也可以不输入也…

异常检测 | PCA马氏距离异常值检测(Matlab)

异常检测 | PCA马氏距离异常值检测&#xff08;Matlab&#xff09; 目录 异常检测 | PCA马氏距离异常值检测&#xff08;Matlab&#xff09;效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab Pca 马氏距离异常值检测&#xff0c;剔除异常样本&#xff0c;置信椭圆可…

15:00面试,15:08就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

4. C++入门:内联函数、auto关键字、范围for及nullptr

内联函数 概念 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方展开&#xff0c;没有函数调用建立栈帧的开销&#xff0c;内联函数提升程序运行的效率 对比C的宏 C语言不足&#xff1a;宏 #define ADD(x, y) ((x)(y))int main() {int ret…