You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Alamofire请求遇401自动刷新Token并重发实现方案问询

解决Alamofire请求401自动刷新Token并重发的问题

问题说明

使用Alamofire构建请求构造器时,请求偶尔返回401状态码(需刷新Token),希望修改Network类实现Token自动刷新并重发请求,但存在两个问题:

  • 无法注入loginService
  • 不清楚如何在管理器中实现请求重发
    已尝试实现RequestInterceptor扩展,但不知道如何让其生效。

修改后的完整Network类代码

import Alamofire

class Network {

    typealias Method = Alamofire.HTTPMethod
    typealias Headers = HTTPHeaders
    typealias ResponseResult = Result<Response, Error>
    typealias Completion = (ResponseResult) -> Void
    
    // 重试次数限制
    private let retryLimit = 1
    // 持有Alamofire Session,将自身作为Interceptor传入
    private lazy var session: Session = {
        let configuration = URLSessionConfiguration.default
        configuration.timeoutIntervalForRequest = 30
        return Session(configuration: configuration, interceptor: self)
    }()

    lazy var baseURL: URL = {
        #if PROD
        return URL(string: "https://xxxxx.xxxx.com/")!
        #else
        return URL(string: "https://xxxxx.xxxx.com/")!
        #endif
    }()
    
    func request(_ target: RequestConvertible,
                 queue: DispatchQueue = .main,
                 completion: @escaping Completion) {
        do {
            let request = try makeURLRequest(for: target)
            // 使用自定义session发起请求,而非AF.request
            performRequest(session.request(request), queue: queue, target: target, completion: completion)
        } catch {
            completion(.failure(error))
        }
    }

