news 2026/6/10 1:51:05

401 刷新 Token 的队列版(请求挂起排队 + 刷新后统一重放/统一失败)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
401 刷新 Token 的队列版(请求挂起排队 + 刷新后统一重放/统一失败)

——更工程化的“中间件”语义,适合中大型项目

共享 Future 方案已经够用;队列版适用于:

  • 想在 refresh 期间“挂起请求”,不立刻抛错

  • 想刷新失败时“一锅端”所有等待请求

  • 想控制重试节奏(顺序/限流/并发重放)

1. 队列版核心思想

当请求 401:

  1. 不立刻handler.next(err)

  2. 把这个请求的(RequestOptions + handler)存入队列

  3. 触发一次 refresh(并发锁保证只一次)

  4. refresh 成功:统一重放队列里所有请求(每个 resolve 回原来的 caller)

  5. refresh 失败:统一 reject(并触发全局登出)

2. 代码实现(可直接用)

2.1 事件总线(可选,但强烈推荐)

import 'dart:async'; enum AuthEvent { expired } class AuthEventBus { AuthEventBus._(); static final AuthEventBus I = AuthEventBus._(); final _c = StreamController<AuthEvent>.broadcast(); Stream<AuthEvent> get stream => _c.stream; void emit(AuthEvent e) => _c.add(e); }

2.2 队列元素

import 'package:dio/dio.dart'; class _QueuedReq { final RequestOptions options; final ErrorInterceptorHandler handler; _QueuedReq(this.options, this.handler); }

2.3 队列版 RefreshInterceptor

