基于黑马教程——微服务架构解析(二)

本篇文章基于黑马程序员的微服务课程内容,结合个人学习过程中的理解与思考进行整理。本节将围绕以下几个问题展开:什么是网关和配置管理

前面那篇文章,我们了解如何把一个单体的项目拆成分布式微服务项目,并且讲解一下各个服务之间是如何通信的。发现一些问题:

  1. 每个微服务都有不同的地址和端口,那么前端需要调用微服务的功能时,就会出现以下问题:
    • 前端需要调用不同微服务的时候,地址和端口太多,不易维护
    • 前端无法调用Nacos,当后端端口发送改变的时候,前端察觉不到
  2. 单体项目需要做登录、权限校验,还有为了方便用户信息传递登录时保存用户信息,那么拆分成微服务就会出现以下问题:
    • 每个微服务都需要编写登录校验、用户信息保存
    • 当微服务之间的调用的时候,该如何传递用户信息?

这篇文章就是解决这些问题的,也是这篇文章的主题——网关:

  • 网关路由:解决前端请求路口统一的问题
  • 网关鉴权:解决统一登录校验和用户信息获取问题
  • 统一配置管理:解决微服务,配置文件重复的问题和配置热更新的问题

1.网关路由

1.1 认识网关

网关(Gateway) 是微服务架构中的统一入口,它位于客户端与服务端之间,接收所有外部请求,然后根据请求内容将其转发到对应的后端微服务。

可以理解为:网关是微服务系统的“前门”。

在这里插入图片描述

从图中可以看出,网关作为后端的一部分,承担了统一入口的作用。前端只需请求网关,不需要关心各个微服务的具体地址。网关接收到请求后,会通过访问 Nacos 注册中心获取服务的最新信息,并根据配置的路由规则将请求转发到对应服务,同时实现负载均衡和服务发现,简化了前端调用逻辑,也增强了系统的灵活性与可维护性。

1.2 快速实现

  • 创建网关模块
  • 引入网关的依赖
  • 编写启动类
  • 编写配置文件

1.3 配置文件

这里主要讲解一下配置文件

server:port: 8080  # 网关端口,给前端的端口spring:application:name: gateway-servicecloud:nacos:discovery:server-addr: localhost:8848  # Nacos注册中心地址gateway:routes:# 订单服务路由- id: order_routeuri: lb://order-service    # 负载均衡到订单服务predicates:- Path=/order/**         # 匹配/order开头的请求- Method=GET,POST        # 允许GET/POST请求filters:- StripPrefix=1           # 移除第一段路径(/order)- AddRequestHeader=Gateway,true  # 添加请求头# 用户服务路由- id: user_routeuri: lb://user-servicepredicates:- Path=/user/**           # 匹配/user路径- After=2025-01-01T00:00:00.000+08:00  # 时间生效范围filters:- PrefixPath=/api         # 添加前缀 /api/user/**

这里我们重点关注predicates,也就是路由断言。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.网关登录校验

2.1 思路分析(JWT为例)

在这里插入图片描述

我们看到这张图网关需要完成的工作为:

  • JWT信息校验
  • 将用户信息传递到下游
  • 然后微服务之间传递用户信息

2.2 网关过滤器

首先,登录校验一定要放在转发路由之前去完成,因此我们需要先了解网关是如何进行工作的。

在这里插入图片描述

  1. 接受前端的请求,HandlerMapper,进行路由匹配
  2. 然后经过一系列的过滤器链
    1. 路径处理过滤器
    2. 添加信息头处理器
    3. 自定义处理器
    4. 等等
  3. 路由转发到相应的微服务中
  4. 微服务返回结果再次经过过处理器链

我们需要做的就是在转发到微服务之前进行登录校验,就是自定义一个网关过滤器。

网关过滤器链中的过滤器有两种:

  • GatewayFilter: 路由过滤器,作用范围比较灵活,可以是任意指定的路由Route
  • GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。

如何理解这个不可配置是什么意思? 意思就是不可以通过配置文件动态配置

2.2.1 基于全局过滤器实现登录校验

我们就自定义一个基于全局过滤器的登录校验过滤器

1. 创建自定义全局过滤器

