芋道源码(无遮羞布版)多租户Starter源码深潜:TenantContext、忽略表与跨租户切换实现原理

发布时间:2026/10/9 23:56:47
芋道源码(无遮羞布版)多租户Starter源码深潜:TenantContext、忽略表与跨租户切换实现原理 芋道源码(无遮羞布版)多租户Starter源码深潜TenantContext、忽略表与跨租户切换实现原理【免费下载链接】ruoyi-spring-boot-all芋道源码(无遮羞布版)项目地址: https://gitcode.com/gh_mirrors/ru/ruoyi-spring-boot-all芋道源码(无遮羞布版)基于yudao-spring-boot-starter-biz-tenant多租户 Starter用一套极简设计实现了共享数据库的多租户隔离请求进入时解析租户编号存入TenantContextHolderSQL 执行时自动拼接tenant_id条件配合忽略表配置与TenantIgnore注解灵活放行再通过TenantUtils优雅完成跨租户切换。本文带你读懂这套多租户实现原理的核心链路。如何获取芋道源码项目如果你想在本地运行或阅读完整源码可以克隆仓库git clone https://gitcode.com/gh_mirrors/ru/ruoyi-spring-boot-all多租户能力全部集中在yudao-framework/yudao-spring-boot-starter-biz-tenant/模块中它是标准 Spring Boot Starter业务模块引入依赖后零配置生效。整个模块的核心文件分布如下文件职责TenantContextHolder.java租户上下文持有者TenantContextWebFilter.java请求入口解析租户编号TenantSecurityWebFilter.java租户越权与合法性校验TenantDatabaseInterceptor.javaDB 层忽略表判断TenantUtils.java跨租户切换工具TenantIgnoreAspect.javaTenantIgnoreAOP核心数据载体TenantContextHolder 如何保存租户编号所有租户能力的基石是 TenantContextHolder.java 这个类它内部只有两个静态 ThreadLocalTENANT_ID当前线程的租户编号IGNORE当前线程是否忽略租户过滤private static final ThreadLocalLong TENANT_ID new TransmittableThreadLocal(); private static final ThreadLocalBoolean IGNORE new TransmittableThreadLocal(); 注意它用的是阿里的TransmittableThreadLocal而非普通ThreadLocal。普通 ThreadLocal 在线程池复用时无法正确传递上下文TTL 则能跨线程池传播保证子线程中租户信息不丢失——这是多租户实现中一个容易被忽略的关键细节。它还提供了getRequiredTenantId()方法取不到租户编号时直接抛异常而不是静默返回null从源头避免忘了带租户号导致的全表查询事故。租户编号如何进入当前线程请求过滤链租户编号的入口由两个过滤器串联完成注册在 YudaoTenantAutoConfiguration.java 自动配置类中并通过yudao.tenant.enablefalse可一键关闭整个多租户能力。TenantContextWebFilter解析请求头 tenant-idTenantContextWebFilter.java 的逻辑极其简单从 HTTP 请求头tenant-id常量定义在 WebFrameworkUtils.java中解析租户编号写入TenantContextHolder请求结束后在finally中清理防止线程池脏数据。Long tenantId WebFrameworkUtils.getTenantId(request); if (tenantId ! null) { TenantContextHolder.setTenantId(tenantId); } try { chain.doFilter(request, response); } finally { TenantContextHolder.clear(); }TenantSecurityWebFilter三重安全防线紧接着的 TenantSecurityWebFilter.java 负责回答这个租户能不能访问共三道关卡防越权已登录用户的租户编号与请求携带的租户编号不一致时直接返回 403您无权访问该租户的数据并记录审计日志强制携带非忽略 URL 未传递租户编号时直接拒绝请求租户合法性调用TenantFrameworkService.validTenant()校验租户是否被禁用、是否到期其中第 3 关的实现在 TenantFrameworkServiceImpl.java用 GuavaLoadingCache缓存校验结果 1 分钟避免每个请求都查库。忽略表实现原理MyBatis-Plus 自动拼接 tenant_id这是多租户 Starter 最有含金量的部分。拦截器注册在自动配置类的tenantLineInnerInterceptor方法中YudaoTenantAutoConfiguration.java必须插在 MyBatis-Plus 拦截器链的第一个位置因为分页插件会基于内层 SQL 计算总数租户条件必须先于分页生效。TenantDatabaseInterceptor 如何实现忽略表判断TenantDatabaseInterceptor.java 实现了 MyBatis-Plus 的TenantLineHandler接口核心只有两个方法Override public Expression getTenantId() { return new LongValue(TenantContextHolder.getRequiredTenantId()); } Override public boolean ignoreTable(String tableName) { return TenantContextHolder.isIgnore() // 情况一全局忽略 || CollUtil.contains(ignoreTables, tableName); // 情况二配置了忽略表 }也就是说SQL 解析到每张表时都会问一句这张表要不要加tenant_id条件命中任一情况就放行。这带来一个重要的设计前提框架默认所有表都参与租户隔离业务实体的基类需要继承带tenantId字段的 TenantBaseDO.java。ignoreTables 配置与大小写兼容忽略表清单通过 TenantProperties.java 从yudao.tenant.ignore-tables配置读取典型场景包括系统级公共表如字典表system_dict_data回调类 Open API 对应数据表租户管理表自身system_tenant显然不能加租户条件构造函数里有两个容易忽略的工程细节每个表名会同时添加小写和大写形式不同数据库对大小写习惯不同并硬编码忽略 Oracle 的DUAL表主键生成器查DUAL时拼接租户条件会报错。TenantIgnore 注解方法级忽略租户当某个方法需要读取所有租户的数据比如定时任务、缓存预热给方法打上 TenantIgnore.java 注解即可。对应的 TenantIgnoreAspect.java 环绕逻辑是先备份 → 置为忽略 → 执行 → 恢复Boolean oldIgnore TenantContextHolder.isIgnore(); try { TenantContextHolder.setIgnore(true); return joinPoint.proceed(); } finally { TenantContextHolder.setIgnore(oldIgnore); }跨租户切换TenantUtils 的安全设计需要以另一个租户的身份执行一段逻辑时典型场景管理员后台代操作、批量任务逐租户执行使用 TenantUtils.java 提供的静态方法而不是手动 set/clearTenantUtils.execute(999L, () - { // 这段逻辑里的所有 SQL 都会以 tenant_id 999 过滤 orderService.auditAll(); });看execute的实现TenantUtils.java三个设计点值得学习先保存旧值、finally 中恢复无论逻辑是否抛异常都不污染原上下文强制将 IGNORE 置为 false即使外层处于忽略租户状态进入 execute 后也一定按指定租户隔离防止想切租户结果却没过滤的隐蔽 Bug配套提供executeIgnore(Runnable)用于暂时关闭隔离执行的对偶场景定时任务如何逐租户执行TenantJob 注解定时任务没有 HTTP 请求入口天然不存在租户上下文。Starter 给出了解法在 Job 方法上标注 TenantJob.java由 TenantJobAspect.java 自动查出全部租户 ID并行流中逐个TenantUtils.execute(tenantId, ...)执行同一套 Job 逻辑并把每个租户的执行结果聚合成 JSON 返回。源码注释里也明确了约束Job 逻辑必须保证幂等因为某租户失败重试时成功过的租户会再次执行。租户上下文在 Redis 与 MQ 中的透明传播多租户隔离不能只停留在 DB 层Starter 让上下文跟着数据走Redis 缓存TenantRedisCacheManager.java 在取缓存名时自动拼接后缀orderCache对租户 1 实际是orderCache:1天然实现缓存隔离MQ 消息以 Redis 消息为例TenantRedisMessageInterceptor.java 在发送时把租户编号写入消息 Header消费时还原到TenantContextHolder消费完再清理RabbitMQ、RocketMQ 均有对应实现 这一层的设计哲学很清晰租户编号像请求头一样在进程间传递任何新入口MQ 消费、定时任务、异步线程都会重新建立上下文从而让隔离规则在整个系统保持一致。总结一张链路图看懂芋道多租户 Starter把整条链路串起来多租户 Starter 的实现原理就是四个环节的接力入口TenantContextWebFilter解析tenant-id请求头 → 写入TenantContextHolder鉴权TenantSecurityWebFilter防越权、强制携带、校验租户合法性执行MyBatis-Plus 拦截器按ignoreTable()结果自动拼接tenant_idTenantIgnore/executeIgnore可局部放行切换与传播TenantUtils.execute安全切换租户TenantJob逐租户跑批Redis Key 与 MQ Header 携带租户标识整套机制没有引入额外的中间件仅靠 ThreadLocal SQL 解析 过滤器三个轻量手段就完成了从 Web 到 DB、Redis、MQ 的全链路租户隔离。如果你想动手验证建议从 yudao-spring-boot-starter-biz-tenant 模块的TenantContextHolder和TenantDatabaseInterceptor两个类入手它们不到 100 行代码却覆盖了多租户隔离 80% 的核心原理。【免费下载链接】ruoyi-spring-boot-all芋道源码(无遮羞布版)项目地址: https://gitcode.com/gh_mirrors/ru/ruoyi-spring-boot-all创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询