import IUploadPattern from "./IUploadPattern" ; import Uploader from "../Uploader" ; import { ChunkTask , Chunk } from "../task/ChunkTask" ; import log from "../../util/Log" ; /**
 * 分块上传
 */ class ChunkUploadPattern implements IUploadPattern { private uploader : Uploader ; private task : ChunkTask ; constructor ( uploader ) { this . uploader = uploader ; } init ( uploader ) { this . uploader = uploader ; } upload ( task ) { this . task = task ; this . uploader . getToken ( task ) . then ( ( token ) => { task . startDate = new Date ( ) ; this . uploadBlock ( token ) ; } ) ; } private uploadBlock ( token ) { log . d ( `准备开始上传块` ) ; let chain = Promise . resolve ( ) ; log . d ( `共${ this . task . blocks . length }块等待上传` ) ; log . d ( `共${ this . task . totalChunkCount }分片等待上传` ) ; this . task . blocks . forEach ( ( block , blockIndex ) => { block . chunks . forEach ( ( chunk , chunkIndex ) => { chain = chain . then ( ( ) => { log . d ( `开始上传第${ ( blockIndex + 1 ) }块,第${ ( chunkIndex + 1 ) }片` ) ; return this . uploadChunk ( chunk , token ) ; } ) ; } ) ; } ) ; chain . then ( ( ) => { return this . concatChunks ( token ) ; } ) . then ( ( ) => { if ( this . uploader . isTaskQueueFinish ( ) ) { log . d ( `上传任务队列已结束` ) ; this . uploader . tasking = false ; this . uploader . listener . onFinish ( this . uploader . taskQueue ) ; } } ) . catch ( ( response ) => { log . w ( `${ this . task . file . name }分块上传失败` ) ; this . task . error = response ; this . task . isSuccess = false ; this . task . isFinish = true ; this . task . endDate = new Date ( ) ; this . uploader . listener . onTaskFail ( this . task ) ; } ) ; } private uploadChunk ( chunk , token ) { return new Promise ( ( resolve , reject ) => { let isFirstChunkInBlock = chunk . block . chunks . indexOf ( chunk ) == 0 ; let chunkIndex = chunk . block . chunks . indexOf ( chunk ) ; let prevChunk = isFirstChunkInBlock ? null : chunk . block . chunks [ chunkIndex - 1 ] ; let url = isFirstChunkInBlock ? this . getUploadBlockUrl ( chunk . block . data . size ) : this . getUploadChunkUrl ( chunk . start , prevChunk ? prevChunk . ctx : null , prevChunk ? prevChunk . host : null ) ; let xhr = new XMLHttpRequest ( ) ; xhr . open ( 'POST' , url += ( ( /\?/ ) . test ( url ) ? "&" : "?" ) + ( new Date ( ) ) . getTime ( ) , true ) ; xhr . setRequestHeader ( 'Content-Type' , 'application/octet-stream' ) ; xhr . setRequestHeader ( 'Authorization' , `UpToken ${ token }` ) ; xhr . upload . onprogress = ( e ) => { if ( e . lengthComputable ) { let progress = Math . round ( ( ( this . task . finishedBlocksSize + chunk . start + e . loaded ) / this . task . file . size ) * 100 ) ; if ( this . task . progress < progress ) { this . task . progress = progress ; this . uploader . listener . onTaskProgress ( this . task ) ; } } } ; xhr . upload . onload = ( ) => { let progress = Math . round ( ( ( this . task . finishedBlocksSize + chunk . start + chunk . data . size ) / this . task . file . size ) * 100 ) ; if ( this . task . progress < progress ) { this . task . progress = progress ; this . uploader . listener . onTaskProgress ( this . task ) ; } } ; xhr . onreadystatechange = ( ) => { if ( xhr . readyState == XMLHttpRequest . DONE ) { if ( xhr . status == 200 && xhr . responseText != '' ) { let result = JSON . parse ( xhr . responseText ) ; chunk . isFinish = true ; chunk . processing = false ; chunk . ctx = result . ctx ; chunk . host = result . host ; let chunkIndex = chunk . block . chunks . indexOf ( chunk ) ; let hasNextChunkInThisBlock = chunkIndex != chunk . block . chunks . length - 1 ; if ( ! hasNextChunkInThisBlock ) { chunk . block . isFinish = true ; chunk . block . processing = false ; } resolve ( ) ; } else { reject ( xhr . response ) ; } } } ; xhr . send ( chunk . data ) ; } ) ; } private concatChunks ( token ) { return new Promise ( ( resolve , reject ) => { let encodedKey = this . task . key ? btoa ( this . task . key ) : null ; if ( encodedKey ) { encodedKey = encodedKey . replace ( /\+/g , '-' ) ; encodedKey = encodedKey . replace ( /\//g , '_' ) ; } let url = this . getMakeFileUrl ( this . task . file . size , encodedKey ) ; let ctxListString = '' ; for ( let block of this . task . blocks ) { let lastChunk = block . chunks [ block . chunks . length - 1 ] ; ctxListString += lastChunk . ctx + ',' ; } if ( ctxListString . endsWith ( ',' ) ) { ctxListString = ctxListString . substring ( 0 , ctxListString . length - 1 ) ; } let xhr = new XMLHttpRequest ( ) ; xhr . open ( 'POST' , url += ( ( /\?/ ) . test ( url ) ? "&" : "?" ) + ( new Date ( ) ) . getTime ( ) , true ) ; xhr . setRequestHeader ( 'Content-Type' , 'text/plain' ) ; xhr . setRequestHeader ( 'Authorization' , `UpToken ${ token }` ) ; xhr . onreadystatechange = ( ) => { if ( xhr . readyState == XMLHttpRequest . DONE ) { this . task . isFinish = true ; if ( xhr . status == 200 && xhr . responseText != '' ) { let result = JSON . parse ( xhr . responseText ) ; this . task . isSuccess = true ; this . task . result = result ; this . task . endDate = new Date ( ) ; this . uploader . listener . onTaskSuccess ( this . task ) ; resolve ( ) ; } else if ( this . retryTask ( this . task ) ) { log . w ( `${ this . task . file . name }分块上传失败,准备开始重传` ) ; this . uploader . listener . onTaskRetry ( this . task ) ; } else { reject ( xhr . response ) ; } } } ; xhr . send ( ctxListString ) ; } ) ; } param blockSize returns { string } private getUploadBlockUrl ( blockSize ) { return `${ this . uploader . domain }/mkblk/${ blockSize }` ; } param start param ctx param host private getUploadChunkUrl ( start , ctx , host ? ) { return `${ host ? host : this . uploader . domain }/bput/${ ctx }/${ start }/` ; } param fileSize param encodedKey returns { string } private getMakeFileUrl ( fileSize , encodedKey ) { if ( encodedKey ) { return `${ this . uploader . domain }/mkfile/${ fileSize }/key/${ encodedKey }` ; } else { return `${ this . uploader . domain }/mkfile/${ fileSize }` ; } } private retryTask ( task ) { if ( task . retry >= this . uploader . retry ) { log . w ( `${ task . file . name }达到重传次数上限${ this . uploader . retry },停止重传` ) ; return false ; } task . retry ++ ; log . w ( `${ task . file . name }开始重传,当前重传次数${ task . retry }` ) ; return true ; } } export default ChunkUploadPattern ;