问题解决
1、前端开发方面的问题
关于electron使用第三方nodejs模块编译打包的注意事项
关于使用electron开发串口项目中编译的一些注意事项
推荐一款更好用的字体抽取工具font-spider
failed Error: not found: python2.7的问题解决
thinkphp5.0.24兼容php7.4
nginx配置api前缀url转发,解决跨越访问
uni-app使用阿里iconfont多色图标
公众里如何发布竖版的视频,使用SVG方式
使用requireJS引入element-ui
2、后端开发方面的问题
记一次在centos7下安装Django博客的安装踩坑
oracle Cause: java.sql.SQLException: 调用中的无效参数
四种方式可以获取到nacos里的配置信息
gradle3.1升级到gracle7.6版本需要更新的地方
Idea解决Service启动服务不显示端口号的问题
docker中部署的flowable流程图乱码
jdk11版本的jenkins如何打包jdk8项目 ?
windows下面批量更新某个文件夹下面所有的git项目目录
在centos7.9环境上安装 nodejs20版本
3、部署运维方面的问题
virtualBox虚拟机拓展磁盘空间
win10家庭版本安装远程桌面
ssh连接虚拟机CentOS缓慢解决方法
jenkins构建的时候报git: Permission denied错误
安装sqlserver2017的时候遇到的两个坑
CentOS 的 YUM安装时卡死解决方案
docker容器在还原nexus3的数据的时候,注意
4、效率提升方面
win10系统右键没有新建文本文档的选项
微信双开的脚本.bat
Github Copilot如何使用,使用的快捷方式
copilot GitHub Copilot could not connect to server. Extension activation failed: “getaddrinfo ENOTFO
使用ffmpeg 将mp4里的音频摄取成mp3
5、问题的反思
记一次解决投票高并发引发的性能问题
uniapp 在远程调试的时候,报错Invalid Host header
解决绘世启动器的报错:Could not initialize Tensile library
Photoshop2024无法拖动图片导入的解决办法
kgm音乐文件解密操作
玄派星曜+开源宇宙eg01-c+rtx 4060ti 的一些配置说明
centos9上面安装wireguard后,无法启动服务Failed to set DNS configuration: Could not activate remote peer.
记一次破解瑞数6的工作过程(完美解决)
一级路由通过静态路由的方式访问二级路由
解决了一个关于ruoyi-vue添加租户id的拦截问题
本文档使用 MrDoc 发布
-
+
首页
解决了一个关于ruoyi-vue添加租户id的拦截问题
## 问题描述 > 在手上目前负责的一个ruoyi-vue-pro的项目中,需要增加 租户id的拦截,也就是使用mybatis-plus的租户拦截功能,在所有的sql语句后面拼接tenant_id = xxx的功能。 代码很简单:主要有以下几段: 租户id的拦截器,从header头里拿到 Tenant-Id的值 ```java @Component public class TenantIdInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String tenantId = request.getHeader("Tenant-Id"); // 手动判断:不为 null,且去除空格后长度大于 0 if (tenantId != null && !tenantId.trim().isEmpty()) { TenantContextHolder.setTenantId(tenantId); return true; } else { throw new RuntimeException("Missing required header: 'tenant-id'"); } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 关键:必须清理,避免线程池中线程复用导致租户污染 TenantContextHolder.clear(); } } ``` TenantContextHolder 是一个典型的 上下文持有者(Context Holder) ```java public class TenantContextHolder { private static final ThreadLocal<String> CONTEXT = new TransmittableThreadLocal<>(); public static void setTenantId(String tenantId) { CONTEXT.set(tenantId); } public static String getTenantId() { return CONTEXT.get(); } public static void clear() { CONTEXT.remove(); // 防止内存泄漏 } } ``` 配置类 MybatisPlusConfig,通过这个配置类,将我们的MybatisPlusInterceptor 托管给spring进行管理。 ```java @Configuration public class MybatisPlusConfig { @Autowired private ApplicationContext applicationContext; @PostConstruct public void init() { System.out.println("🔥🔥🔥 MybatisPlusConfig 被 Spring 初始化了!!!"); System.out.println("💡 当前 Spring 容器: " + applicationContext.getId()); } static { System.out.println("📦 类位置: " + MybatisPlusConfig.class.getResource("/" + MybatisPlusConfig.class.getName().replace(".", "/") + ".class")); System.out.println("🔍 类加载器: " + MybatisPlusConfig.class.getClassLoader()); System.out.println("🧩 类加载器类型: " + MybatisPlusConfig.class.getClassLoader().getClass().getName()); } @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { System.out.println("✅✅✅ 正在创建 MybatisPlusInterceptor Bean!!!"); MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor(new TenantLineHandler() { @Override public Expression getTenantId() { // 关键:这里从 ThreadLocal 中获取 tenant_id return new StringValue("1082"); // TenantContextHolder.getTenantId() } // 指定哪些表需要拼接 tenant_id @Override public String getTenantIdColumn() { return "tenant_id"; // 默认字段名 } @Override public boolean ignoreTable(String tableName) { // 系统表、用户表等不需要多租户隔离的表 return "sys_user".equalsIgnoreCase(tableName) || "sys_dept".equalsIgnoreCase(tableName); } }); interceptor.addInnerInterceptor(tenantInterceptor); return interceptor; } } ``` 在这个里面添加 TenantIdInterceptor的拦截,用于 拦截所有http请求,拿到header里的tenant_id ```java @Configuration public class MvcConfig implements WebMvcConfigurer { @Resource private AuthorizationInterceptor authorizationInterceptor; @Resource private TenantIdInterceptor tenantIdInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authorizationInterceptor); // 多租户拦截器 registry.addInterceptor(tenantIdInterceptor); } } ``` 上面的代码, 在我的mybatis-plus-demo的工程中可以正常运行,但是在我的公司的项目上,也就是ruoyi-vue-pro的项目里 死活 拦截不了tenant_id, 跟大模型对话了一个多小时,排查的原因包括: mapperScan scanBasePackages Configuration 拼写问题 @Bean 方法是 static 或 private 签名问题 类被 final 修饰 条件注解导致跳过 @Import(MybatisPlusConfig.class) 强制导入 全都不行,, 最后我通过在启动类里添加: ```java @Autowired private ApplicationContext applicationContext; @Bean public CommandLineRunner checkMybatisPlusInterceptor() { return args -> { String[] names = applicationContext.getBeanNamesForType(MybatisPlusInterceptor.class); if (names.length > 0) { System.out.println("🔴 已存在的 MybatisPlusInterceptor Bean:"); for (String name : names) { System.out.println(" - " + name + " (来自: " + applicationContext.getType(name).getName() + ")"); } } else { System.out.println("🟢 未发现 MybatisPlusInterceptor Bean,你的 @Bean 应该会被创建"); } }; } ``` 打印的结果是: ~~~ 🔴 已存在的 MybatisPlusInterceptor Bean: - mybatisPlusInterceptor (来自: com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor) ~~~ 大模型瞬间明白了。。。 🔥 根本原因:MyBatis Plus 的自动配置已经创建了 MybatisPlusInterceptor Bean Spring Boot 的 mybatis-plus-boot-starter 会自动配置: ```java @Configuration @ConditionalOnClass(MybatisPlusInterceptor.class) public class MybatisPlusAutoConfiguration { @Bean @ConditionalOnMissingBean public MybatisPlusInterceptor mybatisPlusInterceptor() { return new MybatisPlusInterceptor(); // 空的!没有插件! } } ``` 👉 它创建了一个 空的 MybatisPlusInterceptor 实例,但没有添加任何插件(分页、多租户等)。 导致下面我写的代码永远都不会执行 : ~~~ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { // 这个方法根本不会执行! // 因为上面那个自动配置已经创建了同类型的 Bean } ~~~ 为什么 demo 项目可以? 因为 demo 项目可能:没引入 mybatis-plus-boot-starter ## 解决方法: 第一种:修改MybatisPlusConfig ```java @Configuration public class MybatisPlusConfig { @Autowired private MybatisPlusInterceptor mybatisPlusInterceptor; @PostConstruct public void addTenantPlugin() { System.out.println("向已有的 MybatisPlusInterceptor 添加 Tenant 插件"); TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor(new TenantLineHandler() { @Override public Expression getTenantId() { return new StringValue(TenantContextHolder.getTenantId()); } @Override public String getTenantIdColumn() { return "tenant_id"; } @Override public boolean ignoreTable(String tableName) { return "sys_user".equalsIgnoreCase(tableName) || "sys_dept".equalsIgnoreCase(tableName); } }); // 添加到拦截器链 mybatisPlusInterceptor.addInnerInterceptor(tenantInterceptor); } } ``` 第二种方法: 在 @Bean上面添加一个 @Primary,但是我验证后的结果 是:不行!! ~~~ @Primary @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { // ... } ~~~ 🔍 为什么 @Primary 不行? ✅ @Primary 的作用是: > 当 存在多个同类型 Bean 时,告诉 Spring:“选我作为默认注入的那一个”。 ❌ 但它不能解决的问题是: > @ConditionalOnMissingBean 导致你的 @Bean 方法根本不会被执行! 🧠 关键逻辑梳理 mybatis-plus-boot-starter 的自动配置类: ~~~ @Bean @ConditionalOnMissingBean public MybatisPlusInterceptor mybatisPlusInterceptor() { return new MybatisPlusInterceptor(); // 先创建了! } ~~~ * Spring 处理到你的配置类时: 发现:已经存在一个 MybatisPlusInterceptor 类型的 Bean 所以:跳过你的 @Bean 方法(因为 @ConditionalOnMissingBean 生效) 结果:你的方法压根没执行,更别提 @Primary 了 👉 @Primary 还没登场,你的 Bean 就已经被“消灭”在摇篮里了。 ✅ 类比理解 想象一场“Bean 选拔赛”: 规则:只允许一个 MybatisPlusInterceptor 参赛(@ConditionalOnMissingBean) 自动配置的 Bean 先报名,成功入选 你的 Bean 来晚了,裁判说:“名额已满,你不能参赛” 你说:“但我有 @Primary 金牌!” 裁判:“不好意思,你连赛场都没进,金牌没用” > 在 Spring 的世界里,@ConditionalOnMissingBean 决定你能不能进场,@Primary 决定你进场后坐第几排。 哈哈哈哈,,通义大模型真能白话。。。
superadmin
2025年9月16日 17:29
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码