Compare commits

...

26 Commits

Author SHA1 Message Date
jinyaqiu
396e1bdc63 Merge branch 'upload' of https://git.fairclip.cn/FairClip/wake-ios into upload 2025-08-21 19:44:39 +08:00
jinyaqiu
af88b08728 feat: 键盘动画 2025-08-21 19:40:29 +08:00
jinyaqiu
599ea6eae7 feat: userrname 2025-08-21 19:40:25 +08:00
jinyaqiu
44de40cf83 feat: 头像选择上传 2025-08-21 19:40:20 +08:00
jinyaqiu
5df804d115 feat: 登录页面 2025-08-21 19:40:15 +08:00
jinyaqiu
d27f665009 feat: 单选多选 2025-08-21 19:40:11 +08:00
jinyaqiu
06f7c1e367 feat: 限制选择类型 2025-08-21 19:40:07 +08:00
jinyaqiu
7e807f6a26 feat: 限制选择数量 2025-08-21 19:40:03 +08:00
jinyaqiu
4670b27942 feat: 项目文件整合 2025-08-21 19:39:58 +08:00
jinyaqiu
1a2c1bf959 feat: 上传组件 2025-08-21 19:39:53 +08:00
jinyaqiu
093f9048f9 feat: 日志 2025-08-21 19:39:49 +08:00
jinyaqiu
9e9463571b feat: 视频上传 2025-08-21 19:39:44 +08:00
jinyaqiu
95f4d7c52e feat: 上传图片 2025-08-21 19:39:36 +08:00
jinyaqiu
1e6305ec35 feat: 媒体上传 2025-08-21 19:39:30 +08:00
jinyaqiu
1fca9f413c feat: 多选图片 2025-08-21 19:39:24 +08:00
jinyaqiu
89112e36e6 feat: 多张图片上传 2025-08-21 19:39:19 +08:00
jinyaqiu
4b11885d21 feat: 上传素材 2025-08-21 19:39:15 +08:00
jinyaqiu
6a05bd0dc2 feat: 登录 2025-08-21 19:39:09 +08:00
jinyaqiu
fdd2715ec8 feat: token 2025-08-21 19:39:05 +08:00
jinyaqiu
be25d07d83 feat: 登录接口联调 2025-08-21 19:38:16 +08:00
jinyaqiu
a1f26f13bf feat: 文件上传成功 2025-08-21 19:38:06 +08:00
jinyaqiu
a63d363001 feat: 上传进度 2025-08-21 19:37:57 +08:00
jinyaqiu
d0f0b09f8a feat: 素材上传成 2025-08-21 19:37:37 +08:00
jinyaqiu
13f458fff6 feat: 确认上传 2025-08-21 19:37:26 +08:00
jinyaqiu
53aa3f04cc feat: 图片上传互获取url 2025-08-21 19:37:12 +08:00
jinyaqiu
d15acc9038 feat: 添加中文注释 2025-08-21 19:37:03 +08:00
4 changed files with 724 additions and 11 deletions

View File

@ -3,6 +3,8 @@ import Foundation
/// API
public enum APIConfig {
/// API URL
<<<<<<< HEAD
<<<<<<< HEAD
public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1"
/// token
@ -14,6 +16,26 @@ public enum APIConfig {
return token
}
=======
public static let baseURL = "https://api.memorywake.com/api/v1"
/// token - Keychain
public static let authToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJqdGkiOjczNjM0ODY2MTE1MDc2NDY0NjQsImlkZW50aXR5IjoiNzM1MDQzOTY2MzExNjYxOTg4OCIsImV4cCI6MTc1NjE5NjgxNX0.hRC_So6LHuR6Gx-bDyO8aliVOd-Xumul8M7cydi2pTxHPweBx4421AfZ5BjGoEEwRZPIXJ5z7a1aDB7qvjpLCA"
>>>>>>> a207b78 (feat: )
=======
public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1"
/// token
public static var authToken: String {
let token = KeychainHelper.getAccessToken() ?? ""
if token.isEmpty {
print("⚠️ [APIConfig] 未找到访问令牌")
}
return token
}
>>>>>>> 1814789 (feat: )
///
public static var authHeaders: [String: String] {
let token = authToken
@ -28,4 +50,12 @@ public enum APIConfig {
return headers
}
}
<<<<<<< HEAD
<<<<<<< HEAD
}
=======
}
>>>>>>> a207b78 (feat: )
=======
}
>>>>>>> 1814789 (feat: )

View File