我们创建一个LoginCheckGlobalFilter类,实现GlobalFilterOrdered接口:

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@Component
public class LoginCheckGlobalFilter implements GlobalFilter, Ordered {// 白名单路径(不需要登录校验的路径)private static final List<String> WHITE_LIST = Arrays.asList("/api/user/login","/api/user/register","/api/doc.html","/api/swagger-ui.html");@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1. 获取请求路径String path = exchange.getRequest().getPath().toString();// 2. 检查是否在白名单中if (WHITE_LIST.contains(path)) {return chain.filter(exchange); // 直接放行}// 3. 获取tokenString token = exchange.getRequest().getHeaders().getFirst("Authorization");// 4. 校验tokenif (StringUtils.isBlank(token) || !validateToken(token)) {// 未登录或token无效exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}// 5. token有效,放行请求return chain.filter(exchange);}@Overridepublic int getOrder() {return -1; // 设置过滤器执行顺序(数值越小优先级越高)}// 模拟token校验方法(实际项目中应调用认证服务)private boolean validateToken(String token) {// 这里简单模拟校验逻辑return token != null && token.startsWith("Bearer ");}
}
2. 过滤器逻辑说明
  1. 白名单检查:放行登录、注册等不需要认证的接口
  2. Token获取:从请求头Authorization中获取JWT token
  3. Token校验
    • 无效token:返回401 Unauthorized
    • 有效token:放行请求

2.3 微服务直接如何传递用户信息

现在,网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?

由于网关发送请求到微服务依然采用的是Http请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。

大致流程

首先每一次请求可以看做一个线程,之前在单体项目的时候。经过登录校验的时候,把信息放入线程的上下文中。我微服务项目也可以根据这个方法,进行改造一下:

  1. 网关 → 微服务:通过 HTTP请求头 传递用户信息(如JWT解析后的用户ID)。
  2. 微服务内部:通过 SpringMVC拦截器 + ThreadLocal 存储用户信息,避免重复解析。
  3. 微服务间调用(OpenFeign):通过 Feign拦截器 透传用户信息,确保链路完整。
具体步骤

1. 网关传递用户信息到微服务

网关(如Spring Cloud Gateway):在登录校验后,将用户信息(如userIdusername)添加到请求头中,转发到下游微服务。

# 网关配置示例(Spring Cloud Gateway)
spring:cloud:gateway:default-filters:- name: AddRequestHeaderargs:name: X-User-Idvalue: "#{@userContext.getUserId()}" # 从JWT解析后注入

2. 微服务接收并存储用户信息

2.1 定义ThreadLocal上下文
public class UserContext {private static final ThreadLocal<Long> userIdHolder = new ThreadLocal<>();public static void setUserId(Long userId) {userIdHolder.set(userId);}public static Long getUserId() {return userIdHolder.get();}public static void clear() {userIdHolder.remove();}
}
2.2 拦截器解析请求头
@Component
public class UserInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String userId = request.getHeader("X-User-Id");if (userId != null) {UserContext.setUserId(Long.valueOf(userId));}return true;}@Overridepublic void afterCompletion(...) {UserContext.clear(); // 避免内存泄漏}
}
2.3 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate UserInterceptor userInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(userInterceptor);}
}

3. OpenFeign透传用户信息

微服务间调用时,需通过Feign拦截器将用户信息附加到请求头,确保链路透明。

3.1 Feign拦截器实现

java

复制

@Component
public class FeignUserInterceptor implements RequestInterceptor {@Overridepublic void apply(RequestTemplate template) {Long userId = UserContext.getUserId();if (userId != null) {template.header("X-User-Id", String.valueOf(userId));}}
}
3.2 启用Feign拦截器

确保Feign客户端扫描到该拦截器(通常已自动注入)。


4. 完整流程总结

  1. 用户请求 → 网关:携带JWT令牌。
  2. 网关:解析JWT → 将userId放入请求头 → 转发到微服务A。
  3. 微服务A
    • 拦截器读取X-User-Id → 存入UserContext(ThreadLocal)。
    • 业务代码通过UserContext.getUserId()获取用户信息。
  4. 微服务A → 微服务B(通过OpenFeign):
    • Feign拦截器自动附加X-User-Id到请求头。
  5. 微服务B:重复步骤3的流程。

3. 配置管理

我们已经解决了微服务间的通信、注册发现、路由、登录鉴权等核心问题,但还有三大痛点悬而未决:

  • 网关路由硬编码:路由规则写在application.yml里,改个路径就得重启网关
  • 业务配置写死:数据库连接、业务开关、限流阈值全在代码里,调个参数就得重新打包部署
  • 重复配置爆炸:每个微服务都复制粘贴Redis、MySQL、日志配置,一改全改,维护成本飙升?
解决方案:统一配置中心

