RuoYi-Cloud微服务架构核心技术揭秘

RuoYi-Cloud 核心架构与底层组件学习笔记

本文档是对 RuoYi-Cloud 微服务架构核心机制(权限、通信、日志、上下文等)的学习总结。


一、 权限控制双剑客 (RBAC + DataScope)

微服务环境下的权限控制被严格划分为两个独立的维度,均基于 AOP(面向切面编程)实现无侵入式拦截。

1. 功能权限 (RBAC)

  • 概念:Role-Based Access Control,基于角色的访问控制。解决"用户能不能点击这个按钮/调用这个 API"的问题。

  • 实现机制

    1. 用户登录后,ruoyi-auth 模块将包含用户角色、权限标识集合的 LoginUser 对象存入 Redis,并颁发 JWT Token。

    2. 请求进入业务微服务时,拦截器将 userId 放入 SecurityContextHolder(上下文)。

    3. 业务方法上标注 @RequiresPermissions("system:user:list")

    4. PreAuthorizeAspect 切面(环绕通知 @Around)拦截请求,从 Redis 获取该用户的权限集合,与注解要求的权限进行比对,决定是否执行 proceed() 放行。

2. 数据权限 (DataScope)

  • 概念:解决"用户能看到哪些数据"的问题(例如长沙分公司的 HR 只能查长沙的员工)。

  • 实现机制

    1. 业务方法上标注 @DataScope(deptAlias = "d", userAlias = "u")

    2. DataScopeAspect 切面拦截请求,根据当前用户的部门 ID 和角色数据范围规则(如:本部门及以下),在内存中动态生成一段 SQL 条件(如 AND d.dept_id = 102)。

    3. 将该 SQL 字符串塞入业务实体类(继承自 BaseEntity)的 params.dataScope 属性中。

    4. MyBatis 在执行 Mapper XML 时,通过 ${params.dataScope} 将权限条件直接拼接到业务 SQL 的末尾,实现底层数据过滤。


二、 微服务内部通信与寻址 (OpenFeign)

1. API 契约模块 (ruoyi-api)

  • 本质ruoyi-api-system 并非一个独立运行的微服务进程,而是一个普通的 Maven .jar 依赖包(SDK)。

  • 作用 :存放对外的接口声明(@FeignClient)和 DTO 实体类,实现调用方与提供方的代码解耦。

2. Feign 调用底层映射原理

  1. 翻译 :Feign 根据接口上的注解(如 @PostMapping("/user/register")),将 Java 方法调用翻译为 HTTP 请求报文。

  2. 组装伪 URL :根据 @FeignClient(value="ruoyi-system"),组装出 http://ruoyi-system/user/register

  3. 负载均衡寻址 :Spring Cloud LoadBalancer 拦截请求,向 Nacos 注册中心查询 ruoyi-system 的所有健康实例 IP 列表。

  4. 路由分发 :LoadBalancer 采用轮询算法 (RoundRobin) 等策略挑出一个真实 IP,替换伪 URL,最终向目标 Tomcat 发送标准的 HTTP 请求。目标服务的 Spring MVC DispatcherServlet 接收后,路由到对应的 Controller。

3. Fallback 降级机制

  • 通过 @FeignClientfallbackFactory 参数指定降级工厂类。

  • 优势 :当目标服务宕机或超时时,工厂类不仅能返回友好的兜底 JSON 数据,还能捕获到原始异常 (Throwable),便于记录日志和排查问题,防止雪崩。


三、 全链路上下文透传机制

在微服务中,为了保证用户身份(如 userId)在多线程和多服务间不丢失,系统采用了"双拦截器 + TTL"的闭环设计。

  1. MVC 拦截器 (入口接收)HeaderInterceptor 拦截外部请求,将 HTTP Header 中的 userId 提取并存入当前 JVM 内存。

  2. 异步线程穿透 (TTL) :使用阿里开源的 TransmittableThreadLocal 替代原生 ThreadLocal。当主线程向线程池提交异步任务时,TTL 会自动抓取父线程的上下文,并在子线程执行时回放,解决多线程"失忆"问题。

  3. Feign 拦截器 (出口发送)FeignRequestInterceptor 在 Feign 发起远程 HTTP 请求前,从 TTL 中取出 userId 等信息,强制塞入新的 HTTP Header 中,实现跨服务透传。


四、 动态多数据源与读写分离

  • 核心注解@DataSource(DataSourceType.SLAVE)

  • 实现原理 :通过 AOP 的环绕通知 (@Around),在执行目标方法前,将数据源标识存入 ThreadLocal;底层依赖 AbstractRoutingDataSource 根据标识动态路由数据库连接。执行完毕后在 finally 块中清理标识。

  • 架构意义

    1. 读写分离:缓解主库压力,将复杂的 Select 查询引流至从库,提升系统并发量。

    2. 遗留系统集成:方便在同一个项目中直连异构数据库(如老旧 ERP 系统的 SQL Server)。


五、 全局操作日志与敏感数据脱敏

  • 核心注解@Log(title = "用户管理", businessType = BusinessType.INSERT)

  • 实现机制

    1. 利用 AOP 的 @AfterReturning@AfterThrowing 拦截方法执行结果或异常。

    2. 收集请求参数、耗时、IP 等信息,交由 AsyncManager 线程池异步写入 sys_oper_log 表,不阻塞主业务线程。


