Moya 在用户 token 失效情况下的请求重试

公司的一个项目中的所有 API 接口无论是否登录都要带一个 token,如果没有 token 的话请求就会失败,因此在没有 token 或者是 token 失效的情况下要调用获取 token 的 API 将获取到的 token 存储到本地。

多次调用接口

写完代码后一测试,后台发现用户数量呈不正常的状态在增长,一查,发现每请求一次 token 都会生成一个用户。那么只能限制获取 token 的接口的调用次数了,一旦获取到 token,后续请求 token 的接口都取消。很自然地就想到用 OperationQueue 来做这个事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private let operationQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()

private let group = DispatchGroup()

func getAccesstoken() {
operationQueue.addOperation { [weak self] in
guard let this = self else { return }
this.getToken()
}
}

func getToken() {
func handleResult(_ item: String) {
// 取消排队等候的所有请求
operationQueue.cancelAllOperations()
group.leave()
}

func handleError(_ error: Error) -> Void {
group.leave()
}

group.enter()

webService.getToken()
.done(handleResult)
.catch(handleError)

group.wait()
}

这样就解决了多次调用获取 token 请求的问题。

Moya 重试请求

在 token 失效之后还得解决一个问题,那就是由于 token 失效导致的请求失败的问题,得让失败的请求进行重试,由于用的是 Moya 框架,搜索了许多重试的解决方案,无奈的是试过之后都不起作用,最后只能自己子类化一个 provider 来实现重试的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class CustomProvider<T: TargetType>: MoyaProvider<T> {
    @discardableResult
    override func request(_ target: T, callbackQueue: DispatchQueue? = .none, progress: ProgressBlock? = .none, completion: @escaping Completion) -> Cancellable {
        super.request(target, callbackQueue: callbackQueue, progress: progress) { result in
            switch result {
            case .success(let response):
                // 获取 token 失效时的业务状态码
                ...
                if statusCode == xxx {
                    // 重发请求
                    let queue = DispatchQueue(label: "com.network.retry", attributes: .concurrent)
                    queue.asyncAfter(deadline: .now() + 3) {
                        self.request(target, callbackQueue: callbackQueue, progress: progress, completion: completion)
                    }

                } else {
                    completion(.success(response))
                }

            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
}