news 2026/6/15 18:11:01

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/15 13:25:15

基于数据挖掘的疾病数据可视化与预测系统开题报告

山东中医药大学本科生毕业论文&#xff08;设计&#xff09;开题报告毕业论文&#xff08;设计&#xff09;题目&#xff1a; 学 院&#xff1a;智能信息与工程学院专 业&#xff1a; 班 级&#xff1a; 学 号&#xff1a;学生姓名&#xff1a; 指导教师&#xff1…

作者头像 李华
网站建设 2026/6/15 12:37:42

Linux进程排查实战:strace和lsof救命指南

服务起不来&#xff0c;日志没报错。进程在跑&#xff0c;但就是不干活。 这种问题最恶心&#xff0c;看日志看不出问题&#xff0c;看监控也没异常。 这时候就需要strace和lsof这两个神器了。 strace&#xff1a;跟踪系统调用 strace能看到进程在做什么系统调用&#xff0c;相…

作者头像 李华
网站建设 2026/6/15 13:22:05

二手车线上交易小程序源码系统,全流程闭环管理的小程序源码系统

温馨提示&#xff1a;文末有资源获取方式面向团队作战的流程化管理引擎&#xff1a;本系统设计超越了单一的车辆展示&#xff0c;深度融合了内部协同办公理念&#xff0c;为拥有市场、销售、库存管理等不同岗位的二手车团队&#xff0c;打造了一个从线索获取到成交分析的全流程…

作者头像 李华
网站建设 2026/6/15 12:54:33

手把手教你获取Open-AutoGLM源码,立即构建专属AI代码引擎

第一章&#xff1a;Open-AutoGLM源码下载 获取 Open-AutoGLM 的源码是参与其开发与本地部署的第一步。该项目托管于 GitHub 平台&#xff0c;采用开源协议发布&#xff0c;开发者可通过 Git 工具进行克隆或直接下载压缩包。 准备工作 在开始之前&#xff0c;请确保系统已安装以…

作者头像 李华
网站建设 2026/6/15 13:50:57

python+uniapp微信小程序的高校学生学业预警系统_2435j3ff

文章目录系统截图项目技术简介可行性分析主要运用技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;系统截图 pythonuniapp微信小程序的高校学生学业预警系统_2435j3ff 项目技术简介 Python版本&#xf…

作者头像 李华