
- 修改COSManagerAdapter以支持新的TCCos组件,确保与腾讯云COS的兼容性。 - 在CreateFeedFeature中新增图片上传相关状态和Action,优化图片选择与上传逻辑。 - 更新CreateFeedView以整合图片上传功能,提升用户体验。 - 在多个视图中添加键盘状态管理,改善用户交互体验。 - 新增COS相关的测试文件,确保功能的正确性和稳定性。
284 lines
9.8 KiB
Swift
284 lines
9.8 KiB
Swift
import Foundation
|
||
import QCloudCOSXML
|
||
import UIKit
|
||
import ComposableArchitecture
|
||
|
||
// MARK: - 上传服务协议
|
||
|
||
/// 上传服务协议
|
||
public protocol COSUploadServiceProtocol: Sendable {
|
||
/// 上传图片数据
|
||
func uploadImage(_ imageData: Data, fileName: String) async throws -> String
|
||
|
||
/// 上传UIImage
|
||
func uploadUIImage(_ image: UIImage, fileName: String) async throws -> String
|
||
|
||
/// 取消上传
|
||
func cancelUpload(taskId: UUID) async
|
||
}
|
||
|
||
// MARK: - 上传服务实现
|
||
|
||
/// 上传服务实现
|
||
public struct COSUploadService: COSUploadServiceProtocol {
|
||
private let tokenService: COSTokenServiceProtocol
|
||
private let configurationService: COSConfigurationServiceProtocol
|
||
private let uploadTaskManager: UploadTaskManagerProtocol
|
||
|
||
public init(
|
||
tokenService: COSTokenServiceProtocol,
|
||
configurationService: COSConfigurationServiceProtocol,
|
||
uploadTaskManager: UploadTaskManagerProtocol = UploadTaskManager()
|
||
) {
|
||
self.tokenService = tokenService
|
||
self.configurationService = configurationService
|
||
self.uploadTaskManager = uploadTaskManager
|
||
}
|
||
|
||
/// 上传图片数据
|
||
public func uploadImage(_ imageData: Data, fileName: String) async throws -> String {
|
||
debugInfoSync("🚀 开始上传图片,数据大小: \(imageData.count) bytes")
|
||
|
||
// 获取Token
|
||
let tokenData = try await tokenService.getValidToken()
|
||
|
||
// 确保COS服务已初始化
|
||
try await configurationService.initializeCOSService(with: tokenData)
|
||
|
||
// 创建上传任务
|
||
let task = UploadTask(
|
||
imageData: imageData,
|
||
fileName: fileName
|
||
)
|
||
|
||
// 执行上传
|
||
return try await performUpload(task: task, tokenData: tokenData)
|
||
}
|
||
|
||
/// 上传UIImage
|
||
public func uploadUIImage(_ image: UIImage, fileName: String) async throws -> String {
|
||
guard let data = image.jpegData(compressionQuality: 0.7) else {
|
||
throw COSError.uploadFailed("图片压缩失败,无法生成 JPEG 数据")
|
||
}
|
||
|
||
return try await uploadImage(data, fileName: fileName)
|
||
}
|
||
|
||
/// 取消上传
|
||
public func cancelUpload(taskId: UUID) async {
|
||
await uploadTaskManager.cancelTask(taskId)
|
||
}
|
||
|
||
// MARK: - 私有方法
|
||
|
||
/// 执行上传
|
||
private func performUpload(task: UploadTask, tokenData: TcTokenData) async throws -> String {
|
||
// 注册任务
|
||
await uploadTaskManager.registerTask(task)
|
||
|
||
return try await withCheckedThrowingContinuation { continuation in
|
||
Task {
|
||
do {
|
||
// 创建上传请求
|
||
let request = try await createUploadRequest(task: task, tokenData: tokenData)
|
||
|
||
// 设置进度回调
|
||
request.sendProcessBlock = { @Sendable (bytesSent, totalBytesSent, totalBytesExpectedToSend) in
|
||
Task {
|
||
let progress = UploadProgress(
|
||
bytesSent: bytesSent,
|
||
totalBytesSent: totalBytesSent,
|
||
totalBytesExpectedToSend: totalBytesExpectedToSend
|
||
)
|
||
|
||
await self.uploadTaskManager.updateTaskProgress(
|
||
taskId: task.id,
|
||
progress: progress
|
||
)
|
||
|
||
debugInfoSync("📊 上传进度: \(progress.progress * 100)%")
|
||
}
|
||
}
|
||
|
||
// 设置完成回调
|
||
request.setFinish { @Sendable result, error in
|
||
Task {
|
||
await self.uploadTaskManager.unregisterTask(task.id)
|
||
|
||
if let error = error {
|
||
debugErrorSync("❌ 图片上传失败: \(error.localizedDescription)")
|
||
continuation.resume(throwing: COSError.uploadFailed(error.localizedDescription))
|
||
} else {
|
||
// 构建云地址
|
||
let cloudURL = tokenData.buildCloudURL(for: task.fileName)
|
||
debugInfoSync("✅ 图片上传成功: \(cloudURL)")
|
||
continuation.resume(returning: cloudURL)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 开始上传
|
||
QCloudCOSTransferMangerService.defaultCOSTransferManager().uploadObject(request)
|
||
|
||
} catch {
|
||
await uploadTaskManager.unregisterTask(task.id)
|
||
continuation.resume(throwing: error)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 创建上传请求
|
||
private func createUploadRequest(task: UploadTask, tokenData: TcTokenData) async throws -> QCloudCOSXMLUploadObjectRequest<AnyObject> {
|
||
// 创建凭证
|
||
let credential = QCloudCredential()
|
||
credential.secretID = tokenData.secretId
|
||
credential.secretKey = tokenData.secretKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
||
credential.token = tokenData.sessionToken
|
||
credential.startDate = tokenData.startDate
|
||
credential.expirationDate = tokenData.expirationDate
|
||
|
||
// 创建上传请求
|
||
let request = QCloudCOSXMLUploadObjectRequest<AnyObject>()
|
||
request.bucket = tokenData.bucket
|
||
request.regionName = tokenData.region
|
||
request.credential = credential
|
||
request.object = task.fileName
|
||
request.body = task.imageData as AnyObject
|
||
|
||
// 设置加速
|
||
if tokenData.accelerate {
|
||
request.enableQuic = true
|
||
}
|
||
|
||
return request
|
||
}
|
||
}
|
||
|
||
// MARK: - 上传任务管理协议
|
||
|
||
/// 上传任务管理协议
|
||
public protocol UploadTaskManagerProtocol: Sendable {
|
||
/// 注册任务
|
||
func registerTask(_ task: UploadTask) async
|
||
|
||
/// 注销任务
|
||
func unregisterTask(_ taskId: UUID) async
|
||
|
||
/// 更新任务进度
|
||
func updateTaskProgress(taskId: UUID, progress: UploadProgress) async
|
||
|
||
/// 取消任务
|
||
func cancelTask(_ taskId: UUID) async
|
||
|
||
/// 获取任务状态
|
||
func getTaskStatus(_ taskId: UUID) async -> UploadStatus?
|
||
}
|
||
|
||
// MARK: - 上传任务管理实现
|
||
|
||
/// 上传任务管理实现
|
||
public actor UploadTaskManager: UploadTaskManagerProtocol {
|
||
private var activeTasks: [UUID: UploadTask] = [:]
|
||
|
||
public init() {}
|
||
|
||
/// 注册任务
|
||
public func registerTask(_ task: UploadTask) async {
|
||
activeTasks[task.id] = task
|
||
debugInfoSync("📝 注册上传任务: \(task.id)")
|
||
}
|
||
|
||
/// 注销任务
|
||
public func unregisterTask(_ taskId: UUID) async {
|
||
activeTasks.removeValue(forKey: taskId)
|
||
debugInfoSync("📝 注销上传任务: \(taskId)")
|
||
}
|
||
|
||
/// 更新任务进度
|
||
public func updateTaskProgress(taskId: UUID, progress: UploadProgress) async {
|
||
guard var task = activeTasks[taskId] else { return }
|
||
|
||
let newStatus = UploadStatus.uploading(progress: progress.progress)
|
||
task = task.updatingStatus(newStatus)
|
||
activeTasks[taskId] = task
|
||
}
|
||
|
||
/// 取消任务
|
||
public func cancelTask(_ taskId: UUID) async {
|
||
guard let task = activeTasks[taskId] else { return }
|
||
|
||
// 更新任务状态为失败
|
||
let updatedTask = task.updatingStatus(.failure(error: "任务已取消"))
|
||
activeTasks[taskId] = updatedTask
|
||
|
||
debugInfoSync("❌ 取消上传任务: \(taskId)")
|
||
}
|
||
|
||
/// 获取任务状态
|
||
public func getTaskStatus(_ taskId: UUID) async -> UploadStatus? {
|
||
return activeTasks[taskId]?.status
|
||
}
|
||
}
|
||
|
||
// MARK: - TCA 依赖扩展
|
||
|
||
extension DependencyValues {
|
||
/// COS 上传服务依赖
|
||
public var cosUploadService: COSUploadServiceProtocol {
|
||
get { self[COSUploadServiceKey.self] }
|
||
set { self[COSUploadServiceKey.self] = newValue }
|
||
}
|
||
}
|
||
|
||
/// COS 上传服务依赖键
|
||
private enum COSUploadServiceKey: DependencyKey {
|
||
static let liveValue: COSUploadServiceProtocol = COSUploadService(
|
||
tokenService: COSTokenService(apiService: LiveAPIService()),
|
||
configurationService: COSConfigurationService()
|
||
)
|
||
static let testValue: COSUploadServiceProtocol = MockCOSUploadService()
|
||
}
|
||
|
||
// MARK: - Mock 服务(用于测试)
|
||
|
||
/// Mock 上传服务(用于测试)
|
||
public struct MockCOSUploadService: COSUploadServiceProtocol {
|
||
public var uploadImageResult: Result<String, Error> = .failure(COSError.uploadFailed("Mock error"))
|
||
public var uploadUIImageResult: Result<String, Error> = .failure(COSError.uploadFailed("Mock error"))
|
||
|
||
public init() {}
|
||
|
||
public func uploadImage(_ imageData: Data, fileName: String) async throws -> String {
|
||
switch uploadImageResult {
|
||
case .success(let url):
|
||
return url
|
||
case .failure(let error):
|
||
throw error
|
||
}
|
||
}
|
||
|
||
public func uploadUIImage(_ image: UIImage, fileName: String) async throws -> String {
|
||
switch uploadUIImageResult {
|
||
case .success(let url):
|
||
return url
|
||
case .failure(let error):
|
||
throw error
|
||
}
|
||
}
|
||
|
||
public func cancelUpload(taskId: UUID) async {
|
||
// Mock 实现,无需实际操作
|
||
}
|
||
}
|
||
|
||
// MARK: - 扩展
|
||
|
||
extension UploadTask {
|
||
/// 生成唯一的文件名
|
||
public static func generateFileName(extension: String = "jpg") -> String {
|
||
let uuid = UUID().uuidString
|
||
return "images/\(uuid).\(`extension`)"
|
||
}
|
||
}
|