    func requestWithDecode<T: Decodable>(_ target: RequestConvertible,
                                         value: T.Type,
                                         queue: DispatchQueue = .main,
                                         completion: @escaping (Result<T, Error>) -> Void) {
        request(target, queue: queue) { [weak self] result in
            switch result {
            case .success(let response):
                do {
                    completion(try self?.handleResponse(response: response, value: value)
                                ?? .failure(CommonError.undefined))
                } catch {
                    completion(.failure(error))
                }
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }

    // MARK: - Private

    private func handleResponse<T: Decodable>(response: Response, value: T.Type) throws -> Result<T, Error> {
        let apiResponse = try response.decode(APIResponse<T>.self)
        if let result = apiResponse.data {
            return .success(result)
        } else if let error = apiResponse.errors {
            return .failure(error)
        } else {
            return .failure(CommonError.custom(apiResponse.errors?.message.joined(separator: " ") ?? ""))
        }
    }

    private func makeURLRequest(for target: RequestConvertible) throws -> URLRequest {
        let url = target.baseURL ?? baseURL
        let pathURL = target.path.isEmpty ? url : url.appendingPathComponent(target.path)
        var request = try URLRequest(url: pathURL).encoded(for: target)
        request.httpMethod = target.method.rawValue
        target.headers?.dictionary.forEach { request.setValue($1, forHTTPHeaderField: $0) }
        return request
    }

    private func performRequest(_ request: DataRequest,
                                queue: DispatchQueue,
                                target: RequestConvertible,
                                completion: @escaping Completion) {
        request.responseData(queue: queue) { responseData in
            guard let response = responseData.response else {
                return completion(.failure(responseData.error ?? CommonError.undefined))
            }
            completion(Result { Response(data: responseData.data ?? Data(), response: response) })
        }
        // Alamofire的Session.request会自动启动请求,无需手动resume
    }
    
    // Token刷新锁,避免多个请求同时触发刷新
    private var isRefreshingToken = false
    // 等待刷新完成的请求队列
    private var pendingRequests: [(Bool) -> Void] = []
}

// MARK: - RequestInterceptor 实现
extension Network: RequestInterceptor {

    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        var request = urlRequest
        guard let token = UserDefaultService.token()?.accessToken else {
            completion(.success(urlRequest))
            return
        }
        let bearerToken = "Bearer \(token)"
        request.setValue(bearerToken, forHTTPHeaderField: "Authorization")
        completion(.success(request))
    }

    func retry(_ request: Request, for session: Session, dueTo error: Error,
               completion: @escaping (RetryResult) -> Void) {
        guard let statusCode = request.response?.statusCode else {
            completion(.doNotRetry)
            return
        }

        guard request.retryCount < retryLimit else {
            completion(.doNotRetryWithError(CommonError.tokenRefreshFailed))
            return
        }
        
        switch statusCode {
        case 200...299:
            completion(.doNotRetry)
        case 401:
            handle401Retry(completion: completion)
        default:
            completion(.doNotRetry)
        }
    }
    
    // 处理401重试逻辑,解决并发刷新问题
    private func handle401Retry(completion: @escaping (RetryResult) -> Void) {
        if isRefreshingToken {
            // 已有刷新请求在进行,加入等待队列
            pendingRequests.append { success in
                completion(success ? .retry : .doNotRetryWithError(CommonError.tokenRefreshFailed))
            }
        } else {
            isRefreshingToken = true
            refreshToken { [weak self] success in
                guard let self = self else { return }
                self.isRefreshingToken = false
                // 处理所有等待的请求
                self.pendingRequests.forEach { $0(success) }
                self.pendingRequests.removeAll()
                // 处理当前请求
                completion(success ? .retry : .doNotRetryWithError(CommonError.tokenRefreshFailed))
            }
        }
    }

    func refreshToken(completion: @escaping (_ isSuccess: Bool) -> Void) {
        guard let refreshToken = UserDefaultService.token()?.refreshToken else {
            completion(false)
            return
        }
        
        let url = baseURL.appendingPathComponent("auth/refresh-token")
        let parameters: [String: Any] = ["refresh_token": refreshToken]
        
        // 使用独立Session发起刷新请求,避免触发自身Interceptor导致循环
        let refreshSession = Session(configuration: .default)
        refreshSession.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
            .validate()
            .responseDecodable(of: Token.self) { response in
                switch response.result {
                case .success(let newToken):
                    UserDefaultService.setToken(newToken)
                    completion(true)
                case .failure:
                    // 刷新失败,清除本地Token,后续可跳转登录页
                    UserDefaultService.clearToken()
                    completion(false)
                }
            }
    }
}

// 辅助类型示例
enum CommonError: Error {
    case undefined
    case custom(String)
    case tokenRefreshFailed
}

struct Token: Decodable {
    let accessToken: String
    let refreshToken: String
    let expiresIn: Int
}

struct APIResponse<T: Decodable>: Decodable {
    let data: T?
    let errors: APIError?
}

struct APIError: Error, Decodable {
    let message: [String]
}

protocol RequestConvertible {
    var baseURL: URL? { get }
    var path: String { get }
    var method: Alamofire.HTTPMethod { get }
    var headers: HTTPHeaders? { get }
    // 可添加parameters等其他必要属性
}

extension URLRequest {
    func encoded(for target: RequestConvertible) throws -> URLRequest {
        // 实现参数编码逻辑,比如根据target的parameters进行编码
        return self
    }
}

struct Response {
    let data: Data
    let response: HTTPURLResponse
    
    func decode<T: Decodable>(_ type: T.Type) throws -> T {
        return try JSONDecoder().decode(type, from: data)
    }
    
    var statusCode: Int {
        return response.statusCode
    }
}

// UserDefault操作示例
enum UserDefaultService {
    static func token() -> Token? {
        // 从UserDefaults读取Token逻辑
        return nil
    }
    
    static func setToken(_ token: Token) {
        // 保存Token到UserDefaults逻辑
    }
    
    static func clearToken() {
        // 清除UserDefaults中的Token逻辑
    }
}

关键改动说明

  1. 启用Interceptor机制:

    • 在Network类中创建自定义Session,将自身作为RequestInterceptor传入,确保adapt和retry方法被Alamofire调用。
    • 所有业务请求改用自定义session.request发起,替代全局AF.request。
  2. 解决并发刷新问题:

    • 添加isRefreshingToken标记和pendingRequests队列,避免多个401请求同时触发Token刷新。
    • 刷新完成后统一处理所有等待的请求,保证Token一致性。
  3. 完善重试与刷新逻辑:

    • 限制重试次数,防止无限循环。
    • 刷新失败时返回明确错误,可在回调中处理跳转登录等逻辑。
    • 使用独立Session发起刷新请求,避免触发自身Interceptor导致循环。
  4. 优化冗余代码:

    • 移除performRequest中手动resume的代码,Alamofire会自动启动请求。
    • 修正原有刷新逻辑中错误使用accessToken作为refreshToken的问题。

内容的提问来源于stack exchange,提问作者Evg Enii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 22:41:59