<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          AWS3 分片上傳的解決方案

          共 44503字,需瀏覽 90分鐘

           ·

          2021-10-11 13:03

          ????關注后回復 “進群” ,拉你進程序員交流群????


          背景

          • 背景:最近一次業(yè)務需求是通過本地錄制或者相冊視頻上傳到亞馬遜服務
          • 研究:前端小伙伴嘗試接入 SDK 發(fā)現(xiàn) AWS3 的上傳部分還是需要做很多工作,比如切片部分 > 5M 及 ETAG 處理等
          • 決策:為了減少前端工作,決定采用后端調(diào)用 S3 SDK 方式,前端通過后端預簽名后的 URL 直接進行文件分段上傳

          Github:TWMultiUploadFileManager[1]

          安裝

          使用Cocoapods安裝,或手動拖入項目

          pod 'TWMultiUploadFileManager'

          方案

          后端執(zhí)行執(zhí)行 AWS3 SDK API,前端通過后端預簽名后的 URL 直接進行文件分段上傳

          步驟

          • step1: 前端對文件選取后進行切片 (> 5M )
          • step2: 請求后端上傳 aws3 資源 url
          • step3: 切片后的文件足個上傳 aws3 服務器
          • step4: 請求后端對上傳完畢的資源文件做校驗

          流程圖

          TWMultiUploadFileManager 組件封裝

          功能

          封裝了對文件分片處理,以及上傳功能

          • 具體功能 ??
            • maxConcurrentOperationCount:上傳線程并發(fā)個數(shù)(默認3 )
            • maxSize:文件大小限制(默認2GB )
            • perSlicedSize:每個分片大小(默認5M)
            • retryTimes:每個分片上傳嘗試次數(shù)(默認3)
            • timeoutInterval:請求時長 (默認 120 s)
            • headerFields:附加 header
            • mimeType:文件上傳類型 不為空 (默認 text/plain)
          • TODO ?
            • 上傳文件最大時長(秒s)默認7200
            • 最大緩沖分片數(shù)(默認100,建議不低于10,不高于100)
            • 附加參數(shù), 目前封裝 put 請求,后續(xù)會補充 post 請求
          • 并發(fā)隊列管理依賴 Queuer[2]
            • 根據(jù)場景:自定義 TWConcurrentOperation

          步驟

          step 1

          從相冊中選擇視頻源(文件)

          // MARK: - Action
          /// 選擇影片
          fileprivate func selectPhotoAction(animated: Bool = true) {
              let imagePicker: TZImagePickerController! = TZImagePickerController(maxImagesCount: 9, delegate: self)
              imagePicker.allowPickingVideo = true
              imagePicker.allowPreview = false
              imagePicker.videoMaximumDuration = Macro.videoMaximumDuration
              imagePicker.maxCropVideoDuration = Int(Macro.videoMaximumDuration)
              imagePicker.allowPickingOriginalPhoto = false
              imagePicker.allowPickingImage = false
              imagePicker.allowPickingMultipleVideo = false
              imagePicker.autoDismiss = false
              imagePicker.navLeftBarButtonSettingBlock = { leftButton in
                  leftButton?.isHidden = true
              }
              present(imagePicker, animated: animated, completion: nil)
          }
              
          /// 獲取視頻資源
          fileprivate func handleRequestVideoURL(asset: PHAsset)  {
              /// loading
              print("loading....")
              self.requestVideoURL(asset: asset) { [weak self] (urlasset, url) in
                  guard let self = self else { return }
                  print("success....")
                  self.url = url
                  self.asset = asset
                  self.uploadVideoView.play(videoUrl: url)
              } failure: { (info) in
                  print("fail....")
              }
          }

          對視頻源文件進行切片并創(chuàng)建上傳資源對象(文件)

          /// 上傳影片
          fileprivate func uploadVideoAction() {
              guard let url = url, let asset = asset ,let outputPath: String = self.fetchVideoPath(url: url) else { return }
              let relativePath: String = TWMultiFileManager.copyVideoFile(atPath: outputPath, dirPathName: Macro.dirPathName)
              // 創(chuàng)建上傳資源對象, 對文件進行切片
              let fileSource: TWMultiUploadFileSource = TWMultiUploadFileSource(
                  configure: self.configure,
                  filePath: relativePath,
                  fileType: .video,
                  localIdentifier: asset.localIdentifier
              )
              // ?? 上傳前需要從服務端獲取每個分片的上傳到亞馬遜 url ,執(zhí)行上傳
              // fileSource.setFileFragmentRequestUrls([])
              
              uploadFileManager.uploadFileSource(fileSource)
          }

          切片的核心邏輯

          /// 切片處理
          - (void)cutFileForFragments {
              NSUInteger offset = self.configure.perSlicedSize;
              // 總片數(shù)
              NSUInteger totalFileFragment = (self.totalFileSize%offset==0)?(self.totalFileSize/offset):(self.totalFileSize/(offset) + 1);
              self.totalFileFragment = totalFileFragment;
              NSMutableArray<TWMultiUploadFileFragment *> *fragments = [NSMutableArray array];
              for (NSUInteger i = 0; i < totalFileFragment; i++) {
                  TWMultiUploadFileFragment *fFragment = [[TWMultiUploadFileFragment alloc] init];
                  fFragment.fragmentIndex = i+1// 從 1 開始
                  fFragment.uploadStatus = TWMultiUploadFileUploadStatusWaiting;
                  fFragment.fragmentOffset = i * offset;
                  if (i != totalFileFragment - 1) {
                      fFragment.fragmentSize = offset;
                  } else {
                      fFragment.fragmentSize = self.totalFileSize - fFragment.fragmentOffset;
                  }
                  /// 關聯(lián)屬性
                  fFragment.localIdentifier = self.localIdentifier;
                  fFragment.fragmentId = [NSString stringWithFormat:@"%@-%ld",self.localIdentifier, (long)i];
                  fFragment.fragmentName = [NSString stringWithFormat:@"%@-%ld.%@",self.localIdentifier, (long)i, self.fileName.pathExtension];
                  fFragment.fileType = self.fileType;
                  fFragment.filePath = self.filePath;
                  fFragment.totalFileFragment = self.totalFileFragment ;
                  fFragment.totalFileSize = self.totalFileSize;
                  [fragments addObject:fFragment];
              }
              self.fileFragments = fragments;
          }

          step 2

          • 業(yè)務邏輯:通過后端調(diào)用 AWS3 SDK 獲取資源文件分片上傳的 urls, 后端配合獲取上傳 aws3 的 url
          • ?? 這里也可以上傳到自己服務端的 urls ,組件已封裝的上傳邏輯 put 請求,具體按各自業(yè)務修改即可
          // ?? 上傳前需要從服務端獲取每個分片的上傳到亞馬遜 url ,執(zhí)行上傳
          fileSource.setFileFragmentRequestUrls([])

          step 3

          /// 執(zhí)行上傳到 AWS3 服務端
          uploadFileManager.uploadFileSource(fileSource)

          設置代理回調(diào),當然也支持 block

          extension ViewControllerTWMultiUploadFileManagerDelegate {
              /// 準備開始上傳
              func prepareStart(_ manager: TWMultiUploadFileManager!, fileSource: TWMultiUploadFileSource!) {
                  
              }
              
              /// 文件上傳中進度
              func uploadingFileManager(_ manager: TWMultiUploadFileManager!, progress: CGFloat) {
                  
              }
              
              /// 完成上傳
              func finish(_ manager: TWMultiUploadFileManager!, fileSource: TWMultiUploadFileSource!) {
              
              }
              
              /// 上傳失敗
              func fail(_ manager: TWMultiUploadFileManager!, fileSource: TWMultiUploadFileSource!, fail code: TWMultiUploadFileUploadErrorCode) {
                  
              }
              
              /// 取消上傳
              func cancleUploadFileManager(_ manager: TWMultiUploadFileManager!, fileSource: TWMultiUploadFileSource!) {
                  
              }
              
              /// 上傳中某片文件失敗
              func failUploadingFileManager(_ manager: TWMultiUploadFileManager!, fileSource: TWMultiUploadFileSource!, fileFragment: TWMultiUploadFileFragment!, fail code: TWMultiUploadFileUploadErrorCode) {
                  
              }
          }

          step 4

          業(yè)務邏輯:最后資源上傳完畢后,請求后端接口;對上傳完畢的資源文件做校驗

          ?? 說明

          • 業(yè)務邏輯是各自的業(yè)務方處理, 本組件封裝的是上傳功能:包括切片,重試次數(shù),文件大小,分片大小,最大支持分片數(shù)等
          • 具體看上傳資源的配置對象
          @interface TWMultiUploadConfigure : NSObject
          /// 同時上傳線程 默認3
          @property (nonatomicassignNSInteger maxConcurrentOperationCount;
          /// 上傳文件最大限制(字節(jié)B)默認2GB
          @property (nonatomicassignNSUInteger maxSize;
          /// todo: 上傳文件最大時長(秒s)默認7200
          @property (nonatomicassignNSUInteger maxDuration;
          /// todo:  最大緩沖分片數(shù)(默認100,建議不低于10,不高于100)
          @property (nonatomicassignNSUInteger maxSliceds;
          /// 每個分片占用大小(字節(jié)B)默認5M
          @property (nonatomicassignNSUInteger perSlicedSize;
          /// 每個分片上傳嘗試次數(shù)(默認3)
          @property (nonatomicassignNSUInteger retryTimes;
          /// 請求時長 默認 120 s
          @property (nonatomicassignNSUInteger timeoutInterval;
          /// todo: 附加參數(shù), 目前封裝 put ,后續(xù)會補充 post 請求
          @property (nonatomicstrongNSDictionary *parameters;
          /// 附加 header
          @property (nonatomicstrongNSDictionary<NSString *, NSString *> *headerFields;
          /// 文件上傳類型 不為空 默認 text/plain
          @property (nonatomicstrongNSString *mimeType;
          @end

          業(yè)務使用案例

          場景

          業(yè)務代碼示例

          項目基于 ReactorKit[3] 框架

          定義視頻各個狀態(tài)

          /// 當前的各種狀態(tài)
          enum SaleHouseVideoUploadStatusEquatable {
              /// 默認是未選視頻的狀態(tài)
              case unseleted
              /// 開始選擇視頻,為了解決兩次選擇視頻失敗沒有變化無法進入監(jiān)聽回調(diào)里
              case beginSeletedVideo
              /// 選擇視頻失敗
              case seletedVideoFail(code: SaleHouseVideoUploadStatusSeletedVideoFailCode)
              /// 選擇視頻成功,準備上傳   導航: "上傳"
              case seletedVideoSuccess(asset: PHAsset, url: URL)
              /// 點擊上傳,準備獲取服務器提交信息
              case requestUploadData
              /// 獲取上傳信息成功
              case requestUploadDataSuccess(uploadInfo: SaleHouseVideoUploadInfoModel)
              /// 分片校驗 & 獲取上傳信息失敗 導航: "重新上傳";code: 錯誤碼后端返回用于統(tǒng)計
              case requestUploadDataFail(code: String?)
              /// 上傳中, 導航: "取消上傳"
              case uploading(progress: CGFloat)
              /// 上傳失敗,導航: "重新上傳"
              case uploadFail(code: TWMultiUploadFileUploadErrorCode)
              /// 文件已上傳 aws3 下一步 提交合并
              case uploadFilesComplete
              /// 上傳完成后提交服務失敗 合并 aws3;code: 錯誤碼后端返回用于統(tǒng)計
              case requestMergeFilesFail(code: String?)
              /// 上傳完成后提交合并 aws3 服務成功  導航: "重新上傳" "提交"
              case requestMergeFilesComplete(mergeInfo: SaleHouseVideoMergeFilesCompleteModel)
              /// 上傳成功,但提交失敗 ;code: 錯誤碼后端返回用于統(tǒng)計
              case requestCommitFail(mergeInfo: SaleHouseVideoMergeFilesCompleteModel, code: String?)
              /// 上傳成功,并提交成功  導航: "重新上傳" "提交"
              case requestCommitSuccess(mergeInfo: SaleHouseVideoMergeFilesCompleteModel)
              ///  取消上傳
              case cancel
              /// 處于編輯狀態(tài)成功,刪除 && 編輯影片未完成預處理預覽 導航: "刪除影片" "重新上傳"
              case requestEditInfoSuccess(fileInfo: SaleHouseVideoEditInfoModel)
              /// 獲取編輯狀態(tài)失敗
              case requestEditInfoFail
              /// 刪除成功, 刪除失敗還是之前狀態(tài)
              case deleteSuccess
          }

          SaleHouseVideoUploadReactor 實現(xiàn) Reactor 協(xié)議

          // MARK: - Reactor
          extension SaleHouseVideoUploadReactorReactor {
              enum Action {
                  /// 視頻校驗
                  case checkSelectedVideo(asset: PHAsset)
                  /// 點擊上傳獲取上傳數(shù)據(jù)
                  case requestUploadData
                  /// 重置視頻選擇
                  case resetSelectedVideo
                  /// 取消上傳
                  case cancleUploadVideo
                  /// 上傳完成后,提交視頻
                  case commitUploadedVideo
                  /// 上傳失敗,重新上傳
                  case reuploadVideo
                  /// 刪除影片
                  case deleteVideo
                  /// 獲取影片信息
                  case requestEditVideoInfo
                  /// 恢復原編輯視頻信息
                  case resetEditVideoInfo
              }
              
              enum Mutation {
                  case setStatus(SaleHouseVideoUploadStatus)
                  case setHUDAction(HUDAction)
              }
              
              struct State {
                  /// 當前的操作的狀態(tài),默認是未選視頻
                  var status: SaleHouseVideoUploadStatus = .unseleted
              }
              
              func mutate(action: Action) -> Observable<Mutation> {
                  switch action {
                  case let .checkSelectedVideo(asset: asset):
                      return checkSelectedVideo(asset: asset)
                  case .cancleUploadVideo:
                      return cancleUploadVideo()
                  case .resetSelectedVideo:
                      return updateStatus(.unseleted)
                  case .requestUploadData:
                      return requestUploadData()
                  case .commitUploadedVideo:
                      return commitUploadedVideo()
                  case .reuploadVideo:
                      return reuploadVideo()
                  case .deleteVideo:
                      return deleteVideo()
                  case .requestEditVideoInfo:
                      return fetchEditVideoInfoRequest()
                  case .resetEditVideoInfo:
                      return resetEditVideoInfo()
                  }
              }
              
              func reduce(state: State, mutation: Mutation) -> State {
                  var newState = state
                  switch mutation {
                  case let .setStatus(status):
                      newState.status = status
                  case let .setHUDAction(action):
                      hudAction.accept(action)
                  }
                  return newState
              }
          }

          步驟

          step 1

          基于 TZImagePickerController 獲取視頻,觸發(fā) checkSelectedVideo 校驗視頻事件

          /// 單個視頻選擇回調(diào)
          func imagePickerController(_ picker: TZImagePickerController!, didFinishPickingPhotos photos: [UIImage]!, sourceAssets assets: [Any]!, isSelectOriginalPhoto: Bool) {
              picker.dismiss(animated: true, completion: nil)
              guard let asset = assets.first asPHAsset else { return }
              self.pickerDissmissActionType = .dismiss
              self.reactor?.action.onNext(.checkSelectedVideo(asset: asset))
          }
          /// 校驗視頻資源
          fileprivate func checkSelectedVideo(asset: PHAsset) -> Observable<Mutation>  {
              DLog("assets :\(asset)")
              let beginSeletedVideo: Observable<Mutation> = updateStatus(.beginSeletedVideo)
              if asset.duration > Macro.videoMaximumDuration {
                  let seletedVideoFail = updateStatus(.seletedVideoFail(code: .overTime))
                  return .concat([beginSeletedVideo, seletedVideoFail])
              }
              let size = SaleHouseVideoUploadHelper.requestVideoSize(asset)
              if size > Macro.videoMaximumSize {
                  let seletedVideoFail: Observable<Mutation> = updateStatus(.seletedVideoFail(code: .overSize))
                  return .concat([beginSeletedVideo, seletedVideoFail])
              }
              switch asset.mediaType {
              case .video:
                  let showLoading = Observable.just(Mutation.setHUDAction(.showHint(Macro.loadingVideoSourceTips)))
                  let requestVideoURL = handleRequestVideoURL(asset: asset)
                  return .concat([showLoading, requestVideoURL])
              default:
                  return .empty()
              }
          }

          /// 請求視頻資源
          fileprivate func handleRequestVideoURL(asset: PHAsset) -> Observable<Mutation> {
              return Observable.create { observer in
                  SaleHouseVideoUploadHelper.requestVideoURL(asset: asset) { [weak self] (urlasset, url) in
                      self?.url = url
                      self?.asset = asset
                      // 視頻選擇成功                                                 
                      observer.onNext(.setStatus(.seletedVideoSuccess(asset: asset, url: url)))
                      observer.onNext(.setHUDAction(.hide))
                      observer.onCompleted()
                  } failure: { (info) in
                      observer.onNext(.setHUDAction(.hideHint(Macro.loadFailVideoSourceTips)))
                      observer.onCompleted()
                  }
                  return Disposables.create()
              }
          }

          用戶點擊上傳,獲取切片上傳資源

          ///  對上傳 aws3 的 rxswift 封裝
          /// - Parameters:
          ///   - uploadFileManager: 分片上傳管理類
          ///   - fileSource: 分片資源類
          ///   - continueUpload: 是否繼續(xù)上傳
          /// - Returns: Observable
          static func startUpload(
              uploadFileManager: TWMultiUploadFileManager,
              fileSource: TWMultiUploadFileSource,
              continueUpload: Bool = false
          )
           -> Observable<SaleHouseVideoUploadStatus> {
              return Observable.create { observer in
                  // 準備開始上傳
                  uploadFileManager.prepareStartUploadBlock = { (manager, fileSource) in
                      observer.onNext(.uploading(progress: 0))
                  }
                  // 文件上傳中進度
                  uploadFileManager.uploadingBlock = { (manager, progress) in
                      observer.onNext(.uploading(progress: progress))
                  }
                  // 完成上傳
                  uploadFileManager.finishUploadBlock = { (manager, fileSource) in
                      observer.onNext(.uploadFilesComplete)
                      observer.onCompleted()
                  }
                  // 上傳失敗
                  uploadFileManager.failUploadBlock = { (manager, fileSource, failErrorCode) in
                      observer.onNext(.uploadFail(code: failErrorCode))
                      observer.onCompleted()
                  }
                  // 取消上傳
                  uploadFileManager.cancleUploadBlock = { (manager, fileSource) in
                      observer.onNext(.cancel)
                      observer.onCompleted()
                  }
                  // 上傳中某片文件失敗
                  uploadFileManager.failUploadingBlock = { (manager, fileSource, fileFragment, failErrorCode) in
                      observer.onNext(.uploadFail(code: failErrorCode))
                  }
                  if continueUpload {
                      uploadFileManager.continue(fileSource)
                  } else {
                      uploadFileManager.uploadFileSource(fileSource)
                  }
                  return Disposables.create()
              }
          }

          /// step1 點擊上傳獲取切片上傳資源文件
          fileprivate func requestUploadData(continueUpload: Bool = false) -> Observable<Mutation> {
              let fetchUploadDataFail: Observable<Mutation> = .concat([setRequestUploadDataStatus(), requestUploadDataFail()])
              guard let url = url, let asset = asset else { return fetchUploadDataFail }
              guard let outputPath: String = SaleHouseVideoUploadHelper.fetchVideoPath(url: url) else { return fetchUploadDataFail }
              let fetchUploadData = Observable<Mutation>.deferred { [weak selfin
                  guard let self = self else { return .empty() }
                  if !continueUpload { // 不是繼續(xù)上傳當前這個資源
                      // 直接移動文件到指定目錄(相對路徑)
                      let relativePath: String = TWMultiFileManager.copyVideoFile(atPath: outputPath, dirPathName: Macro.dirPathName)
                      DLog("relativePath ===> \(relativePath)")
                      self.deleteFile() // 刪除無效文件, 并對視頻進行切片,創(chuàng)建上傳資源對象
                      let fileSource: TWMultiUploadFileSource = TWMultiUploadFileSource(
                          configure: self.configure,
                          filePath: relativePath,
                          fileType: .video,
                          localIdentifier: asset.localIdentifier
                      )
                      self.currentFileSource = fileSource // 更新文件
                  }
                  guard let fileSource = self.currentFileSource else  { return fetchUploadDataFail }
                  let fetchUploadInfoModel = SaleHouseVideoFetchUploadInfoModel(
                      filename: fileSource.fileName,
                      category: SaleHouseVideoUploadHeader.video,
                      part: TWSwiftGuardValueString(fileSource.totalFileFragment),
                      size: TWSwiftGuardValueString(fileSource.totalFileSize),
                      pathExtension: TWSwiftGuardValueString(fileSource.pathExtension)
                  )
                  return self.fetchUploadDataRequest(fetchUploadInfoModel: fetchUploadInfoModel, continueUpload: continueUpload)
              }
              return .concat([setRequestUploadDataStatus(), fetchUploadData])
          }

          step 2

          /// step2 獲取上傳 urls 信息請求
          fileprivate func fetchUploadDataRequest(
              fetchUploadInfoModel: SaleHouseVideoFetchUploadInfoModel,
              continueUpload: Bool = false
          )
           -> Observable<Mutation>  {
              // loading 獲取服務 url
              let showLoading = Observable.just(Mutation.setHUDAction(.showLoading))
              let params: [StringAny] = [
                  "filename" : TWSwiftGuardNullString(fetchUploadInfoModel.filename),
                  "part" : TWSwiftGuardNullString(fetchUploadInfoModel.part),
                  "size" : TWSwiftGuardNullString(fetchUploadInfoModel.size),
                  "category" : TWSwiftGuardNullString(fetchUploadInfoModel.category),
                  "type" : TWSwiftGuardNullString(fetchUploadInfoModel.type),
              ]
              let fetchData = TWSwiftHttpTool.rx.request(
                  type: .RequestPost,
                  url: APIAWSUploadPartUtil,
                  parameters:params,
                  isCheckLogin: true,
                  checkNeedLoginHandler: checkNeedLoginHandler()
              )
              .mapResult()
              .flatMap { [weak self] (status, result) -> Observable<Mutationin
                  guard let self = self else  { return .empty() }
                  var hud: Observable<Mutation> = .just(.setHUDAction(.hide))
                  var uploadInfoStatus: Observable<Mutation> = self.updateStatus(.requestUploadDataFail(code: nil)) // 默認獲取失敗
                  switch status {
                  case let .success(isSuccessStatus, data, msg, _):
                      if isSuccessStatus {
                          if let uploadInfoModel = self.getUploadInfoModel(data: data)  {
                              uploadInfoStatus = self.startUploadVideo(uploadInfoModel: uploadInfoModel, continueUpload: continueUpload)
                          } else {
                              hud = .just(.setHUDAction(.hideHint(Macro.requestUploadDataFailTips)))
                          }
                      } else {
                          hud = .just(.setHUDAction(.hideHint(msg)))
                          uploadInfoStatus = self.updateStatus(.requestUploadDataFail(code: self.fetchErrorCode(data: data)))
                      }
                  case let .error(msg, _):
                      hud = .just(.setHUDAction(.hideHint(msg)))
                  case .noNet:
                      hud = .just(.setHUDAction(.hideHint(TWSwiftHttpTool.Macro.ErrorStr.noNet)))
                  }
                  return .concat([hud, uploadInfoStatus])
              }
              return .concat([showLoading, fetchData])
          }

          step 3

          /// step3 設置獲取上傳 urls ,并開始上傳
          /// - Parameters:
          ///   - uploadInfoModel: 上傳資源對象
          ///   - continueUpload: 是否斷點繼續(xù)上傳
          fileprivate func startUploadVideo(
              uploadInfoModel: SaleHouseVideoUploadInfoModel?,
              continueUpload: Bool = false
          )
           -> Observable<Mutation>  {
              var uploadInfoStatus: Observable<Mutation> = self.requestUploadDataFail() // 默認獲取失敗
              if let uploadInfoModel = uploadInfoModel {
                  guard let parts = uploadInfoModel.parts , let fileSource = self.currentFileSource else { return uploadInfoStatus }
                  uploadInfoStatus = updateStatus(.requestUploadDataSuccess(uploadInfo: uploadInfoModel))
                  // step2 設置上傳服務的 urls
                  fileSource.setFileFragmentRequestUrls(parts.map({ $0.url}))
                  // step3 開始上傳 aws3
                  let uploadStatus = SaleHouseVideoUploadHelper.startUpload(uploadFileManager: self.uploadFileManager, fileSource: fileSource, continueUpload: continueUpload)
                      .flatMap { [weak self] status -> Observable<Mutationin
                          var mutation: Observable<Mutation> = .empty()
                          guard let self = self else { return mutation }
                          switch status {
                          case .uploadFilesComplete: // 上傳完成,繼續(xù)下一步,合并操作
                              mutation = self.requestMergeFiles()
                          default:
                              break
                          }
                          return .concat([
                              self.updateStatus(status),
                              mutation
                          ])
                      }
                  return .concat([uploadInfoStatus, uploadStatus])
              }
              return uploadInfoStatus
          }

          step 4

          上傳 aws3 完畢后,請求后端服務接口對資源做合并校驗

          /// step4 完成上傳提交合併請求
          /// - Parameters:
          ///   - bucketKey: 存儲桶路徑
          ///   - uploadId: 上傳唯一標識,step1獲得
          ///   - category: 分類,如video
          ///   - parts: [{"partNumber":1,"Etag":"xxxxx"},{"partNumber":2,"Etag":"xxxxx"}]
          fileprivate func mergeFilesUploadRequest(_ mergeFilesUploadInfoModel: SaleHouseVideoMergeFilesUploadInfoModel) -> Observable<Mutation>  {
              var params: [StringAny] = [
                  "key" : TWSwiftGuardNullString(mergeFilesUploadInfoModel.key),
                  "upload_id" : TWSwiftGuardNullString(mergeFilesUploadInfoModel.upload_id),
                  "category" : TWSwiftGuardNullString(mergeFilesUploadInfoModel.category),
                  "file_id" : TWSwiftGuardValueNumber(mergeFilesUploadInfoModel.file_id),
              ]
              // etga 校驗
              if let parts = mergeFilesUploadInfoModel.parts {
                  params["parts"] = parts.mj_JSONString()
              }

              let fetchData = TWSwiftHttpTool.rx.request(
                  type: .RequestPost,
                  url: APIAWSUploadComplete,
                  parameters:params,
                  isCheckLogin: true,
                  checkNeedLoginHandler: checkNeedLoginHandler()
              )
              .mapResult()
              .flatMap { [weak self] (status, result) -> Observable<Mutationin
                  guard let self = self else  { return .empty() }
                  var hud: Observable<Mutation> = .just(.setHUDAction(.hide))
                  var mergeFilesInfoStatus: Observable<Mutation> = self.updateStatus(.requestMergeFilesFail(code: nil)) // 默認文件合并失敗
                  switch status {
                  case let .success(isSuccessStatus, data, msg, _):
                      if isSuccessStatus {
                          mergeFilesInfoStatus = self.getMergeUploadStatus(data: data)
                      } else {
                          hud = .just(.setHUDAction(.hideHint(msg)))
                          mergeFilesInfoStatus = self.updateStatus(.requestMergeFilesFail(code: self.fetchErrorCode(data: data)))
                      }
                  case let .error(msg, _):
                      hud = .just(.setHUDAction(.hideHint(msg)))
                  case .noNet:
                      hud = .just(.setHUDAction(.hideHint(TWSwiftHttpTool.Macro.ErrorStr.noNet)))
                  }
                  return .concat([hud, mergeFilesInfoStatus])
              }
              return .concat(fetchData)
          }

          參考

          • 如何使用 AWS CLI 將文件分段上傳到 Amazon S3?[4]
          • AWS3 API\_UploadPart[5]
          • Amazon S3 Transfer Utility for iOS[6]

          參考資料

          [1]

          https://github.com/zeqinjie/TWMultiUploadFileManager

          [2]

          https://github.com/FabrizioBrancati/Queuer

          [3]

          https://github.com/ReactorKit/ReactorKit

          [4]

          https://aws.amazon.com/cn/premiumsupport/knowledge-center/s3-multipart-upload-cli/

          [5]

          https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/API_UploadPart.html

          [6]

          https://aws.amazon.com/cn/blogs/mobile/amazon-s3-transfer-utility-for-ios/


          轉(zhuǎn)自:掘金  zeqinjie

          https://juejin.cn/post/7001041806339096590


          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點擊??卡片,關注后回復【面試題】即可獲取

          在看點這里好文分享給更多人↓↓

          瀏覽 101
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品国产91久久久无码 | 欧美日韩视频伦理网页 | 东方AV100在线观看 | 国产一级a爱做片免费 | 操美女在线观看 |