SpringCloud 入门(4)—— 网关
上一篇:SpringCloud 入门(3)—— Nacos配置中心-CSDN博客
Spring Cloud Gateway 作为 Spring Cloud 生态系统的一部分,主要在微服务架构中充当 API 网关的角色。它提供了统一的入口点来处理所有的 HTTP 请求,并将这些请求路由到不同的后端微服务。
Spring Cloud Gateway 是一个强大的工具,它不仅简化了微服务间的通信,还增强了系统的安全性、稳定性和可扩展性。对于任何采用微服务架构的应用程序来说,它都是不可或缺的一部分。
1.快速入门
1.1创建网关
单独创建一个模块,配置网关
首先导入网关所需依赖
<!--网关--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--服务发现中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency>
本地服务中配置,指定nacos地址
#微服务配置
spring:application:name: gatewaycloud:nacos:server-addr: 124.70.208.223:8848discovery:namespace: devgroup: xuecheng-plus-project # Nacos 服务发现的组名,用于进一步隔离服务config:namespace: devgroup: xuecheng-plus-project file-extension: yamlrefresh-enabled: true #动态刷新shared-configs:- data-id: logging-${spring.profiles.active}.yamlrefresh: trueprofiles:active: dev
1.2nacos配置中心
在nacos配置中心中进行远程配置;所有的网关相关配置全部配置到nacos中,创建gateway-dev.yaml
server:port: 63010 # 网关端口
spring:cloud:gateway:routes: # 网关路由配置- id: content-api # 路由id,自定义,只要唯一即可uri: lb://content-api # 路由的目标地址 lb就是负载均衡,后面跟服务名称predicates: # 路由断言,也就是判断请求是否符合路由规则的条件- Path=/content/** # 这个是按照路径匹配,只要以/content/开头就符合要求- id: system-apiuri: lb://system-apipredicates:- Path=/system/**- id: media-apiuri: lb://media-apipredicates:- Path=/media/**
四个属性含义如下:
-
id
:路由的唯一标识 -
uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。 -
predicates
:路由断言,其实就是匹配条件 -
filters
:路由过滤条件,
1.3路由匹配条件
SpringCloudGateway中支持的断言类型有很多:
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org,**.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
weight | 权重处理 |
2.网关全局过滤器
在网关处,在filter包中实现GlobalFilter接口配置全局过滤器,实现 Ordered接口定义过滤器的优先级。
在过滤器中,首先获取需要放行的路径(在配置文件中定义,AuthProperties 类中读取不进行拦截的路径),如果请求路径,不需要拦截,直接放行。
如果该路径在配置文件中没有定义,都需要拦截,首先获取请求头中的authorization,拿到token值,进行解析。拿到用户的id。将用户的id放到请求头中user-info,转发给其他的微服务
package com.hmall.gateway.filter;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.common.utils.UserContext;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@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();}// 5.如果有效,传递用户信息String userInfo = userId.toString();System.out.println("userId"+userInfo);ServerWebExchange swe = exchange.mutate().request(builder -> builder.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;//数字约小,优先级越高}
}
上面我们已经在网关过滤器中,获取请求头中的token,解析出用户id,放回请求头user-info中,传递给各个微服务。
但通过openFeign发起的请求中,并没有通过网关,请求中没有携带user-info。
所以我们需要配置openFeign拦截器,拿到用户id,并放到请求头中,转发给其他微服务
下面是针对openFeign的配置
package com.hmall.api.config;import com.hmall.common.utils.UserContext;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;public class DefaultFeignConfig {@Beanpublic Logger.Level feignLogLevel(){//日志信息return Logger.Level.FULL;}@Beanpublic RequestInterceptor userInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 获取登录用户Long userId = UserContext.getUser();if(userId == null) {// 如果为空则直接跳过return;}// 如果不为空则放入请求头中,传递给下游微服务template.header("user-info", userId.toString());}};}
}
配置完成后需要在使用了openFeign微服务的启动类上加上@EnableFeignClients注解,激活拦截器
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
@MapperScan("com.hmall.pay.mapper")
@SpringBootApplication
public class PayApplication {public static void main(String[] args) {SpringApplication.run(PayApplication.class, args);}
}
3.配置全局id
3.1SpringMvc拦截器
在网关和OpenFeign中我们已经将用户id放到了请求头user-info中,而所有的微服务,都需要依赖常量模块common,故我们在common模块中,定义SpringMVC拦截器,获取userId,保存到TreadLocal中。
这里可能有人会问,为什么不在网关过滤器中直接将用户id保存在TreadLocal中呢?,而是放到请求头中,在common模块中,定义拦截器拿到用户id,在保存TreadLocal中。这就不得不提一下TreadLocal的特性了
ThreadLocal
是 Java 中的一个工具,它为每个线程提供独立的变量副本,使得同一个线程在不同地方访问ThreadLocal
变量时都能得到相同的数据,而不同线程之间不会相互影响。然而,ThreadLocal
的作用范围仅限于当前 JVM(Java 虚拟机)中的线程,并且它的生命周期与线程的生命周期绑定。在微服务架构中,网关和各个微服务通常是独立部署的应用程序,它们可能运行在不同的进程中,甚至是在不同的服务器上。因此,当你在一个微服务(如网关)中使用
ThreadLocal
来保存用户信息时,这些信息不会自动传递到其他微服务中。
在common模块中定义拦截器
import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的用户信息String userInfo = request.getHeader("user-info");// 2.判断是否为空if (StrUtil.isNotBlank(userInfo)) {// 不为空,保存到ThreadLocalUserContext.setUser(Long.valueOf(userInfo));}// 3.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserContext.removeUser();}
}
添加拦截器
import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}
不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是common模块,因为common模块中的扫描包路径与其它微服务的扫描包不一致,且常量模块中,没有单独的SpringBootApplication启动类,无法被扫描到,因此无法生效。
基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.hmall.common.config.MyBatisConfig,\com.hmall.common.config.MvcConfig,\com.hmall.common.config.JsonConfig
注解: @ConditionalOnClass(DispatcherServlet.class)
DispatcherServlet
是 Spring MVC 的核心组件,负责接收和分发 HTTP 请求到相应的控制器。因此,当应用程序是基于 Spring MVC 构建时,类路径中必然包含 DispatcherServlet
类
@ConditionalOnClass
用于自动配置类中,以确保该拦截器只有在处理http请求时才生效,拦截。
3.2TreadLocal
package com.hmall.common.utils;public class UserContext {private static final ThreadLocal<Long> tl = new ThreadLocal<>();/*** 保存当前登录用户信息到ThreadLocal* @param userId 用户id*/public static void setUser(Long userId) {tl.set(userId);}/*** 获取当前登录用户信息* @return 用户id*/public static Long getUser() {return tl.get();}/*** 移除当前登录用户信息*/public static void removeUser(){tl.remove();}
}
相关文章:

