wake-ios/wake/View/Components/Upload/ImageUploaderGetID.swift
2025-08-19 20:36:00 +08:00

233 lines
8.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
import PhotosUI
///
/// file_id
public class ImageUploaderGetID: ObservableObject {
// MARK: -
///
public struct UploadResult {
public let fileUrl: String
public let fileName: String
public let fileSize: Int
public let fileId: String
public init(fileUrl: String, fileName: String, fileSize: Int, fileId: String) {
self.fileUrl = fileUrl
self.fileName = fileName
self.fileSize = fileSize
self.fileId = fileId
}
}
///
public enum UploadError: LocalizedError {
case invalidImageData
case invalidURL
case serverError(String)
case invalidResponse
case uploadFailed(Error?)
case invalidFileId
public var errorDescription: String? {
switch self {
case .invalidImageData:
return "无效的图片数据"
case .invalidURL:
return "无效的URL"
case .serverError(let message):
return "服务器错误: \(message)"
case .invalidResponse:
return "无效的服务器响应"
case .uploadFailed(let error):
return "上传失败: \(error?.localizedDescription ?? "未知错误")"
case .invalidFileId:
return "无效的文件ID"
}
}
}
// 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:
/// - completion: Result
public func uploadImage(_ image: UIImage, completion: @escaping (Result<UploadResult, Error>) -> Void) {
print("🔄 开始准备上传图片...")
// 1. Data
guard let imageData = image.jpegData(compressionQuality: 0.7) else {
let error = UploadError.invalidImageData
print("❌ 错误:\(error.localizedDescription)")
completion(.failure(error))
return
}
// 2. URL
getUploadURL(for: imageData) { [weak self] result in
switch result {
case .success((let fileId, let uploadURL)):
// 3.
self?.confirmUpload(fileId: fileId, fileName: "avatar_\(UUID().uuidString).jpg", fileSize: imageData.count) { confirmResult in
completion(confirmResult)
}
case .failure(let error):
completion(.failure(error))
}
}
}
// MARK: -
/// URL
private func getUploadURL(for imageData: Data, completion: @escaping (Result<(fileId: String, uploadURL: URL), Error>) -> 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()
}
///
private func confirmUpload(fileId: String, fileName: String, fileSize: Int, completion: @escaping (Result<UploadResult, Error>) -> Void) {
let urlString = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: urlString) else {
completion(.failure(UploadError.invalidURL))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.allHTTPHeaderFields = apiConfig.authHeaders
let requestBody: [String: Any] = ["file_id": fileId]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: requestBody)
} catch {
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,
(200...299).contains(httpResponse.statusCode) else {
completion(.failure(UploadError.serverError("确认上传失败,状态码: \((response as? HTTPURLResponse)?.statusCode ?? -1)")))
return
}
//
let uploadResult = UploadResult(
fileUrl: "\(self.apiConfig.baseURL)/files/\(fileId)",
fileName: fileName,
fileSize: fileSize,
fileId: fileId
)
print("✅ 图片上传并确认成功fileId: \(fileId)")
completion(.success(uploadResult))
}
task.resume()
}
}
// 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"
}
}
}