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

Swift:URLSession实现S3后台上传无错误却失败求助

后台URLSession上传文件至S3存储桶失败排查

我尝试使用URLSession将文件上传到S3存储桶,了解到后台上传需使用uploadTask(with:fromFile:)方法,于是按以下步骤实现:

1. 创建后台URLSession

lazy var session: URLSession = {
    let bundleIdentifier = Bundle.main.bundleIdentifier!
    let config = URLSessionConfiguration.background(withIdentifier: bundleIdentifier + ".background")
    config.sharedContainerIdentifier = bundleIdentifier
    config.sessionSendsLaunchEvents = true
    return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()

2. 生成带multipart数据的请求

数据模型

struct UploadRequest {
    let destinationUrl: URL
    let sourceURL: URL
    let params: [String: String]
    let fileName: String
    let mimeType: String
}

请求生成代码

private func requestAndPath(for uploadParam: UploadRequest) -> (request: URLRequest, filePath: URL)? {
    // 创建空文件并写入请求头、文件内容和请求尾
    let uuid = UUID().uuidString
    let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
    let fileURL = directoryURL.appendingPathComponent(uuid)
    let filePath = fileURL.path
    FileManager.default.createFile(atPath: filePath, contents: nil, attributes: nil)
    let file = FileHandle(forWritingAtPath: filePath)!

    let boundary = UUID().uuidString
    let newline = "\r\n"

    do {
        let partName = "file"
        let data = try Data(contentsOf: uploadParam.sourceURL)
        // 写入boundary头部
        var header = ""
        header += "--\(boundary)" + newline
        header += "Content-Disposition: form-data; name=\"\(partName)\"; filename=\"\(uploadParam.fileName)\"" + newline
        for (key, value) in uploadParam.params {
            header += "Content-Disposition: form-data; name=\"\(key)" + newline
            header += newline
            header += value + newline
        }
        
        header += "Content-Type: \(uploadParam.mimeType)" + newline
        header += newline

        let headerData = header.data(using: .utf8, allowLossyConversion: false)
        // 写入数据
        file.write(headerData!)
        file.write(data)

        // 写入boundary尾部
        var footer = ""
        footer += newline
        footer += "--\(boundary)--" + newline
        footer += newline

        let footerData = footer.data(using: .utf8, allowLossyConversion: false)
        file.write(footerData!)
        file.closeFile()

        let contentType = "multipart/form-data; boundary=\(boundary)"
        var urlRequest = URLRequest(url: uploadParam.destinationUrl)
        urlRequest.httpMethod = "POST"
        urlRequest.setValue(contentType, forHTTPHeaderField: "Content-Type")
        return (urlRequest, fileURL)
    } catch {
        debugPrint("Error generating url request")
    }
    return nil
}

3. 执行文件上传

func uploadFile(request: UploadRequest) {
    if let reqPath = requestAndPath(for: uploadRequest) {
        let task = session.uploadTask(with: reqPath.request,
                                      fromFile: reqPath.filePath)
        task.resume()
    }
}

调用uploadFile方法后,代理didSendBodyData仅被调用一次,随后进入didCompleteWithError且error为nil,但文件并未上传至S3存储桶。我使用Alamofire可正常完成上传(代码如下),但Alamofire不支持后台上传,因此希望改用URLSession,请问问题出在哪里?

可正常工作的Alamofire上传代码

AF.upload(multipartFormData: { multipartFormData in
    for (key, value) in uploadRequest.params {
        if let data = value.data(using: String.Encoding.utf8, allowLossyConversion: false) {
            multipartFormData.append(data, withName: key)
        }
    }
    multipartFormData.append(
        uploadRequest.sourceURL,
        withName: "File",
        fileName: uploadRequest.fileName,
        mimeType: uploadRequest.mimeType
    )
}, with: urlRequest).responseData { response in
    if let responseData = response.data {
        let strData = String(decoding: responseData, as: UTF8.self)
        debugPrint("Response data \(strData)")
    } else {
        debugPrint("Error is \(response.error)")
    }
}.uploadProgress { progress in
    debugPrint("Progress \(progress)")
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 07:12:41