import 'package:dio/dio.dart'; import 'package:patbond_flutter/core/network/api_envelope.dart'; import 'package:patbond_flutter/core/network/api_exception.dart'; import 'package:patbond_flutter/features/auth/auth_models.dart'; import 'package:patbond_flutter/features/auth/session_manager.dart'; /// 单飞(single-flight)token 刷新器。 /// /// 并发触发的多个 401/40101 只发出一次 `POST /auth/refresh`, /// 其余调用共享同一个进行中的刷新结果。 /// /// 清会话纪律(12 号组装稿 §7):只有服务端明确判定 refresh 失效 /// (40102 或 HTTP 401)才清除本地会话;网络失败、5xx 一律保留 token。 class TokenRefresher { TokenRefresher({required this._dio, required this._session}); final Dio _dio; final SessionManager _session; Future? _inflight; /// 刷新并轮换 token。成功后新 token 已写入 [SessionManager]。 /// /// 抛出 [SessionExpiredException](会话已被清除)或 /// [ApiNetworkException](token 保留,可重试)。 Future refresh() { return _inflight ??= _doRefresh().whenComplete(() => _inflight = null); } Future _doRefresh() async { await _session.loadFromStorage(); final refreshToken = _session.refreshToken; if (refreshToken == null) { await _session.clearSession(); throw const SessionExpiredException('本地无 refresh token'); } final Response response; try { response = await _dio.post( '/api/v1/auth/refresh', data: {'refreshToken': refreshToken}, ); } on DioException catch (error) { throw ApiNetworkException('刷新请求失败:${error.type.name}'); } final envelope = ApiEnvelope.tryParse(response.data); if (response.statusCode == 401 || envelope?.code == ApiCodes.refreshTokenInvalid) { await _session.clearSession(); throw const SessionExpiredException('refresh token 已失效'); } if (envelope != null && envelope.code == ApiCodes.ok && envelope.data is Map) { await _session.updateTokens( AuthTokens.fromJson(envelope.data! as Map), ); return; } throw ApiNetworkException( '刷新响应异常:HTTP ${response.statusCode},code ${envelope?.code}', ); } }