feat: 添加动态发布功能及相关文档
主要变更: 1. 新增 EPImageUploader.swift 和 EPProgressHUD.swift,提供图片批量上传和进度显示功能。 2. 新建 EPMomentAPISwiftHelper.swift,封装动态 API 的 Swift 版本。 3. 更新 EPMomentPublishViewController,集成新上传功能并实现发布成功通知。 4. 创建多个文档,包括实施报告、检查清单和快速使用指南,详细记录功能实现和使用方法。 5. 更新 Bridging Header,确保 Swift 和 Objective-C 代码的互操作性。 此功能旨在提升用户体验,简化动态发布流程,并提供清晰的文档支持。
This commit is contained in:
253
YuMi/E-P/Common/EPSDKManager.swift
Normal file
253
YuMi/E-P/Common/EPSDKManager.swift
Normal file
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// EPSDKManager.swift
|
||||
// YuMi
|
||||
//
|
||||
// Created by AI on 2025-10-11.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// 第三方 SDK 统一管理器(单例)
|
||||
/// 统一入口:对外提供所有 SDK 能力
|
||||
/// 内部管理:QCloud 初始化、配置、上传等
|
||||
@objc class EPSDKManager: NSObject, QCloudSignatureProvider, QCloudCredentailFenceQueueDelegate {
|
||||
|
||||
// MARK: - Singleton
|
||||
|
||||
@objc static let shared = EPSDKManager()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// QCloud 配置缓存
|
||||
private var qcloudConfig: EPQCloudConfig?
|
||||
|
||||
// QCloud 初始化状态
|
||||
private var isQCloudInitializing = false
|
||||
|
||||
// QCloud 初始化回调队列
|
||||
private var qcloudInitCallbacks: [(Bool, String?) -> Void] = []
|
||||
|
||||
// QCloud 凭证队列
|
||||
private var credentialFenceQueue: QCloudCredentailFenceQueue?
|
||||
|
||||
// 线程安全锁
|
||||
private let lock = NSLock()
|
||||
|
||||
// 内部图片上传器
|
||||
private let uploader = EPImageUploader()
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Public API (对外统一入口)
|
||||
|
||||
/// 批量上传图片(统一入口)
|
||||
/// - Parameters:
|
||||
/// - images: 要上传的图片数组
|
||||
/// - progress: 进度回调 (已上传数, 总数)
|
||||
/// - success: 成功回调,返回图片信息数组
|
||||
/// - failure: 失败回调
|
||||
@objc func uploadImages(
|
||||
_ images: [UIImage],
|
||||
progress: @escaping (Int, Int) -> Void,
|
||||
success: @escaping ([[String: Any]]) -> Void,
|
||||
failure: @escaping (String) -> Void
|
||||
) {
|
||||
guard !images.isEmpty else {
|
||||
success([])
|
||||
return
|
||||
}
|
||||
|
||||
// 确保 QCloud 已就绪
|
||||
ensureQCloudReady { [weak self] isReady, errorMsg in
|
||||
guard let self = self, isReady else {
|
||||
DispatchQueue.main.async {
|
||||
failure(errorMsg ?? "QCloud 初始化失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 委托给内部 uploader 执行
|
||||
self.uploader.performBatchUpload(
|
||||
images,
|
||||
bucket: self.qcloudConfig?.bucket ?? "",
|
||||
customDomain: self.qcloudConfig?.customDomain ?? "",
|
||||
progress: progress,
|
||||
success: success,
|
||||
failure: failure
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查 QCloud 是否已就绪
|
||||
/// - Returns: true 表示已初始化且未过期
|
||||
@objc func isQCloudReady() -> Bool {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
guard let config = qcloudConfig else {
|
||||
return false
|
||||
}
|
||||
return !config.isExpired
|
||||
}
|
||||
|
||||
// MARK: - Internal Methods
|
||||
|
||||
/// 确保 QCloud 已就绪(自动初始化)
|
||||
private func ensureQCloudReady(completion: @escaping (Bool, String?) -> Void) {
|
||||
if isQCloudReady() {
|
||||
completion(true, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 未初始化或已过期,重新初始化
|
||||
initializeQCloud(completion: completion)
|
||||
}
|
||||
|
||||
/// 初始化 QCloud(获取 Token 并配置 SDK)
|
||||
private func initializeQCloud(completion: @escaping (Bool, String?) -> Void) {
|
||||
lock.lock()
|
||||
|
||||
// 如果正在初始化,加入回调队列
|
||||
if isQCloudInitializing {
|
||||
qcloudInitCallbacks.append(completion)
|
||||
lock.unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已初始化且未过期,直接返回
|
||||
if let config = qcloudConfig, !config.isExpired {
|
||||
lock.unlock()
|
||||
completion(true, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 开始初始化
|
||||
isQCloudInitializing = true
|
||||
qcloudInitCallbacks.append(completion)
|
||||
lock.unlock()
|
||||
|
||||
// 调用 API 获取 QCloud Token
|
||||
// API: GET tencent/cos/getToken
|
||||
Api.getQCloudInfo { [weak self] (data, code, msg) in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.lock.lock()
|
||||
|
||||
if code == 200,
|
||||
let dict = data?.data as? [String: Any],
|
||||
let config = EPQCloudConfig(dictionary: dict) {
|
||||
|
||||
// 保存配置
|
||||
self.qcloudConfig = config
|
||||
|
||||
// 配置 QCloud SDK
|
||||
self.configureQCloudSDK(with: config)
|
||||
|
||||
// 初始化完成
|
||||
self.isQCloudInitializing = false
|
||||
let callbacks = self.qcloudInitCallbacks
|
||||
self.qcloudInitCallbacks.removeAll()
|
||||
self.lock.unlock()
|
||||
|
||||
// 短暂延迟确保 SDK 配置完成
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
callbacks.forEach { $0(true, nil) }
|
||||
}
|
||||
} else {
|
||||
// 初始化失败
|
||||
self.isQCloudInitializing = false
|
||||
let callbacks = self.qcloudInitCallbacks
|
||||
self.qcloudInitCallbacks.removeAll()
|
||||
self.lock.unlock()
|
||||
|
||||
let errorMsg = msg ?? "获取 QCloud 配置失败"
|
||||
DispatchQueue.main.async {
|
||||
callbacks.forEach { $0(false, errorMsg) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 配置 QCloud SDK(参考 UploadFile.m line 42-64)
|
||||
private func configureQCloudSDK(with config: EPQCloudConfig) {
|
||||
let configuration = QCloudServiceConfiguration()
|
||||
configuration.appID = config.appId
|
||||
|
||||
let endpoint = QCloudCOSXMLEndPoint()
|
||||
endpoint.regionName = config.region
|
||||
endpoint.useHTTPS = true
|
||||
|
||||
// 全球加速(参考 UploadFile.m line 56-59)
|
||||
if config.accelerate == 1 {
|
||||
endpoint.suffix = "cos.accelerate.myqcloud.com"
|
||||
}
|
||||
|
||||
configuration.endpoint = endpoint
|
||||
configuration.signatureProvider = self
|
||||
|
||||
// 注册 COS 服务
|
||||
QCloudCOSXMLService.registerDefaultCOSXML(with: configuration)
|
||||
QCloudCOSTransferMangerService.registerDefaultCOSTransferManger(with: configuration)
|
||||
|
||||
// 初始化凭证队列
|
||||
credentialFenceQueue = QCloudCredentailFenceQueue()
|
||||
credentialFenceQueue?.delegate = self
|
||||
}
|
||||
|
||||
// MARK: - QCloudSignatureProvider Protocol
|
||||
|
||||
/// 提供签名(参考 UploadFile.m line 67-104)
|
||||
func signature(
|
||||
with fields: QCloudSignatureFields,
|
||||
request: QCloudBizHTTPRequest,
|
||||
urlRequest: NSMutableURLRequest,
|
||||
compelete: @escaping QCloudHTTPAuthentationContinueBlock
|
||||
) {
|
||||
guard let config = qcloudConfig else {
|
||||
let error = NSError(domain: "com.yumi.qcloud", code: -1,
|
||||
userInfo: [NSLocalizedDescriptionKey: "QCloud 配置未初始化"])
|
||||
compelete(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
let credential = QCloudCredential()
|
||||
credential.secretID = config.secretId
|
||||
credential.secretKey = config.secretKey
|
||||
credential.token = config.sessionToken
|
||||
credential.startDate = Date(timeIntervalSince1970: TimeInterval(config.startTime))
|
||||
credential.expirationDate = Date(timeIntervalSince1970: TimeInterval(config.expireTime))
|
||||
|
||||
let creator = QCloudAuthentationV5Creator(credential: credential)
|
||||
let signature = creator?.signature(forData: urlRequest)
|
||||
compelete(signature, nil)
|
||||
}
|
||||
|
||||
// MARK: - QCloudCredentailFenceQueueDelegate Protocol
|
||||
|
||||
/// 管理凭证(参考 UploadFile.m line 107-133)
|
||||
func fenceQueue(
|
||||
_ queue: QCloudCredentailFenceQueue,
|
||||
requestCreatorWithContinue continueBlock: @escaping QCloudCredentailFenceQueueContinue
|
||||
) {
|
||||
guard let config = qcloudConfig else {
|
||||
let error = NSError(domain: "com.yumi.qcloud", code: -1,
|
||||
userInfo: [NSLocalizedDescriptionKey: "QCloud 配置未初始化"])
|
||||
continueBlock(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
let credential = QCloudCredential()
|
||||
credential.secretID = config.secretId
|
||||
credential.secretKey = config.secretKey
|
||||
credential.token = config.sessionToken
|
||||
credential.startDate = Date(timeIntervalSince1970: TimeInterval(config.startTime))
|
||||
credential.expirationDate = Date(timeIntervalSince1970: TimeInterval(config.expireTime))
|
||||
|
||||
let creator = QCloudAuthentationV5Creator(credential: credential)
|
||||
continueBlock(creator, nil)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user