import BaseTask from "./task/BaseTask" ; import DirectTask from "./task/DirectTask" ; import { ChunkTask } from "./task/ChunkTask" ; import TokenFunc from "./TokenFunc" ; import UUID from "./uuid/UUID" ; import UploaderBuilder from "./UploaderBuilder" ; import log from "../util/Log" ; import Interceptor from "./interceptor/UploadInterceptor" ; import UploadListener from "./hook/UploadListener" ; import SimpleUploadListener from "./hook/SimpleUploadListener" ; import DirectUploadPattern from "./pattren/DirectUploadPattern" ; import ChunkUploadPattern from "./pattren/ChunkUploadPattern" ; import "../util/Polyfill" ; class Uploader { private FILE_INPUT_EL_ID : string = 'qiniu4js-input' ; private _fileInputId : string ; private _fileInput : HTMLInputElement ; private _token : string ; private _taskQueue : BaseTask [ ] = [ ] ; private _tasking : boolean = false ; private _retry : number ; private _size : number ; private _chunk : boolean ; private _auto : boolean ; private _multiple : boolean ; private _accept : string [ ] ; private _button : string ; private _buttonEventName : string ; private _compress : number ; private _scale : number [ ] = [ ] ; private _listener : UploadListener ; private _saveKey : boolean | string = false ; private _tokenFunc : TokenFunc ; private _tokenShare : boolean ; private _interceptors : Interceptor [ ] ; private _domain : string ; constructor ( builder ) { this . _retry = builder . getRetry ; this . _size = builder . getSize ; this . _chunk = builder . getChunk ; this . _auto = builder . getAuto ; this . _multiple = builder . getMultiple ; this . _accept = builder . getAccept ; this . _button = builder . getButton ; this . _buttonEventName = builder . getButtonEventName ; this . _compress = builder . getCompress ; this . _scale = builder . getScale ; this . _saveKey = builder . getSaveKey ; this . _tokenFunc = builder . getTokenFunc ; this . _tokenShare = builder . getTokenShare ; this . _listener = Object . assign ( new SimpleUploadListener ( ) , builder . getListener ) ; this . _interceptors = builder . getInterceptors ; this . _domain = builder . getDomain ; this . _fileInputId = `${ this . FILE_INPUT_EL_ID }_${ new Date ( ) . getTime ( ) }` ; log . enable = builder . getIsDebug ; this . validateOptions ( ) ; this . init ( ) ; } /**
     * 初始化操作
     */ private init ( ) { this . initFileInputEl ( ) ; } /**
     * 初始化file input element
     */ private initFileInputEl ( ) { let exist = < HTMLInputElement > document . getElementById ( this . _fileInputId ) ; this . _fileInput = exist ? exist : document . createElement ( 'input' ) ; this . fileInput . type = 'file' ; this . fileInput . id = this . _fileInputId ; this . fileInput . style . display = 'none' ; if ( this . multiple ) { this . fileInput . multiple = true ; } if ( this . accept && this . accept . length != 0 ) { let acceptValue = '' ; for ( let value of this . accept ) { acceptValue += value ; acceptValue += ',' ; } if ( acceptValue . endsWith ( ',' ) ) { acceptValue = acceptValue . substring ( 0 , acceptValue . length - 1 ) ; } this . fileInput . accept = acceptValue ; log . d ( `accept类型 ${ acceptValue }` ) ; } document . body . appendChild ( this . fileInput ) ; this . fileInput . addEventListener ( 'change' , this . handleFiles , false ) ; if ( this . _button != undefined ) { let button = document . getElementById ( this . _button ) ; button . addEventListener ( this . _buttonEventName , this . chooseFile . bind ( this ) ) ; } } /**
     * 上传完成或者失败后,对本次上传任务进行清扫
     */ private resetUploader ( ) { log . d ( "开始重置 uploader" ) ; this . taskQueue . length = 0 ; log . d ( "任务队列已清空" ) ; this . _token = null ; log . d ( "token已清空" ) ; log . d ( "uploader 重置完毕" ) ; } /**
     * 处理文件
     */ private handleFiles = ( ) => { if ( this . fileInput . files . length == 0 ) { return ; } this . generateTask ( ) ; let isInterrupt = false ; let interceptedTasks = [ ] ; for ( let task of this . taskQueue ) { for ( let interceptor of this . interceptors ) { if ( interceptor . onIntercept ( task , this . taskQueue ) ) { interceptedTasks . push ( task ) ; log . d ( "任务拦截器拦截了任务:" ) ; log . d ( task ) ; } if ( interceptor . onInterrupt ( task , this . taskQueue ) ) { isInterrupt = true ; break ; } } } if ( isInterrupt ) { log . w ( "任务拦截器中断了任务队列" ) ; return ; } for ( let task of interceptedTasks ) { let index = this . taskQueue . indexOf ( task ) ; if ( index != - 1 ) { this . taskQueue . splice ( index , 1 ) ; } } this . listener . onReady ( this . taskQueue ) ; this . handleImages ( ) . then ( ( ) => { if ( this . auto ) { log . d ( "开始自动上传" ) ; this . start ( ) ; } } ) ; } ; param task returns { boolean } private static isChunkTask ( task ) { return task . constructor . name === ChunkTask . name && task instanceof ChunkTask ; } param task returns { boolean } private static isDirectTask ( task ) { return task . constructor . name === DirectTask . name && task instanceof DirectTask ; } /**
     * 生成task
     */ private generateTask ( ) { this . resetUploader ( ) ; let files = this . fileInput . files ; for ( let i = 0 ; i < this . fileInput . files . length ; i ++ ) { let file = files [ i ] ; let task ; if ( this . chunk && file . size > UploaderBuilder . BLOCK_SIZE ) { task = new ChunkTask ( file , UploaderBuilder . BLOCK_SIZE , this . size ) ; } else { task = new DirectTask ( file ) ; } if ( this . _saveKey == false ) { task . key = this . listener . onTaskGetKey ( task ) ; } this . taskQueue . push ( task ) ; } } /**
     * 处理图片-缩放-质量压缩
     */ private handleImages ( ) { let promises = [ ] ; if ( this . compress != 1 || this . scale [ 0 ] != 0 || this . scale [ 1 ] != 0 ) { for ( let task of this . taskQueue ) { if ( ! task . file . type . match ( 'image.*' ) ) { continue ; } log . d ( `${ task . file . name } 处理前的图片大小:${ task . file . size / 1024 } kb` ) ; let canvas = < HTMLCanvasElement > document . createElement ( 'canvas' ) ; let img = new Image ( ) ; let ctx = < CanvasRenderingContext2D > canvas . getContext ( '2d' ) ; img . src = URL . createObjectURL ( task . file ) ; let _this = this ; promises . push ( new Promise < Blob > ( ( resolve ) => img . onload = ( ) => { let imgW = img . width ; let imgH = img . height ; let scaleW = _this . scale [ 0 ] ; let scaleH = _this . scale [ 1 ] ; if ( scaleW == 0 && scaleH > 0 ) { canvas . width = imgW / imgH * scaleH ; canvas . height = scaleH ; } else if ( scaleH == 0 && scaleW > 0 ) { canvas . width = scaleW ; canvas . height = imgH / imgW * scaleW ; } else if ( scaleW > 0 && scaleH > 0 ) { canvas . width = scaleW ; canvas . height = scaleH ; } else { canvas . width = img . width ; canvas . height = img . height ; } ctx . drawImage ( img , 0 , 0 , canvas . width , canvas . height ) ; console . log ( canvas ) ; console . log ( canvas . toBlob ) ; canvas . toBlob ( ( blob ) => { resolve ( blob ) ; log . d ( `${ task . file . name } 处理后的图片大小:${ blob . size / 1024 } kb` ) ; } , "image/jpeg" , _this . compress * 0.95 ) ; } ) . then ( ( blob ) => { blob . name = task . file . name ; task . file = blob ; if ( Uploader . isChunkTask ( task ) ) { ( < ChunkTask > task ) . spliceFile2Block ( ) ; } } ) ) ; } } return Promise . all ( promises ) ; } /**
     * 检验选项合法性
     */ private validateOptions ( ) { log . d ( "开始检查构建参数合法性" ) ; if ( ! this . _tokenFunc ) { throw new Error ( '你必须提供一个获取Token的回调函数' ) ; } if ( ! this . scale || ! ( this . scale instanceof Array ) || this . scale . length != 2 || this . scale [ 0 ] < 0 || this . scale [ 1 ] < 0 ) { throw new Error ( 'scale必须是长度为2的number类型的数组,scale[0]为宽度，scale[1]为长度,必须大于等于0' ) ; } log . d ( "构建参数检查完毕" ) ; } /**
     * 开始上传
     */ public start ( ) { log . d ( `上传任务遍历开始` ) ; if ( this . fileInput . files . length == 0 ) { throw new Error ( '没有选中的文件，无法开始上传' ) ; } if ( this . tasking ) { throw new Error ( '任务执行中，请不要重复上传' ) ; } this . listener . onStart ( this . taskQueue ) ; for ( let task of this . taskQueue ) { log . d ( `上传文件名：${ task . file . name }` ) ; log . d ( `上传文件大小：${ task . file . size }字节，${ task . file . size / 1024 } kb，${ task . file . size / 1024 / 1024 } mb` ) ; if ( Uploader . isDirectTask ( task ) ) { log . d ( '该上传任务为直传任务' ) ; new DirectUploadPattern ( this ) . upload ( < DirectTask > task ) ; } else if ( Uploader . isChunkTask ( task ) ) { log . d ( '该上传任务为分片任务' ) ; new ChunkUploadPattern ( this ) . upload ( < ChunkTask > task ) ; } else { throw new Error ( '非法的task类型' ) ; } } } returns { boolean } public isTaskQueueFinish ( ) { for ( let task of this . taskQueue ) { if ( ! task . isFinish ) { return false ; } } return true ; } /**
     * 选择文件
     */ public chooseFile ( ) { this . fileInput . click ( ) ; } public getToken ( task ) { if ( this . _tokenShare && this . _token != undefined ) { return Promise . resolve ( this . _token ) ; } log . d ( `开始获取上传token` ) ; return Promise . resolve ( this . _tokenFunc ( this , task ) ) . then ( ( token ) : string => { log . d ( `上传token获取成功: ${ token }` ) ; this . _token = token ; return token ; } ) ; } public requestTaskToken ( task , url ) { return this . resolveSaveKey ( task ) . then ( ( saveKey ) => { return this . requestToken ( url , saveKey ) ; } ) ; } private requestToken ( url , saveKey ) { return new Promise ( ( resolve , reject ) => { if ( typeof saveKey == "string" ) { url += ( ( /\?/ ) . test ( url ) ? "&" : "?" ) + "saveKey=" + encodeURIComponent ( saveKey ) ; } url += ( ( /\?/ ) . test ( url ) ? "&" : "?" ) + ( new Date ( ) ) . getTime ( ) ; let xhr = new XMLHttpRequest ( ) ; xhr . open ( 'GET' , url , true ) ; xhr . onreadystatechange = ( ) => { if ( xhr . readyState != XMLHttpRequest . DONE ) { return ; } if ( xhr . status == 200 ) { resolve ( xhr . response . uptoken ) ; return ; } reject ( xhr . response ) ; } ; xhr . onabort = ( ) => { reject ( 'aborted' ) ; } ; xhr . responseType = 'json' ; xhr . send ( ) ; } ) ; } private resolveSaveKey ( task ) { let saveKey = this . _saveKey ; if ( typeof saveKey != "string" ) { return Promise . resolve ( undefined ) ; } return Promise . resolve ( saveKey ) . then ( this . resolveUUID ) . then ( saveKey => this . resolveImageInfo ( task . file , saveKey ) ) . then ( this . onSaveKeyResolved ) ; } private resolveUUID = ( s ) : string => { let re = /\$\(uuid\)/ ; if ( re . test ( s ) ) { return s . replace ( re , UUID . uuid ( ) ) ; } return s ; } ; private resolveImageInfo = ( blob , s ) : Promise < string > => { let widthRe = /\$\(imageInfo\.width\)/ ; let heightRe = /\$\(imageInfo\.height\)/ ; if ( ! widthRe . test ( s ) && ! heightRe . test ( s ) ) { return Promise . resolve ( s ) ; } return new Promise < string > ( ( resolve ) => { let img = new Image ( ) ; img . src = URL . createObjectURL ( blob ) ; img . onload = ( ) => { s = s . replace ( widthRe , img . width . toString ( ) ) ; s = s . replace ( heightRe , img . height . toString ( ) ) ; resolve ( s ) ; } ; } ) ; } ; private onSaveKeyResolved = ( saveKey ) : string => { this . _tokenShare = this . _tokenShare && this . _saveKey == saveKey ; return saveKey ; } ; get retry ( ) : number { return this . _retry ; } get size ( ) : number { return this . _size ; } get auto ( ) : boolean { return this . _auto ; } get multiple ( ) : boolean { return this . _multiple ; } get accept ( ) : string [ ] { return this . _accept ; } get compress ( ) : number { return this . _compress ; } get scale ( ) : number [ ] { return this . _scale ; } get listener ( ) : UploadListener { return this . _listener ; } get fileInput ( ) : HTMLInputElement { return this . _fileInput ; } get chunk ( ) : boolean { return this . _chunk ; } get taskQueue ( ) : BaseTask [ ] { return this . _taskQueue ; } get tasking ( ) : boolean { return this . _tasking ; } set tasking ( value ) { this . _tasking = value ; } get interceptors ( ) : Interceptor [ ] { return this . _interceptors ; } get domain ( ) : string { return this . _domain ; } } export default Uploader ;