fix(client): 修复启动时 token 刷新导致 Dio adapter closed 崩溃

apiClientProvider 改用 select(isLoggedIn),token refresh 不触发 provider
重建,避免旧 Dio 被 close 后仍执行 fetch。同时在拦截器内原地更新
_dio.options.headers,保证后续请求使用新 token。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-04-08 08:34:29 +08:00
parent 14e285e756
commit dc6e68154b
+12 -14
View File
@@ -14,25 +14,21 @@ final _publicDio = Dio(BaseOptions(
));
final apiClientProvider = Provider<ApiClient>((ref) {
final authState = ref.watch(authStateProvider);
final myToken = authState.user?.accessToken;
// 只监听登录/登出,不监听 token 内容变化。
// token refresh 只改 accessTokenisLoggedIn 不变,provider 不重建,
// 避免旧 Dio 被 close 后还在执行 fetch 导致 "adapter was closed"。
ref.watch(authStateProvider.select((s) => s.isLoggedIn));
final user = ref.read(authStateProvider).user;
final client = ApiClient(
token: myToken,
refreshToken: authState.user?.refreshToken,
token: user?.accessToken,
refreshToken: user?.refreshToken,
onTokenRefreshed: (newToken) {
debugPrint('[ApiClient] token refreshed');
if (ref.read(authStateProvider).user?.accessToken == myToken) {
ref.read(authStateProvider.notifier).updateAccessToken(newToken);
}
ref.read(authStateProvider.notifier).updateAccessToken(newToken);
},
onAuthFailed: () {
debugPrint('[ApiClient] onAuthFailed! myToken prefix: ${(myToken ?? '').substring(0, (myToken ?? '').length.clamp(0, 20))}');
if (ref.read(authStateProvider).user?.accessToken == myToken) {
debugPrint('[ApiClient] calling logout()');
if (ref.read(authStateProvider).isLoggedIn) {
ref.read(authStateProvider.notifier).logout();
} else {
debugPrint('[ApiClient] skipping logout (stale client)');
}
},
);
@@ -77,8 +73,10 @@ class ApiClient {
});
final newToken =
resp.data['data']['access_token'] as String;
// 原地更新 Dio 默认 headers,后续所有请求生效
_dio.options.headers['Authorization'] = 'Bearer $newToken';
if (!_disposed) onTokenRefreshed?.call(newToken);
// 用新 token 重试原请求
// 用新 token 重试原请求(此时 _dio 仍存活,不会 adapter closed
final opts = e.requestOptions;
opts.headers['Authorization'] = 'Bearer $newToken';
final retryResp = await _dio.fetch(opts);