
- 修改COSManagerAdapter以支持新的TCCos组件,确保与腾讯云COS的兼容性。 - 在CreateFeedFeature中新增图片上传相关状态和Action,优化图片选择与上传逻辑。 - 更新CreateFeedView以整合图片上传功能,提升用户体验。 - 在多个视图中添加键盘状态管理,改善用户交互体验。 - 新增COS相关的测试文件,确保功能的正确性和稳定性。
260 lines
7.0 KiB
Swift
260 lines
7.0 KiB
Swift
import Foundation
|
||
import QCloudCOSXML
|
||
import ComposableArchitecture
|
||
|
||
// MARK: - COS 核心数据模型
|
||
|
||
/// 腾讯云 COS Token 数据模型
|
||
public struct TcTokenData: Codable, Equatable, Sendable {
|
||
/// 存储桶名称
|
||
public let bucket: String
|
||
/// 临时会话令牌
|
||
public let sessionToken: String
|
||
/// 地域
|
||
public let region: String
|
||
/// 自定义域名
|
||
public let customDomain: String
|
||
/// 是否启用加速
|
||
public let accelerate: Bool
|
||
/// 应用 ID
|
||
public let appId: String
|
||
/// 临时密钥
|
||
public let secretKey: String
|
||
/// 过期时间戳
|
||
public let expireTime: Int64
|
||
/// 开始时间戳
|
||
public let startTime: Int64
|
||
/// 临时密钥 ID
|
||
public let secretId: String
|
||
|
||
public init(
|
||
bucket: String,
|
||
sessionToken: String,
|
||
region: String,
|
||
customDomain: String,
|
||
accelerate: Bool,
|
||
appId: String,
|
||
secretKey: String,
|
||
expireTime: Int64,
|
||
startTime: Int64,
|
||
secretId: String
|
||
) {
|
||
self.bucket = bucket
|
||
self.sessionToken = sessionToken
|
||
self.region = region
|
||
self.customDomain = customDomain
|
||
self.accelerate = accelerate
|
||
self.appId = appId
|
||
self.secretKey = secretKey
|
||
self.expireTime = expireTime
|
||
self.startTime = startTime
|
||
self.secretId = secretId
|
||
}
|
||
}
|
||
|
||
/// Token 请求模型
|
||
struct TcTokenRequest: APIRequestProtocol {
|
||
typealias Response = TcTokenResponse
|
||
|
||
let endpoint: String = APIEndpoint.tcToken.path
|
||
let method: HTTPMethod = .GET
|
||
let queryParameters: [String: String]? = nil
|
||
var bodyParameters: [String: Any]? { nil }
|
||
let timeout: TimeInterval = 30.0
|
||
let includeBaseParameters: Bool = true
|
||
let shouldShowLoading: Bool = false // 不显示 loading,避免影响用户体验
|
||
let shouldShowError: Bool = false // 不显示错误,静默处理
|
||
}
|
||
|
||
/// Token 响应模型
|
||
public struct TcTokenResponse: Codable, Equatable, Sendable {
|
||
public let code: Int
|
||
public let message: String
|
||
public let data: TcTokenData?
|
||
public let timestamp: Int64
|
||
|
||
public init(code: Int, message: String, data: TcTokenData?, timestamp: Int64) {
|
||
self.code = code
|
||
self.message = message
|
||
self.data = data
|
||
self.timestamp = timestamp
|
||
}
|
||
}
|
||
|
||
// MARK: - 上传相关模型
|
||
|
||
/// 上传状态枚举
|
||
public enum UploadStatus: Equatable, Sendable {
|
||
case idle
|
||
case uploading(progress: Double)
|
||
case success(url: String)
|
||
case failure(error: String)
|
||
}
|
||
|
||
/// 上传任务模型
|
||
public struct UploadTask: Equatable, Identifiable, Sendable {
|
||
public let id: UUID
|
||
public let imageData: Data
|
||
public let fileName: String
|
||
public let status: UploadStatus
|
||
public let createdAt: Date
|
||
|
||
public init(
|
||
id: UUID = UUID(),
|
||
imageData: Data,
|
||
fileName: String,
|
||
status: UploadStatus = .idle,
|
||
createdAt: Date = Date()
|
||
) {
|
||
self.id = id
|
||
self.imageData = imageData
|
||
self.fileName = fileName
|
||
self.status = status
|
||
self.createdAt = createdAt
|
||
}
|
||
}
|
||
|
||
/// 上传进度模型
|
||
public struct UploadProgress: Equatable, Sendable {
|
||
public let bytesSent: Int64
|
||
public let totalBytesSent: Int64
|
||
public let totalBytesExpectedToSend: Int64
|
||
|
||
public var progress: Double {
|
||
guard totalBytesExpectedToSend > 0 else { return 0.0 }
|
||
return Double(totalBytesSent) / Double(totalBytesExpectedToSend)
|
||
}
|
||
|
||
public init(
|
||
bytesSent: Int64,
|
||
totalBytesSent: Int64,
|
||
totalBytesExpectedToSend: Int64
|
||
) {
|
||
self.bytesSent = bytesSent
|
||
self.totalBytesSent = totalBytesSent
|
||
self.totalBytesExpectedToSend = totalBytesExpectedToSend
|
||
}
|
||
}
|
||
|
||
// MARK: - 配置相关模型
|
||
|
||
/// COS 配置模型
|
||
public struct COSConfiguration: Equatable, Sendable {
|
||
public let region: String
|
||
public let bucket: String
|
||
public let accelerate: Bool
|
||
public let customDomain: String
|
||
public let useHTTPS: Bool
|
||
|
||
public init(
|
||
region: String,
|
||
bucket: String,
|
||
accelerate: Bool = false,
|
||
customDomain: String = "",
|
||
useHTTPS: Bool = true
|
||
) {
|
||
self.region = region
|
||
self.bucket = bucket
|
||
self.accelerate = accelerate
|
||
self.customDomain = customDomain
|
||
self.useHTTPS = useHTTPS
|
||
}
|
||
}
|
||
|
||
/// COS 服务状态
|
||
public enum COSServiceStatus: Equatable, Sendable {
|
||
case notInitialized
|
||
case initializing
|
||
case initialized(configuration: COSConfiguration)
|
||
case failed(error: String)
|
||
}
|
||
|
||
// MARK: - 错误模型
|
||
|
||
/// COS 错误类型
|
||
public enum COSError: Equatable, Sendable, LocalizedError {
|
||
case tokenExpired
|
||
case tokenInvalid
|
||
case serviceNotInitialized
|
||
case uploadFailed(String)
|
||
case configurationFailed(String)
|
||
case networkError(String)
|
||
case unknown(String)
|
||
|
||
public var errorDescription: String? {
|
||
switch self {
|
||
case .tokenExpired:
|
||
return "Token已过期"
|
||
case .tokenInvalid:
|
||
return "Token无效"
|
||
case .serviceNotInitialized:
|
||
return "服务未初始化"
|
||
case .uploadFailed(let message):
|
||
return "上传失败: \(message)"
|
||
case .configurationFailed(let message):
|
||
return "配置失败: \(message)"
|
||
case .networkError(let message):
|
||
return "网络错误: \(message)"
|
||
case .unknown(let message):
|
||
return "未知错误: \(message)"
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - 扩展
|
||
|
||
extension TcTokenData {
|
||
/// 检查 Token 是否已过期
|
||
public var isExpired: Bool {
|
||
let currentTime = Int64(Date().timeIntervalSince1970)
|
||
return currentTime >= expireTime
|
||
}
|
||
|
||
/// 获取过期时间
|
||
public var expirationDate: Date {
|
||
return Date(timeIntervalSince1970: TimeInterval(expireTime))
|
||
}
|
||
|
||
/// 获取开始时间
|
||
public var startDate: Date {
|
||
return Date(timeIntervalSince1970: TimeInterval(startTime))
|
||
}
|
||
|
||
/// 获取剩余有效时间(秒)
|
||
public var remainingTime: Int64 {
|
||
let currentTime = Int64(Date().timeIntervalSince1970)
|
||
return max(0, expireTime - currentTime)
|
||
}
|
||
|
||
/// 检查Token是否有效
|
||
public var isValid: Bool {
|
||
return !isExpired
|
||
}
|
||
|
||
/// 获取剩余有效时间(TimeInterval)
|
||
public var remainingValidTime: TimeInterval {
|
||
return max(0, expirationDate.timeIntervalSinceNow)
|
||
}
|
||
|
||
/// 构建云存储URL
|
||
public func buildCloudURL(for key: String) -> String {
|
||
let domain = customDomain.isEmpty
|
||
? "\(bucket).cos.\(region).myqcloud.com"
|
||
: customDomain
|
||
let prefix = domain.hasPrefix("http") ? "" : "https://"
|
||
return "\(prefix)\(domain)/\(key)"
|
||
}
|
||
}
|
||
|
||
extension UploadTask {
|
||
/// 更新上传状态
|
||
public func updatingStatus(_ newStatus: UploadStatus) -> UploadTask {
|
||
return UploadTask(
|
||
id: id,
|
||
imageData: imageData,
|
||
fileName: fileName,
|
||
status: newStatus,
|
||
createdAt: createdAt
|
||
)
|
||
}
|
||
} |