Nacos/Apollo等配置中心,把所有配置集中管理,实现:

  1. 动态路由:网关路由规则放到配置中心,热更新无需重启。

    # Nacos中动态配置网关路由
    spring:cloud:gateway:routes:- id: user-serviceuri: lb://user-servicepredicates:- Path=/user/**  # 修改后立即生效
    
  2. 业务配置动态化:用@RefreshScope注解,运行时刷新配置

    @Component
    @RefreshScope
    public class DynamicConfig {@Value("${order.timeout:30}") // 配置中心修改后自动更新private Long timeout;
    }
    
  3. 共享配置模板

    • 通用配置(如Redis、MySQL)抽成shared-config.yml,所有微服务复用
    • 差异化配置(如端口、服务名)独立维护,避免冗余。

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

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

相关文章

Text2SQL智能问答系统开发(一)

开发一个面向企业的chatBI工作流 已完成 基础 Text2SQL 功能实现 实现用户输入自然语言问题后&#xff0c;系统能够自动生成 SQL 并执行返回结果。用户交互优化 支持用户通过补充信息对查询进行调整&#xff0c;提升易用性。模糊时间处理机制 对“最近”“近期”等模糊时间关…

Python HTML模块详解:从基础到实战

一、模块体系全景图 Python生态中处理HTML的工具可分为三大层级&#xff1a; 标准库基础层&#xff1a;html模块 html.parser第三方增强层&#xff1a;BeautifulSoup&#xff08;搭配解析器&#xff09;专业级工具层&#xff1a;lxml requests-html 二、标准库核心模块详解…

PyTorch常用Tensor形状变换函数详解

PyTorch常用Tensor形状变换函数详解 在PyTorch中&#xff0c;对张量&#xff08;Tensor&#xff09;进行形状变换是深度学习模型构建中不可或缺的一环。无论是为了匹配网络层的输入要求&#xff0c;还是为了进行数据预处理和维度调整&#xff0c;都需要灵活运用各种形状变换函数…

自主智能Agent如何重塑工作流自动化:技术、经济与未来展望

自主智能Agent的崛起与工作流自动化的范式革命2025年7月&#xff0c;当OpenAI向付费用户推出具备网页浏览和代码执行能力的ChatGPT Agent时&#xff0c;工作流自动化领域迎来了一场静默但彻底的革命。这款不再满足于简单问答的智能体&#xff0c;在一个安全的虚拟计算机环境中运…

技术架构、行业应用、工具链整合、挑战应对及未来趋势五大模块,引用多个权威来源数据与开源项目实现细节。

以下是一份关于AI技术落地的实战经验总结报告&#xff0c;结合代码示例、可视化图表与行业案例&#xff0c;内容分为技术架构、行业应用、工具链整合、挑战应对及未来趋势五大模块&#xff0c;引用多个权威来源数据与开源项目实现细节。AI技术落地实战指南&#xff1a;从架构设…

第 9 篇:神经网络初探——当AI拥有了“大脑”,世界从此不同

《人工智能AI之机器学习基石》系列⑨ 专栏核心理念: 用通俗语言讲清楚机器学习的核心原理,强调“洞察 + 技术理解 + 应用连接”,构建一个完整的、富有启发性的知识体系。

音频焦点 Android Audio Focus 进阶

旧焦点处理 示例调用链: requestAudioFocus() → propagateFocusLossFromGain_syncAf() → handleFocusLossFromGain()。 系统事件(如来电)→ 强制焦点变化 → handleFocusLossFromGain()。 函数 propagateFocusLossFromGain_syncAf 焦点持有者发生的焦点丢失通知 主要功能…

MFC UI对话框

文章目录对话框模态对话框创建销毁关闭CDialog::OnCancel()EndDialog()CDialog::DestroyWindow()非模态对话框创建销毁关闭delete this对话框 模态对话框 ​​阻塞父窗口​​&#xff0c;强制用户先处理对话框。关闭前父窗口无法响应事件。 创建 推荐&#xff1a;非指针方式…

RabbitMQ--@RabbitListener及@RabbitHandle

两者区别 在 Spring AMQP 中&#xff0c;RabbitListener 和 RabbitHandler 是处理 RabbitMQ 消息的核心注解&#xff0c;但职责和使用场景完全不同。以下从 定义、区别、场景、示例 逐层解析&#xff1a;一、核心定义1. RabbitListener作用&#xff1a;标记 方法或类 为 Ra…

【基于CKF的IMM】MATLAB例程,CV和CT两个模型下的IMM,二维,滤波使用CKF(容积卡尔曼滤波),附下载链接

