大数据软件和网站开发那个就业好模具加工东莞网站建设技术支持
大数据软件和网站开发那个就业好,模具加工东莞网站建设技术支持,网页设计实验总结报告,游戏网站建设项目规划——更工程化的“中间件”语义#xff0c;适合中大型项目 共享 Future 方案已经够用#xff1b;队列版适用于#xff1a; 想在 refresh 期间“挂起请求”#xff0c;不立刻抛错 想刷新失败时“一锅端”所有等待请求 想控制重试节奏#xff08;顺序/限流/并发重放#x…——更工程化的“中间件”语义适合中大型项目共享 Future 方案已经够用队列版适用于想在 refresh 期间“挂起请求”不立刻抛错想刷新失败时“一锅端”所有等待请求想控制重试节奏顺序/限流/并发重放1. 队列版核心思想当请求 401不立刻handler.next(err)把这个请求的(RequestOptions handler)存入队列触发一次 refresh并发锁保证只一次refresh 成功统一重放队列里所有请求每个 resolve 回原来的 callerrefresh 失败统一 reject并触发全局登出2. 代码实现可直接用2.1 事件总线可选但强烈推荐import dart:async; enum AuthEvent { expired } class AuthEventBus { AuthEventBus._(); static final AuthEventBus I AuthEventBus._(); final _c StreamControllerAuthEvent.broadcast(); StreamAuthEvent 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 队列版 RefreshInterceptorimport 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 MapString, 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); } } } FutureResponsedynamic _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: MapString, dynamic.from(req.headers) ..[Authorization] Bearer $token, extra: MapString, 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. 队列版怎么接入 DioClientclass 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 事件