@ -1,11 +1,270 @@
import SwiftUI
import PhotosUI
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
// MARK: - Photo Picker
=======
///
///
class ImageUploader: ObservableObject {
private let baseURL = "https://api.memorywake.com/api/v1/file/generate-upload-url"
func uploadImage(_ image: UIImage, completion: @escaping (Result<String, Error>) -> Void) {
print("🔄 开始准备上传图片...")
// 1. Data
guard let imageData = image.jpegData(compressionQuality: 0.7) else {
let error = NSError(domain: "ImageError", code: -1, userInfo: [NSLocalizedDescriptionKey: "图片数据转换失败"])
print("❌ 错误:\(error.localizedDescription)")
completion(.failure(error))
return
}
// 2. URL
guard let url = URL(string: baseURL) else {
let error = NSError(domain: "URLError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的URL"])
print("❌ 错误:\(error.localizedDescription)")
completion(.failure(error))
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJqdGkiOjczNjM0ODY2MTE1MDc2NDY0NjQsImlkZW50aXR5IjoiNzM1MDQzOTY2MzExNjYxOTg4OCIsImV4cCI6MTc1NjE5NjgxNX0.hRC_So6LHuR6Gx-bDyO8aliVOd-Xumul8M7cydi2pTxHPweBx4421AfZ5BjGoEEwRZPIXJ5z7a1aDB7qvjpLCA", forHTTPHeaderField: "Authorization")
// 3.
let fileName = "avatar_\(UUID().uuidString).jpg"
let parameters: [String: Any] = [
"filename": fileName,
"content_type": "image/jpeg",
"file_size": imageData.count
]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
print("📤 准备上传请求,文件名: \(fileName), 大小: \(Double(imageData.count) / 1024.0) KB")
print("📡 请求参数: \(parameters)")
} catch {
print("❌ 序列化请求参数失败: \(error.localizedDescription)")
completion(.failure(error))
return
}
// 4.
print("🌐 请求URL: \(url.absoluteString)")
print("📋 请求头: \(request.allHTTPHeaderFields ?? [:])")
// 5. URL
print("🌐 正在获取上传链接...")
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
// 线
DispatchQueue.main.async {
if let error = error {
print("❌ 请求失败: \(error.localizedDescription)")
completion(.failure(error))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
let error = NSError(domain: "NetworkError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的服务器响应"])
print("❌ 错误:\(error.localizedDescription)")
completion(.failure(error))
return
}
print("📊 响应状态码: \(httpResponse.statusCode)")
print("📦 响应头: \(httpResponse.allHeaderFields)")
guard let data = data else {
let error = NSError(domain: "NetworkError", code: -1, userInfo: [NSLocalizedDescriptionKey: "没有接收到数据"])
print("❌ 错误:\(error.localizedDescription)")
completion(.failure(error))
return
}
//
if let responseString = String(data: data, encoding: .utf8) {
print("📡 原始响应数据: \(responseString)")
}
// 5. URL
do {
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw NSError(domain: "ParseError", code: -1, userInfo: [NSLocalizedDescriptionKey: "响应数据格式错误"])
}
print("📋 解析后的JSON: \(json)")
// API
if let uploadUrlString = json["url"] as? String,
let uploadUrl = URL(string: uploadUrlString) {
print("✅ 成功获取上传链接: \(uploadUrlString)")
self?.uploadImageData(imageData, to: uploadUrl, fileName: fileName, completion: completion)
} else {
throw NSError(domain: "APIError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取上传链接: \(json)"])
}
} catch {
print("❌ 解析响应数据失败: \(error.localizedDescription)")
completion(.failure(error))
}
}
}
task.resume()
}
private func uploadImageData(_ data: Data, to url: URL, fileName: String, completion: @escaping (Result<String, Error>) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.setValue("image/jpeg", forHTTPHeaderField: "Content-Type")
request.httpBody = data
print("🚀 开始上传图片数据...")
let task = URLSession.shared.dataTask(with: request) { _, response, error in
DispatchQueue.main.async {
if let error = error {
print("❌ 上传失败: \(error.localizedDescription)")
completion(.failure(error))
return
}
//
if let httpResponse = response as? HTTPURLResponse {
print("📊 上传响应状态码: \(httpResponse.statusCode)")
if (200...299).contains(httpResponse.statusCode) {
let fileUrl = "https://your-cdn-domain.com/\(fileName)" // CDN
print("✅ 上传成功文件URL: \(fileUrl)")
completion(.success(fileUrl))
} else {
let errorMessage = "上传失败,状态码: \(httpResponse.statusCode)"
print("\(errorMessage)")
let error = NSError(domain: "UploadError",
code: httpResponse.statusCode,
userInfo: [NSLocalizedDescriptionKey: errorMessage])
completion(.failure(error))
}
} else {
let errorMessage = "无效的服务器响应"
print("\(errorMessage)")
let error = NSError(domain: "UploadError",
code: -1,
userInfo: [NSLocalizedDescriptionKey: errorMessage])
completion(.failure(error))
}
}
}
task.resume()
}
}
>>>>>>> a4890a4 (feat: url)
=======
>>>>>>> a207b78 (feat: )
=======
///
struct UploadResults {
let original: ImageUploaderGetID.UploadResult
let compressed: ImageUploaderGetID.UploadResult
}
>>>>>>> 5611df8 (feat: )
=======
// MARK: - Data Models
///
public struct UploadProgress {
public let current: Int
public let total: Int
public let progress: Double
public let isOriginal: Bool
public init(current: Int, total: Int, progress: Double, isOriginal: Bool) {
self.current = current
self.total = total
self.progress = progress
self.isOriginal = isOriginal
}
}
///
public struct UploadResults {
public let original: ImageUploaderGetID.UploadResult
public let compressed: ImageUploaderGetID.UploadResult
public init(original: ImageUploaderGetID.UploadResult,
compressed: ImageUploaderGetID.UploadResult) {
self.original = original
self.compressed = compressed
}
}
=======
>>>>>>> 5edee64 (feat: )
// MARK: - Photo Picker
>>>>>>> 8e641fd (feat: )
///
struct PhotoPicker: UIViewControllerRepresentable {
<<<<<<< HEAD
=======
// MARK: - Properties
<<<<<<< HEAD
<<<<<<< HEAD
///
>>>>>>> a207b78 (feat: )
=======
>>>>>>> 8e641fd (feat: )
@Binding var selectedImages: [UIImage]
let selectionLimit: Int
let filter: PHPickerFilter
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
///
private let uploader = ImageUploader()
=======
///
<<<<<<< HEAD
var onImageUploaded: ((Result<ImageUploaderGetID.UploadResult, Error>) -> Void)?
>>>>>>> a207b78 (feat: )
=======
var onImageUploaded: ((Result<UploadResults, Error>) -> Void)?
>>>>>>> 5611df8 (feat: )
// MARK: - Initialization
///
<<<<<<< HEAD
>>>>>>> a4890a4 (feat: url)
init(selectedImages: Binding<[UIImage]>, selectionLimit: Int = 1, filter: PHPickerFilter = .images) {
=======
/// - Parameters:
/// - selectedImages:
/// - selectionLimit: 1
/// - filter:
/// - onImageUploaded:
init(
selectedImages: Binding<[UIImage]>,
selectionLimit: Int = 1,
filter: PHPickerFilter = .images,
onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil
) {
>>>>>>> a207b78 (feat: )
=======
var onImageUploaded: ((Result<UploadResults, Error>) -> Void)?
var onUploadProgress: ((UploadProgress) -> Void)?
=======
@Binding var selectedImages: [UIImage]
let selectionLimit: Int
@ -13,23 +272,49 @@ struct PhotoPicker: UIViewControllerRepresentable {
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
self.onImageUploaded = onImageUploaded
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 8e641fd (feat: )
self.onUploadProgress = onUploadProgress
}
// MARK: - UIViewControllerRepresentable
<<<<<<< HEAD
<<<<<<< HEAD
// MARK: - UIViewControllerRepresentable
=======
}
// MARK: - UIViewControllerRepresentable
>>>>>>> a207b78 (feat: )
/// PHPickerViewController
=======
>>>>>>> 8e641fd (feat: )
=======
>>>>>>> 5edee64 (feat: )
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = filter
@ -48,10 +333,32 @@ struct PhotoPicker: UIViewControllerRepresentable {
}
// MARK: - Coordinator
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
// MARK: -
=======
>>>>>>> a207b78 (feat: )
/// PHPickerViewController
=======
>>>>>>> 8e641fd (feat: )
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: PhotoPicker
<<<<<<< HEAD
private let uploadService = ImageUploadService.shared
///
=======
>>>>>>> 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
@ -65,14 +372,47 @@ struct PhotoPicker: UIViewControllerRepresentable {
parent.selectedImages.removeAll()
let group = DispatchGroup()
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
var loadedImages: [Int: UIImage] = [:]
var uploadResults: [Int: ImageUploadService.UploadResults] = [:]
var lastError: Error?
=======
var loadedImages: [Int: UIImage] = [:] //
<<<<<<< HEAD
>>>>>>> a207b78 (feat: )
=======
var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?,
compressed: ImageUploaderGetID.UploadResult?)] = [:]
>>>>>>> 5611df8 (feat: )
=======
var loadedImages: [Int: UIImage] = [:]
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 {
<<<<<<< HEAD
=======
//
>>>>>>> a207b78 (feat: )
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
@ -82,14 +422,68 @@ struct PhotoPicker: UIViewControllerRepresentable {
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
}
<<<<<<< HEAD
// 3.
self.uploader.uploadImage(image) { [weak self] originalResult in
guard let self = self else {
group.leave()
return
}
>>>>>>> 5611df8 (feat: )
switch originalResult {
case .success(let originalUploadResult):
// 4.
self.uploader.uploadImage(compressedImage) { 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
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)
}
=======
self.uploader.uploadImage(
=======
// Upload the image
self.uploadService.uploadOriginalAndCompressedImage(
>>>>>>> 5edee64 (feat: )
image,
compressionQuality: 0.5,
progress: { progress in
@ -97,8 +491,17 @@ struct PhotoPicker: UIViewControllerRepresentable {
self.parent.onUploadProgress?(progress)
}
},
<<<<<<< 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 result {
case .success(let results):
@ -161,6 +564,32 @@ struct PhotoPicker: UIViewControllerRepresentable {
}
}
<<<<<<< HEAD
<<<<<<< HEAD
//
group.notify(queue: .main) {
<<<<<<< HEAD
<<<<<<< HEAD
// Sort the images by their original index to maintain selection order
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
self.parent.selectedImages.append(contentsOf: sortedImages)
// Dismiss the picker
=======
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
self.parent.selectedImages.append(contentsOf: sortedImages)
>>>>>>> a4890a4 (feat: url)
=======
//
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
self.parent.selectedImages.append(contentsOf: sortedImages)
//
>>>>>>> a207b78 (feat: )
=======
//
=======
>>>>>>> 8e641fd (feat: )
group.notify(queue: .main) { [weak self] in
guard let self = self else { return }
@ -181,25 +610,59 @@ struct PhotoPicker: UIViewControllerRepresentable {
}
}
<<<<<<< HEAD
<<<<<<< HEAD
// 5.
>>>>>>> 5611df8 (feat: )
=======
>>>>>>> 8e641fd (feat: )
picker.dismiss(animated: true)
=======
self.parent.presentationMode.wrappedValue.dismiss()
>>>>>>> 5edee64 (feat: )
}
}
}
}
<<<<<<< HEAD
<<<<<<< HEAD
// MARK: - Avatar Uploader Component
=======
// MARK: - AvatarUploader
///
///
>>>>>>> a207b78 (feat: )
=======
// MARK: - Avatar Uploader
///
>>>>>>> 8e641fd (feat: )
struct AvatarUploader: View {
@Binding var selectedImage: UIImage?
let size: CGFloat
<<<<<<< HEAD
<<<<<<< HEAD
var onUploadComplete: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
///
=======
>>>>>>> 8e641fd (feat: )
var onUploadComplete: ((Result<UploadResults, Error>) -> Void)?
=======
var onUploadComplete: ((Result<ImageUploadService.UploadResults, Error>) -> Void)?
>>>>>>> 5edee64 (feat: )
@State private var isImagePickerPresented = false
var body: some View {
Button(action: { isImagePickerPresented = true }) {
ZStack {
<<<<<<< HEAD
// Avatar Image or Placeholder
=======
>>>>>>> a4890a4 (feat: url)
if let selectedImage = selectedImage {
Image(uiImage: selectedImage)
.resizable()
@ -207,6 +670,17 @@ struct AvatarUploader: View {
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: size * 0.1))
} else {
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
// Default avatar container
=======
>>>>>>> a4890a4 (feat: url)
=======
//
>>>>>>> a207b78 (feat: )
=======
>>>>>>> 8e641fd (feat: )
Color.gray.opacity(0.1)
.frame(width: size, height: size)
.overlay(
@ -221,9 +695,27 @@ struct AvatarUploader: View {
}
}
.frame(width: size, height: size)
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
.contentShape(Rectangle()) // Make the entire area tappable
}
.buttonStyle(PlainButtonStyle()) // Remove button highlight effect
=======
.contentShape(Rectangle())
}
.buttonStyle(PlainButtonStyle())
>>>>>>> a4890a4 (feat: url)
=======
.contentShape(Rectangle()) //
}
.buttonStyle(PlainButtonStyle()) // 使
>>>>>>> a207b78 (feat: )
=======
.contentShape(Rectangle())
}
.buttonStyle(PlainButtonStyle())
>>>>>>> 8e641fd (feat: )
.sheet(isPresented: $isImagePickerPresented) {
PhotoPicker(
selectedImages: Binding(

View File

@ -7,7 +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
@ -29,7 +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 {
@ -45,8 +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: )
}
}
}
@ -72,6 +95,10 @@ public class ImageUploaderGetID: ObservableObject {
///
/// - Parameters:
/// - image:
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 8e641fd (feat: )
/// - progress: (0.0 1.0)
/// - completion:
public func uploadImage(
@ -79,6 +106,13 @@ public class ImageUploaderGetID: ObservableObject {
progress: @escaping (Double) -> Void,
completion: @escaping (Result<UploadResult, Error>) -> Void
) {
<<<<<<< HEAD
=======
/// - completion: Result
public func uploadImage(_ image: UIImage, completion: @escaping (Result<UploadResult, Error>) -> Void) {
>>>>>>> a207b78 (feat: )
=======
>>>>>>> 8e641fd (feat: )
print("🔄 开始准备上传图片...")
// 1. Data
@ -93,6 +127,10 @@ public class ImageUploaderGetID: ObservableObject {
getUploadURL(for: imageData) { [weak self] result in
switch result {
case .success((let fileId, let uploadURL)):
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 8e641fd (feat: )
print("📤 获取到上传URL开始上传文件...")
// 3.
@ -122,8 +160,20 @@ public class ImageUploaderGetID: ObservableObject {
}
)
<<<<<<< HEAD
case .failure(let error):
print("❌ 获取上传URL失败: \(error.localizedDescription)")
=======
// 3.
self?.confirmUpload(fileId: fileId, fileName: "avatar_\(UUID().uuidString).jpg", fileSize: imageData.count) { confirmResult in
completion(confirmResult)
}
case .failure(let error):
>>>>>>> a207b78 (feat: )
=======
case .failure(let error):
print("❌ 获取上传URL失败: \(error.localizedDescription)")
>>>>>>> 8e641fd (feat: )
completion(.failure(error))
}
}
@ -291,6 +341,8 @@ public class ImageUploaderGetID: ObservableObject {
let urlString = "\(apiConfig.baseURL)/file/generate-upload-url"
guard let url = URL(string: urlString) else {
<<<<<<< HEAD
=======
completion(.failure(UploadError.invalidURL))
return
}
@ -352,6 +404,7 @@ public class ImageUploaderGetID: ObservableObject {
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 {
>>>>>>> 58f560c7142446b532224203135dad4d7827da5e
completion(.failure(UploadError.invalidURL))
return
}
@ -360,28 +413,129 @@ public class ImageUploaderGetID: ObservableObject {
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)")
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
print("📤 准备上传请求,文件名: \(fileName), 大小: \(Double(fileData.count) / 1024.0) KB")
} catch {
print("❌ 序列化确认上传参数失败: \(error.localizedDescription)")
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 {
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) {
<<<<<<< HEAD
<<<<<<< HEAD
let endpoint = "\(apiConfig.baseURL)/file/confirm-upload"
guard let url = URL(string: endpoint) else {
=======
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
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
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)
print("📤 确认上传请求fileId: \(fileId), 文件名: \(fileName)")
} catch {
print("❌ 序列化确认上传参数失败: \(error.localizedDescription)")
=======
let requestBody: [String: Any] = ["file_id": fileId]
=======
>>>>>>> 5edee64 (feat: )
do {
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))
@ -393,6 +547,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
}
@ -410,6 +572,10 @@ public class ImageUploaderGetID: ObservableObject {
task.resume()
}
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 8e641fd (feat: )
/// URL
/// - Parameters:
@ -438,6 +604,10 @@ public class ImageUploaderGetID: ObservableObject {
return
}
<<<<<<< HEAD
<<<<<<< HEAD
=======
>>>>>>> 5edee64 (feat: )
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(UploadError.invalidResponse))
return
@ -445,6 +615,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
}
@ -465,7 +643,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
@ -590,6 +776,11 @@ private extension URLSessionTask {
}
}
}
<<<<<<< HEAD
=======
>>>>>>> a207b78 (feat: )
=======
>>>>>>> 8e641fd (feat: )
}
// MARK: -