前提
虽然大部分情况下服务器给的接口是单token,但是仍然有不少情况下会设计成双token,一个access_token,一个refresh_token。access_token有效期短,网络接口都需要带access_token。只有在access_token失效的情况下才去用刷新token的接口,通过带refresh_token去刷新。
问题
网络请求都是并发的。比如隔了几天,打开首页,要执行A,B,C三个并发网络请求,这个时候access_token超期了,A,B,C都会失败,如果都去请求刷新token的请求D,会请求三次D,三次D的返回的access_token都会不一致,A,B,C以三个不同的access_token去再次请求,服务器可能会认为其中的access_token不正确。
解决方法
采用锁的方式,比如refreshToken()方法用来执行刷新token请求,A,B,C并发去请求refreshToken,采用锁,只有一个能执行refreshToken,获取到新的access_token和refresh_token后缓存到本地,并缓存时间戳token_time,其他的请求从本地缓存中获取token_time,获取的token_time和当前时间戳curTime比较,比如curTime - token_time < 30s,就认为是有效的时间,取本地缓存的access_token即可。
另外,执行refreshToken()方法过程中,可能会遇到网络请求失败,这个时候可以写个逻辑,执行三次失败情况下认为是网络请求失败。
Moya下具体实现
public class MyMoyaProvider<Target>: MoyaProvider<Target> where Target: Moya.TargetType {
privateletdisposeBag=DisposeBag()
private var refreshCount: Int = 0
private lazy var lock: NSLock = {
lettheLock =NSLock()
returntheLock
}()
publicoverridefuncrequest(_target:Target, callbackQueue:DispatchQueue? = .none, progress:ProgressBlock? = .none, completion:@escapingCompletion) ->Cancellable{
returnsuper.request(target, callbackQueue: callbackQueue, progress: progress, completion: { [unownedself] resultin
switchresult {
caselet.success(moyaResponse):
ifmoyaResponse.statusCode==401{
ifself.refreshCount>3{
self.refreshCount=0
KMLogInfo("refreshCount > 3")
completion(result)
return
}else{
self.refreshCount=self.refreshCount+1
}
ifmoyaResponse.request?.allHTTPHeaderFields?.index(forKey:"Authorization")!=nil{
self.lock.lock()
KMLogInfo("objc_sync_enter")
// self.semaphore.wait()
letrefreshToken =Profile.getRefreshToken()
ifrefreshToken==""{
KMLogInfo("refreshToken is nil")
KMLogInfo("objc_sync_exit")
self.lock.unlock()
return
}
letsaveTokenTime =Profile.getSaveAccessTokenTime()
letcurTime =Date().timeIntervalSince1970
KMLogInfo("saveTokenTime:\(saveTokenTime), curTime: \(curTime)")
if(curTime-saveTokenTime) <30{
// 表示已经是最新的token
self.refreshCount=0
self.request(target, callbackQueue: callbackQueue, progress: progress, completion: completion)
// self.semaphore.signal()
KMLogInfo("objc_sync_exit")
self.lock.unlock()
}else{
// 需要去重新请求token
// let access_token = Profile.getAccessToken()
// 加锁
// objc_sync_enter(self)
letparameters:Dictionary= ["refresh_token": refreshToken]as[String:Any]
// 异步转同步
// let sema = DispatchSemaphore(value: 1)
NetworkProvider.rx.request(.refreshToken(paras: parameters)).filterSuccessfulStatusCodes().mapJSON().asObservable().mapObject(type:KMLoginModel.self).subscribe(onNext: { [unownedself] elementin
KMLogInfo("refreshToken success")
Profile.saveAccessToken(token: element.access_token??"")
Profile.saveRefreshToken(token: element.refresh_token??"")
Profile.setSaveAccessTokenTime(saveTokenTime:Date().timeIntervalSince1970)
self.refreshCount=0
KMLogInfo("objc_sync_exit")
self.lock.unlock()
self.request(target, callbackQueue: callbackQueue, progress: progress, completion: completion)
}, onError: {_in
// YBProgressHUD.showTipMessage(text: "您的账号权限过期,请重新登录!")
KMLogInfo("refreshToken fail")
KMProgressHUD.shareInstance.showHUDAutoHide(message:"您的账号权限过期,请重新登录")
// 请求错误,直接回到登录页
// NotificationCenter.default.post(name: Notification.Name(rawValue: "NOTIFICATION_LOGOUT"), object: nil)
// 清空本地token信息
completion(result)
letappDelegate = (UIApplication.shared.delegate)as!AppDelegate
appDelegate.logout()
KMLogInfo("objc_sync_exit")
self.lock.unlock()
// cancelBlock(result)
}, onCompleted:nil, onDisposed:nil).disposed(by:self.disposeBag)
}
// self.semaphore.wait()
}
}
caselet.failure(error):
KMLogInfo(error.localizedDescription)
}
completion(result)
})
}
}