Moya 刷新Token

4,993

应用场景,需求:

调用接口A,如果token过期,那么就调用可以刷新token的接口B,重新获取token。获取到新的token以后自动调用接口A,完成接口A的请求。

本文只提供基本思路,大概写法基本如此了,基本逻辑请根据自己的实际业务需求来。

现在用两种方法来实现。

实现方法一

思路:

创建一个MoyaProvider 的子类,重写init的初始化方法,在初始化方法里判断statusCode为401的时候,去调用刷新token的B接口,B接口获取到token以后再调用A接口

实现

import Moya
import SwiftyJSON

class RefreshbleMoyaProvider<T: TargetType>: MoyaProvider<T>{
    
    // 重写MoyaProvider的初始化方法,在初始化方法里做相应的判断
    @discardableResult
    open override func request(_ target: T,
                               callbackQueue: DispatchQueue? = .none,
                               progress: ProgressBlock? = .none,
                               completion: @escaping Completion) -> Cancellable {
        
        return super.request(target, callbackQueue: callbackQueue, progress: progress, completion: { response in
            print(response,"3")
            switch response {
            case .success(let result):
            do {
                // 如果调用原来的接口直接成功,没有401,没有token 过期,那就直接回调completion
                let res = try result.filterSuccessfulStatusAndRedirectCodes()
                print(res,"4")
                completion(response)
            } catch  {
                // 如果调用原来的接口返回401,token过期
                if result.statusCode == 400 {
                    // 那就去刷新token
                    self.refreshTokenSyncRequest(success: {
                        // 刷新token成功以后,再次请求之前的接口
                        self.request(target, callbackQueue: callbackQueue, progress: progress) { (newResponse) in
                        // 返回新的结果
                            completion(newResponse)
                        }
                    }, failure: {
                        // 返回失败结果
                        //  completion(response)
                    }, relogin: {
			// 失败以后返回失败结果,重新登录
			    completion(result)
				// 重新登录实现
				//------实现代码-----
	        	})
                }
            }

            case .failure(_):
                // 如果不是401,是请求超时啊,断网啊之类的错误,直接回调completion,不去刷新token
                completion(response)
            }
        })
    }
    
    // 刷新token的接口调用方法
    func refreshTokenSyncRequest(success: @escaping ()->(),failure: @escaping ()->(),relogin: @escaping ()->()) {
        // 判断refreshToken 是否存在
        guard let refreshToken = UserDefaults.standard.string(forKey: refresh_token) else{
            // 直接登录
            
            return
        }
       // 使用moya调用刷新token的接口(这个地方请实现你自己的刷新token的接口。其他地方都不用动)
        refreshProvider.request(.refreshToken(refreshToken: refreshToken)) { (response) in
           switch response {
           case .success(let result):
               print(result,"5")
               do {
                    // 获取token成功以后,存储token
                    let res = try result.filterSuccessfulStatusAndRedirectCodes()
                    let json = JSON(res.data)
                    UserDefaults.standard.set(json["data"]["access_token"].string, forKey: access_token)
                    UserDefaults.standard.set(json["data"]["refresh_token"].string, forKey: refresh_token)
                    UserDefaults.standard.set(json["data"]["user"].string, forKey: user)
                    UserDefaults.standard.set(json["data"]["jti"].string, forKey: jti)
                    UserDefaults.standard.set(json["data"]["token_type"].string, forKey: tokenType)
                    // 闭包回调成功
                    success()
               } catch  {
                    // 如果刷新token都失败了。。。那就登录吧
                    if result.statusCode == 401 {
                        // 直接登录
						relogin()
					}else {
						failure()
					}
               }
           case .failure(let err):
                // 直接登录
                SVProgressHUD.showError(withStatus: err.localizedDescription)
                print(err.localizedDescription,"6")
           }
        }
    }
}


使用方法

let afterSaleProvider = RefreshbleMoyaProvider<TargetType>()

和MoyaProvider的使用方法一样,只不过使用RefreshbleMoyaProvider来创建,TargetType是你自己创建的那个TargetType。然后就正常使用。

实现方法二

思路:

使用RxSwift+Moya,利用RxSwift的retryWhen来进行刷新token,然后再请求之前的接口。 主要思路来源于这里

需要导入的库

 pod ‘Moya’, ’13.0.0’
 pod ‘SwiftyJSON’
 pod ‘RxSwift’,    ‘~> 4.0’
 pod ‘RxCocoa’,    ‘~> 4.0’
 pod ‘Moya/RxSwift’, ‘~> 13.0’

实现

// 定义一个协议
public protocol SessionProtocol {
    // 刷新token的协议
    func getTokenRefreshService() -> Single<Response>
    // 获取token失败的协议
    func didFailedToRefreshToken()
    // token刷新成功的协议
    func tokenDidRefresh (response : JSON)
}


extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response {

    public func refreshAuthenticationTokenIfNeeded(sessionServiceDelegate : SessionProtocol) -> Single<Response> {
        
        return self.retryWhen { responseFromFirstRequest in
            
            
            responseFromFirstRequest.flatMap { originalRequestResponseError -> PrimitiveSequence<SingleTrait, ElementType> in
                
                
                if let lucidErrorOfOriginalRequest : MoyaError = originalRequestResponseError as? MoyaError {
                    // MoyaError是一个枚举
                    let moyaError = lucidErrorOfOriginalRequest
                    switch moyaError {
                        
                    case .statusCode(let res ):
                        //拿到Response
                        let response = res
                        print(response)
                        // 判断是不是401,token过期
                        if response.statusCode == 401 {
                            // token 过期,去请求新的token
                            // sessionServiceDelegate 代理的方法getTokenRefreshService 来得到刷新token的接口的请求客观察序列
                            return sessionServiceDelegate
                                .getTokenRefreshService()
                                // 对可观察序列进行筛选,筛选 statusCode 再200-399之间的请求结果
                                .filterSuccessfulStatusAndRedirectCodes()
                                .catchError { tokeRefreshRequestError -> Single<Response> in
                                    // 如果请求新的接口筛选失败
                                    if let lucidErrorOfTokenRefreshRequest : MoyaError = tokeRefreshRequestError as? MoyaError {
                                        
                                        // 这个时候只能重新登录
                                        sessionServiceDelegate.didFailedToRefreshToken()
                                        // 并返回失败
                                        return Single.error(lucidErrorOfTokenRefreshRequest)
                                    }
                                    // 返回失败
                                    return Single.error(tokeRefreshRequestError)
                                }
                                .flatMap { tokenRefreshResponse -> Single<Response> in
                                    // 如果请求新的接口筛选成功 那么刷新token成功
                                    let result = tokenRefreshResponse
                                    
                                    let json = JSON(result.data)
                                    // 判断接口返回中的code是不是成功,如果成功
                                    if json[“code”] == “000000” {
                                        if json["data"].exists() {
                                            
                                            // 回调成功以后拿到的json,并在sessionServiceDelegate 协议的 tokenDidRefresh 方法中 保存这个新的token
                                            sessionServiceDelegate.tokenDidRefresh(response: json)
                                            //然后重新请求之前的接口
                                            return self.retry(1)
                                        }
                                    }else {
                                        // 如果code不成功,返回错误
                                        return Single.error(lucidErrorOfOriginalRequest)
                                    }
                                    // 返回错误,因为成功只有 json["code"] == "000000" 的时候
                                return Single.error(lucidErrorOfOriginalRequest)
                            }
                        }
                    default:
                        // 其他不为401的时候  返回错误,
                        return Single.error(lucidErrorOfOriginalRequest)
                    }
                }
                
                // 如果第一个接口返回的错误 无法解析,那就返回失败,不需要刷新token
                return Single.error(originalRequestResponseError)
            }
            
        }

    }
}



// MARK: - SwiftyJSON
extension ObservableType where E == Response {

    func mapSwiftyJSON(_ failsOnEmptyData: Bool = true) -> Observable<JSON> {
        return flatMap { response -> Observable<JSON> in
            return Observable.just(try JSON(response.mapJSON(failsOnEmptyData: failsOnEmptyData)))
        }
    }
}

// MARK: - SwiftyJSON
extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response {

    func mapSwiftyJSON(_ failsOnEmptyData: Bool = true) -> Single<JSON> {
        return flatMap { response -> Single<JSON> in
            return Single.just(try JSON(response.mapJSON(failsOnEmptyData: failsOnEmptyData)))
        }
    }
}




使用

  1. 初始化一个provider let provider = MoyaProvider <TargetType>()

  2. 创建一个结构体,实现协议,通过provider实现接口的调用封装

// 创建一个结构体,遵循SessionProtocol 协议
struct Network: SessionProtocol {

    
    func getTokenRefreshService() -> Single<Response> {
			// 
        guard let refreshToken = UserDefaults.standard.string(forKey: refresh_token) else{
            return Single.just(Response.init(statusCode: 401, data: Data()))
        }
	// return 刷新token的rx方法
        return refreshProvider.rx.request(.refreshToken(refreshToken: refreshToken))
    }

    func didFailedToRefreshToken() {
        print(“didFailedToRefreshToken”)
			// 刷新token失败以后的操作,可以重新登录
//        let vc = getCurrentController()
//
//        let loginVC = UIStoryboard(name: "Login", bundle: nil).instantiateViewController(withIdentifier: "LoginVC") as! LoginVC
//
//        vc?.present(loginVC, animated: true, completion: nil)
    }

    func tokenDidRefresh(response: JSON) {
        print(“tokenDidRefresh=========\n”,response)
			// 刷新成功以后存储token和 refresh_token
        UserDefaults.standard.set(response["data"]["access_token"].string, forKey: access_token)
        UserDefaults.standard.set(response["data"]["refresh_token"].string, forKey: refresh_token)

    }

// 定义一个request方法,封装实现接口的调用
    func request(
                    target: YNETCAfterSalesService,
                    success successCallback: @escaping (JSON) -> Void,
                    failure failureCallback: @escaping (MoyaError) -> Void) {
// 调用接口 provider 是定义的MoyaProvider
        let _ = provider.rx.request(target)
        .filterSuccessfulStatusAndRedirectCodes()
        .refreshAuthenticationTokenIfNeeded(sessionServiceDelegate: self)
        .mapSwiftyJSON(true)
        .subscribe { event in
            switch event {
            case .success(let res) :
               
                successCallback(res)
            case .error(let error):
                let err = error
                failureCallback(err as! MoyaError)
            }
        }
        
    }

}
  1. 请求接口
        Network().request(target: .getList, success: { (json) in
			// 此时的json是完全成功之后的json,直接用来转为model,不用再去判断乱七八糟的成功失败
            print(json)
        }, failure: { (err) in
				// 此时的失败就是所有的失败的情况
            print(err)
        })

以上就是实现的moya刷新token的全部接口,有时间会上传demo.