六、 敏感数据脱敏实现机制

数据脱敏(Data Desensitization)是为了防止接口直接将手机号、身份证等敏感信息明文返回给前端。若依没有采用 AOP 拦截,而是巧妙地利用了 Jackson 序列化机制 来实现。

1. 核心组件与注解

  • 注解@Sensitive(desensitizedType = DesensitizedType.PHONE)

  • 底层依赖 :Jackson 框架的 @JsonSerialize(using = SensitiveJsonSerializer.class)

2. 执行流程与原理解析

  1. 打标 :在需要脱敏的实体类字段(如 phonenumber)上标注 @Sensitive 注解。

  2. 序列化拦截:当 Spring MVC 准备将对象转换为 JSON 字符串作为 HTTP 响应返回时,Jackson 遍历到该字段,发现其被指定了自定义序列化器。

  3. 动态加工 (SensitiveJsonSerializer)

    • 上下文获取 :首先通过 SecurityContextHolder 检查当前用户的身份。如果当前登录人是超级管理员,则直接放行,返回真实明文数据。

    • 正则掩码 :如果是普通用户,根据注解配置的 DesensitizedType(如 PHONE),执行相应的脱敏策略(底层通过正则表达式或字符串截取),将 13812345678 转换为 138****5678,写入 JSON 流中。

3. 架构设计优势 (为什么不用 AOP?)

  • 性能更优 :如果用 AOP 拦截 Controller 的返回值,当返回复杂的深层嵌套对象(如 List<User>)时,需要大量递归反射查找字段,性能损耗极大。而利用 Jackson,是在 JSON 流生成的必经之路上"顺水推舟"地进行转换,开销极小。

  • 动态权限结合:脱敏序列化器内部可以随时获取当前线程的用户上下文,轻松实现"不同角色看到不同颗粒度数据"的高级需求。


七、 分布式事务解决方案 (Seata AT 模式)

在微服务架构中,一次业务操作(如电商下单)可能跨越多个独立的服务和数据库。传统的 @Transactional 无法解决跨库的数据一致性问题。若依集成了阿里开源的 Seata 组件来解决此痛点。

1. 核心角色与职责

  • TC (Transaction Coordinator):事务协调者。独立运行的 Seata 服务端,负责维护全局和分支事务的状态,驱动全局事务的提交或回滚。

  • TM (Transaction Manager):事务管理器。通常是发起业务的微服务(如订单服务),负责向 TC 申请开启全局事务,并最终发起全局提交或回滚决议。

  • RM (Resource Manager):资源管理器。参与全局事务的各个下游微服务,负责管理本地数据库,向 TC 汇报分支事务状态,并接收 TC 的指令执行本地提交或回滚。

2. AT 模式工作原理 (两阶段提交 + 自动补偿)

AT(Automatic Transaction)模式是 Seata 默认且对业务代码零侵入的模式。其核心依赖于每个业务库中创建的 undo_log 表。

  • 一阶段(执行与备份)

    • 业务代码执行前,Seata 数据源代理会拦截执行的 SQL,记录数据修改前的状态(前置镜像 Before Image)。

    • 执行业务 SQL。

    • 记录数据修改后的状态(后置镜像 After Image)。

    • 将前、后置镜像以及业务 SQL 组装成回滚日志,插入本地的 undo_log 表。

    • 关键点 :业务数据和回滚日志在同一个本地事务中提交,并立刻释放本地数据库锁,从而保证了极高的并发性能。

  • 二阶段(决议)

    • 如果全局成功 :TM 决议全局提交,TC 异步通知各个 RM。RM 收到通知后,直接放入一个异步队列,批量删除对应的 undo_log 记录。

    • 如果全局失败(抛出异常) :TM 决议全局回滚,TC 同步通知各个 RM。RM 根据 undo_log 中的镜像数据,自动生成反向 SQL(如将 Insert 变为 Delete),执行并提交,完成数据回滚补偿。

3. 开发落地规范

  • 前提 :所有参与事务的微服务对应数据库中必须存在 undo_log 表。

  • 使用 :在事务发起的入口方法上(如 TM 端),添加 @GlobalTransactional 注解即可,无需其他侵入式代码。

相关推荐
贵慜_Derek18 小时前
《从零实现 Agent 系统》连载 32|闭集 IE 与小模型:分类、意图与字段抽取
人工智能·架构·agent
江米小枣tonylua1 天前
译:设计生产级 RAG 架构
架构
怕浪猫1 天前
领域特定语言(Domain-Specific Language, DSL)
设计模式·程序员·架构
怕浪猫1 天前
哪些软件对 Chrome DevTools Protocol 频繁使用
人工智能·架构·前端框架
Jack202 天前
HarmonyOS APP事件驱动大揭秘
架构
米丘2 天前
微前端之 Web Components 完全指南
微服务·html
秋播2 天前
国内本地WSL2编译rancher源码
云原生
Colin草率地做慢慢地改2 天前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构
candyTong2 天前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
唐某人丶2 天前
从画架构图开始:架构分析与进阶指南
架构