SpringCloud 入门(4)—— 网关
上一篇:SpringCloud 入门(3)—— Nacos配置中心-CSDN博客 Spring Cloud Gateway 作为 Spring Cloud 生态系统的一部分,主要在微服务架构中充当 API 网关的角色。它提供了统一的入口点来处理所有的 HTTP 请求,并将这些请…...

什么是WebAssembly?怎么使用?
一、简述 WebAssembly,也称为Wasm,是基于堆栈的虚拟机的二进制指令格式。它被设计为一个可移植的目标,用于编译C、C和Rust等高级编程语言,允许代码以接近本机速度在web浏览器中运行。WebAssembly于2015年由包括谷歌、微软、Mozill…...

v3s点RGB屏 40pin 800x480,不一样的点屏,不通过chosen。
一、背景、目的、简介。 一般来说,通过uboot将屏幕参数传给kernel,是通过修改设备树。 uboot和kernel都需要屏幕点亮。uboot侧重于显示一张图片。而kernel则多是动画。 在这里,我先是找到了一个裸机点屏的代码。将其编译成静态库后&#x…...

某科技局国产服务器PVE虚拟化技术文档
环境介绍 硬件配置 服务器品牌:黄河 型号:Huanghe 2280 V2 Cpu型号:kunpeng-920 磁盘信息 :480SSD * 2 ,4T*4 网卡:板载四口千兆 如下表 四台服务器同等型号配置,均做单节点虚拟化,数据保护采用底层r…...

