Swift 云存储(阿里云、腾讯云) 上传工具看我就够了

前言

iOS 开发中需要用到第三方文件储存,那么在国内一般就是使用这两个比较多,国外的话一般就是亚马逊,不过都是大同小异,在这边我代码介绍下阿里腾讯SDK的上传工具如何使用,上代码

一、 阿里云

1.pod 'AliyunOSSiOS' pod导入sdk

  1. 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

  1. 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)

你可能感兴趣的:(Swift 云存储(阿里云、腾讯云) 上传工具看我就够了)