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

TTL缓存用户数据

ThreadLocal 使用场景

用户会话信息
public class UserContext {private static ThreadLocal<String> userHolder = ThreadLocal.withInitial(() -> null);public static void setUser(String user) {userHolder.set(user);}public static String getUser() {return userHolder.get();}public static void clear() {userHolder.remove();}
}// 在某个请求处理线程中使用
UserContext.setUser("UserA");
String currentUser = UserContext.getUser();
System.out.println("Current User: " + currentUser);
UserContext.clear();

格式化工具

public class DateFormatter {private static ThreadLocal<SimpleDateFormat> dateFormatHolder = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));public static String format(Date date) {return dateFormatHolder.get().format(date);}
}// 在某个线程中使用
String formattedDate = DateFormatter.format(new Date());
System.out.println("Formatted Date: " + formattedDate);

上下文日志信息传递

public class LogContext {private static ThreadLocal<String> requestIdHolder = ThreadLocal.withInitial(() -> null);public static void setRequestId(String requestId) {requestIdHolder.set(requestId);}public static String getRequestId() {return requestIdHolder.get();}public static void clear() {requestIdHolder.remove();}
}// 在某个请求处理线程中使用
LogContext.setRequestId("123456");
String requestId = LogContext.getRequestId();
System.out.println("Request ID: " + requestId);
LogContext.clear();

TransmittableThreadLocal (TTL)

  • TTL 开源地址:https://github.com/alibaba/transmittable-thread-local

TTL实现原理
  • 上下文拷贝:在任务提交时,TTL 会拷贝当前线程的上下文到任务中。
  • 任务执行前设置上下文:在任务执行前,TTL 会将拷贝的上下文设置到当前线程中。
  • 任务执行后清理上下文:任务执行完毕后,TTL 会清理线程中的上下文,防止内存泄漏。

TTL使用场景
  • 分布式追踪:在分布式系统中传递追踪 ID,方便日志的关联和问题排查。
  • 事务管理:在分布式事务中传递事务上下文,确保事务的一致性。
  • 上下文信息传递:在多线程环境中传递用户会话、请求上下文等信息。

#### TTL与ThreadLocal对比
FeatureThreadLocalTTL
上下文传递仅在当前线程内存储, 无法跨线程传递能够在线程池和多线程框架中传递上下文信息
线程复用支持线程池复用线程时无法保证变量一致性支持线程池复用, 确保变量在任务间传递和保持一致
无侵入性手动管理变量设置和清除替换ThreadLocal即可自动管理上下文传递和清除
集成方便适用于简单线程环境可与各种线程池和多线程框架无缝集成
使用场景适用于单线程或不用跨线程传递上下文适用于复杂多线程环境, 特别是跨线程传递上下文

TTL实战

  • 将用户登录后的信息保存在上下文变量中,并进行跨线程之间传递
  • 用户登录之后会返回 token,之后的请求将会带上这个 token,当然 token 中会携带有用户的信息,
  • 所有请求最先经过网关的过滤器 AuthFilter,在过滤器中用户信息放到请求头,
  • 所有请求经过网关后会来到自定义请求头拦截器 HeaderInterceptor,
  • 在拦截器中拿出请求头中的用户信息放到 TTL 中,链路上的服务可以直接从 TTL 中取出用户信息

在这里插入图片描述

/*** 网关鉴权** @author canghe*/
@Component
public class AuthFilter implements GlobalFilter, Ordered {private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);private static final String BEGIN_VISIT_TIME = "begin_visit_time";//开始访问时间// 排除过滤的 uri 地址,nacos自行添加@Autowiredprivate IgnoreWhiteProperties ignoreWhite;@Autowiredprivate RedisService redisService;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerHttpRequest.Builder mutate = request.mutate();String url = request.getURI().getPath();// 跳过不需要验证的路径if (StringUtils.matches(url, ignoreWhite.getWhites())) {return chain.filter(exchange);}String token = getToken(request);if (StringUtils.isEmpty(token)) {return unauthorizedResponse(exchange, "令牌不能为空");}Claims claims = JwtUtils.parseToken(token);if (claims == null) {return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");}String userkey = JwtUtils.getUserKey(claims);boolean islogin = redisService.hasKey(getTokenKey(userkey));if (!islogin) {return unauthorizedResponse(exchange, "登录状态已过期");}String userid = JwtUtils.getUserId(claims);String username = JwtUtils.getUserName(claims);if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) {return unauthorizedResponse(exchange, "令牌验证失败");}// 设置用户信息到请求addHeader(mutate, SecurityConstants.USER_KEY, userkey);addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid);addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username);// 内部请求来源参数清除(防止网关携带内部请求标识,造成系统安全风险)removeHeader(mutate, SecurityConstants.FROM_SOURCE);//先记录下访问接口的开始时间exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());return chain.filter(exchange.mutate().request(mutate.build()).build());}private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {if (value == null) {return;}String valueStr = value.toString();
}
  • 在过滤器**AuthFilter**中将用户信息放到请求头

image.png