import 'package:dio/dio.dart'; class QueueRefreshInterceptor extends Interceptor { final Dio dio; final Dio cleanDio; final RefreshManager mgr; final List<_QueuedReq> _queue = []; bool _expiredEmitted = false; QueueRefreshInterceptor({ required this.dio, required this.cleanDio, required this.mgr, }); @override void onError(DioException err, ErrorInterceptorHandler handler) async { if (err.response?.statusCode != 401) { return handler.next(err); } final req = err.requestOptions; // 防死循环:同一请求只重放一次 if (req.extra["retried"] == true) { return handler.next(err); } final pair = TokenStore.get(); if (pair == null || pair.refreshToken.isEmpty) { _emitExpiredOnce(); return handler.next(err); } // ① 入队 + 挂起(此刻不 next,不 resolve) _queue.add(_QueuedReq(req, handler)); // ② 触发“只一次”的刷新 final ok = await mgr.getOrCreate(() async { try { final res = await cleanDio.post("/auth/refresh", data: { "refreshToken": pair.refreshToken, }); final data = res.data as Map<String, dynamic>; final access = data["accessToken"] as String; final refresh = data["refreshToken"] as String; await TokenStore.set(TokenPair(access, refresh)); return access; // 返回非空代表成功 } catch (_) { await TokenStore.clear(); return null; } }); // ③ 注意:多个 onError 都会走到这里。为了避免重复 drain: if (_queue.isEmpty) return; final pending = List<_QueuedReq>.from(_queue); _queue.clear(); // ④ 刷新失败:统一失败 + 全局登出事件 if (ok == null) { _emitExpiredOnce(); for (final q in pending) { q.handler.next(err); } return; } // ⑤ 刷新成功:统一重放队列请求(这里选择顺序重放,最稳) for (final q in pending) { try { final resp = await _replay(q.options); q.handler.resolve(resp); } catch (e) { q.handler.next(e is DioException ? e : err); } } } Future<Response<dynamic>> _replay(RequestOptions req) { final token = TokenStore.get()?.accessToken ?? ""; return dio.request( req.path, data: req.data, queryParameters: req.queryParameters, options: Options( method: req.method, headers: Map<String, dynamic>.from(req.headers) ..["Authorization"] = "Bearer $token", extra: Map<String, dynamic>.from(req.extra)..["retried"] = true, ), cancelToken: req.cancelToken, onSendProgress: req.onSendProgress, onReceiveProgress: req.onReceiveProgress, ); } void _emitExpiredOnce() { if (_expiredEmitted) return; _expiredEmitted = true; AuthEventBus.I.emit(AuthEvent.expired); } }

3. 队列版怎么接入 DioClient?

class DioClient { DioClient._(); static final DioClient instance = DioClient._(); late final Dio dio; late final Dio cleanDio; final RefreshManager mgr = RefreshManager(); void init() { dio = Dio(BaseOptions(baseUrl: "https://api.example.com")); cleanDio = Dio(BaseOptions(baseUrl: "https://api.example.com")); dio.interceptors.addAll([ AuthInterceptor(), QueueRefreshInterceptor(dio: dio, cleanDio: cleanDio, mgr: mgr), ]); } }

4. 共享 Future 版 vs 队列版怎么选?

  • 小中型项目:共享 Future 版足够(简单稳定)

  • 中大型项目:队列版更工程化(挂起/统一重放/统一失败)

结语:你真正学会的是“异步并发控制”

这套方案的本质不是 Dio,而是:

  • 共享 Future 作为异步锁

  • 临界区只执行一次

  • 失败请求恢复(retry / replay)

  • 全局状态一致(expired 事件)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 22:38:32

Jmeter 压测-性能调优5大注意

性能调优主要涉及这些方面&#xff1a; 代码、数据库、网络、硬件、系统构架 1、代码 ①缓存 缓存是典型的空间换时间&#xff0c;在软件项目中&#xff0c;用的最多的是redis缓存&#xff0c;第一次查询的时候&#xff0c;将查询数据存储到缓存中。 后面每次查询&#xff…

作者头像 李华
网站建设 2026/6/9 16:09:18

Open-AutoGLM到底有多强?实测对比7大AutoML框架后的惊人结论

第一章&#xff1a;Open-AutoGLM开源网址 Open-AutoGLM 是一个面向自动化自然语言处理任务的开源框架&#xff0c;旨在简化大语言模型&#xff08;LLM&#xff09;在实际业务场景中的部署与调优流程。该项目由社区驱动&#xff0c;托管于主流代码托管平台&#xff0c;开发者可通…

作者头像 李华
网站建设 2026/6/9 16:12:46

Open-AutoGLM源码哪里下?一文解决99%的克隆与编译难题

第一章&#xff1a;Open-AutoGLM源码下载获取 Open-AutoGLM 的源码是参与其开发与本地部署的第一步。该项目托管在 GitHub 平台上&#xff0c;采用开源协议发布&#xff0c;支持社区协作与二次开发。环境准备 在下载源码前&#xff0c;请确保系统已安装以下基础工具&#xff1a…

作者头像 李华
网站建设 2026/6/9 16:09:41

原产地证明办理:所需材料与模板自动生成

原产地证明办理&#xff1a;所需材料与模板自动生成 在全球化贸易日益紧密的今天&#xff0c;一张薄薄的原产地证明&#xff08;Certificate of Origin, COO&#xff09;往往决定着一批货物能否顺利通关、享受关税减免&#xff0c;甚至影响整个订单的利润空间。对于外贸企业而言…

作者头像 李华
网站建设 2026/6/9 16:08:39

太流批了,实用工具,吾爱出品

今天给大家推荐三款工具&#xff0c;一款是Office文档图片导出工具&#xff0c;一款是环境变量添加工具&#xff0c;一款是GitHub下载工具&#xff0c;有需要的小伙伴可以下载收藏。 第一款&#xff1a;Office文档图导出工具 Office文档里图片批量导出其实可以用把文档后缀改成…

作者头像 李华
网站建设 2026/6/9 16:08:30

大模型自动化推理新突破,Open-AutoGLM在阿里云上的7大应用场景全曝光

第一章&#xff1a;大模型自动化推理新突破&#xff0c;Open-AutoGLM的诞生与演进随着大规模语言模型在自然语言处理领域的广泛应用&#xff0c;如何实现高效、自动化的推理流程成为研究热点。Open-AutoGLM应运而生&#xff0c;作为开源社区驱动的自动化推理框架&#xff0c;它…

作者头像 李华