微服务——网关、网关登录校验、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()));}}
......
相关文章:

微服务——网关、网关登录校验、OpenFeign传递共享信息、Nacos共享配置以及热更新、动态路由
之前学习了Nacos,用于发现并注册、管理项目里所有的微服务,而OpenFeign简化微服务之间的通信,而为了使得前端可以使用微服务项目里的每一个微服务的接口,就应该将所有微服务的接口管理起来方便前端调用,所以有了网关。…...

【数据结构】二叉搜索树、平衡搜索树、红黑树
二叉搜索树(Binary Search Tree) 二叉搜索树是一种特殊的二叉树,它用来快速搜索某个值,对于每个节点都应该满足以下条件: 若该节点有左子树,那么左子树中所有节点的值都应该小于该节点的值。若该节点有右…...

Spring Boot 解析 LocalDateTime 失败?Uniapp 传输时间变 1970 的原因与解决方案
目录 前言1. 问题分析2. 时间戳(推荐,可尝试)3. 使用 JsonDeserialize & JsonSerialize(中立)4. 前端传 ISO-8601 格式(不推荐,可尝试)5. 用 String(中立)…...
Xilinx ZYNQ FSBL解读:LoadBootImage()
篇首 最近突发奇想,Xilinx 的集成开发环境已经很好了,很多必要的代码都直接生成了,这给开发者带来了巨大便利的同时,也让人错过了很多代码的精彩,可能有很多人用了很多年了,都还无法清楚的理解其中过程。博…...

mysql中in和exists的区别?
大家好,我是锋哥。今天分享关于【mysql中in和exists的区别?】面试题。希望对大家有帮助; mysql中in和exists的区别? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 MySQL 中,IN 和 EXISTS 都用于进行子查询,但它…...

oracle 数据导出方案
工作中有遇到需要将oracle 数据库表全部导出,还需要去除表数据中的换行符。 方案 shell 设计 封装函数 1 function con_oracle() 用于连接oracle 2 function send_file() 用于发送文件 3 主程序 使用循环将所有表导出并发送到数据服务器 主程序 程序代码 #!…...
Apache Commons Lang3 和 Commons Net 详解
目录 1. Apache Commons Lang3 1.1 什么是 Apache Commons Lang3? 1.2 主要功能 1.3 示例代码 2. Commons Net 2.1 什么是 Commons Net? 2.2 主要功能 2.3 示例代码 3. 总结 3.1 Apache Commons Lang3 3.2 Commons Net 3.3 使用建议 4. 参考…...
从0开始的操作系统手搓教程33:挂载我们的文件系统
目录 代码实现 添加到初始化上 上电看现象 挂载分区可能是一些朋友不理解的——实际上挂载就是将我们的文件系统封装好了的设备(硬盘啊,SD卡啊,U盘啊等等),挂到我们的默认分区路径下。这样我们就能访问到了ÿ…...
【Linux】36.简单的TCP网络程序
文章目录 1. TCP socket API 详解1.1 socket():打开一个网络通讯端口1.2 bind():绑定一个固定的网络地址和端口号1.3 listen():声明sockfd处于监听状态1.4 accept():接受连接1.5 connect():连接服务器 2. 实现一个TCP网络服务器2.1 Log.hpp - "多级日志系统"2.2 Daem…...
时序分析
1、基本概念介绍 1.1、 建立时间 T(su) 建立时间:setup time,它是指有效的边沿信号到来之前,输入端口数据保持稳定的时间。 1.1.1、 建立时间要求: 建立时间要求指的是 想要寄存器如期的工作,在有效时…...
doris:ClickHouse
Doris JDBC Catalog 支持通过标准 JDBC 接口连接 ClickHouse 数据库。本文档介绍如何配置 ClickHouse 数据库连接。 使用须知 要连接到 ClickHouse 数据库,您需要 ClickHouse 23.x 或更高版本 (低于此版本未经充分测试)。 ClickHouse 数据库的 JDBC 驱动程序&a…...
NLP常见任务专题介绍(1)-关系抽取(Relation Extraction, RE)任务训练模板
📌 关系抽取(Relation Extraction, RE)任务训练示例 本示例展示如何训练一个关系抽取模型,以识别两个实体之间的关系。 1️⃣ 任务描述 目标:从文本中提取两个实体之间的语义关系,例如 “人物 - 组织”、“药物 - 疾病”、“公司 - 创始人” 等。输入:句子 + 标注的实…...

大模型Transformer的MOE架构介绍及方案整理
前言:DeepSeek模型最近引起了NLP领域的极大关注,也让大家进一步对MOE(混合专家网络)架构提起了信心,借此机会整理下MOE的简单知识和对应的大模型。本文的思路是MOE的起源介绍、原理解释、再到现有MOE大模型的整理。 一…...
零基础掌握Linux SCP命令:5分钟实现高效文件传输,小白必看!
引言 “为什么我传个文件到服务器要折腾半小时?” 如果你也曾在Linux系统中为文件传输抓狂,今天这篇保姆级教程就是你的救星!SCP命令——一个基于SSH协议的高效传输工具,只需5分钟,彻底告别FTP客户端和繁琐操作&#…...
分类评价指标
基础概念解释 TP、TN、FP、FN 这里T是True,F是False,P为Positive,N为Negative TP:被模型正确地预测为正样本(原本为正样本,预测为正样本) TN:被模型正确地预测为负样本࿰…...
Python项目-基于Django的在线教育平台开发
1. 项目概述 在线教育平台已成为现代教育的重要组成部分,特别是在后疫情时代,远程学习的需求显著增加。本文将详细介绍如何使用Python的Django框架开发一个功能完善的在线教育平台,包括系统设计、核心功能实现以及部署上线等关键环节。 本项…...

子数组问题——动态规划
个人主页:敲上瘾-CSDN博客 动态规划 基础dp:基础dp——动态规划-CSDN博客多状态dp:多状态dp——动态规划-CSDN博客 目录 一、解题技巧 二、最大子数组和 三、乘积最大子数组 四、最长湍流子数组 五、单词拆分 一、解题技巧 区分子数组&…...

linux设置pem免密登录和密码登录
其实现在chatgpt 上面很多东西问题都可以找到比较好答案了,最近换了一个服务器,记录一下。 如果设置root用户,就直接切换到cd .ssh目录下生成ssh key即可,不需要创建用户创建用户的ssh文件夹了 比如说我要让danny这个用户可以用p…...
什么是Flask
Flask是Python中一个简单、灵活和易用的Web框架,适合初学者使用。它提供了丰富的功能和扩展性,可以帮助开发者快速构建功能完善的Web应用程序。 以下是Python Flask框架的一些特点和功能: Flask 是一个使用 Python 编写的轻量级 WSGI 微 Web…...

Spark(8)配置Hadoop集群环境-使用脚本命令实现集群文件同步
一.hadoop的运行模式 二.scp命令————基本使用 三.scp命令———拓展使用 四.rsync远程同步 五.xsync脚本集群之间的同步 一.hadoop的运行模式 hadoop一共有如下三种运行方式: 1. 本地运行。数据存储在linux本地,测试偶尔用一下。我们上一节课使用…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...

算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...

若依登录用户名和密码加密
/*** 获取公钥:前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...

动态规划-1035.不相交的线-力扣(LeetCode)
一、题目解析 光看题目要求和例图,感觉这题好麻烦,直线不能相交啊,每个数字只属于一条连线啊等等,但我们结合题目所给的信息和例图的内容,这不就是最长公共子序列吗?,我们把最长公共子序列连线起…...