微服务——网关、网关登录校验、OpenFeign传递共享信息、Nacos共享配置以及热更新、动态路由

之前学习了Nacos,用于发现并注册、管理项目里所有的微服务,而OpenFeign简化微服务之间的通信,而为了使得前端可以使用微服务项目里的每一个微服务的接口,就应该将所有微服务的接口管理起来方便前端调用,所以有了网关。

前端调用后端微服务项目的接口时,不需要指定每一个接口具体的地址,只需要将请求发送到后端的网关即可。

网关介绍

网关是网络的关口,负责请求的路由、转发、身份校验 。

网关模块的配置

1、新建一个maven空模块,配置一下依赖

<dependencies>
......<!-- 其它依赖 --><!--网关-->  
<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-gateway</artifactId>  
</dependency> <!--nocos discovery-->  
<dependency>  <groupId>com.alibaba.cloud</groupId>  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>  
</dependency>  <!--负载均衡-->  
<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-loadbalancer</artifactId>  
</dependency>  </dependencies>  
<build>  <finalName>${project.artifactId}</finalName>  <plugins>  <plugin>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-maven-plugin</artifactId>  </plugin>  </plugins>  
</build>  

2、创建com.<XXX项目名称>.gateway包,报下名新建配置类

@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}

3、在静态资源目录下新建application.yaml文件,配置网关相关属性

server:port: 8080  # 网关服务的端口号,指定网关运行在 8080 端口
spring:application:name: gateway  # 应用名称,注册到 Nacos 的服务名称cloud:nacos:server-addr: 192.168.52.128:8848  # Nacos 服务器地址,配置 Nacos 注册中心地址gateway:routes:  # 路由配置- id: item-service  # 路由 ID,唯一标识,可以随便命名uri: lb://item-service  # 目标服务地址,即从注册中心获取 item-service 的地址predicates:  # 断言,即路由转发的规则- Path=/items/**,/search/**  # 匹配 /items/ 开头的和 /search/ 开头的请求到 item-service 服务获取响应- id: user-service  uri: lb://user-service  predicates:  - Path=/items/**,/search/** 

4、最后启动整个项目的时候也要把网关启动

由下图可见网关的效果有了

 网关的登录校验

网关过滤器有两种,分别是:

  • GatewayFilter: 路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
  • GlobalFilter: 全局过滤器,作用范围是所有路由;声明后自动生效。

网关加公共依赖XXX-common实现请求的校验

1、网关过滤器过滤请求(Filters文件夹)

@Component // 将该类标记为Spring组件,使其成为Spring容器管理的Bean
@RequiredArgsConstructor // Lombok注解,自动生成一个包含所有final字段的构造函数
public class AuthGlobalFilter implements GlobalFilter, Ordered {// 依赖注入JwtTool,用于JWT的解析和验证private final JwtTool jwtTool;// 依赖注入AuthProperties,包含认证相关的配置信息,如排除路径等private final AuthProperties authProperties;// AntPathMatcher用于路径匹配,判断请求路径是否在排除路径中private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1. 获取当前请求对象ServerHttpRequest 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 (headers != null && !headers.isEmpty()) {token = headers.get(0); // 获取第一个authorization头,通常为Bearer Token}// 4. 校验并解析tokenLong userId = null;try {// 使用JwtTool解析token,获取用户IDuserId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果token无效或解析失败,拦截请求并返回401 Unauthorized状态码ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete(); // 结束请求处理}// 打印用户ID(通常用于调试,生产环境中不建议直接打印敏感信息)System.out.println("userId = " + userId);String userInfo = userId.toString();// 将用户信息存入请求头ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("user-info", userInfo)).build();// 5. 如果token有效,继续执行后续的过滤器链return chain.filter(swe);}// 判断请求路径是否在排除路径列表中private boolean isExclude(String path) {for (String pathPattern : authProperties.getExcludePaths()) {// 使用AntPathMatcher进行路径匹配if (antPathMatcher.match(pathPattern, path)) {return true; // 如果匹配到排除路径,返回true}}return false; // 否则返回false}@Overridepublic int getOrder() {// 返回过滤器的执行顺序,0表示最高优先级return 0;}
}

 过滤器里涉及的一些依赖

