feat: 暂提
This commit is contained in:
parent
e2c4f5d882
commit
03d8288833
@ -18,7 +18,84 @@ private struct RequestIdentifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NetworkError: Error {
|
public protocol NetworkServiceProtocol {
|
||||||
|
func postWithToken<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any],
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
)
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func upload(
|
||||||
|
request: URLRequest,
|
||||||
|
fileData: Data,
|
||||||
|
onProgress: @escaping (Double) -> Void,
|
||||||
|
completion: @escaping (Result<(Data?, URLResponse), Error>) -> Void
|
||||||
|
) -> URLSessionUploadTask?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NetworkService: NetworkServiceProtocol {
|
||||||
|
public func postWithToken<T: Decodable>(
|
||||||
|
path: String,
|
||||||
|
parameters: [String: Any],
|
||||||
|
completion: @escaping (Result<T, NetworkError>) -> Void
|
||||||
|
) {
|
||||||
|
var headers = [String: String]()
|
||||||
|
if let token = KeychainHelper.getAccessToken() {
|
||||||
|
headers["Authorization"] = "Bearer \(token)"
|
||||||
|
}
|
||||||
|
|
||||||
|
post(path: path, parameters: parameters, headers: headers, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public func upload(
|
||||||
|
request: URLRequest,
|
||||||
|
fileData: Data,
|
||||||
|
onProgress: @escaping (Double) -> Void,
|
||||||
|
completion: @escaping (Result<(Data?, URLResponse), Error>) -> Void
|
||||||
|
) -> URLSessionUploadTask? {
|
||||||
|
var request = request
|
||||||
|
|
||||||
|
// Set content length header if not already set
|
||||||
|
if request.value(forHTTPHeaderField: "Content-Length") == nil {
|
||||||
|
request.setValue("\(fileData.count)", forHTTPHeaderField: "Content-Length")
|
||||||
|
}
|
||||||
|
|
||||||
|
var progressObserver: NSKeyValueObservation?
|
||||||
|
|
||||||
|
let task = URLSession.shared.uploadTask(with: request, from: fileData) { [weak self] data, response, error in
|
||||||
|
// Invalidate the progress observer when the task completes
|
||||||
|
progressObserver?.invalidate()
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let response = response else {
|
||||||
|
completion(.failure(NetworkError.invalidURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
completion(.success((data, response)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add progress tracking if available
|
||||||
|
if #available(iOS 11.0, *) {
|
||||||
|
progressObserver = task.progress.observe(\.fractionCompleted) { progressValue, _ in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
onProgress(progressValue.fractionCompleted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NetworkError: Error {
|
||||||
case invalidURL
|
case invalidURL
|
||||||
case noData
|
case noData
|
||||||
case decodingError(Error)
|
case decodingError(Error)
|
||||||
@ -28,14 +105,14 @@ enum NetworkError: Error {
|
|||||||
case networkError(Error)
|
case networkError(Error)
|
||||||
case unknownError(Error)
|
case unknownError(Error)
|
||||||
|
|
||||||
var localizedDescription: String {
|
public var localizedDescription: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .invalidURL:
|
case .invalidURL:
|
||||||
return "无效的URL"
|
return "无效的URL"
|
||||||
case .noData:
|
case .noData:
|
||||||
return "没有收到数据"
|
return "没有接收到数据"
|
||||||
case .decodingError(let error):
|
case .decodingError(let error):
|
||||||
return "数据解析错误: \(error.localizedDescription)"
|
return "解码错误: \(error.localizedDescription)"
|
||||||
case .serverError(let message):
|
case .serverError(let message):
|
||||||
return "服务器错误: \(message)"
|
return "服务器错误: \(message)"
|
||||||
case .unauthorized:
|
case .unauthorized:
|
||||||
@ -43,7 +120,7 @@ enum NetworkError: Error {
|
|||||||
case .other(let error):
|
case .other(let error):
|
||||||
return error.localizedDescription
|
return error.localizedDescription
|
||||||
case .networkError(let error):
|
case .networkError(let error):
|
||||||
return "网络请求错误: \(error.localizedDescription)"
|
return "网络错误: \(error.localizedDescription)"
|
||||||
case .unknownError(let error):
|
case .unknownError(let error):
|
||||||
return "未知错误: \(error.localizedDescription)"
|
return "未知错误: \(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ public class ImageUploadService {
|
|||||||
public func uploadImage(
|
public func uploadImage(
|
||||||
_ image: UIImage,
|
_ image: UIImage,
|
||||||
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
|
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
|
||||||
completion: @escaping (Result<ImageUploaderGetID.UploadResult, Error>) -> Void
|
completion: @escaping (Result<ImageUploaderGetID.UploadResult, ImageUploaderGetID.UploadError>) -> Void
|
||||||
) {
|
) {
|
||||||
uploader.uploadImage(
|
uploader.uploadImage(
|
||||||
image,
|
image,
|
||||||
@ -61,10 +61,10 @@ public class ImageUploadService {
|
|||||||
_ image: UIImage,
|
_ image: UIImage,
|
||||||
compressionQuality: CGFloat = 0.5,
|
compressionQuality: CGFloat = 0.5,
|
||||||
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
|
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
|
||||||
completion: @escaping (Result<ImageUploaderGetID.UploadResult, Error>) -> Void
|
completion: @escaping (Result<ImageUploaderGetID.UploadResult, ImageUploaderGetID.UploadError>) -> Void
|
||||||
) {
|
) {
|
||||||
guard let compressedImage = image.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else {
|
guard let compressedImage = image.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else {
|
||||||
completion(.failure(NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "图片压缩失败"])))
|
completion(.failure(ImageUploaderGetID.UploadError.invalidImageData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ public class ImageUploadService {
|
|||||||
_ image: UIImage,
|
_ image: UIImage,
|
||||||
compressionQuality: CGFloat = 0.5,
|
compressionQuality: CGFloat = 0.5,
|
||||||
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
|
progress progressHandler: @escaping (ImageUploadService.UploadProgress) -> Void,
|
||||||
completion: @escaping (Result<ImageUploadService.UploadResults, Error>) -> Void
|
completion: @escaping (Result<ImageUploadService.UploadResults, ImageUploaderGetID.UploadError>) -> Void
|
||||||
) {
|
) {
|
||||||
// 上传原图
|
// 上传原图
|
||||||
uploadImage(image, progress: { progress in
|
uploadImage(image, progress: { progress in
|
||||||
@ -145,7 +145,7 @@ public class ImageUploadService {
|
|||||||
public func uploadMedia(
|
public func uploadMedia(
|
||||||
_ media: MediaType,
|
_ media: MediaType,
|
||||||
progress: @escaping (UploadProgress) -> Void,
|
progress: @escaping (UploadProgress) -> Void,
|
||||||
completion: @escaping (Result<MediaUploadResult, Error>) -> Void
|
completion: @escaping (Result<MediaUploadResult, ImageUploaderGetID.UploadError>) -> Void
|
||||||
) {
|
) {
|
||||||
switch media {
|
switch media {
|
||||||
case .image(let image):
|
case .image(let image):
|
||||||
@ -217,14 +217,14 @@ public class ImageUploadService {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let error = NSError(domain: "ImageUploadService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to compress thumbnail"])
|
let error = ImageUploaderGetID.UploadError.invalidImageData
|
||||||
print("❌ 视频缩略图压缩失败")
|
print("❌ 视频缩略图压缩失败")
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print("❌ 视频缩略图提取失败: \(error.localizedDescription)")
|
print("❌ 视频缩略图提取失败: \(error.localizedDescription)")
|
||||||
completion(.failure(error))
|
completion(.failure(ImageUploaderGetID.UploadError.uploadFailed(error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,13 +243,13 @@ public class ImageUploadService {
|
|||||||
existingThumbnail: UIImage?,
|
existingThumbnail: UIImage?,
|
||||||
compressionQuality: CGFloat,
|
compressionQuality: CGFloat,
|
||||||
progress progressHandler: @escaping (UploadProgress) -> Void,
|
progress progressHandler: @escaping (UploadProgress) -> Void,
|
||||||
completion: @escaping (Result<MediaUploadResult, Error>) -> Void
|
completion: @escaping (Result<MediaUploadResult, ImageUploaderGetID.UploadError>) -> Void
|
||||||
) {
|
) {
|
||||||
// 1. 提取视频缩略图
|
// 1. 提取视频缩略图
|
||||||
func processThumbnail(_ thumbnail: UIImage) {
|
func processThumbnail(_ thumbnail: UIImage) {
|
||||||
// 2. 压缩缩略图
|
// 2. 压缩缩略图
|
||||||
guard let compressedThumbnail = thumbnail.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else {
|
guard let compressedThumbnail = thumbnail.jpegData(compressionQuality: compressionQuality).flatMap(UIImage.init(data:)) else {
|
||||||
let error = NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "缩略图压缩失败"])
|
let error = ImageUploaderGetID.UploadError.invalidImageData
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -325,7 +325,7 @@ public class ImageUploadService {
|
|||||||
case .success(let thumbnail):
|
case .success(let thumbnail):
|
||||||
processThumbnail(thumbnail)
|
processThumbnail(thumbnail)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(.failure(error))
|
completion(.failure(ImageUploaderGetID.UploadError.uploadFailed(error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,55 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
|
import CommonCrypto
|
||||||
|
|
||||||
/// 处理图片上传到远程服务器的类
|
/// 处理图片上传到远程服务器的类
|
||||||
/// 支持上传图片并获取服务器返回的file_id
|
/// 支持上传图片并获取服务器返回的file_id
|
||||||
public class ImageUploaderGetID: ObservableObject {
|
public class ImageUploaderGetID: ObservableObject {
|
||||||
// MARK: - 类型定义
|
// MARK: - 类型定义
|
||||||
|
private let session: URLSession
|
||||||
|
private let networkService: NetworkServiceProtocol
|
||||||
|
private let networkHandler: (_ path: String, _ parameters: [String: Any], _ completion: @escaping (Result<Data, UploadError>) -> Void) -> Void
|
||||||
|
|
||||||
|
public init(session: URLSession = .shared, networkService: NetworkServiceProtocol? = nil) {
|
||||||
|
self.session = session
|
||||||
|
let service = networkService ?? NetworkService.shared
|
||||||
|
self.networkService = service
|
||||||
|
self.networkHandler = { path, parameters, completion in
|
||||||
|
service.postWithToken(
|
||||||
|
path: path,
|
||||||
|
parameters: parameters,
|
||||||
|
completion: { (result: Result<UploadURLResponse, NetworkError>) in
|
||||||
|
switch result {
|
||||||
|
case .success(let response):
|
||||||
|
// Convert the response back to Data for the completion handler
|
||||||
|
do {
|
||||||
|
let data = try JSONEncoder().encode(response)
|
||||||
|
completion(.success(data))
|
||||||
|
} catch {
|
||||||
|
completion(.failure(.invalidResponseData))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
completion(.failure(.networkError(error)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 测试专用的初始化方法
|
||||||
|
/// - Parameters:
|
||||||
|
/// - session: 用于测试的URLSession
|
||||||
|
/// - networkService: 用于测试的NetworkService
|
||||||
|
/// - networkHandler: 用于测试的网络请求处理器
|
||||||
|
internal init(
|
||||||
|
session: URLSession,
|
||||||
|
networkService: NetworkServiceProtocol? = nil,
|
||||||
|
networkHandler: @escaping (String, [String: Any], @escaping (Result<Data, UploadError>) -> Void) -> Void
|
||||||
|
) {
|
||||||
|
self.session = session
|
||||||
|
self.networkService = networkService ?? NetworkService.shared
|
||||||
|
self.networkHandler = networkHandler
|
||||||
|
}
|
||||||
|
|
||||||
/// 上传结果
|
/// 上传结果
|
||||||
public struct UploadResult: Codable {
|
public struct UploadResult: Codable {
|
||||||
@ -30,6 +75,9 @@ public class ImageUploaderGetID: ObservableObject {
|
|||||||
case uploadFailed(Error?)
|
case uploadFailed(Error?)
|
||||||
case invalidFileId
|
case invalidFileId
|
||||||
case invalidResponseData
|
case invalidResponseData
|
||||||
|
case networkError(Error)
|
||||||
|
case decodingError(Error)
|
||||||
|
case unauthorized
|
||||||
|
|
||||||
public var errorDescription: String? {
|
public var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
@ -47,41 +95,26 @@ public class ImageUploaderGetID: ObservableObject {
|
|||||||
return "无效的文件ID"
|
return "无效的文件ID"
|
||||||
case .invalidResponseData:
|
case .invalidResponseData:
|
||||||
return "无效的响应数据"
|
return "无效的响应数据"
|
||||||
|
case .networkError(let error):
|
||||||
|
return "网络错误: \(error.localizedDescription)"
|
||||||
|
case .decodingError(let error):
|
||||||
|
return "解码错误: \(error.localizedDescription)"
|
||||||
|
case .unauthorized:
|
||||||
|
return "认证失败,需要重新登录"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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: - 公开方法
|
// MARK: - 公开方法
|
||||||
|
|
||||||
/// 上传图片到服务器
|
/// 上传图片到服务器
|
||||||
/// - Parameters:
|
|
||||||
/// - image: 要上传的图片
|
|
||||||
/// - progress: 上传进度回调 (0.0 到 1.0)
|
|
||||||
/// - completion: 完成回调
|
|
||||||
public func uploadImage(
|
public func uploadImage(
|
||||||
_ image: UIImage,
|
_ image: UIImage,
|
||||||
progress: @escaping (Double) -> Void,
|
progress: @escaping (Double) -> Void,
|
||||||
completion: @escaping (Result<UploadResult, Error>) -> Void
|
completion: @escaping (Result<UploadResult, UploadError>) -> Void
|
||||||
) {
|
) {
|
||||||
print("🔄 开始准备上传图片...")
|
print("🔄 开始准备上传图片...")
|
||||||
|
|
||||||
// 1. 转换图片为Data
|
|
||||||
guard let imageData = image.jpegData(compressionQuality: 0.7) else {
|
guard let imageData = image.jpegData(compressionQuality: 0.7) else {
|
||||||
let error = UploadError.invalidImageData
|
let error = UploadError.invalidImageData
|
||||||
print("❌ 错误:\(error.localizedDescription)")
|
print("❌ 错误:\(error.localizedDescription)")
|
||||||
@ -89,14 +122,21 @@ public class ImageUploaderGetID: ObservableObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 获取上传URL
|
requestUploadURL(fileName: "image_\(UUID().uuidString).jpg", fileData: imageData) { [weak self] result in
|
||||||
getUploadURL(for: imageData) { [weak self] result in
|
guard let self = self else { return }
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case .success((let fileId, let uploadURL)):
|
case .success(let response):
|
||||||
print("📤 获取到上传URL,开始上传文件...")
|
print("📤 获取到上传URL,开始上传文件...")
|
||||||
|
|
||||||
// 3. 上传文件
|
guard let uploadURL = URL(string: response.data.uploadUrl) else {
|
||||||
_ = self?.uploadFile(
|
let error = UploadError.invalidURL
|
||||||
|
print("❌ [ImageUploader] 上传URL格式无效: \(response.data.uploadUrl)")
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.uploadFile(
|
||||||
fileData: imageData,
|
fileData: imageData,
|
||||||
to: uploadURL,
|
to: uploadURL,
|
||||||
mimeType: "image/jpeg",
|
mimeType: "image/jpeg",
|
||||||
@ -104,87 +144,73 @@ public class ImageUploaderGetID: ObservableObject {
|
|||||||
print("📊 上传进度: \(Int(uploadProgress * 100))%")
|
print("📊 上传进度: \(Int(uploadProgress * 100))%")
|
||||||
progress(uploadProgress)
|
progress(uploadProgress)
|
||||||
},
|
},
|
||||||
completion: { uploadResult in
|
completion: { [weak self] uploadResult in
|
||||||
switch uploadResult {
|
switch uploadResult {
|
||||||
case .success:
|
case .success:
|
||||||
// 4. 确认上传
|
|
||||||
self?.confirmUpload(
|
self?.confirmUpload(
|
||||||
fileId: fileId,
|
fileId: response.data.fileId,
|
||||||
fileName: "avatar_\(UUID().uuidString).jpg",
|
fileName: "image_\(UUID().uuidString).jpg",
|
||||||
fileSize: imageData.count,
|
fileSize: imageData.count,
|
||||||
completion: completion
|
completion: completion
|
||||||
)
|
)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print("❌ 文件上传失败: \(error.localizedDescription)")
|
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print("❌ 获取上传URL失败: \(error.localizedDescription)")
|
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Video Upload
|
|
||||||
|
|
||||||
/// 上传视频文件到服务器
|
/// 上传视频文件到服务器
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - videoURL: 要上传的视频文件URL
|
/// - videoURL: 视频文件的本地URL
|
||||||
/// - progress: 上传进度回调 (0.0 到 1.0)
|
/// - progress: 上传进度回调 (0.0 到 1.0)
|
||||||
/// - completion: 完成回调
|
/// - completion: 完成回调,返回上传结果或错误
|
||||||
public func uploadVideo(
|
public func uploadVideo(
|
||||||
_ videoURL: URL,
|
_ videoURL: URL,
|
||||||
progress: @escaping (Double) -> Void,
|
progress: @escaping (Double) -> Void,
|
||||||
completion: @escaping (Result<UploadResult, Error>) -> Void
|
completion: @escaping (Result<UploadResult, UploadError>) -> Void
|
||||||
) {
|
) {
|
||||||
print("🔄 开始准备上传视频...")
|
print("🎥 开始准备上传视频...")
|
||||||
|
|
||||||
// 1. 读取视频文件数据
|
|
||||||
do {
|
do {
|
||||||
let videoData = try Data(contentsOf: videoURL)
|
let videoData = try Data(contentsOf: videoURL)
|
||||||
let fileExtension = videoURL.pathExtension.lowercased()
|
let fileName = "video_\(UUID().uuidString).mp4"
|
||||||
let mimeType: String
|
|
||||||
|
|
||||||
// 根据文件扩展名设置MIME类型
|
requestUploadURL(
|
||||||
switch fileExtension {
|
fileName: fileName,
|
||||||
case "mp4":
|
fileData: videoData
|
||||||
mimeType = "video/mp4"
|
|
||||||
case "mov":
|
|
||||||
mimeType = "video/quicktime"
|
|
||||||
case "m4v":
|
|
||||||
mimeType = "video/x-m4v"
|
|
||||||
case "avi":
|
|
||||||
mimeType = "video/x-msvideo"
|
|
||||||
default:
|
|
||||||
mimeType = "video/mp4" // 默认使用mp4
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 获取上传URL
|
|
||||||
getUploadURL(
|
|
||||||
for: videoData,
|
|
||||||
mimeType: mimeType,
|
|
||||||
originalFilename: videoURL.lastPathComponent
|
|
||||||
) { [weak self] result in
|
) { [weak self] result in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
case .success((let fileId, let uploadURL)):
|
case .success(let response):
|
||||||
print("📤 获取到视频上传URL,开始上传文件...")
|
print("📤 获取到视频上传URL,开始上传文件...")
|
||||||
|
|
||||||
// 3. 上传文件
|
guard let uploadURL = URL(string: response.data.uploadUrl) else {
|
||||||
_ = self?.uploadFile(
|
let error = UploadError.invalidURL
|
||||||
|
print("❌ [ImageUploader] 视频上传URL格式无效: \(response.data.uploadUrl)")
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.uploadFile(
|
||||||
fileData: videoData,
|
fileData: videoData,
|
||||||
to: uploadURL,
|
to: uploadURL,
|
||||||
mimeType: mimeType,
|
mimeType: "video/mp4",
|
||||||
onProgress: progress,
|
onProgress: progress,
|
||||||
completion: { result in
|
completion: { [weak self] uploadResult in
|
||||||
switch result {
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
switch uploadResult {
|
||||||
case .success:
|
case .success:
|
||||||
// 4. 确认上传完成
|
self.confirmUpload(
|
||||||
self?.confirmUpload(
|
fileId: response.data.fileId,
|
||||||
fileId: fileId,
|
fileName: fileName,
|
||||||
fileName: videoURL.lastPathComponent,
|
|
||||||
fileSize: videoData.count,
|
fileSize: videoData.count,
|
||||||
completion: completion
|
completion: completion
|
||||||
)
|
)
|
||||||
@ -200,221 +226,55 @@ public class ImageUploaderGetID: ObservableObject {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print("❌ 读取视频文件失败: \(error.localizedDescription)")
|
print("❌ 读取视频文件失败: \(error.localizedDescription)")
|
||||||
completion(.failure(error))
|
completion(.failure(.uploadFailed(error)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 私有方法
|
// MARK: - 私有方法
|
||||||
|
|
||||||
/// 获取上传URL
|
/// 发起网络请求
|
||||||
private func getUploadURL(
|
private func request<T: Decodable>(
|
||||||
for imageData: Data,
|
path: String,
|
||||||
completion: @escaping (Result<(fileId: String, uploadURL: URL), Error>) -> Void
|
parameters: [String: Any],
|
||||||
|
completion: @escaping (Result<T, UploadError>) -> Void
|
||||||
|
) {
|
||||||
|
networkHandler(path, parameters) { (result: Result<Data, UploadError>) in
|
||||||
|
switch result {
|
||||||
|
case .success(let data):
|
||||||
|
do {
|
||||||
|
let decoded = try JSONDecoder().decode(T.self, from: data)
|
||||||
|
completion(.success(decoded))
|
||||||
|
} catch {
|
||||||
|
print("❌ [ImageUploader] 解析响应失败: \(error)")
|
||||||
|
completion(.failure(.invalidResponseData))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
print("❌ [ImageUploader] 网络请求失败: \(error)")
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 请求上传URL
|
||||||
|
private func requestUploadURL(
|
||||||
|
fileName: String,
|
||||||
|
fileData: Data,
|
||||||
|
completion: @escaping (Result<UploadURLResponse, UploadError>) -> Void
|
||||||
) {
|
) {
|
||||||
let fileName = "avatar_\(UUID().uuidString).jpg"
|
|
||||||
let parameters: [String: Any] = [
|
let parameters: [String: Any] = [
|
||||||
"filename": fileName,
|
"filename": fileName,
|
||||||
"content_type": "image/jpeg",
|
"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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取上传URL
|
|
||||||
private func getUploadURL(
|
|
||||||
for fileData: Data,
|
|
||||||
mimeType: String,
|
|
||||||
originalFilename: String? = nil,
|
|
||||||
completion: @escaping (Result<(fileId: String, uploadURL: URL), Error>) -> Void
|
|
||||||
) {
|
|
||||||
let fileName = originalFilename ?? "file_\(UUID().uuidString)"
|
|
||||||
let parameters: [String: Any] = [
|
|
||||||
"filename": fileName,
|
|
||||||
"content_type": mimeType,
|
|
||||||
"file_size": fileData.count
|
"file_size": fileData.count
|
||||||
]
|
]
|
||||||
|
|
||||||
let urlString = "\(apiConfig.baseURL)/file/generate-upload-url"
|
print("""
|
||||||
guard let url = URL(string: urlString) else {
|
📝 [ImageUploader] 开始请求上传URL
|
||||||
completion(.failure(UploadError.invalidURL))
|
文件名: \(fileName)
|
||||||
return
|
📏 文件大小: \(fileData.count) 字节
|
||||||
}
|
📋 参数: \(parameters)
|
||||||
|
""")
|
||||||
|
|
||||||
var request = URLRequest(url: url)
|
request(path: "/file/generate-upload-url", parameters: parameters, completion: completion)
|
||||||
request.httpMethod = "POST"
|
|
||||||
request.allHTTPHeaderFields = apiConfig.authHeaders
|
|
||||||
|
|
||||||
do {
|
|
||||||
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
|
|
||||||
print("📤 准备上传请求,文件名: \(fileName), 大小: \(Double(fileData.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 {
|
|
||||||
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
|
|
||||||
) {
|
|
||||||
let endpoint = "\(apiConfig.baseURL)/file/confirm-upload"
|
|
||||||
guard let url = URL(string: endpoint) else {
|
|
||||||
completion(.failure(UploadError.invalidURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = URLRequest(url: url)
|
|
||||||
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)")
|
|
||||||
} catch {
|
|
||||||
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 {
|
|
||||||
print("❌ 无效的服务器响应")
|
|
||||||
completion(.failure(UploadError.invalidResponse))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard (200...299).contains(httpResponse.statusCode) else {
|
|
||||||
let statusCode = httpResponse.statusCode
|
|
||||||
let errorMessage = "确认上传失败,状态码: \(statusCode)"
|
|
||||||
print("❌ \(errorMessage)")
|
|
||||||
completion(.failure(UploadError.serverError(errorMessage)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建上传结果
|
|
||||||
let uploadResult = UploadResult(
|
|
||||||
fileUrl: "\(self.apiConfig.baseURL)/files/\(fileId)",
|
|
||||||
fileName: fileName,
|
|
||||||
fileSize: fileSize,
|
|
||||||
fileId: fileId
|
|
||||||
)
|
|
||||||
|
|
||||||
print("✅ 图片上传并确认成功,fileId: \(fileId)")
|
|
||||||
completion(.success(uploadResult))
|
|
||||||
}
|
|
||||||
|
|
||||||
task.resume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 上传文件到指定URL
|
/// 上传文件到指定URL
|
||||||
@ -423,96 +283,203 @@ public class ImageUploaderGetID: ObservableObject {
|
|||||||
to uploadURL: URL,
|
to uploadURL: URL,
|
||||||
mimeType: String = "application/octet-stream",
|
mimeType: String = "application/octet-stream",
|
||||||
onProgress: @escaping (Double) -> Void,
|
onProgress: @escaping (Double) -> Void,
|
||||||
completion: @escaping (Result<Void, Error>) -> Void
|
completion: @escaping (Result<Void, UploadError>) -> Void
|
||||||
) -> URLSessionUploadTask {
|
) -> URLSessionUploadTask? {
|
||||||
|
print("""
|
||||||
|
⬆️ [ImageUploader] 开始上传文件
|
||||||
|
🔗 目标URL: \(uploadURL.absoluteString)
|
||||||
|
📏 文件大小: \(fileData.count) 字节
|
||||||
|
📋 MIME类型: \(mimeType)
|
||||||
|
""")
|
||||||
|
|
||||||
|
// 创建请求
|
||||||
var request = URLRequest(url: uploadURL)
|
var request = URLRequest(url: uploadURL)
|
||||||
request.httpMethod = "PUT"
|
request.httpMethod = "PUT"
|
||||||
request.setValue(mimeType, forHTTPHeaderField: "Content-Type")
|
|
||||||
|
|
||||||
let task = session.uploadTask(with: request, from: fileData) { _, response, error in
|
// 设置请求头
|
||||||
if let error = error {
|
var headers: [String: String] = [
|
||||||
completion(.failure(error))
|
"Content-Type": mimeType,
|
||||||
return
|
"Content-Length": String(fileData.count)
|
||||||
}
|
]
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
|
||||||
completion(.failure(UploadError.invalidResponse))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard (200...299).contains(httpResponse.statusCode) else {
|
|
||||||
let statusCode = httpResponse.statusCode
|
|
||||||
completion(.failure(UploadError.serverError("上传失败,状态码: \(statusCode)")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
completion(.success(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加进度观察
|
// 添加认证头
|
||||||
if #available(iOS 11.0, *) {
|
if let token = KeychainHelper.getAccessToken() {
|
||||||
let progressObserver = task.progress.observe(\.fractionCompleted) { progressValue, _ in
|
headers["Authorization"] = "Bearer \(token)"
|
||||||
DispatchQueue.main.async {
|
print("🔑 [ImageUploader] 添加认证头,Token: \(token.prefix(10))...")
|
||||||
onProgress(progressValue.fractionCompleted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task.addCompletionHandler { [weak task] in
|
|
||||||
progressObserver.invalidate()
|
|
||||||
task?.progress.cancel()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
var lastProgress: Double = 0
|
print("⚠️ [ImageUploader] 未找到认证Token")
|
||||||
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
|
||||||
let bytesSent = task.countOfBytesSent
|
|
||||||
let totalBytes = task.countOfBytesExpectedToSend
|
|
||||||
let currentProgress = totalBytes > 0 ? Double(bytesSent) / Double(totalBytes) : 0
|
|
||||||
|
|
||||||
// 只有当进度有显著变化时才回调,避免频繁更新UI
|
|
||||||
if abs(currentProgress - lastProgress) > 0.01 || currentProgress >= 1.0 {
|
|
||||||
lastProgress = currentProgress
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
onProgress(min(currentProgress, 1.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentProgress >= 1.0 {
|
|
||||||
timer.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task.addCompletionHandler {
|
|
||||||
timer.invalidate()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task.resume()
|
// 设置所有请求头
|
||||||
|
headers.forEach { key, value in
|
||||||
|
request.setValue(value, forHTTPHeaderField: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建上传任务
|
||||||
|
let task = networkService.upload(
|
||||||
|
request: request,
|
||||||
|
fileData: fileData,
|
||||||
|
onProgress: onProgress,
|
||||||
|
completion: { [weak self] result in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let data, let response):
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
print("❌ [ImageUploader] 无效的响应")
|
||||||
|
completion(.failure(.invalidResponse))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusCode = httpResponse.statusCode
|
||||||
|
let responseBody = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
|
||||||
|
|
||||||
|
print("""
|
||||||
|
📡 [ImageUploader] 服务器响应
|
||||||
|
🔢 状态码: \(statusCode)
|
||||||
|
🔗 请求URL: \(uploadURL.absoluteString)
|
||||||
|
📦 响应: \(responseBody)
|
||||||
|
""")
|
||||||
|
|
||||||
|
switch statusCode {
|
||||||
|
case 200...299:
|
||||||
|
print("✅ [ImageUploader] 文件上传成功")
|
||||||
|
completion(.success(()))
|
||||||
|
|
||||||
|
case 401:
|
||||||
|
print("🔑 [ImageUploader] 认证失败,需要重新登录")
|
||||||
|
completion(.failure(.unauthorized))
|
||||||
|
|
||||||
|
case 403:
|
||||||
|
let errorMessage = "访问被拒绝 (403) - URL: \(uploadURL.absoluteString)"
|
||||||
|
print("❌ [ImageUploader] \(errorMessage)")
|
||||||
|
completion(.failure(.serverError(errorMessage)))
|
||||||
|
|
||||||
|
default:
|
||||||
|
let errorMessage = "服务器返回错误: \(statusCode)"
|
||||||
|
print("❌ [ImageUploader] \(errorMessage)")
|
||||||
|
completion(.failure(.serverError(errorMessage)))
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
print("❌ [ImageUploader] 上传失败: \(error.localizedDescription)")
|
||||||
|
completion(.failure(.networkError(error)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 开始上传
|
||||||
|
task?.resume()
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 文件上传状态
|
/// 确认上传完成
|
||||||
|
private func confirmUpload(
|
||||||
/// 文件上传状态
|
fileId: String,
|
||||||
public struct FileStatus {
|
fileName: String,
|
||||||
public let file: Data
|
fileSize: Int,
|
||||||
public var status: UploadStatus
|
completion: @escaping (Result<UploadResult, UploadError>) -> Void
|
||||||
public var progress: Double
|
) {
|
||||||
|
let parameters: [String: Any] = [
|
||||||
|
"file_id": fileId,
|
||||||
|
"file_name": fileName,
|
||||||
|
"file_size": fileSize
|
||||||
|
]
|
||||||
|
|
||||||
public enum UploadStatus {
|
print("""
|
||||||
case pending
|
📨 [ImageUploader] 开始确认上传完成
|
||||||
case uploading
|
📁 文件ID: \(fileId)
|
||||||
case completed
|
📝 文件名: \(fileName)
|
||||||
case failed(Error)
|
📏 文件大小: \(fileSize) 字节
|
||||||
|
📋 参数: \(parameters)
|
||||||
|
""")
|
||||||
|
|
||||||
|
struct ConfirmUploadResponse: Codable {
|
||||||
|
let code: Int
|
||||||
|
let message: String
|
||||||
|
let data: [String: String]?
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(file: Data, status: UploadStatus = .pending, progress: Double = 0) {
|
request(path: "/file/confirm-upload", parameters: parameters) { (result: Result<ConfirmUploadResponse, UploadError>) in
|
||||||
self.file = file
|
switch result {
|
||||||
self.status = status
|
case .success(_):
|
||||||
self.progress = progress
|
let uploadResult = UploadResult(
|
||||||
|
fileUrl: "\(APIConfig.baseURL)/files/\(fileId)",
|
||||||
|
fileName: fileName,
|
||||||
|
fileSize: fileSize,
|
||||||
|
fileId: fileId
|
||||||
|
)
|
||||||
|
print("""
|
||||||
|
✅ [ImageUploader] 文件上传确认成功
|
||||||
|
📁 文件ID: \(fileId)
|
||||||
|
🔗 文件URL: \(uploadResult.fileUrl)
|
||||||
|
📝 文件名: \(fileName)
|
||||||
|
📏 文件大小: \(fileSize) 字节
|
||||||
|
""")
|
||||||
|
completion(.success(uploadResult))
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
print("❌ [ImageUploader] 文件上传确认失败: \(error.localizedDescription)")
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 辅助方法
|
||||||
|
|
||||||
|
private func calculateSpeed(bytes: Int, seconds: TimeInterval) -> String {
|
||||||
|
guard seconds > 0 else { return "0 KB/s" }
|
||||||
|
let bytesPerSecond = Double(bytes) / seconds
|
||||||
|
if bytesPerSecond >= 1024 * 1024 {
|
||||||
|
return String(format: "%.1f MB/s", bytesPerSecond / (1024 * 1024))
|
||||||
|
} else {
|
||||||
|
return String(format: "%.1f KB/s", bytesPerSecond / 1024)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Data Extension for MD5
|
||||||
|
|
||||||
|
extension Data {
|
||||||
|
func md5Base64EncodedString() -> String? {
|
||||||
|
#if canImport(CommonCrypto)
|
||||||
|
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
|
||||||
|
_ = self.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in
|
||||||
|
if let baseAddress = ptr.baseAddress, ptr.count > 0 {
|
||||||
|
CC_MD5(baseAddress, CC_LONG(self.count), &digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Data(digest).base64EncodedString()
|
||||||
|
#else
|
||||||
|
return nil
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 响应模型
|
||||||
|
|
||||||
|
private struct UploadURLResponse: Codable {
|
||||||
|
let code: Int
|
||||||
|
let message: String?
|
||||||
|
let data: UploadData
|
||||||
|
|
||||||
|
struct UploadData: Codable {
|
||||||
|
let fileId: String
|
||||||
|
let filePath: String
|
||||||
|
let uploadUrl: String
|
||||||
|
let expiresIn: Int
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case fileId = "file_id"
|
||||||
|
case filePath = "file_path"
|
||||||
|
case uploadUrl = "upload_url"
|
||||||
|
case expiresIn = "expires_in"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct EmptyResponse: Codable {}
|
||||||
|
|
||||||
// MARK: - URLSessionTask 扩展
|
// MARK: - URLSessionTask 扩展
|
||||||
|
|
||||||
private class TaskObserver: NSObject {
|
private class TaskObserver: NSObject {
|
||||||
@ -541,7 +508,6 @@ private class TaskObserver: NSObject {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用所有完成处理器
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.handlers.forEach { $0() }
|
self?.handlers.forEach { $0() }
|
||||||
self?.cleanup()
|
self?.cleanup()
|
||||||
@ -580,7 +546,6 @@ private extension URLSessionTask {
|
|||||||
taskObserver = observer
|
taskObserver = observer
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// iOS 11 以下版本使用通知
|
|
||||||
let name = NSNotification.Name("TaskCompleted\(self.taskIdentifier)")
|
let name = NSNotification.Name("TaskCompleted\(self.taskIdentifier)")
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
forName: name,
|
forName: name,
|
||||||
@ -592,21 +557,3 @@ private extension URLSessionTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,7 @@ struct SplashView: View {
|
|||||||
)
|
)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
VStack(spacing: 50) {
|
VStack(spacing: 50) {
|
||||||
FilmAnimation()
|
// FilmAnimation()
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user