前言
iOS 开发中需要用到第三方文件储存,那么在国内一般就是使用这两个比较多,国外的话一般就是亚马逊,不过都是大同小异,在这边我代码介绍下阿里腾讯SDK的上传工具如何使用,上代码
一、 阿里云
1.pod 'AliyunOSSiOS' pod导入sdk
- UploadParamModel 为配置model,所有配置信息由后台下发,我这边图片用的是webp格式仅支持iOS14 + 如果你用的普通png,jpeg,那么直接拿UIImage 的data即可。
3.创建一个AliYunUtil 文件,我这边有用到 ffmpegkit 视频压缩(如果不需要就直接拿视频路径上传即可)、以及PromiseKit 异步编程工具(如果不需要则自己修改逻辑),大部分应该是可以看懂的:
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() {
// 初始化具有自动刷新的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
// 请求上传参数
CommonRequest.uploadParams(number: images.count, prefixType: prefixType, type: fileType).done { result in
// 获取后台返回的参数
guard let uploadParams = result.model else {
resolver.reject(RequestError(code: result.code, data: result.data, message: result.msg))
return
}
self.configModel = uploadParams
self.setupOSSClient()
// 准备上传
var uploads: [Promise] = []
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转换成功,大小:\(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
// 请求上传参数
CommonRequest.uploadParams(number: fileURLs.count, prefixType: .chat, type: fileType).done { result in
// 获取后台返回的参数
guard let uploadParams = result.model else {
resolver.reject(RequestError(code: result.code, data: result.data, message: result.msg))
return
}
self.configModel = uploadParams
self.setupOSSClient()
// 准备上传
var uploads: [Promise] = []
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)
}
}
}
/// 上传单个视频和对应的封面
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 {
// 返回Promise
return Promise { resolver in
// 设置参数
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? in
DispatchAfter(after: 1, handler: {
if (task.error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上传错误".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 {
// 返回Promise
return Promise { resolver in
//开始压缩
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 = 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? in
DispatchAfter(after: 1, handler: {
if (task.error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上传错误".localized))
} else {
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
})
return nil
})
}
}
}
}
}
阿里云上传工具的使用
图片(photos) 单张或者多张都可以,所有传完回调 拿到
AliyunOSSiOS.share.uploadImage(images: photos, contentType: .webp, prefixType: .chat, fileType: .image).done {[weak self] imagesArray in
//imagesArray 为路径结果
}.catch {[weak self] error in
}
视频 、带封面上传 回调 拿到视频路径(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导入sdk
- UploadParamModel 为配置model,所有配置信息由后台下发,我这边图片用的是webp格式仅支持iOS14 + 如果你用的普通png,jpeg,那么直接拿UIImage 的data即可。
3.创建一个TencentOSS 文件,我这边有用到 ffmpegkit 视频压缩、以及PromiseKit 异步编程工具,大部分应该是可以看懂的:
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
// 请求上传参数
CommonRequest.uploadParams(number: images.count, prefixType: prefixType, type: fileType).done { result in
// 获取后台返回的参数
guard let uploadParams = result.model else {
resolver.reject(RequestError(code: result.code, data: result.data, message: result.msg))
return
}
self.configModel = uploadParams
self.setupOSSClient()
// 准备上传
var uploads: [Promise] = []
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转换成功,大小:\(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
// 请求上传参数
CommonRequest.uploadParams(number: fileURLs.count, prefixType: .chat, type: fileType).done { result in
// 获取后台返回的参数
guard let uploadParams = result.model else {
resolver.reject(RequestError(code: result.code, data: result.data, message: result.msg))
return
}
self.configModel = uploadParams
self.setupOSSClient()
// 准备上传
var uploads: [Promise] = []
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)
}
}
}
/// 上传单个视频和对应的封面
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 {
// 返回Promise
return Promise { resolver in
// 设置参数
let fileName = name + contentType.suffix
let put : QCloudCOSXMLUploadObjectRequest = QCloudCOSXMLUploadObjectRequest()
// 存储桶名称,由BucketName-Appid 组成
put.bucket = uploadParams.bucket
// 对象键,是对象在 COS 上的完整路径,如果带目录的话,格式为 "video/xxx/movie.mp4"
put.object = fileName
//需要上传的对象内容。可以传入NSData*或者NSURL*类型的变量
put.body = data
//监听上传结果
put.setFinish { (result, error) in
// 获取上传结果
DispatchAfter(after: 0.5, handler: {
if (error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上传错误".localized))
} else {
print(result!)
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
})
}
//监听上传进度
put.sendProcessBlock = { (bytesSent, totalBytesSent,
totalBytesExpectedToSend) in
//bytesSent 本次要发送的字节数(一个大文件可能要分多次发送)
//totalBytesSent 已发送的字节数
//totalBytesExpectedToSend 本次上传要发送的总字节数(即一个文件大小)
print("bytesSent:\(bytesSent),totalBytesSent:\(totalBytesSent),totalBytesExpectedToSend:\(totalBytesExpectedToSend)");
}
QCloudCOSTransferMangerService.defaultCOSTransferManager().uploadObject(put)
}
}
/// 上传视频文件
private func uploadVideo(_ fileURL: URL, contentType: AmazonContentType, uploadParams: UploadParamModel, name: String) -> Promise {
// 返回Promise
return Promise { resolver in
//开始压缩
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()
put.bucket = uploadParams.bucket
put.object = fileName
put.body = outFileVideoUrl
//监听上传结果
put.setFinish { (result, error) in
// 获取上传结果
if (error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上传错误".localized))
} else {
print(result!)
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
}
//监听上传进度
put.sendProcessBlock = { (bytesSent, totalBytesSent,
totalBytesExpectedToSend) in
//bytesSent 本次要发送的字节数(一个大文件可能要分多次发送)
//totalBytesSent 已发送的字节数
//totalBytesExpectedToSend 本次上传要发送的总字节数(即一个文件大小)
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("配置后台返回的临时文件进行签名===\(self.configModel.kj.JSONString())")
let credential = QCloudCredential.init();
// 临时密钥 SecretId
credential.secretID = self.configModel.tmpSecretId
// 临时密钥 SecretKey
credential.secretKey = self.configModel.tmpSecretKey
// 临时密钥 Token
credential.token = self.configModel.sessionToken
/** 强烈建议返回服务器时间作为签名的开始时间, 用来避免由于用户手机本地时间偏差过大导致的签名不正确(参数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);
// 注意 这里不要对urlRequst 进行copy以及mutableCopy操作
let signature = creator?.signature(forData: urlRequst);
continueBlock(signature,nil);
}
}
使用方法和阿里云一样 把 AliYunUtil 换成 TencentOSS 即可 ,有不明白的可以私❤️
⚠️ 腾讯云上面用的是 分片上传,如果不需要则使用简单上传。这样写
//视频
let fileName = name + ".mp4"
let outFileVideoUrl = NSURL(fileURLWithPath: outFilePath)
let putObject = QCloudPutObjectRequest.init()
putObject.bucket = uploadParams.bucket
putObject.body = outFileVideoUrl
putObject.object = fileName
putObject.finishBlock = {(result,error) in
// 获取上传结果
if (error != nil) {
resolver.reject(RequestError(code: "", data: "", message: "上传错误".localized))
} else {
print(result!)
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
}
QCloudCOSXMLService.defaultCOSXML().putObject(putObject)
//图片
// 设置参数
let fileName = name + contentType.suffix
let putObject = QCloudPutObjectRequest.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: "上传错误".localized))
} else {
print(result!)
let key = fileName.components(separatedBy: "/").last ?? fileName
resolver.fulfill(key)
}
})
}
QCloudCOSXMLService.defaultCOSXML().putObject(putObject)