Flutter中基于graphql_flutter包的GraphQL拦截器实现:处理错误及401时自动刷新AccessToken
嘿,我刚好做过类似的需求,用graphql_flutter实现错误拦截和401自动刷新token其实逻辑很清晰,咱们一步步来拆解:
核心思路:用自定义Link实现拦截
在graphql_flutter里,Link是处理请求链路的核心组件,咱们可以自定义一个Link来拦截请求和响应,专门处理错误场景——尤其是401未授权的情况。
第一步:实现自定义AuthLink
这个Link会负责给请求自动加上Authorization头,同时拦截响应,判断是否返回401状态码;如果是,就自动刷新token并重发请求。还要注意处理并发请求的情况,避免多次重复调用刷新接口:
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; class AuthLink extends Link { final Future<String> Function() getToken; final Future<String> Function() refreshToken; Future<String>? _ongoingRefresh; // 用来锁定刷新请求,避免并发重复刷新 AuthLink({required this.getToken, required this.refreshToken}); @override Stream<Response> request(Request request, [NextLink? forward]) async* { // 先获取当前存储的AccessToken String currentToken = await getToken(); // 给请求添加Authorization头 final authenticatedRequest = request.updateContextEntry<HttpLinkHeaders>( (headers) => HttpLinkHeaders( headers: { ...headers?.headers ?? {}, 'Authorization': 'Bearer $currentToken', }, ), ); // 发送初始请求 final responseStream = forward!(authenticatedRequest); await for (final response in responseStream) { // 检查HTTP响应状态码是否为401 final httpContext = response.context.entry<HttpLinkResponseContext>(); if (httpContext?.statusCode == 401) { // 处理并发刷新:如果已有刷新请求在进行,直接等待它完成 if (_ongoingRefresh == null) { _ongoingRefresh = refreshToken().whenComplete(() { // 刷新完成后清空标记 _ongoingRefresh = null; }); } try { // 获取新的AccessToken final newToken = await _ongoingRefresh!; currentToken = newToken; // 用新token重新构造请求并发送 final reAuthenticatedRequest = authenticatedRequest.updateContextEntry<HttpLinkHeaders>( (headers) => HttpLinkHeaders( headers: { ...headers?.headers ?? {}, 'Authorization': 'Bearer $currentToken', }, ), ); // 返回重新请求的响应 yield* forward(reAuthenticatedRequest); } catch (refreshError) { // 刷新失败,这里可以跳转到登录页或者抛出更明确的错误 final updatedErrors = [ ...?response.errors, GraphQLError(message: 'Token刷新失败: ${refreshError.toString()}') ]; yield response.copyWith(errors: updatedErrors); } } else { // 非401的响应直接返回 // 如果有其他GraphQL错误,也可以在这里统一处理(比如弹窗提示) if (response.errors?.isNotEmpty ?? false) { print('GraphQL请求错误: ${response.errors!.first.message}'); } yield response; } } } }
第二步:实现token的存储和刷新逻辑
你需要自己实现token的持久化存储(推荐用flutter_secure_storage存敏感信息),以及调用后端接口刷新token的逻辑:
// 示例:用flutter_secure_storage存储token import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class SecureStorage { final _storage = FlutterSecureStorage(); static const _accessTokenKey = 'access_token'; static const _refreshTokenKey = 'refresh_token'; Future<String?> readToken() async { return await _storage.read(key: _accessTokenKey); } Future<String?> readRefreshToken() async { return await _storage.read(key: _refreshTokenKey); } Future<void> saveToken(String token) async { await _storage.write(key: _accessTokenKey, value: token); } Future<void> saveRefreshToken(String token) async { await _storage.write(key: _refreshTokenKey, value: token); } } // 刷新token的方法,替换成你自己的后端API接口 Future<String> refreshAccessToken() async { final refreshToken = await SecureStorage().readRefreshToken(); if (refreshToken == null) throw Exception('没有可用的RefreshToken'); final response = await http.post( Uri.parse('https://your-api-domain.com/refresh-token'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'refreshToken': refreshToken}), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); final newAccessToken = data['accessToken']; final newRefreshToken = data['refreshToken']; // 如果后端返回新的refreshToken,记得保存 await SecureStorage().saveToken(newAccessToken); await SecureStorage().saveRefreshToken(newRefreshToken); return newAccessToken; } else { throw Exception('刷新Token失败,HTTP状态码: ${response.statusCode}'); } }
第三步:整合Link到GraphQLClient
最后把自定义的AuthLink和HttpLink组合起来,创建GraphQLClient实例:
final httpLink = HttpLink('https://your-graphql-endpoint.com/graphql'); final authLink = AuthLink( getToken: () async => SecureStorage().readToken() ?? '', refreshToken: refreshAccessToken, ); // 组合链路:先经过AuthLink处理,再发送到HttpLink final combinedLink = authLink.concat(httpLink); // 创建GraphQLClient实例 final graphQLClient = GraphQLClient( link: combinedLink, cache: GraphQLCache(store: HiveStore()), // 这里用Hive缓存,你也可以换成其他缓存方式 );
额外说明
- 如果你的后端是通过GraphQL的
errors字段返回401(而非HTTP状态码),那你需要在response.errors里检查对应的错误标识(比如extensions.code为UNAUTHENTICATED),再触发刷新逻辑。 - 刷新失败后,建议直接跳转到登录页面,让用户重新登录,避免陷入无限刷新的循环。
内容的提问来源于stack exchange,提问作者Hadi Norouzi
相关产品推荐
相关产品推荐