// jwt校验工具
@Component
public class JwtTool {private final JWTSigner jwtSigner;public JwtTool(KeyPair keyPair) {this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);}public String createToken(Long userId, Duration ttl) {// 1.生成jwsreturn JWT.create().setPayload("user", userId).setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis())).setSigner(jwtSigner).sign();}/*** 解析token** @param token token* @return 解析刷新token得到的用户信息*/public Long parseToken(String token) {// 1.校验token是否为空if (token == null) {throw new UnauthorizedException("未登录");}// 2.校验并解析jwtJWT jwt;try {jwt = JWT.of(token).setSigner(jwtSigner);} catch (Exception e) {throw new UnauthorizedException("无效的token", e);}// 2.校验jwt是否有效if (!jwt.verify()) {// 验证失败throw new UnauthorizedException("无效的token");}// 3.校验是否过期try {JWTValidator.of(jwt).validateDate();} catch (ValidateException e) {throw new UnauthorizedException("token已经过期");}// 4.数据格式校验Object userPayload = jwt.getPayload("user");if (userPayload == null) {// 数据为空throw new UnauthorizedException("无效的token");}// 5.数据解析try {return Long.valueOf(userPayload.toString());} catch (RuntimeException e) {// 数据格式有误throw new UnauthorizedException("无效的token");}}
}// 拦截器拦截
@Data
@Component
@ConfigurationProperties(prefix = "hm.auth")
public class AuthProperties {private List<String> includePaths;private List<String> excludePaths;
}

2、网关的yaml文件里配置不需要校验直接放行的请求 

hm:jwt: #解析jwt密钥文件location: classpath:hmall.jksalias: hmallpassword: hmall123tokenTTL: 30mauth:excludePaths:- /search/**- /users/login- /items/**- /hi

3、由于每一个微服务都导入了XX-common模块的依赖,所以在XX-common模块里配置并注册拦截器,拦截所有发送到每个微服务里的请求,用于将请求头里用户信息存入线程池。

public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取登录用户信息String userInfo = request.getHeader("user-info"); // 从请求头里获取// 2. 判断是否获取了用户,如果有,存入ThreadLocalif (StrUtil.isNotBlank(userInfo)) {UserContext.setUser(Long.valueOf(userInfo));}// 3. 放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理用户UserContext.removeUser();}
}

 4、注册XX-common模块里的拦截器

@Configuration
@ConditionalOnClass(DispatcherServlet.class) // 使得网关不去生效改拦截器
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());// 默认拦截所有的请求,目的是为了将每一个请求里包含的用户信息存入线程池}
}

5、配置静态资源文件夹下的spring.factories文件,取保每个微服务可以读取到XX-common模块里的拦截器

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.hmall.common.config.MyBatisConfig,\com.hmall.common.config.MvcConfig,\com.hmall.common.config.JsonConfig

OpenFeign传递用户信息

使用OpenFeign时,一个微服务发送给另一个微服务的请求也要携带用户信息到请求头里,要和网关发送给微服务的请求一样。所有要在公共api模块里加拦截器,使得每一个请求的请求头里添加用户信息。

写到OpenFeign的配置类里,且微服务的启动类加上@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)的注解

 // 写到OpenFeign的配置类里,且微服务的启动类加上
// @EnableFeignClients(basePackages = "com.hmall.api.client",
//  defaultConfiguration = DefaultFeignConfig.class)的注解
@Bean // 声明为一个Bean,可以被Spring容器管理public RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 获取当前用户的IDLong userId = UserContext.getUser(); // 导入XXX-common模块里的线程池// 如果用户ID不为空,则添加到请求头中if (userId != null) { // 确保每一个微服务之间发送的请求也携带user-info到请求头里// 将用户ID添加到请求头中,key为"user-info"System.out.println("将用户ID添加到请求头中,key为user-info,id为" + userId);template.header("user-info", userId.toString());}}};}

nacos共享配置

由于每一个微服务的yaml文件里有多个共同的配置信息,所有可以将其抽取出来的配置共同使用nacos注册中心配置。

 每一个微服务里导入如下依赖即可实现。

<!-- 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>  

nacos里共同配置的信息()${XXX:YY}表示如果读取不到XXX则默认为YY

# 数据库和mybatis
spring:datasource:url: jdbc:mysql://${hm.db.host:192.168.52.128}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: ${hm.db.un:root}password: ${hm.db.pw:123}
mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandlerglobal-config:db-config:update-strategy: not_nullid-type: auto# 日志记录
logging:level:com.hmall: debugpattern:dateformat: HH:mm:ss:SSSfile:path: "logs/${spring.application.name}"# swagger配置
knife4j:enable: trueopenapi:title: ${hm.swagger.title:黑马商城接口文档}description: ${hm.swagger.desc:黑马商城接口文档}email: zhanghuyi@itcast.cnconcat: 虎哥url: https://www.itcast.cnversion: v1.0.0group:default:group-name: defaultapi-rule: packageapi-rule-resources:- ${hm.swagger.package}

拉取nacos里的配置文件到本地微服务(以下为bootstrap.yaml文件)

