feat: 暂提

This commit is contained in:
jinyaqiu 2025-08-25 18:58:10 +08:00
parent e2c4f5d882
commit 03d8288833
5 changed files with 1006 additions and 807 deletions

View File

@ -18,7 +18,84 @@ private struct RequestIdentifier {
}
}
enum NetworkError: Error {
public protocol NetworkServiceProtocol {
func postWithToken<T: Decodable>(
path: String,
parameters: [String: Any],
completion: @escaping (Result<T, NetworkError>) -> Void
)
@discardableResult
func upload(
request: URLRequest,
fileData: Data,
onProgress: @escaping (Double) -> Void,
completion: @escaping (Result<(Data?, URLResponse), Error>) -> Void
) -> URLSessionUploadTask?
}
extension NetworkService: NetworkServiceProtocol {
public func postWithToken<T: Decodable>(
path: String,
parameters: [String: Any],
completion: @escaping (Result<T, NetworkError>) -> Void
) {
var headers = [String: String]()
if let token = KeychainHelper.getAccessToken() {
headers["Authorization"] = "Bearer \(token)"
}
post(path: path, parameters: parameters, headers: headers, completion: completion)
}
@discardableResult
public func upload(
request: URLRequest,
fileData: Data,
onProgress: @escaping (Double) -> Void,
completion: @escaping (Result<(Data?, URLResponse), Error>) -> Void
) -> URLSessionUploadTask? {
var request = request
// Set content length header if not already set
if request.value(forHTTPHeaderField: "Content-Length") == nil {
request.setValue("\(fileData.count)", forHTTPHeaderField: "Content-Length")
}
var progressObserver: NSKeyValueObservation?
let task = URLSession.shared.uploadTask(with: request, from: fileData) { [weak self] data, response, error in
// Invalidate the progress observer when the task completes
progressObserver?.invalidate()
if let error = error {
completion(.failure(error))
return
}
guard let response = response else {
completion(.failure(NetworkError.invalidURL))
return
}
completion(.success((data, response)))
}
// Add progress tracking if available
if #available(iOS 11.0, *) {
progressObserver = task.progress.observe(\.fractionCompleted) { progressValue, _ in
DispatchQueue.main.async {
onProgress(progressValue.fractionCompleted)
}
}
}
task.resume()
return task
}
}
public enum NetworkError: Error {
case invalidURL
case noData
case decodingError(Error)
@ -28,14 +105,14 @@ enum NetworkError: Error {
case networkError(Error)
case unknownError(Error)
var localizedDescription: String {
public var localizedDescription: String {
switch self {
case .invalidURL:
return "无效的URL"
case .noData:
return "没有收到数据"
return "没有收到数据"
case .decodingError(let error):
return "数据解析错误: \(error.localizedDescription)"
return "解码错误: \(error.localizedDescription)"
case .serverError(let message):
return "服务器错误: \(message)"
case .unauthorized:
@ -43,7 +120,7 @@ enum NetworkError: Error {
case .other(let error):
return error.localizedDescription
case .networkError(let error):
return "网络请求错误: \(error.localizedDescription)"
return "网络错误: \(error.localizedDescription)"
case .unknownError(let error):
return "未知错误: \(error.localizedDescription)"
}

View File

@ -28,7 +28,7 @@ public class ImageUploadService {
public func uploadImage(
_ image: UIImage,
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
completion: @escaping (Result<ImageUploaderGetID.UploadResult, Error>) -> Void
completion: @escaping (Result<ImageUploaderGetID.UploadResult, ImageUploaderGetID.UploadError>) -> Void
) {
uploader.uploadImage(
image,
@ -61,10 +61,10 @@ public class ImageUploadService {
_ image: UIImage,
compressionQuality: CGFloat = 0.5,
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
completion: @escaping (Result<ImageUploaderGetID.UploadResult, Error>) -> Void
completion: @escaping (Result<ImageUploaderGetID.UploadResult, ImageUploaderGetID.UploadError>) -> Void
) {
guard let compressedImage = image.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else {
completion(.failure(NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "图片压缩失败"])))
completion(.failure(ImageUploaderGetID.UploadError.invalidImageData))
return
}
@ -85,7 +85,7 @@ public class ImageUploadService {
_ image: UIImage,
compressionQuality: CGFloat = 0.5,
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
completion: @escaping (Result<ImageUploadService.UploadResults, Error>) -> Void
completion: @escaping (Result<ImageUploadService.UploadResults, ImageUploaderGetID.UploadError>) -> Void
) {
//
uploadImage(image, progress: { progress in
@ -145,7 +145,7 @@ public class ImageUploadService {
public func uploadMedia(
_ media: MediaType,
progress: @escaping (UploadProgress) -> Void,
completion: @escaping (Result<MediaUploadResult, Error>) -> Void
completion: @escaping (Result<MediaUploadResult, ImageUploaderGetID.UploadError>) -> Void
) {
switch media {
case .image(let image):
@ -217,14 +217,14 @@ public class ImageUploadService {
}
)
} else {
let error = NSError(domain: "ImageUploadService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to compress thumbnail"])
let error = ImageUploaderGetID.UploadError.invalidImageData
print("❌ 视频缩略图压缩失败")
completion(.failure(error))
}
case .failure(let error):
print("❌ 视频缩略图提取失败: \(error.localizedDescription)")
completion(.failure(error))
completion(.failure(ImageUploaderGetID.UploadError.uploadFailed(error)))
}
}
@ -243,13 +243,13 @@ public class ImageUploadService {
existingThumbnail: UIImage?,
compressionQuality: CGFloat,
progress progressHandler: @escaping (UploadProgress) -> Void,
completion: @escaping (Result<MediaUploadResult, Error>) -> Void
completion: @escaping (Result<MediaUploadResult, ImageUploaderGetID.UploadError>) -> Void
) {
// 1.
func processThumbnail(_ thumbnail: UIImage) {
// 2.
guard let compressedThumbnail = thumbnail.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else {
let error = NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "缩略图压缩失败"])
let error = ImageUploaderGetID.UploadError.invalidImageData
completion(.failure(error))
return
}
@ -325,7 +325,7 @@ public class ImageUploadService {
case .success(let thumbnail):
processThumbnail(thumbnail)
case .failure(let error):
completion(.failure(error))
completion(.failure(ImageUploaderGetID.UploadError.uploadFailed(error)))
}
}
}

View File

@ -1,10 +1,55 @@
import SwiftUI
import PhotosUI
import CommonCrypto
///
/// file_id
public class ImageUploaderGetID: ObservableObject {
// MARK: -
private let session: URLSession
private let networkService: NetworkServiceProtocol
private let networkHandler: (_ path: String, _ parameters: [String: Any], _ completion: @escaping (Result<Data, UploadError>) -> Void) -> Void
public init(session: URLSession = .shared, networkService: NetworkServiceProtocol? = nil) {
self.session = session
let service = networkService ?? NetworkService.shared
self.networkService = service
self.networkHandler = { path, parameters, completion in
service.postWithToken(
path: path,
parameters: parameters,
completion: { (result: Result<UploadURLResponse, NetworkError>) in
switch result {
case .success(let response):
// Convert the response back to Data for the completion handler
do {
let data = try JSONEncoder().encode(response)
completion(.success(data))
} catch {
completion(.failure(.invalidResponseData))
}
case .failure(let error):
completion(.failure(.networkError(error)))
}
}
)
}
}
///
/// - Parameters:
/// - session: URLSession
/// - networkService: NetworkService
/// - networkHandler:
internal init(
session: URLSession,
networkService: NetworkServiceProtocol? = nil,
networkHandler: @escaping (String, [String: Any], @escaping (Result<Data, UploadError>) -> Void) -> Void
) {
self.session = session
self.networkService = networkService ?? NetworkService.shared
self.networkHandler = networkHandler
}
///
public struct UploadResult: Codable {
@ -30,6 +75,9 @@ public class ImageUploaderGetID: ObservableObject {
case uploadFailed(Error?)
case invalidFileId
case invalidResponseData
case networkError(Error)
case decodingError(Error)
case unauthorized
public var errorDescription: String? {
switch self {
@ -47,41 +95,26 @@ public class ImageUploaderGetID: ObservableObject {
return "无效的文件ID"
case .invalidResponseData:
return "无效的响应数据"
case .networkError(let error):
return "网络错误: \(error.localizedDescription)"
case .decodingError(let error):
return "解码错误: \(error.localizedDescription)"
case .unauthorized:
return "认证失败,需要重新登录"
}
}
}
// MARK: -
private let session: URLSession
private let apiConfig: APIConfig.Type
// MARK: -
///
/// - Parameters:
/// - session: URLSession
/// - apiConfig: API
public init(session: URLSession = .shared, apiConfig: APIConfig.Type = APIConfig.self) {
self.session = session
self.apiConfig = apiConfig
}
// MARK: -
///
/// - Parameters:
/// - image:
/// - progress: (0.0 1.0)
/// - completion:
public func uploadImage(
_ image: UIImage,
progress: @escaping (Double) -> Void,
completion: @escaping (Result<UploadResult, Error>) -> Void
completion: @escaping (Result<UploadResult, UploadError>) -> Void
) {
print("🔄 开始准备上传图片...")
// 1. Data
guard let imageData = image.jpegData(compressionQuality: 0.7) else {
let error = UploadError.invalidImageData
print("❌ 错误:\(error.localizedDescription)")
@ -89,14 +122,21 @@ public class ImageUploaderGetID: ObservableObject {
return
}
// 2. URL
getUploadURL(for: imageData) { [weak self] result in
requestUploadURL(fileName: "image_\(UUID().uuidString).jpg", fileData: imageData) { [weak self] result in
guard let self = self else { return }
switch result {
case .success((let fileId, let uploadURL)):
case .success(let response):
print("📤 获取到上传URL开始上传文件...")
// 3.
_ = self?.uploadFile(
guard let uploadURL = URL(string: response.data.uploadUrl) else {
let error = UploadError.invalidURL
print("❌ [ImageUploader] 上传URL格式无效: \(response.data.uploadUrl)")
completion(.failure(error))
return
}
self.uploadFile(
fileData: imageData,
to: uploadURL,
mimeType: "image/jpeg",
@ -104,87 +144,73 @@ public class ImageUploaderGetID: ObservableObject {
print("📊 上传进度: \(Int(uploadProgress * 100))%")
progress(uploadProgress)
},
completion: { uploadResult in
completion: { [weak self] uploadResult in
switch uploadResult {
case .success:
// 4.
self?.confirmUpload(
fileId: fileId,
fileName: "avatar_\(UUID().uuidString).jpg",
fileId: response.data.fileId,
fileName: "image_\(UUID().uuidString).jpg",
fileSize: imageData.count,
completion: completion
)
case .failure(let error):
print("❌ 文件上传失败: \(error.localizedDescription)")
completion(.failure(error))
}
}
)
case .failure(let error):
print("❌ 获取上传URL失败: \(error.localizedDescription)")
completion(.failure(error))
}
}
}
// MARK: - Video Upload
///
/// - Parameters:
/// - videoURL: URL
/// - videoURL: URL
/// - progress: (0.0 1.0)
/// - completion:
/// - completion:
public func uploadVideo(
_ videoURL: URL,
progress: @escaping (Double) -> Void,
completion: @escaping (Result<UploadResult, Error>) -> Void
completion: @escaping (Result<UploadResult, UploadError>) -> Void
) {
print("🔄 开始准备上传视频...")
print("🎥 开始准备上传视频...")
// 1.
do {
let videoData = try Data(contentsOf: videoURL)
let fileExtension = videoURL.pathExtension.lowercased()
let mimeType: String
let fileName = "video_\(UUID().uuidString).mp4"
// MIME
switch fileExtension {
case "mp4":
mimeType = "video/mp4"
case "mov":
mimeType = "video/quicktime"
case "m4v":
mimeType = "video/x-m4v"
case "avi":
mimeType = "video/x-msvideo"
default:
mimeType = "video/mp4" // 使mp4
}
// 2. URL
getUploadURL(
for: videoData,
mimeType: mimeType,
originalFilename: videoURL.lastPathComponent
requestUploadURL(
fileName: fileName,
fileData: videoData
) { [weak self] result in
guard let self = self else { return }
switch result {
case .success((let fileId, let uploadURL)):
case .success(let response):
print("📤 获取到视频上传URL开始上传文件...")
// 3.
_ = self?.uploadFile(
guard let uploadURL = URL(string: response.data.uploadUrl) else {
let error = UploadError.invalidURL
print("❌ [ImageUploader] 视频上传URL格式无效: \(response.data.uploadUrl)")
completion(.failure(error))
return
}
self.uploadFile(
fileData: videoData,
to: uploadURL,
mimeType: mimeType,
mimeType: "video/mp4",
onProgress: progress,
completion: { result in
switch result {
completion: { [weak self] uploadResult in
guard let self = self else { return }
switch uploadResult {
case .success:
// 4.
self?.confirmUpload(
fileId: fileId,
fileName: videoURL.lastPathComponent,
self.confirmUpload(
fileId: response.data.fileId,
fileName: fileName,
fileSize: videoData.count,
completion: completion
)
@ -200,221 +226,55 @@ public class ImageUploaderGetID: ObservableObject {
}
} catch {
print("❌ 读取视频文件失败: \(error.localizedDescription)")
completion(.failure(error))
completion(.failure(.uploadFailed(error)))
}
}
// MARK: -
/// URL
private func getUploadURL(
for imageData: Data,
completion: @escaping (Result<(fileId: String, uploadURL: URL), Error>) -> Void
///
private func request<T: Decodable>(
path: String,
parameters: [String: Any],
completion: @escaping (Result<T, UploadError>) -> Void
) {
networkHandler(path, parameters) { (result: Result<Data, UploadError>) in
switch result {
case .success(let data):
do {
let decoded = try JSONDecoder().decode(T.self, from: data)
completion(.success(decoded))
} catch {
print("❌ [ImageUploader] 解析响应失败: \(error)")
completion(.failure(.invalidResponseData))
}
case .failure(let error):
print("❌ [ImageUploader] 网络请求失败: \(error)")
completion(.failure(error))
}
}
}
/// URL
private func requestUploadURL(
fileName: String,
fileData: Data,
completion: @escaping (Result<UploadURLResponse, UploadError>) -> Void
) {
let fileName = "avatar_\(UUID().uuidString).jpg"
let parameters: [String: Any] = [
"filename": fileName,
"content_type": "image/jpeg",
"file_size": imageData.count
]
let urlString = "\(apiConfig.baseURL)/file/generate-upload-url"
guard let url = URL(string: urlString) else {
completion(.failure(UploadError.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.allHTTPHeaderFields = apiConfig.authHeaders
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
print("📤 准备上传请求,文件名: \(fileName), 大小: \(Double(imageData.count) / 1024.0) KB")
} catch {
print("❌ 序列化请求参数失败: \(error.localizedDescription)")
completion(.failure(error))
return
}
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(UploadError.uploadFailed(error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(UploadError.invalidResponse))
return
}
guard let data = data else {
completion(.failure(UploadError.invalidResponse))
return
}
//
if let responseString = String(data: data, encoding: .utf8) {
print("📥 获取上传URL响应: \(responseString)")
}
do {
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let dataDict = json["data"] as? [String: Any],
let fileId = dataDict["file_id"] as? String,
let uploadURLString = dataDict["upload_url"] as? String,
let uploadURL = URL(string: uploadURLString) else {
throw UploadError.invalidResponse
}
completion(.success((fileId: fileId, uploadURL: uploadURL)))
} catch {
completion(.failure(UploadError.invalidResponse))
}
}
task.resume()
}
/// URL
private func getUploadURL(
for fileData: Data,
mimeType: String,
originalFilename: String? = nil,
completion: @escaping (Result<(fileId: String, uploadURL: URL), Error>) -> Void
) {
let fileName = originalFilename ?? "file_\(UUID().uuidString)"
let parameters: [String: Any] = [
"filename": fileName,
"content_type": mimeType,
"file_size": fileData.count
]
let urlString = "\(apiConfig.baseURL)/file/generate-upload-url"
guard let url = URL(string: urlString) else {
completion(.failure(UploadError.invalidURL))
return
}
print("""
📝 [ImageUploader] URL
: \(fileName)
📏 : \(fileData.count)
📋 : \(parameters)
""")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.allHTTPHeaderFields = apiConfig.authHeaders
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
print("📤 准备上传请求,文件名: \(fileName), 大小: \(Double(fileData.count) / 1024.0) KB")
} catch {
print("❌ 序列化请求参数失败: \(error.localizedDescription)")
completion(.failure(error))
return
}
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(UploadError.uploadFailed(error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(UploadError.invalidResponse))
return
}
guard let data = data else {
completion(.failure(UploadError.invalidResponse))
return
}
//
if let responseString = String(data: data, encoding: .utf8) {
print("📥 上传URL响应: \(responseString)")
}
do {
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
guard let code = json?["code"] as? Int, code == 0,
let dataDict = json?["data"] as? [String: Any],
let fileId = dataDict["file_id"] as? String,
let uploadURLString = dataDict["upload_url"] as? String,
let uploadURL = URL(string: uploadURLString) else {
throw UploadError.invalidResponse
}
completion(.success((fileId: fileId, uploadURL: uploadURL)))
} catch {
completion(.failure(UploadError.invalidResponse))
}
}
task.resume()
}
///
private func confirmUpload(
fileId: String,
fileName: String,
fileSize: Int,
completion: @escaping (Result<UploadResult, Error>) -> Void
) {
let endpoint = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: endpoint) else {
completion(.failure(UploadError.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.allHTTPHeaderFields = apiConfig.authHeaders
let body: [String: Any] = [
"file_id": fileId,
"file_name": fileName,
"file_size": fileSize
]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body)
print("📤 确认上传请求fileId: \(fileId), 文件名: \(fileName)")
} catch {
print("❌ 序列化确认上传参数失败: \(error.localizedDescription)")
completion(.failure(error))
return
}
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
print("❌ 确认上传请求失败: \(error.localizedDescription)")
completion(.failure(UploadError.uploadFailed(error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
print("❌ 无效的服务器响应")
completion(.failure(UploadError.invalidResponse))
return
}
guard (200...299).contains(httpResponse.statusCode) else {
let statusCode = httpResponse.statusCode
let errorMessage = "确认上传失败,状态码: \(statusCode)"
print("\(errorMessage)")
completion(.failure(UploadError.serverError(errorMessage)))
return
}
//
let uploadResult = UploadResult(
fileUrl: "\(self.apiConfig.baseURL)/files/\(fileId)",
fileName: fileName,
fileSize: fileSize,
fileId: fileId
)
print("✅ 图片上传并确认成功fileId: \(fileId)")
completion(.success(uploadResult))
}
task.resume()
request(path: "/file/generate-upload-url", parameters: parameters, completion: completion)
}
/// URL
@ -423,96 +283,203 @@ public class ImageUploaderGetID: ObservableObject {
to uploadURL: URL,
mimeType: String = "application/octet-stream",
onProgress: @escaping (Double) -> Void,
completion: @escaping (Result<Void, Error>) -> Void
) -> URLSessionUploadTask {
completion: @escaping (Result<Void, UploadError>) -> Void
) -> URLSessionUploadTask? {
print("""
[ImageUploader]
🔗 URL: \(uploadURL.absoluteString)
📏 : \(fileData.count)
📋 MIME类型: \(mimeType)
""")
//
var request = URLRequest(url: uploadURL)
request.httpMethod = "PUT"
request.setValue(mimeType, forHTTPHeaderField: "Content-Type")
let task = session.uploadTask(with: request, from: fileData) { _, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(UploadError.invalidResponse))
return
}
guard (200...299).contains(httpResponse.statusCode) else {
let statusCode = httpResponse.statusCode
completion(.failure(UploadError.serverError("上传失败,状态码: \(statusCode)")))
return
}
completion(.success(()))
}
//
var headers: [String: String] = [
"Content-Type": mimeType,
"Content-Length": String(fileData.count)
]
//
if #available(iOS 11.0, *) {
let progressObserver = task.progress.observe(\.fractionCompleted) { progressValue, _ in
DispatchQueue.main.async {
onProgress(progressValue.fractionCompleted)
}
}
task.addCompletionHandler { [weak task] in
progressObserver.invalidate()
task?.progress.cancel()
}
//
if let token = KeychainHelper.getAccessToken() {
headers["Authorization"] = "Bearer \(token)"
print("🔑 [ImageUploader] 添加认证头Token: \(token.prefix(10))...")
} else {
var lastProgress: Double = 0
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
let bytesSent = task.countOfBytesSent
let totalBytes = task.countOfBytesExpectedToSend
let currentProgress = totalBytes > 0 ? Double(bytesSent) / Double(totalBytes) : 0
// UI
if abs(currentProgress - lastProgress) > 0.01 || currentProgress >= 1.0 {
lastProgress = currentProgress
DispatchQueue.main.async {
onProgress(min(currentProgress, 1.0))
}
}
if currentProgress >= 1.0 {
timer.invalidate()
}
}
task.addCompletionHandler {
timer.invalidate()
}
print("⚠️ [ImageUploader] 未找到认证Token")
}
task.resume()
//
headers.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
//
let task = networkService.upload(
request: request,
fileData: fileData,
onProgress: onProgress,
completion: { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let data, let response):
guard let httpResponse = response as? HTTPURLResponse else {
print("❌ [ImageUploader] 无效的响应")
completion(.failure(.invalidResponse))
return
}
let statusCode = httpResponse.statusCode
let responseBody = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
print("""
📡 [ImageUploader]
🔢 : \(statusCode)
🔗 URL: \(uploadURL.absoluteString)
📦 : \(responseBody)
""")
switch statusCode {
case 200...299:
print("✅ [ImageUploader] 文件上传成功")
completion(.success(()))
case 401:
print("🔑 [ImageUploader] 认证失败,需要重新登录")
completion(.failure(.unauthorized))
case 403:
let errorMessage = "访问被拒绝 (403) - URL: \(uploadURL.absoluteString)"
print("❌ [ImageUploader] \(errorMessage)")
completion(.failure(.serverError(errorMessage)))
default:
let errorMessage = "服务器返回错误: \(statusCode)"
print("❌ [ImageUploader] \(errorMessage)")
completion(.failure(.serverError(errorMessage)))
}
case .failure(let error):
print("❌ [ImageUploader] 上传失败: \(error.localizedDescription)")
completion(.failure(.networkError(error)))
}
}
)
//
task?.resume()
return task
}
// MARK: -
///
public struct FileStatus {
public let file: Data
public var status: UploadStatus
public var progress: Double
///
private func confirmUpload(
fileId: String,
fileName: String,
fileSize: Int,
completion: @escaping (Result<UploadResult, UploadError>) -> Void
) {
let parameters: [String: Any] = [
"file_id": fileId,
"file_name": fileName,
"file_size": fileSize
]
public enum UploadStatus {
case pending
case uploading
case completed
case failed(Error)
print("""
📨 [ImageUploader]
📁 ID: \(fileId)
📝 : \(fileName)
📏 : \(fileSize)
📋 : \(parameters)
""")
struct ConfirmUploadResponse: Codable {
let code: Int
let message: String
let data: [String: String]?
}
public init(file: Data, status: UploadStatus = .pending, progress: Double = 0) {
self.file = file
self.status = status
self.progress = progress
request(path: "/file/confirm-upload", parameters: parameters) { (result: Result<ConfirmUploadResponse, UploadError>) in
switch result {
case .success(_):
let uploadResult = UploadResult(
fileUrl: "\(APIConfig.baseURL)/files/\(fileId)",
fileName: fileName,
fileSize: fileSize,
fileId: fileId
)
print("""
[ImageUploader]
📁 ID: \(fileId)
🔗 URL: \(uploadResult.fileUrl)
📝 : \(fileName)
📏 : \(fileSize)
""")
completion(.success(uploadResult))
case .failure(let error):
print("❌ [ImageUploader] 文件上传确认失败: \(error.localizedDescription)")
completion(.failure(error))
}
}
}
// MARK: -
private func calculateSpeed(bytes: Int, seconds: TimeInterval) -> String {
guard seconds > 0 else { return "0 KB/s" }
let bytesPerSecond = Double(bytes) / seconds
if bytesPerSecond >= 1024 * 1024 {
return String(format: "%.1f MB/s", bytesPerSecond / (1024 * 1024))
} else {
return String(format: "%.1f KB/s", bytesPerSecond / 1024)
}
}
}
// MARK: - Data Extension for MD5
extension Data {
func md5Base64EncodedString() -> String? {
#if canImport(CommonCrypto)
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
_ = self.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in
if let baseAddress = ptr.baseAddress, ptr.count > 0 {
CC_MD5(baseAddress, CC_LONG(self.count), &digest)
}
}
return Data(digest).base64EncodedString()
#else
return nil
#endif
}
}
// MARK: -
private struct UploadURLResponse: Codable {
let code: Int
let message: String?
let data: UploadData
struct UploadData: Codable {
let fileId: String
let filePath: String
let uploadUrl: String
let expiresIn: Int
enum CodingKeys: String, CodingKey {
case fileId = "file_id"
case filePath = "file_path"
case uploadUrl = "upload_url"
case expiresIn = "expires_in"
}
}
}
private struct EmptyResponse: Codable {}
// MARK: - URLSessionTask
private class TaskObserver: NSObject {
@ -541,7 +508,6 @@ private class TaskObserver: NSObject {
return
}
//
DispatchQueue.main.async { [weak self] in
self?.handlers.forEach { $0() }
self?.cleanup()
@ -580,7 +546,6 @@ private extension URLSessionTask {
taskObserver = observer
}
} else {
// iOS 11 使
let name = NSNotification.Name("TaskCompleted\(self.taskIdentifier)")
NotificationCenter.default.addObserver(
forName: name,
@ -592,21 +557,3 @@ private extension URLSessionTask {
}
}
}
// MARK: -
struct UploadURLResponse: Codable {
let code: Int
let message: String
let data: UploadData
struct UploadData: Codable {
let fileId: String
let uploadUrl: String
enum CodingKeys: String, CodingKey {
case fileId = "file_id"
case uploadUrl = "upload_url"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,7 @@ struct SplashView: View {
)
.edgesIgnoringSafeArea(.all)
VStack(spacing: 50) {
FilmAnimation()
// FilmAnimation()
}
.padding()
}