我有一个函数,如果之前的 API 响应返回错误代码 1000,则刷新令牌。但是,当同时进行多个 API 调用时,会导致多个刷新令牌请求。我想确保刷新令牌只被调用一次。
这是我的代码
requestGet(String endPoint, Map<String, dynamic> params, [bool isUsingToken = false]) async {
String sign = getSign(timestamp + jsonEncode(params));
String deviceId = await SharedPrefsService().getDeviceId();
String token = await SharedPrefsService().getToken();;
final response = await httpGet(endPoint, params, sign, token, deviceId, isUsingToken);
dynamic result = response;
var isRenewed = await renewTokenIfNeeded(deviceId, result, endPoint);
if (isRenewed) {
token = await SharedPrefsService().getToken();
final renewedResponse = await httpGet(endPoint, params, sign, token, deviceId, isUsingToken);
result = renewedResponse;
}
return result;
}
Future<bool> renewTokenIfNeeded(String deviceId, result) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool renewingToken = prefs.getBool('renewingToken') ?? false;
if (result['error_code'] == '1000') {
prefs.setBool('renewingToken', true);
try {
if (renewingToken) {
return true;
}
var isRenewed = await requestRenewToken(deviceId);
if (isRenewed) {
prefs.setBool('renewingToken', false);
return true;
}
} finally {
prefs.setBool('renewingToken', false);
}
}
return false;
}
requestRenewToken(String deviceId) async {
var refresh = await AuthenticationService().refreshToken();
if (refresh.errorCode == '9999') {
SharedPrefsService().clearAllData();
return false; // then back to sign in
}
if (refresh.errorCode == '0000') {
SharedPrefsService().saveTokenData(refresh.token!, refresh.userName!, deviceId);
return true;
}
return false;
}
我尝试过使用同步和互斥包,但它们似乎不起作用,我更喜欢尽量减少外部包的使用。您能建议一个解决方案吗?谢谢!
您可以参考下面的示例,对于多个请求的情况,没有 QueuedInterceptor,并且
class AuthInterceptor extends InterceptorsWrapper {
final Dio dio;
AuthInterceptor(this.dio);
// when accessToken is expired & having multiple requests call
// this variable to lock others request to make sure only trigger call refresh token 01 times
// to prevent duplicate refresh call
bool _isRefreshing = false;
// when having multiple requests call at the same time, you need to store them in a list
// then loop this list to retry every request later, after call refresh token success
final _requestsNeedRetry = <({RequestOptions options, ErrorInterceptorHandler handler})>[];
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
final accessToken = getAccessTokenFromLocalStorage();
options.headers['authorization'] = 'Bearer $accessToken';
return handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
final response = err.response;
if (response != null &&
// status code for unauthorized usually 401
response.statusCode == 401 &&
// refresh token call maybe fail by it self
// eg: when refreshToken also is expired -> can't get new accessToken
// usually server also return 401 unauthorized for this case
// need to exlude it to prevent loop infinite call
response.requestOptions.path != "path/your/endpoint/refresh") {
// if hasn't not refreshing yet, let's start it
if (!_isRefreshing) {
_isRefreshing = true;
// add request (requestOptions and handler) to queue and wait to retry later
_requestsNeedRetry.add((options: response.requestOptions, handler: handler));
// call api refresh token
final isRefreshSuccess = await _refreshToken();
_isRefreshing = false;
if (isRefreshSuccess) {
// refresh success, loop requests need retry
for (var requestNeedRetry in _requestsNeedRetry) {
// don't need set new accessToken to header here, because these retry
// will go through onRequest callback above (where new accessToken will be set to header)
final retry = await dio.fetch(requestNeedRetry.options);
requestNeedRetry.handler.resolve(retry);
}
_requestsNeedRetry.clear();
} else {
_requestsNeedRetry.clear();
// if refresh fail, force logout user here
}
} else {
// if refresh flow is processing, add this request to queue and wait to retry later
_requestsNeedRetry.add((options: response.requestOptions, handler: handler));
}
} else {
// ignore other error is not unauthorized
return handler.next(err);
}
}
Future<bool> _refreshToken() async {
try {
final refreshToken = getRefreshTokenFromLocalStorage();
final res = await callApiRefreshToken(refreshToken);
if (res.response.statusCode == 200) {
print("refresh token success");
final refreshResponse = RefreshResponse.fromJson(res.data);
// save new access + refresh token to your local storage for using later
setAccessTokenToLocalStorage(refreshResponse.accessToken);
setRefreshTokenToLocalStorage(refreshResponse.refreshToken);
return true;
} else {
print("refresh token fail ${res.response.statusMessage ?? res.response.toString()}");
return false;
}
} catch (error) {
print("refresh token fail $error");
return false;
}
}
}