中科岩创边坡自动化监测解决方案
行业现状 由于边坡不稳定性因素,可能会造成斜坡上的岩土体沿着某个面不均匀向下向外滑动,形成滑坡;陡峭山坡上岩土体在重力作用下,发生陡然倾落运动,造成崩塌;在沟谷或山坡上产生的夹带大量泥沙、石块等固体…...

GPT-O3:简单介绍
GPT-O3:人工智能领域的重大突破 近日,OpenAI发布了其最新的AI模型GPT-O3,这一模型在AGI评估中取得了惊人的成绩,展现出强大的能力和潜力。GPT-O3的出现标志着人工智能领域的重大进步,预计将在2025年实现更大的突破。 …...

cudnn版本gpu架构
nvcc --help 可以看 --gpu-architecture 写到的支持的架构 NVIDIA 的 GPU 架构是按代次发布的,以下是这些架构的对应说明: NVIDIA Hopper: 这是 NVIDIA 于 2022 年推出的架构之一,面向高性能计算(HPC)和人工智能&…...

数据库安全-redisCouchdb
1.redis未授权访问 默认端口:6379 1.1 Redis沙盒逃逸漏洞RCE-CVE-2022-0543 介绍:Redis 是一套开源的使用 ANSI C编写、支持网络、可基于内存亦可持久化的日志型、键值存储数据库,并提供多种语言的API。Redis 如果在没有开启认证的情况下,…...

ubuntu22.04安装PaddleX3
PaddleOCR 安装过程可以参考PaddleX本地安装教程 我的电脑环境配置: ubuntu22.04 cuda11.8(之前安装的是12.4没有匹配的paddle-gpu;这里改成11.8) 一、安装基础环境 1、 conda create -n ppx1 python3.10 2、 conda activate ppx1 3、…...

Flutter 实现全局悬浮按钮学习
Flutter 代码如何实现了一个全局悬浮按钮,当点击按钮时,会显示一个可以拖动并且通过长按可以移除的悬浮控件。 前置知识点学习 Offset Offset 是 Flutter 中的一个类,用于表示二维平面中的位置或位移。它通常用于描述坐标系中的一个点&…...

14-C语言多文件编程
一、各种变量 在学习多文件编程之前,先要了解清楚各种变量的作用范围以及生命周期。 1.普通变量 1.1普通局部变量 定义形式:在复合语句{}里面定义的变量为普通局部变量;作用范围:在复合语句{}里面有效;生命周期&am…...

基于Springboot的在线问卷调查系统【附源码】
基于Springboot的在线问卷调查系统 效果如下: 系统主页面 问卷列表页面 个人中心页面 系统登陆页面 管理员主页面 问卷管理页面 研究背景 随着互联网技术的飞速发展,传统的问卷调查方式因其时间和地点的限制,难以高效地收集到足够的数据。…...

Redis热点数据管理全解析:从MySQL同步到高效缓存的完整解决方案
1. 引言 1.1 背景介绍:MySQL与Redis在高性能场景下的结合 在现代互联网应用中,MySQL作为关系型数据库,承担了大量业务数据的存储任务。然而,随着业务的增长,海量数据的查询性能成为一个瓶颈。为了应对高并发和低延迟…...