spring:  main:  additional-properties: --add-opens=java.base/java.lang.invoke=ALL-UNNAMED  application:  # 应用程序名称,用于标识该服务,在Nacos或其他服务注册中心中可用到  name: cart-service  cloud:  nacos:  # Nacos的服务地址,用于连接到Nacos服务器  server-addr: localhost:8848  # nacos地址  config:  # 配置文件的格式,这里指定为YAML格式  file-extension: yaml  # 定义共享配置文件列表,这些配置将从Nacos服务器加载  shared-configs:   # 一定对应好nacos里的Data ID- data-id: shared-jdbc.yaml  # JDBC共享配置文件  - data-id: shared-log.yaml    # 日志共享配置文件  - data-id: shared-swagger.yaml # Swagger共享配置文件  

nacos配置里的变量在本地微服务里配置好(以下为application.yaml文件)

server:port: 8082
feign:okhttp:enabled: true
hm:db:database: hm-cartswagger:title: "黑马城市购物车服务接口文档"package: com.hmall.cart.controller

配置热更新

配置热更新:修改配置文件里的配置的时候,不需要重新启动微服务项目配置就可以生效配置。

具体应用实例

需求:购买车的限定数量目前是写死在业务中的,将其改为读取配置文件属性,并将配置置交给Nacos管理,实现热更新。

首先在nocas配置要限定数量所在的微服务的yaml文件

之后在对应的微服务里添加config文件 

@Data
@Component
@ConfigurationProperties(prefix = "hm.cart") // 对应yaml文件里的配置
public class CartProperties {private Integer maxItems;
}

最后在业务文件里面就可以去使用了

private final CartProperties cartProperties; // 导入依赖
......private void checkCartsFull(Long userId) {int count = lambdaQuery().eq(Cart::getUserId, userId).count();if (count >= cartProperties.getMaxItems()) {throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxItems()));}}
......

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

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

相关文章

2025年3月11日(有限元牛顿迭代法:通俗讲解与示例)

牛顿迭代法的正确流程解释 是的&#xff0c;你的理解基本正确&#xff01;但需要更准确地描述内外力的关系和迭代逻辑。以下是更清晰的步骤说明&#xff1a; 核心流程&#xff08;修正版&#xff09; 假设已知 外力 ( F_{\text{ext}} )&#xff08;如2000 N&#xff09;&…

爬虫的精准识别:基于 User-Agent 的正则实现

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

【AI大模型】LLM训练deepseek如何识别视频

要让像DeepSeek这样的大语言模型&#xff08;LLM&#xff09;具备视频识别能力&#xff0c;需要结合多模态学习技术&#xff0c;将视觉信息与文本语义进行融合。以下是实现这一目标的关键步骤和技术要点&#xff1a; --- 一、视频识别的核心挑战 1. 多模态数据&#xff1a;视频…

【物联网-以太网-W5500】

物联网-以太网-W5500 ■ W5500-简介■■■■ ■ W5500-简介 ■ ■ ■ ■

centos linux安装mysql8 重置密码 远程连接

1. 下载并安装 MySQL Yum 仓库 从 MySQL 官方网站下载并安装 Yum 仓库配置文件。 # 下载MySQL 8.0的Yum仓库包 wget https://dev.mysql.com/get/mysql80-community-release-el7-5.noarch.rpm # 安装Yum仓库包 sudo rpm -ivh mysql80-community-release-el7-5.noarch.rpm2. 启…

C++【类和对象】(超详细!!!)

C【类和对象】 1.运算符重载2.赋值运算符重载3.日期类的实现 1.运算符重载 (1).C规定类类型运算符使用时&#xff0c;必须转换成调用运算符重载。 (2).运算符重载是具有特殊名字的函数&#xff0c;名字等于operator加需要使用的运算符&#xff0c;具有返回类型和参数列表及函数…

【面试】Java 多线程

多线程 1、什么是线程和进程2、创建线程有几种方式3、线程有几种状态4、什么是上下文切换5、什么是守护线程&#xff0c;和普通线程有什么区别6、什么是线程池&#xff0c;如何实现的7、Executor和Executors的区别8、线程池处理任务的流程9、线程数设定成多少更合适10、执行exe…

宿主机运行pyspark任务读取docker hadoop容器上的数据

熬了四个大夜才搞明白&#xff0c;最晚一天熬到早上十点/(ㄒoㄒ)/~~&#xff0c;最后发现只要加个参数就解决了。。。抱头痛哭 问题描述&#xff1a; Hadoop集群部署在docker容器中&#xff0c;宿主机执行pyspark程序读取hive表 问题一&#xff1a;当master(local[*])时&…

《平凡的世界》:在泥土中寻找星辰的勇气

“平凡不是平庸的代名词&#xff0c;而是千万人用脊梁扛起时代的勋章。”——路遥的《平凡的世界》用百万字书写了黄土地上孙少安、孙少平两兄弟的挣扎与觉醒&#xff0c;撕开了“奋斗逆袭”的浪漫滤镜&#xff0c;告诉你&#xff1a;真正的英雄主义&#xff0c;是在认清了生活…

