Spring @Async 异步无法获取当前登录用户?Sa-Token 1.34.0 终极踩坑解决方案

前言

开发中我们经常用 @Async 实现异步任务处理,但是只要一异步,就会遇到一个经典问题:异步线程里拿不到当前登录用户信息

尤其项目使用 Sa-Token 1.34.0 旧版本框架,再加上异步内部嵌套工作流、自定义权限拦截、内置事件监听器 ,底层到处调用 LoginHelper.getLoginUser(),根本没法逐个改方法传参,踩坑直接踩到底。

本文把根源、所有无效方案、最终可用落地解法一次性讲透,看完彻底解决异步上下文丢失问题。

一、问题现象

  1. 普通接口同步逻辑,LoginHelper.getLoginUser() 正常获取登录用户;
  2. 进入 @Async 异步方法后,直接报错:

plaintext

makefile 复制代码
NotWebContextException: 非Web上下文无法获取Request
  1. 尝试在异步里使用 StpUtilSaHolder 全部报错;
  2. 特殊场景:异步内部调用工作流引擎 ,工作流内置事件、权限校验自动调用登录用户方法,改不了源码,只能被动适配

二、根本原因

  1. Web 请求的登录用户、Request、Sa-Token 上下文,都是存在主线程 ThreadLocal 中;
  2. Spring 异步线程池是独立子线程 ,默认不会继承主线程 ThreadLocal 上下文
  3. Sa-Token 1.34.0 老旧版本设计强依赖 Web Request 上下文,脱离 Web 环境直接抛非 Web 上下文异常;
  4. 线程池复用线程,就算临时设置上下文,不清理还会出现串用户、权限错乱问题。

三、网上常见无效踩坑方案(避坑)

方案 1:异步里直接 StpUtil.setTokenValue

看似能用,但有三大问题:

  • Sa-Token 1.34.0 没有官方清除方法,无法清理线程上下文;
  • 线程池复用会造成上下文污染、串登录用户
  • 异步里调用 SaHolder 依然报非 Web 上下文错误。

方案 2:传递 RequestContextHolder 上下文

java

运行

ini 复制代码
RequestAttributes attr = RequestContextHolder.getRequestAttributes();
// 异步传入设置
RequestContextHolder.setRequestAttributes(attr);

对 Sa-Token 1.34.0 无效,框架底层不走 Spring 上下文,依旧报错。

方案 3:自注入自己 AOP 代理

java

运行

less 复制代码
@Autowired
@Lazy
private CaMainService self;

只能解决异步注解生效问题,解决不了登录用户上下文丢失

四、终极落地解决方案(适配工作流 + 旧版 Sa-Token)

核心思路

既然异步线程天生无 Web 上下文、旧版 Sa-Token 不支持手动绑定清理,且工作流内部硬编码调用 LoginHelper.getLoginUser() 改不动,最优解:

自定义全局 ThreadLocal 承载登录用户,改造 LoginHelper 兜底适配异步场景

步骤 1:改造 LoginHelper 工具类

新增异步专用 ThreadLocal 容器,原有逻辑不变,异步场景自动兜底取值:

java

运行

csharp 复制代码
public class LoginHelper {

    // 异步线程专用:保存登录用户
    private static final ThreadLocal<LoginUser> ASYNC_USER_LOCAL = new ThreadLocal<>();
    private static final String LOGIN_USER_KEY = "loginUser";

    // 原有获取用户方法,改造加兜底
    public static LoginUser getLoginUser() {
        try {
            // 优先走Sa-Token Web上下文
            return (LoginUser) SaHolder.getStorage().get(LOGIN_USER_KEY);
        } catch (Exception e) {
            // 非Web上下文、异步场景:从本地ThreadLocal取值
            return ASYNC_USER_LOCAL.get();
        }
    }

    // 给异步线程设置登录用户
    public static void setAsyncUser(LoginUser user) {
        ASYNC_USER_LOCAL.set(user);
    }

    // 用完清理,防止线程池复用串用户
    public static void clearAsyncUser() {
        ASYNC_USER_LOCAL.remove();
    }
}

步骤 2:业务层异步调用改造

主线程提前获取登录用户,传给异步方法,异步入口设置上下文,finally 强制清理:

java

运行

typescript 复制代码
@Service
public class CaMainServiceImpl implements CaMainService {

    @Autowired
    @Lazy
    private CaMainService self;

    public Boolean initiateBatch11(String queueKey) {
        // 主线程:Web上下文正常,提前取出登录用户
        LoginUser loginUser = LoginHelper.getLoginUser();
        // 传给异步任务
        self.handlerTaskAsync(loginUser, queueKey);
        return Boolean.TRUE;
    }

    @Async
    public void handlerTaskAsync(LoginUser loginUser, String queueKey) {
        try {
            // 给当前异步线程绑定登录用户
            LoginHelper.setAsyncUser(loginUser);
            
            // 此处调用工作流、任意内置事件、权限校验
            // 内部所有 LoginHelper.getLoginUser() 自动正常取值
            // 无需修改工作流任何源码

        } finally {
            // 必须清理!线程池复用防止串用户
            LoginHelper.clearAsyncUser();
        }
    }
}

五、方案优势

  1. 零侵入工作流、业务源码:不用改监听器、不用改权限拦截逻辑;
  2. 兼容 Sa-Token 1.34.0 老旧版本:不用升级框架、不用反射硬编码;
  3. 线程安全:finally 强制清除 ThreadLocal,杜绝线程池串用户;
  4. 写法极简:所有异步任务通用一套模板,可全局复用;
  5. 彻底避开非 Web 上下文异常:不依赖 SaHolder、不依赖 Request 上下文。

六、总结

  1. @Async 丢失登录用户本质是 ThreadLocal 上下文不继承
  2. 旧版 Sa-Token 1.34.0 不要强行用 setTokenValue、SaHolder 手动绑定,坑极多;
  3. 嵌套工作流、内置事件无法改源码时,改造 LoginHelper + 自定义 ThreadLocal是唯一稳妥生产方案;
  4. 异步任务务必用完清理上下文,避免线程池复用导致权限错乱、用户串号。
相关推荐
码智社21 分钟前
Java实现RSA密钥生成、加密解密、加签验签
java
MemoleCard22 分钟前
深度解析Java当中的锁机制
后端
wear工程师29 分钟前
ThreadLocal 在线程池里为什么会串数据:别只会答内存泄漏
java·后端
Iruoyaoxh30 分钟前
栈和队列~
java·开发语言
审小匠OpenCPAi34 分钟前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
码上上班1 小时前
docker课程
java·docker·容器
腻害兔1 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:CRM 客户关系模块深度解析——从线索到回款,一套完整的 B2B 销售闭环是怎么搭的?
java·前端·javascript·vue.js·产品经理·ai编程
whyfail1 小时前
前端学 Spring Boot(2):一次点击,如何穿过整个后端?
前端·spring boot·后端
达子6661 小时前
第9章_HarmonyOs图解 用Java开发UI
java·ui·harmonyos
程序员cxuan3 小时前
Loop 还没玩明白,Graph Engineering 又火了。
人工智能·后端·程序员