【图书介绍】】几本Linux C\C++编程图书
Linux C\C编程,是IT领域比较稳定的职业发展方向,本文介绍几本Linux开发方面的图书。 《Linux C与C一线开发实践(第2版)》 《Linux C与C一线开发实践(第2版)(Linux技术丛书)》(朱文…...

MFC/C++学习系列之简单记录7
MFC/C学习系列之简单记录7 前言句柄的介绍句柄的使用AFX开头的函数都是干什么用的?总结 前言 在MFC的使用中发现了句柄,今天来详细学习一下MFC中如何使用句柄吧! 句柄的介绍 句柄的使用是资源管理和传递的关键机制,通过句柄将系…...

使用GPT进行SCI论文润色常用语句
声明:本文仅作为本人记录学习使用。 You are now a professional academic touch-up specialist. Please polish the English draft I am sending you next. After analyzing the paragraph, give suggestions for polishing in terms of sentence structure, gram…...

Redis密码设置与访问限制(网络安全)
现在用redis缓存热数据越来越常见了,甚至一些配置,开关等等的东西也写到redis里。原因就是redis简单高效。redis里的数据也越来越重要了,例如一些业务的中间数据会暂时存放在redis里,所以限制redis的访问还是很有必要。 本文通过…...

php的线程安全与非线程安全版本的区别
PHP的线程安全(Thread Safe,简称TS)与非线程安全(Non-Thread Safe,简称NTS)版本主要在多线程环境下的行为特性、性能、以及适用场景上存在差异。以下是两者的详细对比: 一、定义与概念 线程安…...

标贝科技受邀出席2024ADD数据应用场景大会 共议数据要素发展新契机
12月13日,由北京市通州区人民政府主办,通州区经济和信息化局、通州区台湖镇人民政府承办的2024-ADD数据应用场景大会成功举办。标贝科技作为AI数据领域代表企业受邀出席大会,与数据要素创业者、投资人一起走进通州台湖,共话数据要…...

electron-vite打包后图标不生效问题
在electron-builder.yml中,通过icon配置自己的图标,以下是正确代码 win:executableName: 名称icon: build/icon.ico nsis:artifactName: ${name}-${version}.${ext}shortcutName: ${productName}uninstallDisplayName: ${productName}createDesktopShor…...

systemverilog中的unique if
1 基本概念 在 SystemVerilog 中,unique if是一种条件判断结构。它用于检查多个互斥的条件,以确保在给定的情况下只有一个条件分支被执行。这有助于提高代码的可读性和可维护性,同时也能帮助发现潜在的逻辑错误,报错原因有以下两个…...

【MySQL篇】事务的认识以及四大特性
何为事务? 事务(Transaction)是指一组操作的集合,这些操作要么全部执行成功,要么全部不执行。事务通常用于保证数据库的一致性、完整性和可靠性,确保数据的完整性与正确性。 有效避免部分执行࿰…...

Windows 11 安装 Dify 完整指南 非docker环境
# Windows 11 安装 Dify 完整指南## 前置要求- Python 3.11 - Node.js 18 - PostgreSQL 14 - Redis for Windows - Git - Ollama (可选,用于本地模型)## 详细安装步骤### 1. 安装必要软件1. **Python 3.11**- 从 https://www.python.org/downloads/release/python-…...

电子电气架构 --- 什么是EPS?
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所谓鸡汤,要么蛊惑你认命,要么怂恿你拼命,但都是回避问题的根源&…...

12寸半导体厂等保安全的设计思路
等级保护(等保)二级和三级的主要区别在于安全要求的严格程度、所需部署的安全措施和设备、以及对安全事件响应和处理的能力。以下是等保二级和三级之间的一些关键区别: 一、 安全要求严格程度: - 等保二级:适用于需要较高安全保护的信息系统,要求能够防范轻微的恶意攻击…...

【Chrome Extension】一、CSDN计时扩展设计
【Chrome Extension】一、CSDN计时扩展设计 重点内容内容脚本 content_scripts 文件目录1、整体目录2、manifest.json3、scripts/content.js4、css/content.css 重点内容 内容脚本 content_scripts 1、manifest.json文件配置 {"manifest_version": 3, # *依赖Chro…...

C语言——数据在内存中的存储
目录 前言 一数据类型 类型归类 二整形在内存中的存储 原反补码 大小端 相关练习题 三浮点数在内存中的储存 浮点数储存规则 前言 只有取学习数据在内存中的存储,我们在以后才能定义好(用好)各种类型的数据! 一数据类型…...

Python(二)str、list、tuple、dict、set
string name abcdefprint(name[0]) #a # 切片:取部分数据 print(name[0:3]) # 取 下标为0,1,2的字符 abc print(name[2:]) # 取 下标为2开始到最后的字符 cdef print(name…...

如何在谷歌浏览器中设置邮件客户端
在日常生活和工作中,电子邮件已经成为不可或缺的沟通工具。对于使用谷歌浏览器的用户来说,将Chrome设置为默认邮件客户端可以带来诸多便利。本文将详细介绍如何在谷歌浏览器中设置邮件客户端,帮助大家轻松实现这一目标。 在谷歌浏览器中设置邮…...

Robot Framework搭建自动化测试框架
1.配置环境 需要安装jdk8,andrid sdk(安装adb),pycharm编译环境以及软件 安装Robot Framework 首先,你需要安装Robot Framework,可以使用 pip 进行安装: pip install robotframework安装所需的…...