【SpringMVC】深入解析使用 Postman 和浏览器模拟将单个与多个参数传递到后端和后端接收过程

SpringMVC—请求(Request) 访问不同的路径&#xff0c;就是发送不同的请求&#xff1b;在发送请求时&#xff0c;可能会带一些参数&#xff0c;所以学习Spring的请求&#xff0c;主要是学习如何传递参数到后端以及后端如何接收&#xff1b; 我们主要是使用 浏览器 和 Postman …

04 | 初始化 fastgo 项目仓库

提示&#xff1a; 所有体系课见专栏&#xff1a;Go 项目开发极速入门实战课&#xff1b;欢迎加入我的训练营&#xff1a;云原生AI实战营&#xff0c;一个助力 Go 开发者在 AI 时代建立技术竞争力的实战营&#xff1b;本节课最终源码位于 fastgo 项目的 feature/s01 分支&#x…

Docker 安装成功后,安装 Dify 中文版本的步骤

Docker 安装成功后&#xff0c;安装 Dify 中文版本的步骤如下1&#xff1a; 克隆 Dify 代码仓库&#xff1a;在终端中执行以下命令&#xff0c;将 Dify 源代码克隆至本地环境。 bash git clone https://github.com/langgenius/dify.git进入 Dify 的 docker 目录&#xff1a; b…

RPC服务调用深度解析:从原理到Java实践

一、RPC的核心原理与架构设计 1.1 RPC的本质 RPC&#xff08;Remote Procedure Call&#xff09;是一种分布式系统间通信协议&#xff0c;允许程序像调用本地方法一样调用远程服务。其核心目标是通过位置透明性和协议标准化隐藏网络通信细节。RPC的调用流程可抽象为以下步骤&…

电脑的写字板如何使用?

打开写字板&#xff1a; 直接按一下键盘上的win R 键&#xff0c;然后输入&#xff1a;write &#xff0c; 再按一下回车 , 即可打开写字板 可以在里面写文字 和 插入图片等… &#xff0c; 如下所示&#xff1a; 保存写字板内容&#xff1a; 当我们写好了之后&#xff0c;…

医疗AI测试实战:如何确保人工智能安全赋能医疗行业?

一、医疗AI测试的重要性 人工智能&#xff08;AI&#xff09;正广泛应用于医疗行业&#xff0c;如疾病诊断、医学影像分析、药物研发、手术机器人和智能健康管理等领域。医疗AI技术的应用不仅提高了诊断效率&#xff0c;还能降低误诊率&#xff0c;改善患者治疗效果。然而&…

AI日报 - 2025年3月12日

AI日报 - 2025年3月12日 &#x1f31f; 今日概览&#xff08;60秒速览&#xff09; ▎&#x1f916; AGI突破 | Anthropic CEO预测AI将主导代码编写 &#x1f52c; 自训练技术显著提升LLM思维清晰度 ▎&#x1f4bc; 商业动向 | OpenAI与CoreWeave达成119亿美元基建协议 &…

跳表数据结构

跳表&#xff08;Skip List&#xff09;是一种支持高效插入、删除和查找的链表结构&#xff0c;用于加速查找操作&#xff0c;特别适用于有序数据集合。它在Redis、LevelDB等系统中被用于**有序集合&#xff08;Sorted Set&#xff09;**的实现。 1. 跳表的结构 跳表的核心思…

系统会把原先的对话状态堆栈从 [“assistant“] 更新为 [“assistant“, “update_flight“]这个更新的处理过程

这个更新主要是在 State 定义中通过 Annotated 来自动处理的。在 State 类型中&#xff0c;我们对 dialog_state 字段绑定了 update_dialog_stack 函数&#xff0c;如下所示&#xff1a; class State(TypedDict):messages: Annotated[list[AnyMessage], add_messages]user_inf…

HTTP发送POST请求的两种方式

1、json String json HttpRequest.post(getUrl(method, "v1", url, userId, appKey)).header("Content-type", "application/json") // 设置请求头为 JSON 格式.body(JSONUtil.toJsonStr(params)) // 请求体为 JSON 字符串.execute().body(); …

Windows 万兴恢复专家 Wondershare Recoverit-v13.5.7.9-[电脑数据恢复工具]

Windows 万兴恢复专家Wondershare_Recoverit 链接&#xff1a;https://pan.xunlei.com/s/VOL3z608vzAj_IYTvH-F1q7kA1?pwdiu89# 1. 打开Setup.exe进行安装&#xff0c;安装完不要打开软件&#xff0c;记住安装目录 2. 将"Crack"文件夹内的所有文件复制到安装目录 …