基于Alamofire/RxSwift实现401状态下自动刷新token并重试请求方案咨询
实现方案
核心思路是将401拦截、token刷新、请求重试逻辑统一封装到网络请求底层,业务侧无需做任何适配,完全消除重复代码。
第一步:全局刷新状态管控
首先对SessionManager的刷新逻辑做防抖处理,避免多个请求同时返回401时重复触发token刷新:
extension SessionManager { // 单例共享,管控刷新状态 static let shared = SessionManager() // 标记当前是否正在刷新token private let isRefreshing = BehaviorSubject<Bool>(value: false) // 刷新成功的回调通知 private let refreshSuccess = PublishSubject<Void>() func renewToken() -> Observable<Token> { // 如果正在刷新,直接等待刷新成功的通知 if try! isRefreshing.value() { return refreshSuccess .flatMap { _ -> Observable<Token> in // 刷新完成后返回最新的token return .just(UserDefaults.standard.currentToken!) } .take(1) } // 没有正在刷新的请求,触发刷新 isRefreshing.onNext(true) return Observable.create { observer in // 调用刷新接口 let request = AF.request(Router.renewToken) .responseDecodable(of: Token.self) { [weak self] response in guard let self = self else { return } self.isRefreshing.onNext(false) switch response.result { case .success(let token): // 保存新token到本地,后续请求自动携带 UserDefaults.standard.currentToken = token self.refreshSuccess.onNext(()) observer.onNext(token) observer.onCompleted() case .failure(let error): // 刷新失败,抛出错误,可统一触发登出逻辑 observer.onError(error) NotificationCenter.default.post(name: NSNotification.Name("UserShouldLogout"), object: nil) } } return Disposables.create { request.cancel() } } } }
第二步:封装通用网络请求方法,内置401重试逻辑
将原来每个接口单独写的请求逻辑抽成通用方法,内置401重试逻辑,所有业务接口直接复用:
// 网络请求基础封装 func request<T: Decodable>(router: Router, type: T.Type) -> Observable<T> { return Observable.create { observer in let request = AF.request(router) .responseDecodable(of: T.self) { response in // 处理401状态码 if response.response?.statusCode == 401 { observer.onError(NetworkError.unauthorized) return } // 处理其他错误 guard let value = response.value else { observer.onError(NetworkError.invalidJSON) return } observer.onNext(value) observer.onCompleted() } return Disposables.create { request.cancel() } } // 核心:401错误时触发重试逻辑 .retry { errorObservable in errorObservable.flatMap { error -> Observable<Void> in // 只有401错误才触发重试 guard let networkError = error as? NetworkError, networkError == .unauthorized else { return .error(error) } // 调用刷新token逻辑,刷新成功后触发重试 return SessionManager.shared.renewToken() .map { _ in () } // 只重试1次,避免无限循环 .take(1) } } }
第三步:业务接口简化实现
原来的业务接口直接调用通用请求方法即可,无需额外处理:
public func getSomeEndpointInfo() -> Observable<PKKInfo> { return request(router: Router.info, type: PKKInfo.self) }
第四步:业务侧调用无需额外处理
业务侧完全不用再处理401错误和刷新逻辑,正常订阅即可:
service.getSomeEndpointInfo() .observe(on: MainScheduler.instance) .subscribe { [unowned self] info in self._state.accept(.loaded) } onError: { error in // 只有非401错误、或者刷新token失败的错误才会走到这里 print("请求出错:\(error)") }.disposed(by: disposeBag)
额外优化点
- 可以给
retry操作符增加最大重试次数限制,避免刷新成功后又返回401的无限循环问题 - 刷新token失败时统一触发登出逻辑,无需在每个业务页面单独处理
- 若需要区分不需要自动重试的接口,可以给通用
request方法增加autoRetry401参数,默认开启即可
内容的提问来源于stack exchange,提问作者Kristijan Delivuk
相关产品推荐
相关产品推荐

