当前位置: 首页 > news >正文

SpringBoot ThreadLocal 全局动态变量设置

需求说明:

现有一个游戏后台管理系统,该系统可管理多个大区的数据,但是需要使用大区id实现数据隔离,并且提供了大区选择功能,先择大区后展示对应的数据。需要实现一下几点:
1.前端请求时,area_id是必传的
1.数据隔离,包括查询及增删改:使用mybatis拦截器实现
2.多个用户同时操作互不影响
3.非前端调用场景的处理:定时任务、mq

1.前端决定area_id

为了解决多个用户可以互不影响的使用不同的area_id,因此采用前端传递area_id的方式。前端的area_id可以放在缓存中,调用接口时将数据塞入头部中传给接口,实现了不同浏览器之间area_id互不影响的方式

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
简单来说就是,一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的,即每个线程都只能看到自己线程的值

2.ThreadLocal

接口接收到头部中的area_id后,将其设置到ThreadLocal中,以保证在整个请求的线程中都可以获取到该值。
并且为了防止内存泄漏及数据错乱问题,需要在请求结束时清除ThreadLocal。

3.请求拦截器

使用拦截器实现一下几个步骤:
(1)校验头部area_id,保证请求时改参数必传
(2)对头部area_id的获取、ThreadLocal设置、ThreadLocal清除,这样可以保证每次请求时都会使用头部中area_id

package org.jeecg.modules.game.config.area;import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** 游戏区id拦截器* @author: sxd* @date: 2025-02-06 13:56**/
@Component
public class AreaIdInterceptor implements HandlerInterceptor {@Autowiredprivate AreaIdHolder areaIdHolder;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String areaId = request.getHeader(CommonConstant.GAME_AREA_ID);if (StringUtils.isEmpty(areaId)) {throw new JeecgBootException("请先指定游戏大区");}areaIdHolder.setAreaId(Long.parseLong(areaId));return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理ThreadLocal,防止内存泄露areaIdHolder.remove();}}
package org.jeecg.modules.game.config.area;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** 注册拦截器* @author: sxd* @date: 2025-02-06 14:14**/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate AreaIdInterceptor areaIdInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(areaIdInterceptor).addPathPatterns("/**").excludePathPatterns("/game/area/areaSave", "/game/area/areaServerTree", "/game/common/changeArea");}
}
package org.jeecg.modules.game.config.area;import org.springframework.stereotype.Component;/*** @author: sxd* @date: 2025-02-06 14:18**/
@Component
public class AreaIdHolder {private static final ThreadLocal<Long> areaIdHolder = new ThreadLocal<>();public void setAreaId(Long gameId) {areaIdHolder.set(gameId);}public Long getAreaId() {return areaIdHolder.get();}public void remove() {areaIdHolder.remove();}
}

4.mybatis拦截器

mybatis plus配置:目的是在指定的数据表操作中,在条件中自动追加条件,即area_id
同时ignoreTable方法中设置无需拦截的数据表。并检测当area_id不存在时,不进行拦截处理,以兼容非前端请求时没有area_id的情况,如定时任务、mq消费

package org.jeecg.modules.game.config.area;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @author sxd*/
@Configuration
public class MybatisPlusConfig {@Autowiredprivate GameTenantLineHandler gameTenantLineHandler;@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor1() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(gameTenantLineHandler));return interceptor;}
}
package org.jeecg.modules.game.config.area;import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.stream.Collectors;/*** @author: sxd* @date: 2025-01-13 16:06**/
@Service
public class GameTableService {@Autowiredprivate AreaIdHolder gameIdHolder;public List<String> getGameModuleTableNames() {List<String> tableNames = TableInfoHelper.getTableInfos().stream().map(tableInfo -> tableInfo.getEntityType().getPackage().getName()).filter(packageName -> packageName.startsWith("org.jeecg.modules.game.entity")).distinct().flatMap(packageName -> TableInfoHelper.getTableInfos().stream().filter(tableInfo -> tableInfo.getEntityType().getPackage().getName().equals(packageName)).map(tableInfo -> tableInfo.getTableName())).collect(Collectors.toList());return tableNames;}
}
package org.jeecg.modules.game.config.area;import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.List;/*** @author: sxd* @date: 2025-01-13 15:53**/
@Component
public class GameTenantLineHandler implements TenantLineHandler {@Autowiredprivate AreaIdHolder gameIdHolder;@Autowiredprivate GameTableService gameTableService;@Overridepublic Expression getTenantId() {Long gameId = gameIdHolder.getAreaId();if (gameId == null) {return null;}return new LongValue(gameId);}@Overridepublic String getTenantIdColumn() {return "area_id";}/*** 返回 true 表示不走AreaId逻辑*/@Overridepublic boolean ignoreTable(String tableName) {// 没有区域id则不会走自动在where种追加area_id的逻辑Long gameId = gameIdHolder.getAreaId();if (gameId == null) {return true;}// 忽略不需要添加 area_id 条件的表List<String> gameTableNames = gameTableService.getGameModuleTableNames();return !gameTableNames.contains(tableName) || Arrays.asList(new String[]{"game_area", "game_prop"}).contains(tableName);}
}
http://www.dtcms.com/a/271829.html

相关文章:

  • 机器学习11——支持向量机上
  • 初学者对编译和链接的学习笔记(含预编译详解)
  • 广告匹配策略的智能化之路:人工智能大模型的方法和步骤
  • 多模态大语言模型arxiv论文略读(156)
  • vivo Pulsar 万亿级消息处理实践(3)-KoP指标异常修复
  • 快速上手MongoDB与.NET/C#整合
  • 【AI大模型】LLM模型架构深度解析:BERT vs. GPT vs. T5
  • searxng 对接openweb-UI实现大模型通过国内搜索引擎在线搜索
  • 搜索引擎vs向量数据库:LangChain混合检索架构实战解析
  • 计算机视觉 之 数字图像处理基础
  • 基于 SpringBoot + Vue 的 IT 技术交流和分享平台的设计与实现
  • TCP-与-UDP-协议详解:原理、区别与应用场景全解析
  • 北斗舞动在线监测装置:电力安全的“智慧守护者”
  • SpringMVC @ExceptionHandler 典型用法
  • 了解去中心化金融在现代经济中的作用——安全交易新时代
  • 编写bat文件自动打开chrome浏览器,并通过selenium抓取浏览器操作chrome
  • 双指针-18.四数之和-力扣(LeetCode)
  • linux系统---ISCSI存储服务
  • Language Models are Few-Shot Learners: 开箱即用的GPT-3(二)
  • 节点小宝:手机图片备份至电脑功能实测体验
  • 同一类型,每条数据,执行不同逻辑
  • 偏振相机,偏振图像是怎么样的
  • WebGPU了解
  • 智能体决策机制深度剖析:ReAct、Plan-and-Execute与自适应策略
  • 云蝠智能VoiceAgent重构企业电话客服体系
  • PLC框架-1.3.2 报文750控制汇川伺服的转矩上下限
  • 【前缀和 BFS 并集查找】P3127 [USACO15OPEN] Trapped in the Haybales G|省选-
  • XSS(跨站脚本攻击)
  • RabbitMQ 消息队列:从入门到Spring Boot实战
  • Java 枚举详解:从基础到实战,掌握类型安全与优雅设计