/*** 自定义请求头拦截器,将Header数据封装到线程变量中方便获取* 注意:此拦截器会同时验证当前用户有效期自动刷新有效期** @author canghe*/
public class HeaderInterceptor implements AsyncHandlerInterceptor {// 需要免登录的路径集合private static final Set<String> EXEMPTED_PATHS = new HashSet<>();static {// 在这里添加所有需要免登录默认展示首页的的路径EXEMPTED_PATHS.add("/system/user/getInfo");EXEMPTED_PATHS.add("/project/statistics");EXEMPTED_PATHS.add("/project/doing");EXEMPTED_PATHS.add("/project/queryMyTaskList");EXEMPTED_PATHS.add("/project/select");EXEMPTED_PATHS.add("/system/menu/getRouters");}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));String token = SecurityUtils.getToken();if (StringUtils.isNotEmpty(token)) {LoginUser loginUser = AuthUtil.getLoginUser(token);if (StringUtils.isNotNull(loginUser)) {AuthUtil.verifyLoginUserExpire(loginUser);SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);}} else {// 首页免登场景展示// 检查请求路径是否匹配特定路径String requestURI = request.getRequestURI();if (isExemptedPath(requestURI)) {// 创建一个默认的 LoginUser 对象LoginUser defaultLoginUser = createDefaultLoginUser();SecurityContextHolder.set(SecurityConstants.LOGIN_USER, defaultLoginUser);}}return true;}// 判断请求路径是否匹配特定路径private boolean isExemptedPath(String requestURI) {// 你可以根据需要调整特定路径的匹配逻辑return EXEMPTED_PATHS.stream().anyMatch(requestURI::startsWith);}// 创建一个默认的 LoginUser 对象private LoginUser createDefaultLoginUser() {LoginUser defaultLoginUser = new LoginUser();defaultLoginUser.setUserId(173L);  // 设置默认的用户IDdefaultLoginUser.setUsername(Constants.DEMO_ACCOUNT);  // 设置默认的用户名SysUser demoSysUser = new SysUser();demoSysUser.setUserId(173L);demoSysUser.setUserName(Constants.DEMO_ACCOUNT);demoSysUser.setDeptId(100L);demoSysUser.setStatus("0");defaultLoginUser.setUser(demoSysUser);// 设置其他必要的默认属性return defaultLoginUser;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {SecurityContextHolder.remove();}
}

image.png

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 基于paddlehub 未戴口罩检测算法
  • Python绘图入门:使用 Matplotlib 绘制折线图
  • MySQL 的 InnoDB 缓冲池里有什么?--InnoDB存储梳理(二)
  • 2024 年最佳 7 款 Java 分析器工具
  • Linux驱动入门实验班——基础驱动模板(附百问网视频链接)
  • MongoDB | MongoDB 终端查询
  • 详细分析Python链接Oracle的多种方式(附Demo)
  • “从头开始训练模型,几乎没有意义”
  • 【亲测有效!】ubuntu20.04和Centos7离线安装docker及nvidia-container-toolkit
  • 从零搭建React全家桶框架教程:快速搭建react+react-router+redux项目
  • 新专利:温室土壤温湿度预测模型构建方法和程序产品
  • day09——集合ArrayList
  • vue使用富文本编辑器+自由伸缩图片
  • 在Linux下编译安装Python3.10.0及以上环境(解决了openssl依赖问题)
  • javaEE WebServlet、SpringWebMVC、SpringBoot实现跨域访问的4种方式及优先级
  • JAVA_NIO系列——Channel和Buffer详解
  • PAT A1120
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Shell编程
  • SQLServer之创建数据库快照
  • vuex 笔记整理
  • 给自己的博客网站加上酷炫的初音未来音乐游戏?
  • 聚簇索引和非聚簇索引
  • 力扣(LeetCode)22
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 网络应用优化——时延与带宽
  • 温故知新之javascript面向对象
  • 一天一个设计模式之JS实现——适配器模式
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (C语言)fgets与fputs函数详解
  • (Note)C++中的继承方式
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (力扣题库)跳跃游戏II(c++)
  • (十一)图像的罗伯特梯度锐化
  • (一)Dubbo快速入门、介绍、使用
  • (一)UDP基本编程步骤
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (原創) 物件導向與老子思想 (OO)
  • (转)详解PHP处理密码的几种方式
  • (转)重识new
  • .net MySql
  • .net 验证控件和javaScript的冲突问题
  • .net与java建立WebService再互相调用
  • @ConfigurationProperties注解对数据的自动封装
  • @RequestMapping处理请求异常
  • [ 2222 ]http://e.eqxiu.com/s/wJMf15Ku
  • [16/N]论得趣
  • [2544]最短路 (两种算法)(HDU)
  • [AIGC] CompletableFuture的重要方法有哪些?
  • [Android实例] 保持屏幕长亮的两种方法 [转]
  • [Asp.net MVC]Bundle合并,压缩js、css文件
  • [Big Data - Kafka] kafka学习笔记:知识点整理
  • [C#数据加密]——MD5、SHA、AES、RSA