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
<<<<<<< HEAD <<<<<<< HEAD
<<<<<<< HEAD
// MARK: - Photo Picker // MARK: - Photo Picker
======= =======
/// ///
@ -205,6 +206,8 @@ public struct UploadResults {
} }
} }
=======
>>>>>>> 5edee64 (feat: )
// MARK: - Photo Picker // MARK: - Photo Picker
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
@ -213,6 +216,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
<<<<<<< HEAD <<<<<<< HEAD
======= =======
// MARK: - Properties // MARK: - Properties
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
/// ///
@ -260,14 +264,29 @@ struct PhotoPicker: UIViewControllerRepresentable {
======= =======
var onImageUploaded: ((Result<UploadResults, Error>) -> Void)? var onImageUploaded: ((Result<UploadResults, Error>) -> Void)?
var onUploadProgress: ((UploadProgress) -> 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 // MARK: - Initialization
init(selectedImages: Binding<[UIImage]>, init(selectedImages: Binding<[UIImage]>,
selectionLimit: Int = 1, selectionLimit: Int = 1,
filter: PHPickerFilter = .images, filter: PHPickerFilter = .images,
<<<<<<< HEAD
onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil, onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil,
onUploadProgress: ((UploadProgress) -> Void)? = nil) { onUploadProgress: ((UploadProgress) -> Void)? = nil) {
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
=======
onImageUploaded: ((Result<ImageUploadService.UploadResults, Error>) -> Void)? = nil,
onUploadProgress: ((ImageUploadService.UploadProgress) -> Void)? = nil) {
>>>>>>> 5edee64 (feat: )
self._selectedImages = selectedImages self._selectedImages = selectedImages
self.selectionLimit = selectionLimit self.selectionLimit = selectionLimit
self.filter = filter self.filter = filter
@ -280,6 +299,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
} }
// MARK: - UIViewControllerRepresentable // MARK: - UIViewControllerRepresentable
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
// MARK: - UIViewControllerRepresentable // MARK: - UIViewControllerRepresentable
@ -292,6 +312,9 @@ struct PhotoPicker: UIViewControllerRepresentable {
/// PHPickerViewController /// PHPickerViewController
======= =======
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
=======
>>>>>>> 5edee64 (feat: )
func makeUIViewController(context: Context) -> PHPickerViewController { func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = filter configuration.filter = filter
@ -311,6 +334,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
// MARK: - Coordinator // MARK: - Coordinator
<<<<<<< HEAD <<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
// MARK: - // MARK: -
@ -329,15 +353,27 @@ struct PhotoPicker: UIViewControllerRepresentable {
======= =======
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
private let uploader = ImageUploaderGetID() private let uploader = ImageUploaderGetID()
=======
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: PhotoPicker
private let uploadService = ImageUploadService.shared
>>>>>>> 5edee64 (feat: )
init(_ parent: PhotoPicker) { init(_ parent: PhotoPicker) {
self.parent = parent self.parent = parent
} }
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard !results.isEmpty else {
parent.presentationMode.wrappedValue.dismiss()
return
}
parent.selectedImages.removeAll() parent.selectedImages.removeAll()
let group = DispatchGroup() let group = DispatchGroup()
<<<<<<< HEAD <<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
var loadedImages: [Int: UIImage] = [:] var loadedImages: [Int: UIImage] = [:]
var uploadResults: [Int: ImageUploadService.UploadResults] = [:] var uploadResults: [Int: ImageUploadService.UploadResults] = [:]
@ -355,11 +391,17 @@ struct PhotoPicker: UIViewControllerRepresentable {
var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?, var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?,
compressed: ImageUploaderGetID.UploadResult?)] = [:] compressed: ImageUploaderGetID.UploadResult?)] = [:]
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
=======
var loadedImages: [Int: UIImage] = [:]
var uploadResults: [Int: ImageUploadService.UploadResults] = [:]
var lastError: Error?
>>>>>>> 5edee64 (feat: )
for (index, result) in results.enumerated() { for (index, result) in results.enumerated() {
group.enter() group.enter()
if result.itemProvider.canLoadObject(ofClass: UIImage.self) { if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
<<<<<<< HEAD
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in
<<<<<<< HEAD <<<<<<< HEAD
if let image = image as? UIImage { if let image = image as? UIImage {
@ -370,12 +412,24 @@ struct PhotoPicker: UIViewControllerRepresentable {
loadedImages[index] = image loadedImages[index] = image
======= =======
guard let self = self, let image = image as? UIImage else { 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() group.leave()
return return
} }
loadedImages[index] = image loadedImages[index] = image
<<<<<<< HEAD
guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else { guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else {
group.leave() group.leave()
return return
@ -426,77 +480,48 @@ struct PhotoPicker: UIViewControllerRepresentable {
} }
======= =======
self.uploader.uploadImage( self.uploader.uploadImage(
=======
// Upload the image
self.uploadService.uploadOriginalAndCompressedImage(
>>>>>>> 5edee64 (feat: )
image, image,
progress: { [weak self] progress in compressionQuality: 0.5,
let progressInfo = UploadProgress( progress: { progress in
current: Int(progress * 100),
total: 100,
progress: progress,
isOriginal: true
)
DispatchQueue.main.async { DispatchQueue.main.async {
self?.parent.onUploadProgress?(progressInfo) self.parent.onUploadProgress?(progress)
} }
print("📤 原图上传进度: \(Int(progress * 100))%")
}, },
<<<<<<< HEAD
completion: { [weak self] originalResult in completion: { [weak self] originalResult in
guard let self = self else { guard let self = self else {
group.leave() group.leave()
return return
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
} }
=======
completion: { result in
defer { group.leave() }
>>>>>>> 5edee64 (feat: )
switch originalResult { switch result {
case .success(let originalUploadResult): case .success(let results):
self.uploader.uploadImage( uploadResults[index] = results
compressedImage,
progress: { [weak self] progress in // Upload file info to backend
let progressInfo = UploadProgress( MaterialService.shared.uploadMaterialInfo(
current: Int(progress * 100), fileId: results.original.fileId,
total: 100, previewFileId: results.compressed.fileId
progress: progress, ) { success, errorMessage in
isOriginal: false if success {
) print("✅ 文件信息上传成功")
DispatchQueue.main.async { } else if let errorMessage = errorMessage {
self?.parent.onUploadProgress?(progressInfo) print("❌ 文件信息上传失败: \(errorMessage)")
}
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)
}
} }
) }
case .failure(let error): case .failure(let error):
print("❌ 原图上传失败: \(error.localizedDescription)") lastError = error
group.leave() print("❌ 图片上传失败: \(error.localizedDescription)")
} }
} }
) )
@ -535,28 +560,33 @@ struct PhotoPicker: UIViewControllerRepresentable {
group.notify(queue: .main) { [weak self] in group.notify(queue: .main) { [weak self] in
guard let self = self else { return } guard let self = self else { return }
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value } if let error = lastError {
self.parent.selectedImages.append(contentsOf: sortedImages) self.parent.onImageUploaded?(.failure(error))
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))
} else { } else {
self.parent.onImageUploaded?(.failure(NSError( let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
domain: "com.wake.upload", self.parent.selectedImages.append(contentsOf: sortedImages)
code: -1,
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"] 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 <<<<<<< HEAD
// 5. // 5.
>>>>>>> 5611df8 (feat: ) >>>>>>> 5611df8 (feat: )
======= =======
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
picker.dismiss(animated: true) picker.dismiss(animated: true)
=======
self.parent.presentationMode.wrappedValue.dismiss()
>>>>>>> 5edee64 (feat: )
} }
} }
} }
@ -579,6 +609,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
struct AvatarUploader: View { struct AvatarUploader: View {
@Binding var selectedImage: UIImage? @Binding var selectedImage: UIImage?
let size: CGFloat let size: CGFloat
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
var onUploadComplete: ((Result<ImageUploadService.UploadResults, Error>) -> Void)? var onUploadComplete: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
@ -586,6 +617,9 @@ struct AvatarUploader: View {
======= =======
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
var onUploadComplete: ((Result<UploadResults, Error>) -> Void)? var onUploadComplete: ((Result<UploadResults, Error>) -> Void)?
=======
var onUploadComplete: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
>>>>>>> 5edee64 (feat: )
@State private var isImagePickerPresented = false @State private var isImagePickerPresented = false
@ -662,7 +696,7 @@ struct AvatarUploader: View {
onUploadComplete?(result) onUploadComplete?(result)
}, },
onUploadProgress: { progress in 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: - // MARK: -
/// ///
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
public struct UploadResult: Codable { public struct UploadResult: Codable {
======= =======
public struct UploadResult { public struct UploadResult {
>>>>>>> a207b78 (feat: ) >>>>>>> a207b78 (feat: )
=======
public struct UploadResult: Codable {
>>>>>>> 5edee64 (feat: )
public let fileUrl: String public let fileUrl: String
public let fileName: String public let fileName: String
public let fileSize: Int public let fileSize: Int
@ -33,10 +37,14 @@ public class ImageUploaderGetID: ObservableObject {
case invalidResponse case invalidResponse
case uploadFailed(Error?) case uploadFailed(Error?)
case invalidFileId case invalidFileId
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
case invalidResponseData case invalidResponseData
======= =======
>>>>>>> a207b78 (feat: ) >>>>>>> a207b78 (feat: )
=======
case invalidResponseData
>>>>>>> 5edee64 (feat: )
public var errorDescription: String? { public var errorDescription: String? {
switch self { switch self {
@ -52,11 +60,16 @@ public class ImageUploaderGetID: ObservableObject {
return "上传失败: \(error?.localizedDescription ?? "未知错误")" return "上传失败: \(error?.localizedDescription ?? "未知错误")"
case .invalidFileId: case .invalidFileId:
return "无效的文件ID" return "无效的文件ID"
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
case .invalidResponseData: case .invalidResponseData:
return "无效的响应数据" return "无效的响应数据"
======= =======
>>>>>>> a207b78 (feat: ) >>>>>>> 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) { private func confirmUpload(fileId: String, fileName: String, fileSize: Int, completion: @escaping (Result<UploadResult, Error>) -> Void) {
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
let endpoint = "\(apiConfig.baseURL)/file/confirm-upload" let endpoint = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: endpoint) else { guard let url = URL(string: endpoint) else {
@ -244,6 +258,10 @@ public class ImageUploaderGetID: ObservableObject {
let urlString = "\(apiConfig.baseURL)/file/confirm-upload" let urlString = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: urlString) else { guard let url = URL(string: urlString) else {
>>>>>>> a207b78 (feat: ) >>>>>>> a207b78 (feat: )
=======
let endpoint = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: endpoint) else {
>>>>>>> 5edee64 (feat: )
completion(.failure(UploadError.invalidURL)) completion(.failure(UploadError.invalidURL))
return return
} }
@ -253,11 +271,15 @@ public class ImageUploaderGetID: ObservableObject {
request.allHTTPHeaderFields = apiConfig.authHeaders request.allHTTPHeaderFields = apiConfig.authHeaders
<<<<<<< HEAD <<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 5edee64 (feat: )
let body: [String: Any] = [ let body: [String: Any] = [
"file_id": fileId, "file_id": fileId,
"file_name": fileName, "file_name": fileName,
"file_size": fileSize "file_size": fileSize
] ]
<<<<<<< HEAD
do { do {
request.httpBody = try JSONSerialization.data(withJSONObject: body) request.httpBody = try JSONSerialization.data(withJSONObject: body)
@ -266,26 +288,40 @@ public class ImageUploaderGetID: ObservableObject {
print("❌ 序列化确认上传参数失败: \(error.localizedDescription)") print("❌ 序列化确认上传参数失败: \(error.localizedDescription)")
======= =======
let requestBody: [String: Any] = ["file_id": fileId] let requestBody: [String: Any] = ["file_id": fileId]
=======
>>>>>>> 5edee64 (feat: )
do { do {
request.httpBody = try JSONSerialization.data(withJSONObject: requestBody) request.httpBody = try JSONSerialization.data(withJSONObject: body)
print("📤 确认上传请求fileId: \(fileId), 文件名: \(fileName)")
} catch { } catch {
<<<<<<< HEAD
>>>>>>> a207b78 (feat: ) >>>>>>> a207b78 (feat: )
=======
print("❌ 序列化确认上传参数失败: \(error.localizedDescription)")
>>>>>>> 5edee64 (feat: )
completion(.failure(error)) completion(.failure(error))
return return
} }
let task = session.dataTask(with: request) { data, response, error in let task = session.dataTask(with: request) { data, response, error in
if let error = error { if let error = error {
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
print("❌ 确认上传请求失败: \(error.localizedDescription)") print("❌ 确认上传请求失败: \(error.localizedDescription)")
======= =======
>>>>>>> a207b78 (feat: ) >>>>>>> a207b78 (feat: )
=======
print("❌ 确认上传请求失败: \(error.localizedDescription)")
>>>>>>> 5edee64 (feat: )
completion(.failure(UploadError.uploadFailed(error))) completion(.failure(UploadError.uploadFailed(error)))
return return
} }
<<<<<<< HEAD <<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 5edee64 (feat: )
guard let httpResponse = response as? HTTPURLResponse else { guard let httpResponse = response as? HTTPURLResponse else {
print("❌ 无效的服务器响应") print("❌ 无效的服务器响应")
completion(.failure(UploadError.invalidResponse)) completion(.failure(UploadError.invalidResponse))
@ -297,11 +333,14 @@ public class ImageUploaderGetID: ObservableObject {
let errorMessage = "确认上传失败,状态码: \(statusCode)" let errorMessage = "确认上传失败,状态码: \(statusCode)"
print("\(errorMessage)") print("\(errorMessage)")
completion(.failure(UploadError.serverError(errorMessage))) completion(.failure(UploadError.serverError(errorMessage)))
<<<<<<< HEAD
======= =======
guard let httpResponse = response as? HTTPURLResponse, guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else { (200...299).contains(httpResponse.statusCode) else {
completion(.failure(UploadError.serverError("确认上传失败,状态码: \((response as? HTTPURLResponse)?.statusCode ?? -1)"))) completion(.failure(UploadError.serverError("确认上传失败,状态码: \((response as? HTTPURLResponse)?.statusCode ?? -1)")))
>>>>>>> a207b78 (feat: ) >>>>>>> a207b78 (feat: )
=======
>>>>>>> 5edee64 (feat: )
return return
} }
@ -352,6 +391,9 @@ public class ImageUploaderGetID: ObservableObject {
} }
<<<<<<< HEAD <<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 5edee64 (feat: )
guard let httpResponse = response as? HTTPURLResponse else { guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(UploadError.invalidResponse)) completion(.failure(UploadError.invalidResponse))
return return
@ -359,11 +401,14 @@ public class ImageUploaderGetID: ObservableObject {
guard httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 else { guard httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 else {
let statusCode = httpResponse.statusCode let statusCode = httpResponse.statusCode
<<<<<<< HEAD
======= =======
guard let httpResponse = response as? HTTPURLResponse, guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else { (200...299).contains(httpResponse.statusCode) else {
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
=======
>>>>>>> 5edee64 (feat: )
completion(.failure(UploadError.serverError("上传失败,状态码: \(statusCode)"))) completion(.failure(UploadError.serverError("上传失败,状态码: \(statusCode)")))
return return
} }
@ -384,11 +429,15 @@ public class ImageUploaderGetID: ObservableObject {
task?.progress.cancel() task?.progress.cancel()
} }
} else { } else {
<<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD
// iOS 11 使 // iOS 11 使
======= =======
// Fallback for earlier iOS versions // Fallback for earlier iOS versions
>>>>>>> 8e641fd (feat: ) >>>>>>> 8e641fd (feat: )
=======
// iOS 11 使
>>>>>>> 5edee64 (feat: )
var lastProgress: Double = 0 var lastProgress: Double = 0
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
let bytesSent = task.countOfBytesSent let bytesSent = task.countOfBytesSent