ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Sa-Token 在 @Async 和定时任务等异步上下文调用 StpUtil 报 SaTokenContextException 怎么解决?

2026/9/15 19:51:28 拓冰建站 浏览量
Sa-Token 在 @Async 和定时任务等异步上下文调用 StpUtil 报 SaTokenContextException 怎么解决? Sa-Token 在 Async 和定时任务等异步上下文调用 StpUtil 报 SaTokenContextException 怎么解决【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token在 Sa-Token 项目中登录认证通常发生在 Web 请求里Controller 里调用StpUtil.isLogin()、StpUtil.getLoginId()、StpUtil.getTokenValue()都能正常工作。但一旦把这些调用挪到Async标注的异步方法、Scheduled定时任务、线程池任务或 MQ 消费者里就会抛出cn.dev33.satoken.exception.SaTokenContextException: SaTokenContext 上下文尚未初始化这篇文章说明这个异常的成因并给出 Sa-Token 官方文档给出的解决方案在异步线程中用SaTokenContextMockUtil.setMockContext手动 Mock 一个上下文后再调用StpUtilAPI。仓库中的 sa-token-demo-async 示例 覆盖了Async、Scheduled、线程池、new Thread四种场景可直接对照修改和验证。为什么异步上下文会报这个异常根据文档 async--mock.mdStpUtil.isLogin()这类方法只可以在同步的 Web 上下文中调用因为它们需要从前端提交的HttpServletRequest中读取 Token 参数。而异步上下文通常不是一次请求没有HttpServletRequest的概念所以无法成功调用。文档列出的典型异步上下文包括通过new Thread(() - { ... }).start()启动子线程通过taskExecutor.execute(() - { ... })线程池启动异步任务通过Async注解标注的方法通过Scheduled(cron )启动的定时任务消息队列中消费消息的函数。凡是不通过 web 请求调用触发的线程在 Sa-Token 中均属于异步上下文也可以称作非 Web 上下文此时调用StpUtil.isLogin()、StpUtil.getLoginId()等需要 Web 上下文的 API就会抛出上述SaTokenContextException。解决方式Mock 上下文 指定 token 值文档给出的做法分两步调用SaTokenContextMockUtil.setMockContext(() - { ... })Mock 出一个 Web 上下文填充到上下文管理器中在 Mock 上下文范围内调用StpUtil.setTokenValueToStorage(tokenValue)指定当前上下文的 token 值其效果等同于在 web 上下文中前端提交了此 token 值。工具类 SaTokenContextMockUtil.java 位于sa-token-core的cn.dev33.satoken.context.mock包中带回调的setMockContext(SaFunction fun)方法会在回调执行完毕后自动清除上下文因此建议总是使用带回调的形式把需要访问StpUtil的代码都包在回调内。注意token 值必须在 Web 请求线程中提前取到例如StpUtil.getTokenValue()再传入异步任务不能指望子线程自己继承请求里的 token。Async 场景仓库示例 TestController.java 中的isLogin4方法演示了Async方法内的写法// 【异步】Async --- http://localhost:8081/test/isLogin4 Async RequestMapping(isLogin4) public SaResult isLogin4(CookieValue(satoken) String satoken) { SaTokenContextMockUtil.setMockContext(()-{ StpUtil.setTokenValueToStorage(satoken); System.out.println(是否登录 StpUtil.isLogin()); }); return SaResult.ok(); }这里satoken是从请求 Cookie 中读出的 token 值token-name: satoken见下文配置传入 Mock 上下文后再判断登录状态。如果你是在 Controller 里同步取 token 再交给异步方法处理可以先StpUtil.getTokenValue()拿到tokenValue再在异步回调里StpUtil.setTokenValueToStorage(tokenValue)。Scheduled 定时任务场景同一示例中的scheduledMethod演示了定时任务每分钟执行一次Scheduled(cron 0 * * * * ?) // 一分钟执行一次 public void scheduledMethod(){ // 错误写法直接调用 Sa-Token API 会报错 // System.out.println(定时任务Mock 范围外是否登录 StpUtil.isLogin()); System.out.println(SaFoxUtil.formatDate(new Date())); // 需要先设置模拟上下文 SaTokenContextMockUtil.setMockContext(() - { // StpUtil.setTokenValueToStorage(f452571f-bfdb-413d-aba9-e26992cf07be); // 模拟 Token System.out.println(定时任务Mock 范围内是否登录 StpUtil.isLogin()); // 模拟登录 // StpUtil.login(10066); // 模拟 登录 // System.out.println(定时任务Mock 范围内登录账号 StpUtil.getLoginId()); }); }注意示例注释明确标注在 Mock 范围外直接调用StpUtil.isLogin()会报错必须放进setMockContext回调内定时任务这种没有请求来源的场景要么用setTokenValueToStorage指定一个已知 token如示例注释中展示的固定 token 值仅为文档示例要么在 Mock 范围内直接StpUtil.login(10066)模拟登录。线程池与 new Thread 场景同文件中的isLogin3ThreadPoolTaskExecutor与isLogin2new Thread写法一致核心都是先在请求线程中StpUtil.getTokenValue()取出 token再在异步任务里 Mock// 【异步】线程池 ThreadPoolTaskExecutor --- http://localhost:8081/test/isLogin3 RequestMapping(isLogin3) public SaResult isLogin3(HttpServletRequest request, HttpServletResponse response) { System.out.println(是否登录 StpUtil.isLogin()); String tokenValue StpUtil.getTokenValue(); taskExecutor.execute(() - { SaTokenContextMockUtil.setMockContext(()-{ StpUtil.setTokenValueToStorage(tokenValue); System.out.println(是否登录 StpUtil.isLogin()); }); }); return SaResult.data(StpUtil.getTokenValue()); }按仓库示例验证示例项目位于 sa-token-demo-async验证前提来自该项目的配置文件启动类 SaTokenAsyncApplication.java 上标注了EnableAsync启用异步和EnableScheduling启动定时任务你的项目要使用Async/Scheduled也需要具备相应注解。application.yml 中服务端口为8081token-name: satoken并配置了127.0.0.1:6379的 Redis 连接如果照示例运行需要本地有可用 Redis。验证步骤示例中的访问路径来自代码注释启动sa-token-demo-async先访问http://localhost:8081/test/login?id10001完成登录带 token 访问http://localhost:8081/test/isLogin4Async场景或http://localhost:8081/test/isLogin3线程池场景观察控制台输出修复前异步线程中直接调用StpUtil.isLogin()会抛出SaTokenContextException按上述方式 Mock 后控制台会打印是否登录的判断结果且不再抛出该异常。定时任务场景则等待下一个整分钟0 * * * * ?观察控制台打印定时任务Mock 范围内是否登录...而不是抛出异常。限制与注意事项不要依赖InheritableThreadLocal传递上下文。示例 TestController.java 文末的注释记录了这个坑子线程虽然能读到 request但 SpringBoot 在请求结束后会清除 request 里的数据导致代码能成功运行、但逻辑错误的隐形 bug例如子线程先 sleep 一秒再取值结果从 true 变成 false。官方结论是应该避免使用InheritableThreadLocal存储上下文数据而应显式 Mock。Mock 范围只包住回调内的代码。setMockContext(fun)执行完会clearContext()把StpUtil调用放在回调外仍会报原异常。本文针对的是 ServletHttpServletRequest体系项目。若在 WebFlux 等响应式环境中出现同名异常文档给出的是另一套处理方式SaReactorSyncHolder.setContext(exchange)后再调用同步 API并在 finally 中清除见 async--mock.md 的响应式上下文一节。完成上述修改后Async方法与定时任务中即可按Mock 上下文 → 指定 token → 调用 StpUtil API的固定路径访问登录态不再出现SaTokenContextException: SaTokenContext 上下文尚未初始化。【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考