前言
iOS 開發(fā)中需要用到第三方文件儲(chǔ)存调窍,那么在國(guó)內(nèi)一般就是使用這兩個(gè)比較多,國(guó)外的話一般就是亞馬遜张遭,不過都是大同小異邓萨,在這邊我代碼介紹下阿里騰訊SDK的上傳工具如何使用,上代碼
一菊卷、 阿里云
1.pod 'AliyunOSSiOS' pod導(dǎo)入sdk
- UploadParamModel 為配置model缔恳,所有配置信息由后臺(tái)下發(fā),我這邊圖片用的是webp格式僅支持iOS14 + 如果你用的普通png洁闰,jpeg歉甚,那么直接拿UIImage 的data即可。
3.創(chuàng)建一個(gè)AliYunUtil 文件,我這邊有用到 ffmpegkit 視頻壓縮(如果不需要就直接拿視頻路徑上傳即可)扑眉、以及PromiseKit 異步編程工具(如果不需要?jiǎng)t自己修改邏輯)纸泄,大部分應(yīng)該是可以看懂的:
import Foundation
import UIKit
import PromiseKit
import libwebp
import AVFoundation
import ffmpegkit
import AliyunOSSiOS
class AliYunUtil {
static let share = AliYunUtil()
private var client: OSSClient!
//配置model
var configModel: UploadParamModel!
func setupOSSClient() {
// 初始化具有自動(dòng)刷新的provider
let credentialProvider = OSSStsTokenCredentialProvider(accessKeyId: self.configModel.accessKeyId,
secretKeyId: self.configModel.accessKeySecret,
securityToken: self.configModel.securityToken)
self.client = OSSClient(endpoint: self.configModel.endpoint, credentialProvider: credentialProvider)
}
// MARK: - Public
/// 上傳圖片
public func uploadImage(images: [UIImage], contentType: AmazonContentType, prefixType: AmazonURLType, fileType: AmazonFileType) -> Promise<[String]> {
// 返回Promise
return Promise<[String]> { resolver in
// 請(qǐng)求上傳參數(shù)
CommonRequest.uploadParams(number: images.count, prefixType: prefixType, type: fileType).done { result in
// 獲取后臺(tái)返回的參數(shù)
guard let uploadParams = result.model else {
resolver.reject(RequestError(code: result.code, data: result.data, message: result.msg))
return
}
self.configModel = uploadParams
self.setupOSSClient()
// 準(zhǔn)備上傳
var uploads: [Promise<String>] = []
DispatchQueue.global().async {
for (i, image) in images.enumerated() {
let data_count = image.jpegData(compressionQuality: 1)?.count ?? 0
print("原始圖片大小:\(data_count)")
let corver_data = UIImage.image(toWebP: image, compressionQuality: 100)
print("webp轉(zhuǎn)換成功,大醒亍:\(corver_data.count)");
if i < uploadParams.nameList.count {
let name = uploadParams.nameList[i]
let uploadRequest = self.uploadData(corver_data, contentType: contentType, uploadParams: uploadParams, name: name)
uploads.append(uploadRequest)
}
}
// 上傳
when(fulfilled: uploads).done { names in
resolver.fulfill(names)
}.catch { error in
resolver.reject(error)
}
}
}.catch { error in
resolver.reject(error)
}
}
}
/// 上傳視頻
public func uploadVideo(fileURLs: [URL], contentType: AmazonContentType, prefixType: AmazonURLType, fileType: AmazonFileType) -> Promise<[String]> {
// 返回Promise
return Promise<[String]> { resolver in
// 請(qǐng)求上傳參數(shù)
CommonRequest.uploadParams(number: fileURLs.count, prefixType: .chat, type: fileType).done { result in
// 獲取后臺(tái)返回的參數(shù)
guard let uploadParams = result.model else {
resolver.reject(RequestError(code: result.code, data: result.data, message: result.msg))
return
}
self.configModel = uploadParams
self.setupOSSClient()
// 準(zhǔn)備上傳
var uploads: [Promise<String>] = []
for (i, fileURL) in fileURLs.enumerated() {
if i < uploadParams.nameList.count {
let name = uploadParams.nameList[i]
let uploadRequest = self.uploadVideo(fileURL, contentType: contentType, uploadParams: uploadParams, name: name)
uploads.append(uploadRequest)
}
}
// 上傳
when(fulfilled: uploads).done { names in
resolver.fulfill(names)
}.catch { error in
resolver.reject(error)
}
}.catch { error in
resolver.reject(error)
}
}
}
/// 上傳單個(gè)視頻和對(duì)應(yīng)的封面
public func uploadVideoAndCover(coverImage: UIImage, videoURL:URL, contentType: AmazonContentType) -> Promise<[String]> {
return Promise<[String]> { resolver in
var uploads: [Promise<[String]>] = []
let image_request = self.uploadImage(images: [coverImage], contentType: .webp, prefixType: .chat, fileType: .image)
uploads.append(image_request)
let video_request = self.uploadVideo(fileURLs: [videoURL], contentType: contentType, prefixType: .chat, fileType: .video)
uploads.append(video_request)
// 上傳
when(fulfilled: uploads).done { names in
guard let imageURLs = names.first, let videoURLs = names.last else {
return
}
guard let videoURL = videoURLs.first, let imageURL = imageURLs.first else {
return
}
resolver.fulfill([imageURL, videoURL])
}.catch { error in
resolver.reject(error)
}
}
}
// MARK: - Private
/// 上傳圖片文件
private func uploadData(_ data: Data, contentType: AmazonContentType, uploadParams: UploadParamModel, name: String) -> Promise<String> {
// 返回Promise
return Promise<String> { resolver in
// 設(shè)置參數(shù)
let fileName = name + contentType.suffix
let put = OSSPutObjectRequest()
put.bucketName = uploadParams.bucketName
put.objectKey = fileName
put.contentType = "application/octet-stream"
put.uploadingData = data
put.expires = uploadParams.expiration
put.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
print("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)");
}
let putTask = self.client.putObject(put)
putTask.continue({(task) -> OSSTask<AnyObject>? in
DispatchAfter(after: 1, handler: {
if (task.error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上傳錯(cuò)誤".localized))
} else {
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
})
return nil
})
}
}
/// 上傳視頻文件
private func uploadVideo(_ fileURL: URL, contentType: AmazonContentType, uploadParams: UploadParamModel, name: String) -> Promise<String> {
// 返回Promise
return Promise<String> { resolver in
//開始?jí)嚎s
let inputPath = fileURL
let outFilePath = WMCameraFileTools.wm_createFileUrl("mp4")
let avAsset = AVURLAsset(url: fileURL)
let array = avAsset.tracks
var size = CGSize.zero
var bitNumber : Float = 2.0
for track in array{
if track.mediaType == .video{
size = track.naturalSize
let bit = track.estimatedDataRate
bitNumber = Float((bit / 1000.0 / 1000.0))
}
}
bitNumber = bitNumber < 2.0 ? bitNumber : 2.0
let z = String(format: "%.2f", bitNumber)
var cmd = "-i \(inputPath) -b:v \(z)M -vf scale=720:-2 -vcodec libx264 -y \(outFilePath)"
if (size.width < size.height) {
cmd = "-i \(inputPath) -b:v \(z)M -vf scale=-2:720 -vcodec libx264 -y \(outFilePath)"
}
FFmpegKit.executeAsync(cmd) { result in
DispatchQueue.main.async {
// 設(shè)置參數(shù)
let fileName = name + ".mp4"
let outFileVideoUrl = URL(fileURLWithPath: outFilePath)
let put = OSSPutObjectRequest()
put.bucketName = uploadParams.bucketName
put.objectKey = fileName
put.contentType = "application/octet-stream"
put.uploadingFileURL = outFileVideoUrl
put.expires = uploadParams.expiration
put.uploadProgress = { (bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) -> Void in
print("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)");
}
let putTask = self.client.putObject(put)
putTask.continue({(task) -> OSSTask<AnyObject>? in
DispatchAfter(after: 1, handler: {
if (task.error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上傳錯(cuò)誤".localized))
} else {
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
})
return nil
})
}
}
}
}
}
阿里云上傳工具的使用
圖片(photos) 單張或者多張都可以聘裁,所有傳完回調(diào) 拿到
AliyunOSSiOS.share.uploadImage(images: photos, contentType: .webp, prefixType: .chat, fileType: .image).done {[weak self] imagesArray in
//imagesArray 為路徑結(jié)果
}.catch {[weak self] error in
}
視頻 、帶封面上傳 回調(diào) 拿到視頻路徑(fileURL)弓千、和第一幀(coverImage)圖片路徑
AliyunOSSiOS.share.uploadVideoAndCover(coverImage: coverImage, videoURL: fileURL, contentType: .video(type: fileURL.pathExtension)).done {[weak self] result in
//result.first!,
//result.last!
}.catch {[weak self] error in
}
二衡便、 騰訊云
1.pod 'QCloudCOSXML' pod導(dǎo)入sdk
- UploadParamModel 為配置model,所有配置信息由后臺(tái)下發(fā)计呈,我這邊圖片用的是webp格式僅支持iOS14 + 如果你用的普通png砰诵,jpeg,那么直接拿UIImage 的data即可捌显。
3.創(chuàng)建一個(gè)TencentOSS 文件,我這邊有用到 ffmpegkit 視頻壓縮茁彭、以及PromiseKit 異步編程工具,大部分應(yīng)該是可以看懂的:
import Foundation
import PromiseKit
import ffmpegkit
import QCloudCOSXML
import QCloudCore
class TencentOSS : NSObject{
static let share = TencentOSS()
var configModel: UploadParamModel!
func setupOSSClient() {
/// 配置騰訊云桶
let config = QCloudServiceConfiguration.init()
config.signatureProvider = self
config.appID = self.configModel.appId
let endpoint = QCloudCOSXMLEndPoint.init()
endpoint.regionName = self.configModel.region
endpoint.useHTTPS = true
config.endpoint = endpoint
QCloudCOSXMLService.registerDefaultCOSXML(with: config)
QCloudCOSTransferMangerService.registerDefaultCOSTransferManger(with: config)
}
// MARK: - Public
/// 上傳圖片
public func uploadImage(images: [UIImage], contentType: AmazonContentType, prefixType: AmazonURLType, fileType: AmazonFileType) -> Promise<[String]> {
// 返回Promise
return Promise<[String]> { resolver in
// 請(qǐng)求上傳參數(shù)
CommonRequest.uploadParams(number: images.count, prefixType: prefixType, type: fileType).done { result in
// 獲取后臺(tái)返回的參數(shù)
guard let uploadParams = result.model else {
resolver.reject(RequestError(code: result.code, data: result.data, message: result.msg))
return
}
self.configModel = uploadParams
self.setupOSSClient()
// 準(zhǔn)備上傳
var uploads: [Promise<String>] = []
DispatchQueue.global().async {
for (i, image) in images.enumerated() {
let data_count = image.jpegData(compressionQuality: 1)?.count ?? 0
print("原始圖片大蟹鐾帷:\(data_count)")
let corver_data = UIImage.image(toWebP: image, compressionQuality: 100) as NSData
print("webp轉(zhuǎn)換成功,大欣矸巍:\(corver_data.count)");
if i < uploadParams.nameList.count {
let name = uploadParams.nameList[i]
let uploadRequest = self.uploadData(corver_data, contentType: contentType, uploadParams: uploadParams, name: name)
uploads.append(uploadRequest)
}
}
// 上傳
when(fulfilled: uploads).done { names in
resolver.fulfill(names)
}.catch { error in
resolver.reject(error)
}
}
}.catch { error in
resolver.reject(error)
}
}
}
/// 上傳視頻
public func uploadVideo(fileURLs: [URL], contentType: AmazonContentType, prefixType: AmazonURLType, fileType: AmazonFileType) -> Promise<[String]> {
// 返回Promise
return Promise<[String]> { resolver in
// 請(qǐng)求上傳參數(shù)
CommonRequest.uploadParams(number: fileURLs.count, prefixType: .chat, type: fileType).done { result in
// 獲取后臺(tái)返回的參數(shù)
guard let uploadParams = result.model else {
resolver.reject(RequestError(code: result.code, data: result.data, message: result.msg))
return
}
self.configModel = uploadParams
self.setupOSSClient()
// 準(zhǔn)備上傳
var uploads: [Promise<String>] = []
for (i, fileURL) in fileURLs.enumerated() {
if i < uploadParams.nameList.count {
let name = uploadParams.nameList[i]
let uploadRequest = self.uploadVideo(fileURL, contentType: contentType, uploadParams: uploadParams, name: name)
uploads.append(uploadRequest)
}
}
// 上傳
when(fulfilled: uploads).done { names in
resolver.fulfill(names)
}.catch { error in
resolver.reject(error)
}
}.catch { error in
resolver.reject(error)
}
}
}
/// 上傳單個(gè)視頻和對(duì)應(yīng)的封面
public func uploadVideoAndCover(coverImage: UIImage, videoURL:URL, contentType: AmazonContentType) -> Promise<[String]> {
return Promise<[String]> { resolver in
var uploads: [Promise<[String]>] = []
let image_request = self.uploadImage(images: [coverImage], contentType: .webp, prefixType: .chat, fileType: .image)
uploads.append(image_request)
let video_request = self.uploadVideo(fileURLs: [videoURL], contentType: contentType, prefixType: .chat, fileType: .video)
uploads.append(video_request)
// 上傳
when(fulfilled: uploads).done { names in
guard let imageURLs = names.first, let videoURLs = names.last else {
return
}
guard let videoURL = videoURLs.first, let imageURL = imageURLs.first else {
return
}
resolver.fulfill([imageURL, videoURL])
}.catch { error in
resolver.reject(error)
}
}
}
// MARK: - Private
/// 上傳圖片文件
private func uploadData(_ data: NSData, contentType: AmazonContentType, uploadParams: UploadParamModel, name: String) -> Promise<String> {
// 返回Promise
return Promise<String> { resolver in
// 設(shè)置參數(shù)
let fileName = name + contentType.suffix
let put : QCloudCOSXMLUploadObjectRequest = QCloudCOSXMLUploadObjectRequest<AnyObject>()
// 存儲(chǔ)桶名稱,由BucketName-Appid 組成
put.bucket = uploadParams.bucket
// 對(duì)象鍵善镰,是對(duì)象在 COS 上的完整路徑妹萨,如果帶目錄的話,格式為 "video/xxx/movie.mp4"
put.object = fileName
//需要上傳的對(duì)象內(nèi)容炫欺『跬辏可以傳入NSData*或者NSURL*類型的變量
put.body = data
//監(jiān)聽上傳結(jié)果
put.setFinish { (result, error) in
// 獲取上傳結(jié)果
DispatchAfter(after: 0.5, handler: {
if (error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上傳錯(cuò)誤".localized))
} else {
print(result!)
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
})
}
//監(jiān)聽上傳進(jìn)度
put.sendProcessBlock = { (bytesSent, totalBytesSent,
totalBytesExpectedToSend) in
//bytesSent 本次要發(fā)送的字節(jié)數(shù)(一個(gè)大文件可能要分多次發(fā)送)
//totalBytesSent 已發(fā)送的字節(jié)數(shù)
//totalBytesExpectedToSend 本次上傳要發(fā)送的總字節(jié)數(shù)(即一個(gè)文件大小)
print("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)");
}
QCloudCOSTransferMangerService.defaultCOSTransferManager().uploadObject(put)
}
}
/// 上傳視頻文件
private func uploadVideo(_ fileURL: URL, contentType: AmazonContentType, uploadParams: UploadParamModel, name: String) -> Promise<String> {
// 返回Promise
return Promise<String> { resolver in
//開始?jí)嚎s
let inputPath = fileURL
let outFilePath = WMCameraFileTools.wm_createFileUrl("mp4")
let avAsset = AVURLAsset(url: fileURL)
let array = avAsset.tracks
var size = CGSize.zero
var bitNumber : Float = 2.0
for track in array{
if track.mediaType == .video{
size = track.naturalSize
let bit = track.estimatedDataRate
bitNumber = Float((bit / 1000.0 / 1000.0))
}
}
bitNumber = bitNumber < 2.0 ? bitNumber : 2.0
let z = String(format: "%.2f", bitNumber)
var cmd = "-i \(inputPath) -b:v \(z)M -vf scale=720:-2 -vcodec libx264 -y \(outFilePath)"
if (size.width < size.height) {
cmd = "-i \(inputPath) -b:v \(z)M -vf scale=-2:720 -vcodec libx264 -y \(outFilePath)"
}
FFmpegKit.executeAsync(cmd) { result in
DispatchQueue.main.async {
let fileName = name + ".mp4"
let outFileVideoUrl = NSURL(fileURLWithPath: outFilePath)
let put : QCloudCOSXMLUploadObjectRequest = QCloudCOSXMLUploadObjectRequest<AnyObject>()
put.bucket = uploadParams.bucket
put.object = fileName
put.body = outFileVideoUrl
//監(jiān)聽上傳結(jié)果
put.setFinish { (result, error) in
// 獲取上傳結(jié)果
if (error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上傳錯(cuò)誤".localized))
} else {
print(result!)
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
}
//監(jiān)聽上傳進(jìn)度
put.sendProcessBlock = { (bytesSent, totalBytesSent,
totalBytesExpectedToSend) in
//bytesSent 本次要發(fā)送的字節(jié)數(shù)(一個(gè)大文件可能要分多次發(fā)送)
//totalBytesSent 已發(fā)送的字節(jié)數(shù)
//totalBytesExpectedToSend 本次上傳要發(fā)送的總字節(jié)數(shù)(即一個(gè)文件大衅仿濉)
print("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)");
}
QCloudCOSTransferMangerService.defaultCOSTransferManager().uploadObject(put)
}
}
}
}
}
// 配置簽名
extension TencentOSS : QCloudSignatureProvider {
func signature(with fileds: QCloudSignatureFields!, request: QCloudBizHTTPRequest!, urlRequest urlRequst: NSMutableURLRequest!, compelete continueBlock: QCloudHTTPAuthentationContinueBlock!) {
PLog("配置后臺(tái)返回的臨時(shí)文件進(jìn)行簽名===\(self.configModel.kj.JSONString())")
let credential = QCloudCredential.init();
// 臨時(shí)密鑰 SecretId
credential.secretID = self.configModel.tmpSecretId
// 臨時(shí)密鑰 SecretKey
credential.secretKey = self.configModel.tmpSecretKey
// 臨時(shí)密鑰 Token
credential.token = self.configModel.sessionToken
/** 強(qiáng)烈建議返回服務(wù)器時(shí)間作為簽名的開始時(shí)間, 用來避免由于用戶手機(jī)本地時(shí)間偏差過大導(dǎo)致的簽名不正確(參數(shù)startTime和expiredTime單位為秒)
*/
credential.startDate = Date.init(timeIntervalSince1970: TimeInterval(self.configModel.startTime)!)
credential.expirationDate = Date.init(timeIntervalSince1970: TimeInterval(self.configModel.expiredTime)!)
let creator = QCloudAuthentationV5Creator.init(credential: credential);
// 注意 這里不要對(duì)urlRequst 進(jìn)行copy以及mutableCopy操作
let signature = creator?.signature(forData: urlRequst);
continueBlock(signature,nil);
}
}
使用方法和阿里云一樣 把 AliYunUtil 換成 TencentOSS 即可 树姨,有不明白的可以私??
?? 騰訊云上面用的是 分片上傳摩桶,如果不需要?jiǎng)t使用簡(jiǎn)單上傳。這樣寫
//視頻
let fileName = name + ".mp4"
let outFileVideoUrl = NSURL(fileURLWithPath: outFilePath)
let putObject = QCloudPutObjectRequest<AnyObject>.init()
putObject.bucket = uploadParams.bucket
putObject.body = outFileVideoUrl
putObject.object = fileName
putObject.finishBlock = {(result,error) in
// 獲取上傳結(jié)果
if (error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上傳錯(cuò)誤".localized))
} else {
print(result!)
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
}
QCloudCOSXMLService.defaultCOSXML().putObject(putObject)
//圖片
// 設(shè)置參數(shù)
let fileName = name + contentType.suffix
let putObject = QCloudPutObjectRequest<AnyObject>.init()
putObject.bucket = uploadParams.bucket
putObject.body = data
putObject.object = fileName
putObject.finishBlock = {(result,error) in
DispatchAfter(after: 0.5, handler: {
if (error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上傳錯(cuò)誤".localized))
} else {
print(result!)
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
})
}
QCloudCOSXMLService.defaultCOSXML().putObject(putObject)