本程序实现了基于交互多模型&#xff08;IMM&#xff09;容积卡尔曼滤波&#xff08;CKF&#xff09;的多模型融合定位方法&#xff0c;并与纯CV−CKFCV-CKFCV−CKF&#xff08;匀速模型&#xff09;和CT−CKFCT-CKFCT−CKF&#xff08;匀角速度转弯模型&#xff09;方法对比。…

AI资讯日报 - 2025年07月28日

AI资讯日报 | 2025年07月28日 周一 今日核心要点 精华提炼 技术突破 • 腾讯混元&#xff1a;开源发布、3D技术 • 书生Intern&#xff1a;开源发布、多模态 企业动态 • AI工具集&#xff1a;协同创作、视频生成 数据概览分类数量重点关注技术突破2 条开源发布、3D技术企业动态…

大语言模型 LLM 通过 Excel 知识库 增强日志分析,根因分析能力的技术方案(1):总体介绍

文章大纲 1. 核心目标 2. 系统总体架构 3. Google Cloud 端到端方案(含无 RAG & RAG 双模式) 3.1 无 RAG:Function-Calling 查表模式 3.2 RAG:托管式向量检索 4. 开源轻量级方案 5. 数字孪生联合验证(实验性) 6. 知识图谱增强(Neo4j) 7. 监控与持续优化(CometLLM)…

Deepseek + browser-use 轻松实现浏览器自动化

在数字化时代&#xff0c;浏览器应用广泛&#xff0c;浏览器自动化可大幅提升效率。Deepseek 是强大的智能语言模型&#xff0c;能精准解析复杂指令&#xff0c;browser - use 是专注浏览器操作的工具&#xff0c;提供丰富 API 接口&#xff0c;支持主流浏览器的各类自动化操作…

开疆智能ModbusTCP转Profient网关连接西门子PLC与川崎机器人配置案例

本案例是西门子PLC与川崎机器人通过Profient转ModbusTCP网关进行通讯转换的配置案例&#xff0c;西门子作为profinet主站&#xff0c;机器人作为ModbusTCP服务器。配置过程&#xff1a;机器人配置川崎机器人控制器提供了RS232、以太网的通信接口&#xff0c;同时也可通过加装选…

Docker多主机网络连接:实现跨主机通信

Docker 是一种流行的容器化平台&#xff0c;它可以帮助开发人员更方便地构建、发布和管理应用程序。在 Docker 中&#xff0c;容器是独立运行的应用程序包装&#xff0c;包含了运行所需的所有文件、库和环境变量。Docker 提供了多种网络连接方式&#xff0c;使得容器之间可以进…

OSPF笔记

一、OSPF基础1、技术背景&#xff08;RIP中存在的问题&#xff09;RIP中存在最大跳数为15的限制&#xff0c;不能适应大规模组网周期性发送全部路由信息&#xff0c;占用大量的带宽资源路由收敛速度慢以跳数作为度量值存在路由环路可能性每隔30秒更新2、OSPF协议特点没有跳数限…

kotlin基础【3】

Kotlin Playground: Edit, Run, Share Kotlin Code Online 资料&#xff1a;kotlin实战 第一章 data class Person(val name: String,val age:Int?null)//允许接受以age为空&#xff0c;当为空将它赋值为null,如果不这么写直接写age:Int?是否可以fun main(args:Array<St…

Java-数构二叉树

1.树 1.1概念 树是一种非线性的数据结构&#xff0c;它是由n个有限节点组成一个具有层次关系。这种结构有以下特点&#xff1a; 一个特殊的结点&#xff0c;称为根节点&#xff0c;根节点没有前驱节点除根节点以外&#xff0c;其余节点分成M个互不相交的集合。每个集合又是一…

编程中水合的理解

在编程中&#xff0c;水合&#xff08;Hydration&#xff09; 是一个常见概念&#xff0c;尤其在 前端开发 和 服务端渲染&#xff08;SSR&#xff09; 场景中频繁出现。它的核心含义是&#xff1a;将静态内容“激活”为交互式动态内容。1. 水合的本质简单理解&#xff1a;水合…

使用ffmpeg转码h265后mac默认播放器不支持问题

由于mac自带录屏是mov并且文件特别大&#xff0c;我使用ffmpeg转码视频为h265使用如下命令ffmpeg_command [ffmpeg_path,"-i", input_path,"-c:v", "libx265","-preset", "veryslow","-map_metadata", "0&q…