RuoYi-Cloud 核心架构与底层组件学习笔记
本文档是对 RuoYi-Cloud 微服务架构核心机制(权限、通信、日志、上下文等)的学习总结。
一、 权限控制双剑客 (RBAC + DataScope)
微服务环境下的权限控制被严格划分为两个独立的维度,均基于 AOP(面向切面编程)实现无侵入式拦截。
1. 功能权限 (RBAC)
-
概念:Role-Based Access Control,基于角色的访问控制。解决"用户能不能点击这个按钮/调用这个 API"的问题。
-
实现机制:
-
用户登录后,
ruoyi-auth模块将包含用户角色、权限标识集合的LoginUser对象存入 Redis,并颁发 JWT Token。 -
请求进入业务微服务时,拦截器将
userId放入SecurityContextHolder(上下文)。 -
业务方法上标注
@RequiresPermissions("system:user:list")。 -
PreAuthorizeAspect切面(环绕通知@Around)拦截请求,从 Redis 获取该用户的权限集合,与注解要求的权限进行比对,决定是否执行proceed()放行。
-
2. 数据权限 (DataScope)
-
概念:解决"用户能看到哪些数据"的问题(例如长沙分公司的 HR 只能查长沙的员工)。
-
实现机制:
-
业务方法上标注
@DataScope(deptAlias = "d", userAlias = "u")。 -
DataScopeAspect切面拦截请求,根据当前用户的部门 ID 和角色数据范围规则(如:本部门及以下),在内存中动态生成一段 SQL 条件(如AND d.dept_id = 102)。 -
将该 SQL 字符串塞入业务实体类(继承自
BaseEntity)的params.dataScope属性中。 -
MyBatis 在执行 Mapper XML 时,通过
${params.dataScope}将权限条件直接拼接到业务 SQL 的末尾,实现底层数据过滤。
-
二、 微服务内部通信与寻址 (OpenFeign)
1. API 契约模块 (ruoyi-api)
-
本质 :
ruoyi-api-system并非一个独立运行的微服务进程,而是一个普通的 Maven.jar依赖包(SDK)。 -
作用 :存放对外的接口声明(
@FeignClient)和 DTO 实体类,实现调用方与提供方的代码解耦。
2. Feign 调用底层映射原理
-
翻译 :Feign 根据接口上的注解(如
@PostMapping("/user/register")),将 Java 方法调用翻译为 HTTP 请求报文。 -
组装伪 URL :根据
@FeignClient(value="ruoyi-system"),组装出http://ruoyi-system/user/register。 -
负载均衡寻址 :Spring Cloud LoadBalancer 拦截请求,向 Nacos 注册中心查询
ruoyi-system的所有健康实例 IP 列表。 -
路由分发 :LoadBalancer 采用轮询算法 (RoundRobin) 等策略挑出一个真实 IP,替换伪 URL,最终向目标 Tomcat 发送标准的 HTTP 请求。目标服务的 Spring MVC
DispatcherServlet接收后,路由到对应的 Controller。
3. Fallback 降级机制
-
通过
@FeignClient的fallbackFactory参数指定降级工厂类。 -
优势 :当目标服务宕机或超时时,工厂类不仅能返回友好的兜底 JSON 数据,还能捕获到原始异常 (
Throwable),便于记录日志和排查问题,防止雪崩。
三、 全链路上下文透传机制
在微服务中,为了保证用户身份(如 userId)在多线程和多服务间不丢失,系统采用了"双拦截器 + TTL"的闭环设计。
-
MVC 拦截器 (入口接收) :
HeaderInterceptor拦截外部请求,将 HTTP Header 中的userId提取并存入当前 JVM 内存。 -
异步线程穿透 (TTL) :使用阿里开源的
TransmittableThreadLocal替代原生ThreadLocal。当主线程向线程池提交异步任务时,TTL 会自动抓取父线程的上下文,并在子线程执行时回放,解决多线程"失忆"问题。 -
Feign 拦截器 (出口发送) :
FeignRequestInterceptor在 Feign 发起远程 HTTP 请求前,从 TTL 中取出userId等信息,强制塞入新的 HTTP Header 中,实现跨服务透传。
四、 动态多数据源与读写分离
-
核心注解 :
@DataSource(DataSourceType.SLAVE) -
实现原理 :通过 AOP 的环绕通知 (
@Around),在执行目标方法前,将数据源标识存入ThreadLocal;底层依赖AbstractRoutingDataSource根据标识动态路由数据库连接。执行完毕后在finally块中清理标识。 -
架构意义:
-
读写分离:缓解主库压力,将复杂的 Select 查询引流至从库,提升系统并发量。
-
遗留系统集成:方便在同一个项目中直连异构数据库(如老旧 ERP 系统的 SQL Server)。
-
五、 全局操作日志与敏感数据脱敏
-
核心注解 :
@Log(title = "用户管理", businessType = BusinessType.INSERT) -
实现机制:
-
利用 AOP 的
@AfterReturning和@AfterThrowing拦截方法执行结果或异常。 -
收集请求参数、耗时、IP 等信息,交由
AsyncManager线程池异步写入sys_oper_log表,不阻塞主业务线程。
-
六、 敏感数据脱敏实现机制
数据脱敏(Data Desensitization)是为了防止接口直接将手机号、身份证等敏感信息明文返回给前端。若依没有采用 AOP 拦截,而是巧妙地利用了 Jackson 序列化机制 来实现。
1. 核心组件与注解
-
注解 :
@Sensitive(desensitizedType = DesensitizedType.PHONE) -
底层依赖 :Jackson 框架的
@JsonSerialize(using = SensitiveJsonSerializer.class)。
2. 执行流程与原理解析
-
打标 :在需要脱敏的实体类字段(如
phonenumber)上标注@Sensitive注解。 -
序列化拦截:当 Spring MVC 准备将对象转换为 JSON 字符串作为 HTTP 响应返回时,Jackson 遍历到该字段,发现其被指定了自定义序列化器。
-
动态加工 (
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注解即可,无需其他侵入式代码。