- 新增 FileUploadService:获取预签名URL → PUT到COS → confirm → 拿下载链接 - 新增 FileUploadUrlRequest/Response 等文件上传相关模型 - EditProfilePage 新增头像区域: - PhotosPicker 选择照片 - 正方形裁剪 + 缩放到 256x256 - 上传中显示 ProgressView - 上传完成后自动更新 profile avatarUrl - ProfileView 支持显示真实头像图片(AsyncImage) - 保存时携带 avatarUrl 不再写死 nil Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
63 lines
2.0 KiB
Swift
63 lines
2.0 KiB
Swift
import Foundation
|
||
import UIKit
|
||
|
||
@MainActor
|
||
class FileUploadService {
|
||
static let shared = FileUploadService()
|
||
private let client = APIClient.shared
|
||
|
||
/// 上传图片到 COS 并返回文件 ID
|
||
func uploadImage(_ image: UIImage, filename: String = "avatar.jpg") async throws -> String {
|
||
// 压缩为 JPEG,最大 512KB
|
||
guard let imageData = image.jpegData(compressionQuality: 0.7) else {
|
||
throw FileUploadError.compressFailed
|
||
}
|
||
|
||
// 请求预签名上传 URL
|
||
let urlReq = FileUploadUrlRequest(
|
||
filename: filename,
|
||
mimeType: "image/jpeg",
|
||
sizeBytes: imageData.count
|
||
)
|
||
let urlResp: FileUploadUrlResponse = try await client.request(
|
||
"/files/upload-url", method: "POST", body: urlReq
|
||
)
|
||
|
||
// 直接 PUT 到 COS
|
||
var request = URLRequest(url: URL(string: urlResp.uploadUrl)!)
|
||
request.httpMethod = "PUT"
|
||
request.setValue("image/jpeg", forHTTPHeaderField: "Content-Type")
|
||
request.httpBody = imageData
|
||
let (_, response) = try await URLSession.shared.data(for: request)
|
||
guard let httpResp = response as? HTTPURLResponse, httpResp.statusCode == 200 else {
|
||
throw FileUploadError.uploadFailed
|
||
}
|
||
|
||
// 确认上传
|
||
let confirmResp: FileConfirmUploadResponse = try await client.request(
|
||
"/files/confirm-upload", method: "POST",
|
||
body: FileConfirmUploadRequest(objectKey: urlResp.objectKey, checksum: nil)
|
||
)
|
||
|
||
return confirmResp.id
|
||
}
|
||
|
||
/// 获取文件下载 URL
|
||
func getDownloadUrl(fileId: String) async throws -> String {
|
||
let detail: FileDetailResponse = try await client.request("/files/\(fileId)")
|
||
return detail.downloadUrl
|
||
}
|
||
}
|
||
|
||
enum FileUploadError: LocalizedError {
|
||
case compressFailed
|
||
case uploadFailed
|
||
|
||
var errorDescription: String? {
|
||
switch self {
|
||
case .compressFailed: return "图片压缩失败"
|
||
case .uploadFailed: return "上传失败,请重试"
|
||
}
|
||
}
|
||
}
|