| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- //
- // LNFileUploader.swift
- // Lanu
- //
- // Created by OneeChan on 2025/12/2.
- //
- import Foundation
- private class LNFileUploadTask {
- let id: String
- let fileUrl: String
- let task: URLSessionUploadTask
- let progress: ((Float) -> Void)?
- let completion: ((String?, String?) -> Void)?
-
- init(id: String,
- fileUrl: String,
- task: URLSessionUploadTask,
- progress: ((Float) -> Void)? = nil,
- completion: ((String?, String?) -> Void)? = nil) {
- self.id = id
- self.fileUrl = fileUrl
- self.task = task
- self.progress = progress
- self.completion = completion
- }
- }
- class LNFileUploader: NSObject {
- static let shared = LNFileUploader()
-
- private lazy var session: URLSession = {
- let config = URLSessionConfiguration.default
- return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
- }()
- private var uploadTasks: [String: LNFileUploadTask] = [:]
- private let lock = NSLock()
-
- @discardableResult
- func startUpload(
- type: LNUploadFileType,
- fileURL: URL,
- headers: [String: String] = [:],
- progressHandler: ((Float) -> Void)?,
- completionHandler: ((String?, String?) -> Void)?
- ) -> String? {
- lock.lock()
- defer { lock.unlock() }
-
- let taskId = "\(type.rawValue)-\(fileURL.absoluteString.md5)"
-
- guard uploadTasks[taskId] == nil else {
- completionHandler?(nil, .init(key: "B00017"))
- return nil
- }
-
- guard FileManager.default.fileExists(atPath: fileURL.path) else {
- completionHandler?(nil, .init(key: "B00014"))
- return nil
- }
-
- LNHttpManager.shared.getUploadOssUrl(type: type, suffix: fileURL.pathExtension) { [weak self] res, err in
- guard let self else { return }
- guard err == nil, let res, let url = URL(string: res.preSignUrl) else {
- completionHandler?(nil, err?.errorDesc ?? LNHttpError.invalidResponse.errorDesc)
- return
- }
- var request = URLRequest(url: url)
- request.httpMethod = "PUT"
- let defaultHeaders: [String: String] = ["Content-Type": "application/octet-stream"]
- let allHeaders = defaultHeaders.merging(headers) { $1 }
- allHeaders.forEach { key, value in
- request.setValue(value, forHTTPHeaderField: key)
- }
-
- let task = session.uploadTask(with: request, fromFile: fileURL)
-
- let uploadTask = LNFileUploadTask(
- id: taskId, fileUrl: res.fileUrl,
- task: task, progress: progressHandler,
- completion: completionHandler)
- uploadTasks[taskId] = uploadTask
-
- task.resume()
- }
-
- return taskId
- }
-
- @discardableResult
- func startUpload(
- type: LNUploadFileType,
- fileData: Data,
- suffix: String,
- headers: [String: String] = [:],
- progressHandler: ((Float) -> Void)?,
- completionHandler: ((String?, String?) -> Void)?
- ) -> String? {
- lock.lock()
- defer { lock.unlock() }
-
- let taskId = "\(type.rawValue)-\(fileData.md5)"
-
- guard uploadTasks[taskId] == nil else {
- completionHandler?(nil, .init(key: "B00017"))
- return nil
- }
-
- LNHttpManager.shared.getUploadOssUrl(type: type, suffix: suffix) { [weak self] res, err in
- guard let self else { return }
- guard err == nil, let res, let url = URL(string: res.preSignUrl) else {
- runOnMain {
- completionHandler?(nil, err?.errorDesc ?? LNHttpError.invalidResponse.errorDesc)
- }
- return
- }
- var request = URLRequest(url: url)
- request.httpMethod = "PUT"
- let defaultHeaders: [String: String] = ["Content-Type": "application/octet-stream"]
- let allHeaders = defaultHeaders.merging(headers) { $1 }
- allHeaders.forEach { key, value in
- request.setValue(value, forHTTPHeaderField: key)
- }
-
- let task = session.uploadTask(with: request, from: fileData)
-
- let uploadTask = LNFileUploadTask(
- id: taskId, fileUrl: res.fileUrl,
- task: task, progress: progressHandler,
- completion: completionHandler)
- uploadTasks[taskId] = uploadTask
-
- task.resume()
- }
- return taskId
- }
-
- func cancelUpload(taskID: String) {
- lock.lock()
- defer { lock.unlock() }
-
- // 清理任务和回调
- let uploadTask = uploadTasks.removeValue(forKey: taskID)
- uploadTask?.task.cancel()
- }
-
- /// 取消所有上传任务
- func cancelAllUploads() {
- lock.lock()
- defer { lock.unlock() }
-
- uploadTasks.values.forEach { $0.task.cancel() }
- uploadTasks.removeAll()
- }
- }
- extension LNFileUploader: URLSessionTaskDelegate, URLSessionDataDelegate {
- func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
- lock.lock()
- defer { lock.unlock() }
-
- guard let progressHandler = uploadTasks.first(where: { $0.value.task == task })?.value.progress else { return }
- guard totalBytesExpectedToSend > 0 else { return }
- let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
- runOnMain {
- progressHandler(progress)
- }
- }
-
- func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
- lock.lock()
- defer { lock.unlock() }
-
- guard let uploadTask = uploadTasks.first(where: { $0.value.task == task })?.value else { return }
- uploadTasks.removeValue(forKey: uploadTask.id)
- guard let completionHandler = uploadTask.completion else { return }
-
- runOnMain {
- if let error = error {
- if (error as NSError).code == NSURLErrorCancelled {
- completionHandler(nil, .init(key: "B00015"))
- } else {
- completionHandler(nil, error.localizedDescription)
- }
- } else {
- if let httpResponse = task.response as? HTTPURLResponse, (200...201).contains(httpResponse.statusCode) {
- completionHandler(uploadTask.fileUrl, nil)
- } else {
- let statusCode = (task.response as? HTTPURLResponse)?.statusCode ?? -4
- completionHandler(nil, .init(key: "B00016"))
- }
- }
- }
- }
- }
|