feat: 文件上传成功

This commit is contained in:
jinyaqiu 2025-08-19 19:33:10 +08:00
parent a63d363001
commit a1f26f13bf
3 changed files with 155 additions and 72 deletions

View File

@ -5,6 +5,7 @@ import PhotosUI
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
// MARK: - Photo Picker
=======
///
@ -205,6 +206,8 @@ public struct UploadResults {
}
}
=======
>>>>>>> 5edee64 (feat: )
// MARK: - Photo Picker
>>>>>>> 8e641fd (feat: )
@ -213,6 +216,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
<<<<<<< HEAD
=======
// MARK: - Properties
<<<<<<< HEAD
<<<<<<< HEAD
///
@ -260,14 +264,29 @@ struct PhotoPicker: UIViewControllerRepresentable {
=======
var onImageUploaded: ((Result<UploadResults, Error>) -> Void)?
var onUploadProgress: ((UploadProgress) -> Void)?
=======
@Binding var selectedImages: [UIImage]
let selectionLimit: Int
let filter: PHPickerFilter
var onImageUploaded: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
var onUploadProgress: ((ImageUploadService.UploadProgress) -> Void)?
@Environment(\.presentationMode) private var presentationMode
>>>>>>> 5edee64 (feat: )
// MARK: - Initialization
init(selectedImages: Binding<[UIImage]>,
selectionLimit: Int = 1,
filter: PHPickerFilter = .images,
<<<<<<< HEAD
onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil,
onUploadProgress: ((UploadProgress) -> Void)? = nil) {
>>>>>>> 8e641fd (feat: )
=======
onImageUploaded: ((Result<ImageUploadService.UploadResults, Error>) -> Void)? = nil,
onUploadProgress: ((ImageUploadService.UploadProgress) -> Void)? = nil) {
>>>>>>> 5edee64 (feat: )
self._selectedImages = selectedImages
self.selectionLimit = selectionLimit
self.filter = filter
@ -280,6 +299,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
}
// MARK: - UIViewControllerRepresentable
<<<<<<< HEAD
<<<<<<< HEAD
// MARK: - UIViewControllerRepresentable
@ -292,6 +312,9 @@ struct PhotoPicker: UIViewControllerRepresentable {
/// PHPickerViewController
=======
>>>>>>> 8e641fd (feat: )
=======
>>>>>>> 5edee64 (feat: )
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = filter
@ -311,6 +334,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
// MARK: - Coordinator
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
// MARK: -
@ -329,15 +353,27 @@ struct PhotoPicker: UIViewControllerRepresentable {
=======
>>>>>>> 8e641fd (feat: )
private let uploader = ImageUploaderGetID()
=======
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: PhotoPicker
private let uploadService = ImageUploadService.shared
>>>>>>> 5edee64 (feat: )
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard !results.isEmpty else {
parent.presentationMode.wrappedValue.dismiss()
return
}
parent.selectedImages.removeAll()
let group = DispatchGroup()
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
var loadedImages: [Int: UIImage] = [:]
var uploadResults: [Int: ImageUploadService.UploadResults] = [:]
@ -355,11 +391,17 @@ struct PhotoPicker: UIViewControllerRepresentable {
var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?,
compressed: ImageUploaderGetID.UploadResult?)] = [:]
>>>>>>> 8e641fd (feat: )
=======
var loadedImages: [Int: UIImage] = [:]
var uploadResults: [Int: ImageUploadService.UploadResults] = [:]
var lastError: Error?
>>>>>>> 5edee64 (feat: )
for (index, result) in results.enumerated() {
group.enter()
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
<<<<<<< HEAD
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in
<<<<<<< HEAD
if let image = image as? UIImage {
@ -370,12 +412,24 @@ struct PhotoPicker: UIViewControllerRepresentable {
loadedImages[index] = image
=======
guard let self = self, let image = image as? UIImage else {
=======
result.itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
if let error = error {
lastError = error
group.leave()
return
}
guard let image = image as? UIImage else {
lastError = NSError(domain: "com.wake.upload", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to load image"])
>>>>>>> 5edee64 (feat: )
group.leave()
return
}
loadedImages[index] = image
<<<<<<< HEAD
guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else {
group.leave()
return
@ -426,77 +480,48 @@ struct PhotoPicker: UIViewControllerRepresentable {
}
=======
self.uploader.uploadImage(
=======
// Upload the image
self.uploadService.uploadOriginalAndCompressedImage(
>>>>>>> 5edee64 (feat: )
image,
progress: { [weak self] progress in
let progressInfo = UploadProgress(
current: Int(progress * 100),
total: 100,
progress: progress,
isOriginal: true
)
compressionQuality: 0.5,
progress: { progress in
DispatchQueue.main.async {
self?.parent.onUploadProgress?(progressInfo)
self.parent.onUploadProgress?(progress)
}
print("📤 原图上传进度: \(Int(progress * 100))%")
},
<<<<<<< HEAD
completion: { [weak self] originalResult in
guard let self = self else {
group.leave()
return
>>>>>>> 8e641fd (feat: )
}
=======
completion: { result in
defer { group.leave() }
>>>>>>> 5edee64 (feat: )
switch originalResult {
case .success(let originalUploadResult):
self.uploader.uploadImage(
compressedImage,
progress: { [weak self] progress in
let progressInfo = UploadProgress(
current: Int(progress * 100),
total: 100,
progress: progress,
isOriginal: false
)
DispatchQueue.main.async {
self?.parent.onUploadProgress?(progressInfo)
}
print("📊 压缩图上传进度: \(Int(progress * 100))%")
},
completion: { compressedResult in
defer { group.leave() }
switch compressedResult {
case .success(let compressedUploadResult):
uploadResults[index] = (originalUploadResult, compressedUploadResult)
print("✅ 原图和压缩图上传成功!")
print("📂 原图信息:")
print(" - 文件ID: \(originalUploadResult.fileId)")
print(" - 文件大小: \(originalUploadResult.fileSize) 字节")
print("📦 压缩图信息:")
print(" - 文件ID: \(compressedUploadResult.fileId)")
print(" - 文件大小: \(compressedUploadResult.fileSize) 字节")
MaterialService.shared.uploadMaterialInfo(
fileId: originalUploadResult.fileId,
previewFileId: compressedUploadResult.fileId
) { success, errorMessage in
if success {
print("✅ 文件信息上传成功 素材上传成功!!!!!")
} else if let errorMessage = errorMessage {
print("❌ 文件信息上传失败: \(errorMessage)")
}
}
case .failure(let error):
print("❌ 压缩图上传失败: \(error.localizedDescription)")
uploadResults[index] = (originalUploadResult, nil)
}
switch result {
case .success(let results):
uploadResults[index] = results
// Upload file info to backend
MaterialService.shared.uploadMaterialInfo(
fileId: results.original.fileId,
previewFileId: results.compressed.fileId
) { success, errorMessage in
if success {
print("✅ 文件信息上传成功")
} else if let errorMessage = errorMessage {
print("❌ 文件信息上传失败: \(errorMessage)")
}
)
}
case .failure(let error):
print("❌ 原图上传失败: \(error.localizedDescription)")
group.leave()
lastError = error
print("❌ 图片上传失败: \(error.localizedDescription)")
}
}
)
@ -535,28 +560,33 @@ struct PhotoPicker: UIViewControllerRepresentable {
group.notify(queue: .main) { [weak self] in
guard let self = self else { return }
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
self.parent.selectedImages.append(contentsOf: sortedImages)
if let firstResult = uploadResults.first?.value,
let original = firstResult.original,
let compressed = firstResult.compressed {
let results = UploadResults(original: original, compressed: compressed)
self.parent.onImageUploaded?(.success(results))
if let error = lastError {
self.parent.onImageUploaded?(.failure(error))
} else {
self.parent.onImageUploaded?(.failure(NSError(
domain: "com.wake.upload",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"]
)))
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
self.parent.selectedImages.append(contentsOf: sortedImages)
if let firstResult = uploadResults.first?.value {
self.parent.onImageUploaded?(.success(firstResult))
} else {
self.parent.onImageUploaded?(.failure(NSError(
domain: "com.wake.upload",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"]
)))
}
}
<<<<<<< HEAD
<<<<<<< HEAD
// 5.
>>>>>>> 5611df8 (feat: )
=======
>>>>>>> 8e641fd (feat: )
picker.dismiss(animated: true)
=======
self.parent.presentationMode.wrappedValue.dismiss()
>>>>>>> 5edee64 (feat: )
}
}
}
@ -579,6 +609,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
struct AvatarUploader: View {
@Binding var selectedImage: UIImage?
let size: CGFloat
<<<<<<< HEAD
<<<<<<< HEAD
var onUploadComplete: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
@ -586,6 +617,9 @@ struct AvatarUploader: View {
=======
>>>>>>> 8e641fd (feat: )
var onUploadComplete: ((Result<UploadResults, Error>) -> Void)?
=======
var onUploadComplete: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
>>>>>>> 5edee64 (feat: )
@State private var isImagePickerPresented = false
@ -662,7 +696,7 @@ struct AvatarUploader: View {
onUploadComplete?(result)
},
onUploadProgress: { progress in
print("上传进度:\(progress.current)/\(progress.total),进度:\(progress.progress * 100)%")
print("上传进度:\(progress.current)/\(progress.total),进度:\(Int(progress.progress * 100))%")
}
)
}

View File

@ -7,11 +7,15 @@ public class ImageUploaderGetID: ObservableObject {
// MARK: -
///
<<<<<<< HEAD
<<<<<<< HEAD
public struct UploadResult: Codable {
=======
public struct UploadResult {
>>>>>>> a207b78 (feat: )
=======
public struct UploadResult: Codable {
>>>>>>> 5edee64 (feat: )
public let fileUrl: String
public let fileName: String
public let fileSize: Int
@ -33,10 +37,14 @@ public class ImageUploaderGetID: ObservableObject {
case invalidResponse
case uploadFailed(Error?)
case invalidFileId
<<<<<<< HEAD
<<<<<<< HEAD
case invalidResponseData
=======
>>>>>>> a207b78 (feat: )
=======
case invalidResponseData
>>>>>>> 5edee64 (feat: )
public var errorDescription: String? {
switch self {
@ -52,11 +60,16 @@ public class ImageUploaderGetID: ObservableObject {
return "上传失败: \(error?.localizedDescription ?? "未知错误")"
case .invalidFileId:
return "无效的文件ID"
<<<<<<< HEAD
<<<<<<< HEAD
case .invalidResponseData:
return "无效的响应数据"
=======
>>>>>>> a207b78 (feat: )
=======
case .invalidResponseData:
return "无效的响应数据"
>>>>>>> 5edee64 (feat: )
}
}
}
@ -237,6 +250,7 @@ public class ImageUploaderGetID: ObservableObject {
///
private func confirmUpload(fileId: String, fileName: String, fileSize: Int, completion: @escaping (Result<UploadResult, Error>) -> Void) {
<<<<<<< HEAD
<<<<<<< HEAD
let endpoint = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: endpoint) else {
@ -244,6 +258,10 @@ public class ImageUploaderGetID: ObservableObject {
let urlString = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: urlString) else {
>>>>>>> a207b78 (feat: )
=======
let endpoint = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: endpoint) else {
>>>>>>> 5edee64 (feat: )
completion(.failure(UploadError.invalidURL))
return
}
@ -253,11 +271,15 @@ public class ImageUploaderGetID: ObservableObject {
request.allHTTPHeaderFields = apiConfig.authHeaders
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 5edee64 (feat: )
let body: [String: Any] = [
"file_id": fileId,
"file_name": fileName,
"file_size": fileSize
]
<<<<<<< HEAD
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body)
@ -266,26 +288,40 @@ public class ImageUploaderGetID: ObservableObject {
print("❌ 序列化确认上传参数失败: \(error.localizedDescription)")
=======
let requestBody: [String: Any] = ["file_id": fileId]
=======
>>>>>>> 5edee64 (feat: )
do {
request.httpBody = try JSONSerialization.data(withJSONObject: requestBody)
request.httpBody = try JSONSerialization.data(withJSONObject: body)
print("📤 确认上传请求fileId: \(fileId), 文件名: \(fileName)")
} catch {
<<<<<<< HEAD
>>>>>>> a207b78 (feat: )
=======
print("❌ 序列化确认上传参数失败: \(error.localizedDescription)")
>>>>>>> 5edee64 (feat: )
completion(.failure(error))
return
}
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
<<<<<<< HEAD
<<<<<<< HEAD
print("❌ 确认上传请求失败: \(error.localizedDescription)")
=======
>>>>>>> a207b78 (feat: )
=======
print("❌ 确认上传请求失败: \(error.localizedDescription)")
>>>>>>> 5edee64 (feat: )
completion(.failure(UploadError.uploadFailed(error)))
return
}
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 5edee64 (feat: )
guard let httpResponse = response as? HTTPURLResponse else {
print("❌ 无效的服务器响应")
completion(.failure(UploadError.invalidResponse))
@ -297,11 +333,14 @@ public class ImageUploaderGetID: ObservableObject {
let errorMessage = "确认上传失败,状态码: \(statusCode)"
print("\(errorMessage)")
completion(.failure(UploadError.serverError(errorMessage)))
<<<<<<< HEAD
=======
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
completion(.failure(UploadError.serverError("确认上传失败,状态码: \((response as? HTTPURLResponse)?.statusCode ?? -1)")))
>>>>>>> a207b78 (feat: )
=======
>>>>>>> 5edee64 (feat: )
return
}
@ -352,6 +391,9 @@ public class ImageUploaderGetID: ObservableObject {
}
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 5edee64 (feat: )
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(UploadError.invalidResponse))
return
@ -359,11 +401,14 @@ public class ImageUploaderGetID: ObservableObject {
guard httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 else {
let statusCode = httpResponse.statusCode
<<<<<<< HEAD
=======
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
>>>>>>> 8e641fd (feat: )
=======
>>>>>>> 5edee64 (feat: )
completion(.failure(UploadError.serverError("上传失败,状态码: \(statusCode)")))
return
}
@ -384,11 +429,15 @@ public class ImageUploaderGetID: ObservableObject {
task?.progress.cancel()
}
} else {
<<<<<<< HEAD
<<<<<<< HEAD
// iOS 11 使
=======
// Fallback for earlier iOS versions
>>>>>>> 8e641fd (feat: )
=======
// iOS 11 使
>>>>>>> 5edee64 (feat: )
var lastProgress: Double = 0
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
let bytesSent = task.countOfBytesSent